Skip to content

Commit

Permalink
fix documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Avrahami committed Apr 19, 2024
1 parent 5b8e97e commit 2675de3
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 35 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* `CollectionParser`'s `closer` argument now correctly handles overlapping matches
* `CollectionParser`'s `closer` argument is now faster when using non-regex matches
* `CollectionParser.pair_wise_delimited` will now be more memory efficient when using a mapping `value_type`
### Internal
* fixed some documentation typos
## 1.4.0
### Deprecated
* this is the last release to support python 3.7
Expand Down
10 changes: 5 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::
.. 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::
.. 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::
.. code-block::python
from envolved import ...
Expand All @@ -87,7 +87,7 @@ 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::
.. code-block::python
from envolved import ...
Expand All @@ -104,7 +104,7 @@ 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::
.. code-block::python
from envolved import ..., missing
Expand Down
4 changes: 2 additions & 2 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::
.. 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 Down Expand Up @@ -46,7 +46,7 @@ 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::
.. code-block::python
point_args = dict(
x=env_var('_x', type=int, description='x coordinate'),
Expand Down
8 changes: 4 additions & 4 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::
.. 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 @@ -105,7 +105,7 @@ EnvVars
return value
# getting the value of the environment variable will now raise an error if the value is not positive
.. code-block::
.. 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 +190,7 @@ EnvVars
:param kwargs: Additional keyword arguments to pass to the :attr:`type` callable.
:return: The value of the retrieved environment variable.

.. code-block::
.. code-block::python
:caption: Using SingleEnvVar to fetch a value from an environment variable, with additional keyword arguments.
from dataclasses import dataclass
Expand Down Expand Up @@ -262,7 +262,7 @@ EnvVars
:param kwargs: Additional keyword arguments to pass to the :attr:`type` callable.
:return: The value of the environment variable.

.. code-block::
.. code-block::python
:caption: Using SchemaEnvVar to create a class from multiple environment variables, with additional keyword arguments.
from dataclasses import dataclass
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::
.. 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::
.. code-block::python
# If we to accept connection info for another API, we don't need to repeat ourselves
Expand Down
26 changes: 13 additions & 13 deletions docs/string_parsing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@ 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::
.. code-block::python
enable_cache_ev = env_var("ENABLE_CACHE", type=bool)
os.environ["ENABLE_CACHE"] = "False"
assert enable_cache_ev.get() == False
assert enable_cache_ev.get() is False
Users can disable the special meaning of some types by wrapping them in a dummy callable.

.. code-block::
.. code-block::python
enable_cache_ev = env_var("ENABLE_CACHE", type=lambda x: bool(x))
os.environ["ENABLE_CACHE"] = "False"
assert enable_cache_ev.get() == True
assert enable_cache_ev.get() is True
All the special parsers are:

Expand Down Expand Up @@ -81,9 +81,9 @@ 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::
.. code-block::python
countries = env_var("COUNTRIES", type=CollectionParser(",", str.to_lower, set))
countries = env_var("COUNTRIES", type=CollectionParser(",", str.lower, set))
os.environ["COUNTRIES"] = "United States,Canada,Mexico"
Expand Down Expand Up @@ -116,18 +116,18 @@ 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::
.. code-block::python
:caption: Using CollectionParser.pair_wise_delimited to parse arbitrary HTTP headers.
headers_ev = env_var("HTTP_HEADERS",
type=CollectionParser.pair_wise_delimited(";", ":", str.to_upper,
type=CollectionParser.pair_wise_delimited(";", ":", str.upper,
str))
os.environ["HTTP_HEADERS"] = "Foo:bar;baz:qux"
assert headers_ev.get() == {"FOO": "bar", "BAZ": "qux"}
.. code-block::
.. code-block::python
:caption: Using CollectionParser.pair_wise_delimited to parse a key-value collection with differing value
types.
Expand Down Expand Up @@ -156,14 +156,14 @@ 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::
.. code-block::python
:caption: Using FindIterCollectionParser to parse a string of comma-separated groups of numbers.
def parse_group(match: re.Match) -> set[int]:
return {int(x) for x in match.group(1).split(',')}
groups_ev = env_var("GROUPS", type=FindIterCollectionParser(
re.compile(r"{([,\d]+)},?"),
re.compile(r"{([,\d]+)}(,|$)"),
parse_group
))
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::
.. 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::
.. 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::
.. 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::
.. 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::
.. 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::
.. 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::
.. 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::
.. 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::
.. code-block::python
cache_time_ev = env_var('CACHE_TIME', type=int)
Expand Down
110 changes: 110 additions & 0 deletions tests/unittests/test_examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import enum
import re
from types import SimpleNamespace

from pytest import MonkeyPatch, fixture

from envolved import env_var
from envolved.parsers import CollectionParser, FindIterCollectionParser, LookupParser, MatchParser


class FakeEnviron:
def __init__(self, monkeypatch: MonkeyPatch) -> None:
self.monkeypatch = monkeypatch

def __setitem__(self, key: str, value: str) -> None:
self.monkeypatch.setenv(key, value)


@fixture()
def os():
return SimpleNamespace(environ=FakeEnviron(MonkeyPatch()))


def test_bool_special_parser(os):
enable_cache_ev = env_var("ENABLE_CACHE", type=bool)

os.environ["ENABLE_CACHE"] = "False"

assert enable_cache_ev.get() is False


def test_bypass_bool_parser(os):
enable_cache_ev = env_var("ENABLE_CACHE", type=lambda x: bool(x))

os.environ["ENABLE_CACHE"] = "False"

assert enable_cache_ev.get() is True


def test_collection_parser(os):
countries = env_var("COUNTRIES", type=CollectionParser(",", str.lower, set))

os.environ["COUNTRIES"] = "United States,Canada,Mexico"

assert countries.get() == {"united states", "canada", "mexico"}


def test_collection_parser_pairwise(os):
headers_ev = env_var("HTTP_HEADERS", type=CollectionParser.pair_wise_delimited(";", ":", str.upper, str))

os.environ["HTTP_HEADERS"] = "Foo:bar;baz:qux"

assert headers_ev.get() == {"FOO": "bar", "BAZ": "qux"}


def test_collection_parser_pairwise_2(os):
server_params_ev = env_var(
"SERVER_PARAMS",
type=CollectionParser.pair_wise_delimited(
";",
":",
str,
{
"host": str,
"port": int,
"is_ssl": bool,
},
),
)

os.environ["SERVER_PARAMS"] = "host:localhost;port:8080;is_ssl:false"

assert server_params_ev.get() == {"host": "localhost", "port": 8080, "is_ssl": False}


def test_find_iter_collection_parser(os):
def parse_group(match: re.Match) -> set[int]:
return {int(x) for x in match.group(1).split(",")}

groups_ev = env_var("GROUPS", type=FindIterCollectionParser(re.compile(r"{([,\d]+)}(,|$)"), parse_group))

os.environ["GROUPS"] = "{1,2,3},{4,5,6},{7,8,9}"

assert groups_ev.get() == [{1, 2, 3}, {4, 5, 6}, {7, 8, 9}]


def test_match_parser(os):
class Color(enum.Enum):
RED = 1
GREEN = 2
BLUE = 3

color_ev = env_var("COLOR", type=MatchParser(Color))

os.environ["COLOR"] = "RED"

assert color_ev.get() == Color.RED


def test_lookup_parser(os):
class Color(enum.Enum):
RED = 1
GREEN = 2
BLUE = 3

color_ev = env_var("COLOR", type=LookupParser(Color))

os.environ["COLOR"] = "RED"

assert color_ev.get() == Color.RED
Loading

0 comments on commit 2675de3

Please sign in to comment.