Skip to content

Commit

Permalink
deprecating 3.7 (#16)
Browse files Browse the repository at this point in the history
* preparing for 1.5.0

* fix typing for 3.8

* with_prefix can now override

* minor improvements

* fix documentation

* Add import for future annotations in test_examples.py

---------

Co-authored-by: Ben Avrahami <[email protected]>
  • Loading branch information
bentheiii and Ben Avrahami authored Apr 19, 2024
1 parent 6bb7bf4 commit 216af20
Show file tree
Hide file tree
Showing 19 changed files with 498 additions and 152 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
unittest:
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] # format: 3.7, 3.8, 3.9
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12"] # format: 3.7, 3.8, 3.9
platform: [ubuntu-latest, macos-latest, windows-latest]
fail-fast: false
runs-on: ${{ matrix.platform }}
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
# envolved Changelog
## 1.5.0
### Removed
* `envolved` no longer supports python 3.7
### Added
* `FindIterCollectionParser`
* `with_prefix` can now override many of an env-var's parameters
### Fixed
* `CollectionParser`'s `opener` and `closer` arguments now correctly handle matches that would be split by the delimiter
* `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
22 changes: 16 additions & 6 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 All @@ -119,12 +119,14 @@ EnvVars
.. warning::
Even if the validator does not mutate the value, it should still return the original value.

.. method:: with_prefix(prefix: str) -> EnvVar[T]
.. method:: with_prefix(prefix: str, *, default = ..., description = ...) -> EnvVar[T]

Return a new EnvVar with the parameters but with a given prefix. This method can be used to re-use an env-var
schema to multiple env-vars.
schema to multiple env-vars. Can also override additional parameters of the new EnvVar.

:param prefix: The prefix to use.
:param other: If specified, will override the parameters of the new EnvVar. If not specified, the
parameters of the original EnvVar will be used. Different subclasses can allow to override additional parameters.
:return: A new EnvVar with the given prefix, of the same type as the envar being used.

.. method:: patch(value: T | missing | discard) -> typing.ContextManager
Expand Down Expand Up @@ -188,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 All @@ -202,6 +204,10 @@ EnvVars
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
.. method:: with_prefix(prefix: str, *, default = ..., description = ..., type = ..., case_sensitive = ..., strip_whitespaces = ...) -> SingleEnvVar[T]

See :meth:`Superclass method <EnvVar.with_prefix>`


.. class:: SchemaEnvVar
Expand Down Expand Up @@ -256,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 All @@ -273,6 +279,10 @@ EnvVars
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]

See :meth:`Superclass method <EnvVar.with_prefix>`

.. class:: Factory(callback: collections.abc.Callable[[], T])

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
57 changes: 44 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 @@ -75,14 +75,15 @@ Utility Parsers
:param delimiter: The delimiter string or pattern to split the string on.
:param inner_parser: The parser to use to parse the elements of the collection. Note this parser is treated the
same an an EnvVar type, so :ref:`string_parsing:Special parsers` apply.
:param output_type: The type to use to aggregate the parsed items to a collection defaults to list.
:param output_type: The type to use to aggregate the parsed items to a collection. Defaults to list.
:param opener: If set, specifies a string or pattern that should be at the beginning of the delimited string.
:param closer: If set, specifies a string or pattern that should be at the end of the delimited string.
:param closer: If set, specifies a string or pattern that should be at the end of the delimited string. Note that providing
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 @@ -115,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 All @@ -140,6 +141,36 @@ Utility Parsers
assert server_params_ev.get() == {"host": "localhost", "port": 8080, "is_ssl": False}
.. class:: FindIterCollectionParser(element_pattern: typing.Pattern, element_func: collections.abc.Callable[[re.Match], E], \
output_type: collections.abc.Callable[[collections.abc.Iterator[E]], G] = list, \
opener: str | typing.Pattern = '', closer: str | typing.Pattern = '')

A parser to translate a string to a collection of values by splitting the string to continguous elements that match
a regex pattern. This parser is useful for parsing strings that have a repeating, complex structure, or in cases where
a :class:`naive split <CollectionParser>` would split the string incorrectly.

:param element_pattern: A regex pattern to find the elements in the string.
:param element_func: A function that takes a regex match object and returns an element.
:param output_type: The type to use to aggregate the parsed items to a collection. Defaults to list.
:param opener: If set, specifies a string or pattern that should be at the beginning of the string.
: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
: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]+)}(,|$)"),
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}]
.. class:: MatchParser(cases: collections.abc.Iterable[tuple[typing.Pattern[str] | str, T]] | \
collections.abc.Mapping[str, T] | type[enum.Enum], fallback: T = ...)
Expand All @@ -151,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 @@ -183,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
2 changes: 1 addition & 1 deletion envolved/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.4.0"
__version__ = "1.5.0"
Loading

0 comments on commit 216af20

Please sign in to comment.