Skip to content

Commit

Permalink
added AbsoluteName (#17)
Browse files Browse the repository at this point in the history
* added `AbsoluteName`

* bump version

---------

Co-authored-by: Ben Avrahami <[email protected]>
  • Loading branch information
bentheiii and Ben Avrahami authored Jun 10, 2024
1 parent 216af20 commit 0807477
Show file tree
Hide file tree
Showing 14 changed files with 115 additions and 46 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# envolved Changelog
## 1.6.0
### Added
* added `AbsoluteName` to create env vars with names that never have a prefix
### Documentation
* fixed code snippets around documentation
## 1.5.0
### Removed
* `envolved` no longer supports python 3.7
Expand Down
36 changes: 31 additions & 5 deletions docs/cookbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ EnvVars are best defined as global variables (so they will be included in the
:ref:`description <describing:Describing Environment Variables>`). Also, to differentiate the environment variables and
their eventually retrieved values, we should end the name of the EnvVar variables with the suffix ``_ev``.

.. code-block::python
.. code-block:: python
board_size_ev : EnvVar[int] = env_var('BOARD_SIZE', type=int, default=8)
Expand Down Expand Up @@ -37,7 +37,7 @@ Here are some common types and factories to use when creating a :class:`~envvar.
* :class:`typing.NamedTuple`: A quick and easy way to create an annotated named tuple.
* :class:`typing.TypedDict`: To create type annotated dictionaries.

.. code-block::python
.. code-block:: python
class Point(typing.NamedTuple):
x: int
Expand Down Expand Up @@ -72,7 +72,7 @@ Inferring Schema Parameter Names Without a Schema
We can actually use :func:`~envvar.inferred_env_var` to infer the name of :class:`~envvar.EnvVar` parameters without a schema. This is useful when
we want to prototype a schema without having to create a schema class.

.. code-block::python
.. code-block:: python
from envolved import ...
Expand All @@ -87,14 +87,15 @@ we want to prototype a schema without having to create a schema class.
Note a sticking point here, we have to specify not only the type of the inferred env var, but also the default value.

.. code-block::python
.. code-block:: python
from envolved import ...
my_schema_ev = env_var('FOO_', type=SimpleNamespace, args={
'x': inferred_env_var(type=int), # <-- this code will raise an exception
})
.. note:: Why is this the behaviour?

In normal :func:`~envvar.env_var`, not passing a `default` implies that the EnvVar is required, why can't we do the same for :func:`~envvar.inferred_env_var`? We do this to reduce side
Expand All @@ -104,7 +105,8 @@ Note a sticking point here, we have to specify not only the type of the inferred

We can specify that an inferred env var is required by explicitly stating `default=missing`

.. code-block::python

.. code-block:: python
from envolved import ..., missing
Expand All @@ -115,3 +117,27 @@ We can specify that an inferred env var is required by explicitly stating `defau
# this will result in a namespace that fills `x` with the value of `FOO_X`
# and will raise an exception if `FOO_X` is not set
Absolute Variable Names
------------------------
When creating a schema, we can specify a child env var whose name will not be prefixed with the schema name by making the key of the child env var an intance of the
:class:`~absolute_name.AbsoluteName` class.

.. code-block:: python
from envolved import AbsoluteName
my_schema_ev = env_var('FOO_', type=SimpleNamespace, args={
'x': env_var("X", type=int),
'y': env_var(AbsoluteName("BAR_Y"), type=int),
})
# this will result in a namespace that fills `x` with the value of `FOO_X`,
# but `y` with the value of `BAR_Y`
.. module:: absolute_name

.. class:: AbsoluteName

A subclass of :class:`str` that is used to specify that an env var should not be prefixed.
20 changes: 11 additions & 9 deletions docs/describing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Describing Environment Variables

Another feature of envolved is the ability to describe all EnvVars.

.. code-block::python
.. code-block:: python
cache_time_ev = env_var('CACHE_TIME', type=int, default=3600, description='Cache time, in seconds')
backlog_size_ev = env_var('BACKLOG_SIZE', type=int, default=100, description='Backlog size')
Expand All @@ -30,7 +30,7 @@ Another feature of envolved is the ability to describe all EnvVars.

.. module:: describe

.. function:: describe_env_vars(**kwargs)->List[str]
.. function:: describe_env_vars(**kwargs)->list[str]

Returns a list of string lines that describe all the EnvVars. All keyword arguments are passed to
:func:`textwrap.wrap` to wrap the lines.
Expand All @@ -46,18 +46,20 @@ Excluding EnvVars from the description
In some cases it is useful to exclude some EnvVars from the description. This can be done with the
:func:`exclude_from_description` function.

.. code-block::python
.. code-block:: python
point_args = dict(
x=env_var('_x', type=int, description='x coordinate'),
y=env_var('_y', type=int, description='y coordinate')
) # point_args is a common argument set that we will provide to other envars.
origin_ev = env_var('ORIGIN', type=Point, description='Origin point', args=point_args)
destination_ev = env_var('DESTINATION', type=Point, description='Destination point', args=point_args)
destination_ev = env_var(
'DESTINATION', type=Point, description='Destination point', args=point_args
)
# but the problem is that now the env-vars defined in the original point_args dict will be included in the
# description even though we never read them. We exclude them like this:
# but the problem is that now the env-vars defined in the original point_args dict will be
# included in the description even though we never read them. We exclude them like this:
exclude_from_description(point_args)
Expand Down Expand Up @@ -91,15 +93,15 @@ In some cases it is useful to exclude some EnvVars from the description. This ca

A flat representation of the EnvVars description. Only single-environment variable EnvVars (or single-environment variable children of envars) will be described.

.. method:: wrap_sorted(*, unique_keys: bool = True, **kwargs)->List[str]
.. method:: wrap_sorted(*, unique_keys: bool = True, **kwargs)->list[str]

Returns a list of string lines that describe the EnvVars, sorted by their environment variable key.

:param unique_keys: If True, and if any EnvVars share an environment variable key, they will be combined into one description.
:param kwargs: Keyword arguments to pass to :func:`textwrap.wrap`.
:return: A list of string lines that describe the EnvVars.

.. method:: wrap_grouped(**kwargs)->List[str]
.. method:: wrap_grouped(**kwargs)->list[str]

Returns a list of string lines that describe the EnvVars, sorted by their environment variable key, but env-vars that are used by the same schema will appear together.

Expand All @@ -112,7 +114,7 @@ In some cases it is useful to exclude some EnvVars from the description. This ca

A nested representation of the EnvVars description. All EnvVars will be described.

.. method:: wrap(indent_increment: str = ..., **kwargs)->List[str]
.. method:: wrap(indent_increment: str = ..., **kwargs)->list[str]

Returns a list of string lines that describe the EnvVars in a tree structure.

Expand Down
18 changes: 10 additions & 8 deletions docs/envvar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ EnvVars
:param validator: A callable that will be added as a validator.
:return: The validator, to allow usage of this function as a decorator.

.. code-block::python
.. code-block:: python
:caption: Using validators to assert that an environment variable is valid.
connection_timeout_ev = env_var('CONNECTION_TIMEOUT_SECONDS', type=int)
Expand All @@ -103,9 +103,10 @@ EnvVars
if value <= 0:
raise ValueError('Connection timeout must be positive')
return value
# getting the value of the environment variable will now raise an error if the value is not positive
# getting the value of the environment variable will now raise an error
# if the value is not positive
.. code-block::python
.. code-block:: python
:caption: Using validators to mutate the value of an environment variable.
title_ev = env_var('TITLE', type=str)
Expand Down Expand Up @@ -190,7 +191,7 @@ EnvVars
:param kwargs: Additional keyword arguments to pass to the :attr:`type` callable.
:return: The value of the retrieved environment variable.

.. code-block::python
.. code-block:: python
:caption: Using SingleEnvVar to fetch a value from an environment variable, with additional keyword arguments.
from dataclasses import dataclass
Expand All @@ -201,7 +202,8 @@ EnvVars
users_ev = env_var("USERNAMES", type=parse_users)
if desc:
users = users_ev.get(reverse=True) # will return a list of usernames sorted in reverse order
users = users_ev.get(reverse=True) # will return a list of usernames sorted
# in reverse order
else:
users = users_ev.get() # will return a list of usernames sorted in ascending order
Expand Down Expand Up @@ -262,7 +264,7 @@ EnvVars
:param kwargs: Additional keyword arguments to pass to the :attr:`type` callable.
:return: The value of the environment variable.

.. code-block::python
.. code-block:: python
:caption: Using SchemaEnvVar to create a class from multiple environment variables, with additional keyword arguments.
from dataclasses import dataclass
Expand All @@ -277,8 +279,8 @@ EnvVars
args={'name': env_var('NAME', type=str),
'age': env_var('AGE', type=int)})
user_ev.get(age=20, height=168) # will return a User object with the name taken from the environment variables,
# but with the age and height overridden by the keyword arguments.
user_ev.get(age=20, height=168) # will return a User object with the name taken from the
# environment variables, but with the age and height overridden by the keyword arguments.
.. method:: with_prefix(prefix: str, *, default = ..., description = ..., type = ..., on_partial = ...) -> SchemaEnvVar[T]

Expand Down
6 changes: 4 additions & 2 deletions docs/infer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ extracted from the factory's type annotation.
grid_size_ev = env_var('GRID_', type=GridSize, args=dict(
width=inferred_env_var('WIDTH'), # GRID_WIDTH will be parsed as int
height=inferred_env_var('HEIGHT'), # GRID_HEIGHT will be parsed as int, and will have default 10
diagonal=inferred_env_var(), # GRID_DIAGONAL will be parsed as bool, and will have default False
height=inferred_env_var('HEIGHT'), # GRID_HEIGHT will be parsed as int, and will have
# default 10
diagonal=inferred_env_var(), # GRID_DIAGONAL will be parsed as bool, and will have
# default False
))
Type inference can be performed for the following factory types:
Expand Down
4 changes: 2 additions & 2 deletions docs/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Introduction
===============
Envolved is a python library that makes reading and parsing environment variables easy.

.. code-block::python
.. code-block:: python
from envolved import *
Expand Down Expand Up @@ -36,7 +36,7 @@ Envolved is a python library that makes reading and parsing environment variable
Envolved cuts down on boilerplate and allows for more reusable code.

.. code-block::python
.. code-block:: python
# If we to accept connection info for another API, we don't need to repeat ourselves
Expand Down
16 changes: 8 additions & 8 deletions docs/string_parsing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Some built-in callables translate to special predefined parsers. For example, th
ineffective on its own as a parser, which is why envolved knows to treat the ``bool`` type as a special parser that
translates the string ``"True"`` and ``"False"`` to ``True`` and ``False`` respectively.

.. code-block::python
.. code-block:: python
enable_cache_ev = env_var("ENABLE_CACHE", type=bool)
Expand All @@ -27,7 +27,7 @@ translates the string ``"True"`` and ``"False"`` to ``True`` and ``False`` respe
Users can disable the special meaning of some types by wrapping them in a dummy callable.

.. code-block::python
.. code-block:: python
enable_cache_ev = env_var("ENABLE_CACHE", type=lambda x: bool(x))
Expand Down Expand Up @@ -81,7 +81,7 @@ Utility Parsers
a pattern will slow down the parsing process.
:param strip: Whether or not to strip whitespaces from the beginning and end of each item.

.. code-block::python
.. code-block:: python
countries = env_var("COUNTRIES", type=CollectionParser(",", str.lower, set))
Expand Down Expand Up @@ -116,7 +116,7 @@ Utility Parsers
:param strip_keys: Whether or not to strip whitespaces from the beginning and end of each key in every pair.
:param strip_values: Whether or not to strip whitespaces from the beginning and end of each value in every pair.

.. code-block::python
.. code-block:: python
:caption: Using CollectionParser.pair_wise_delimited to parse arbitrary HTTP headers.
headers_ev = env_var("HTTP_HEADERS",
Expand All @@ -127,7 +127,7 @@ Utility Parsers
assert headers_ev.get() == {"FOO": "bar", "BAZ": "qux"}
.. code-block::python
.. code-block:: python
:caption: Using CollectionParser.pair_wise_delimited to parse a key-value collection with differing value
types.
Expand Down Expand Up @@ -156,7 +156,7 @@ Utility Parsers
:param closer: If set, specifies a string or pattern that should be at the end of the string. Note that providing
a pattern will slow down the parsing process.

.. code-block::python
.. code-block:: python
:caption: Using FindIterCollectionParser to parse a string of comma-separated groups of numbers.
def parse_group(match: re.Match) -> set[int]:
Expand All @@ -182,7 +182,7 @@ Utility Parsers
which case the names of the enum members will be used as the matches.
:param fallback: The value to return if no case matches. If not specified, an exception will be raised.

.. code-block::python
.. code-block:: python
class Color(enum.Enum):
RED = 1
Expand Down Expand Up @@ -214,7 +214,7 @@ Utility Parsers
in which case the names of the enum members will be used as the matches.
:param fallback: The value to return if no case matches. If not specified, an exception will be raised.

.. code-block::python
.. code-block:: python
class Color(enum.Enum):
RED = 1
Expand Down
14 changes: 7 additions & 7 deletions docs/testing_utilities.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Envolved makes testing environment variables easy with the :attr:`~envvar.EnvVar
:meth:`~envvar.EnvVar.patch` context method. They allows you to set a predefined EnvVar value and then restore the
original value when the test is finished.

.. code-block::python
.. code-block:: python
:emphasize-lines: 5-6
cache_time_ev = env_var('CACHE_TIME', type=10)
Expand All @@ -20,7 +20,7 @@ original value when the test is finished.
note that `cache_time_ev.patch(10)` just sets attribute `cache_time_ev.monkeypatch` to ``10``, and restores it to its
previous value when the context is exited. We might as well have done:

.. code-block::python
.. code-block:: python
:emphasize-lines: 5-6, 9
cache_time_ev = env_var('CACHE_TIME', type=10)
Expand All @@ -40,7 +40,7 @@ Unittest
In :mod:`unittest` tests, we can use the :any:`unittest.mock.patch.object` method decorate a test method to the values we
want to test with.

.. code-block::python
.. code-block:: python
:emphasize-lines: 4, 6
cache_time_ev = env_var('CACHE_TIME', type=10)
Expand All @@ -58,7 +58,7 @@ Pytest
When using :mod:`pytest` we can use the
`monkeypatch fixture <https://docs.pytest.org/en/latest/how-to/monkeypatch.html>`_ fixture to patch our EnvVars.

.. code-block::python
.. code-block:: python
:emphasize-lines: 2
def test_app_startup(monkeypatch):
Expand All @@ -74,7 +74,7 @@ Sometimes we may want to apply a monkeypatch over a non-function-scope fixture.
because the built-in monkeypatch fixture is only available in function scope. To overcome this, we can create our own
monkeypatch fixture.

.. code-block::python
.. code-block:: python
from pytest import fixture, MonkeyPatch
Expand All @@ -98,7 +98,7 @@ monkeypatch fixture.
An important thing to note is that the ``monkeypatch`` fixture doesn't affect the actual environment, only the specific
EnvVar that was patched.

.. code-block::python
.. code-block:: python
cache_time_ev = env_var('CACHE_TIME', type=int)
Expand All @@ -116,7 +116,7 @@ In cases where an environment variable is retrieved from different EnvVars, or w
have to set the environment directly, by using the :attr:`envvar.SingleEnvVar.key` property to get the actual
environment name. In pytest we can use the monkeypatch fixture to do this.

.. code-block::python
.. code-block:: python
cache_time_ev = env_var('CACHE_TIME', type=int)
Expand Down
Loading

0 comments on commit 0807477

Please sign in to comment.