Skip to content

Commit

Permalink
doc done
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Avrahami committed Jan 2, 2024
1 parent 23f9041 commit f938fe6
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 33 deletions.
47 changes: 46 additions & 1 deletion docs/cookbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,49 @@ Here are some common types and factories to use when creating a :class:`~envvar.
'y': inferred_env_var(),
})
# this will result in a dict that has ints for keys "x" and "y"
# this will result in a dict that has ints for keys "x" and "y"
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::
from envolved import ...
my_schema_ev = env_var('FOO_', type=SimpleNamespace, args={
'x': inferred_env_var(type=int, default=0),
'y': inferred_env_var(type=string, default='hello'),
})
# this will result in a namespace that fills `x` and `y` with the values of `FOO_X` and `FOO_Y` respectively
Note a sticking point here, he have to specify not only the type of the inferred env var, but also the default value.

.. code-block::
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
effects when an actual schema is passed in. If we were to assume that the inferred env var is required, then plugging in a schema that has a default value for that parameter would be
a hard-to-detect breaking change that can have catostraphic consequences. By requiring the default value to be passed in, we force the user to be explicit about the default values,
ehan it might be inferred.

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

.. code-block::
from envolved import ..., missing
my_schema_ev = env_var('FOO_', type=SimpleNamespace, args={
'x': inferred_env_var(type=int, default=missing),
'y': inferred_env_var(type=string, default='hello'),
})
# 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
52 changes: 50 additions & 2 deletions docs/describing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Another feature of envolved is the ability to describe all EnvVars.
'level': env_var('_LEVEL', type=int, default=20),
})
print('\n'join(describe_env_vars()))
print('\n'.join(describe_env_vars()))
# OUTPUT:
# BACKLOG_SIZE: Backlog size
Expand All @@ -33,13 +33,15 @@ Another feature of envolved is the ability to describe all EnvVars.
.. function:: describe_env_vars(**kwargs)->List[str]

Returns a list of string lines that describe all the EnvVars. All keyword arguments are passed to
:class:`textwrap.wrap` to wrap the lines.
:func:`textwrap.wrap` to wrap the lines.

.. note::

This function will include a description of every alive EnvVar. EnvVars defined in functions, for instance, will
not be included.

Descriptions

Excluding EnvVars from the description
------------------------------------------

Expand Down Expand Up @@ -70,3 +72,49 @@ In some cases it is useful to exclude some EnvVars from the description. This ca
of EnvVar names to EnvVars.
:return: `env_vars`, to allow for piping.

.. class:: EnvVarsDescription(env_vars: collections.abc.Iterable[EnvVar] | None)

A class that allows for more fine-grained control over the description of EnvVars.

:param env_vars: A collection of EnvVars to describe. If None, all alive EnvVars will be described. If the collection
includes two EnvVars, one which is a parent of the other, only the parent will be described.

.. method:: flat()->FlatEnvVarsDescription

Returns a flat description of the EnvVars.

.. method:: nested()->NestedEnvVarsDescription

Returns a nested description of the EnvVars.

.. class:: FlatEnvVarsDescription

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]

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]

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.

:param kwargs: Keyword arguments to pass to :func:`textwrap.wrap`.
:return: A list of string lines that describe the EnvVars.

.. class:: NestedEnvVarsDescription

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

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

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

:param indent_increment: The string to use to increment the indentation of the description with each level. If not provided,
will use the keyword argument "subsequent_indent" from :func:`textwrap.wrap`, if provided. Otherwise, will use a single space.
:param kwargs: Keyword arguments to pass to :func:`textwrap.wrap`.
:return: A list of string lines that describe the EnvVars.
2 changes: 1 addition & 1 deletion docs/envvar.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Creating EnvVars
EnvVars
=========================================================

.. module:: envvar
Expand Down
2 changes: 1 addition & 1 deletion docs/string_parsing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Utility Parsers
value_type: ParserInput[V] | collections.abc.Mapping[K, ParserInput[V]], \
output_type: collections.abc.Callable[[collections.abc.Iterable[tuple[K,V]]], G] = ..., *, \
key_first: bool = True, opener: str | typing.Pattern = '', \
closer: str | typing.Pattern = '', strip: bool = True, strip_keys: bool = True, strip_values: bool = True, strip_items) -> CollectionParser[G]
closer: str | typing.Pattern = '', strip: bool = True, strip_keys: bool = True, strip_values: bool = True) -> CollectionParser[G]
A factory method to create a :class:`CollectionParser` where each item is a delimited key-value pair.

Expand Down
11 changes: 11 additions & 0 deletions envolved/basevar.py
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
# this module is to preserved backwards compatibility
from envolved.envvar import EnvVar, SchemaEnvVar, SingleEnvVar, as_default, discard, missing, no_patch

__all__ = [
"EnvVar",
"as_default",
"discard",
"missing",
"no_patch",
"SchemaEnvVar",
"SingleEnvVar",
]
10 changes: 5 additions & 5 deletions envolved/describe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

from typing import Any, Iterable, List, Mapping, Set, TypeVar, Union

from envolved.describe.flat import SingleEnvVarDescriptionSet
from envolved.describe.nested import NestedDescription, RootNestedDescription
from envolved.describe.flat import FlatEnvVarsDescription
from envolved.describe.nested import NestedEnvVarsDescription, RootNestedDescription
from envolved.envvar import EnvVar, InferEnvVar, top_level_env_vars


Expand All @@ -26,10 +26,10 @@ def __init__(self, env_vars: Iterable[EnvVar] | None = None) -> None:
# remove any children we found along the way
self.env_var_roots -= children

def flat(self) -> SingleEnvVarDescriptionSet:
return SingleEnvVarDescriptionSet.from_envvars(self.env_var_roots)
def flat(self) -> FlatEnvVarsDescription:
return FlatEnvVarsDescription.from_envvars(self.env_var_roots)

def nested(self) -> NestedDescription:
def nested(self) -> NestedEnvVarsDescription:
return RootNestedDescription.from_envvars(self.env_var_roots)


Expand Down
21 changes: 10 additions & 11 deletions envolved/describe/flat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dataclasses import dataclass
from itertools import chain, groupby
from typing import Any, Iterable, List, Optional, Tuple
from typing import Any, Iterable, List, Tuple
from warnings import warn

from envolved.describe.util import prefix_description, wrap_description as wrap
Expand Down Expand Up @@ -77,7 +77,7 @@ def collate(cls, instances: Iterable[SingleEnvVarDescription]) -> SingleEnvVarDe
return without_description[0]


class SingleEnvVarDescriptionSet:
class FlatEnvVarsDescription:
def __init__(self, env_var_descriptions: Iterable[SingleEnvVarDescription]) -> None:
self.env_var_descriptions = env_var_descriptions

Expand All @@ -98,19 +98,18 @@ def key(i: SingleEnvVarDescription) -> str:

return ret

def wrap_grouped(self, group_sep_line: Optional[str] = None, **kwargs: Any) -> Iterable[str]:
def wrap_grouped(self, **kwargs: Any) -> Iterable[str]:
env_var_descriptions = sorted(self.env_var_descriptions, key=lambda i: (i.path, i.env_var.key))
first_group = True
ret = []
for _, group in groupby(env_var_descriptions, key=lambda i: i.path):
if not first_group and group_sep_line is not None:
ret.append(group_sep_line)
first_group = False
ret.extend(chain.from_iterable(d.wrap(**kwargs) for d in group))
ret = list(
chain.from_iterable(
chain.from_iterable(d.wrap(**kwargs) for d in group)
for _, group in groupby(env_var_descriptions, key=lambda i: i.path)
)
)
return ret

@classmethod
def from_envvars(cls, env_vars: Iterable[EnvVar]) -> SingleEnvVarDescriptionSet:
def from_envvars(cls, env_vars: Iterable[EnvVar]) -> FlatEnvVarsDescription:
env_var_descriptions = list(
chain.from_iterable(SingleEnvVarDescription.from_envvar((), env_var) for env_var in env_vars)
)
Expand Down
20 changes: 10 additions & 10 deletions envolved/describe/nested.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any, Iterable, Tuple
from typing import Any, Iterable, Optional, Tuple

from envolved.describe.util import prefix_description, suffix_description, wrap_description as wrap
from envolved.envvar import Description, EnvVar, SchemaEnvVar, SingleEnvVar


class NestedDescription(ABC):
class NestedEnvVarsDescription(ABC):
@abstractmethod
def get_path(self) -> Tuple[str, ...]:
...
Expand All @@ -18,7 +18,7 @@ def wrap(self, *, indent_increment: str, **kwargs: Any) -> Iterable[str]:
...

@classmethod
def from_env_var(cls, path: Tuple[str, ...], env_var: EnvVar) -> NestedDescription:
def from_env_var(cls, path: Tuple[str, ...], env_var: EnvVar) -> NestedEnvVarsDescription:
if isinstance(env_var, SingleEnvVar):
path = (*path, env_var.key.upper())
return SingleNestedDescription(path, env_var)
Expand All @@ -35,7 +35,7 @@ def from_env_var(cls, path: Tuple[str, ...], env_var: EnvVar) -> NestedDescripti


@dataclass
class SingleNestedDescription(NestedDescription):
class SingleNestedDescription(NestedEnvVarsDescription):
path: Tuple[str, ...]
env_var: SingleEnvVar

Expand All @@ -62,8 +62,8 @@ def wrap(self, *, indent_increment: str, **kwargs: Any) -> Iterable[str]:
return wrap(text, **kwargs)


class NestedDescriptionWithChildren(NestedDescription):
children: Iterable[NestedDescription]
class NestedDescriptionWithChildren(NestedEnvVarsDescription):
children: Iterable[NestedEnvVarsDescription]

@abstractmethod
def title(self) -> Description | None:
Expand All @@ -83,7 +83,7 @@ def wrap(self, *, indent_increment: str, **kwargs: Any) -> Iterable[str]:
class SchemaNestedDescription(NestedDescriptionWithChildren):
path: Tuple[str, ...]
env_var: SchemaEnvVar
children: Iterable[NestedDescription]
children: Iterable[NestedEnvVarsDescription]

def get_path(self) -> Tuple[str, ...]:
return self.path
Expand All @@ -97,7 +97,7 @@ def title(self) -> Description | None:

@dataclass
class RootNestedDescription(NestedDescriptionWithChildren):
children: Iterable[NestedDescription]
children: Iterable[NestedEnvVarsDescription]

def get_path(self) -> Tuple[str, ...]:
return ()
Expand All @@ -107,9 +107,9 @@ def title(self) -> Description | None:

@classmethod
def from_envvars(cls, env_vars: Iterable[EnvVar]) -> RootNestedDescription:
return cls([NestedDescription.from_env_var((), env_var) for env_var in env_vars])
return cls([NestedEnvVarsDescription.from_env_var((), env_var) for env_var in env_vars])

def wrap(self, *, indent_increment: str | None = None, **kwargs: Any) -> Iterable[str]:
def wrap(self, *, indent_increment: Optional[str] = None, **kwargs: Any) -> Iterable[str]:
if indent_increment is None:
indent_increment = kwargs.get("subsequent_indent", " ")
assert isinstance(indent_increment, str)
Expand Down
4 changes: 3 additions & 1 deletion envolved/infer_env_var.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from envolved.envvar import InferEnvVar
from envolved.envvar import InferEnvVar, inferred_env_var

__all__ = ["InferEnvVar", "inferred_env_var"]

# this module is to preserved backwards compatibility

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "envolved"
version = "1.1.2"
version = "1.2.0"
description = ""
authors = ["ben avrahami <[email protected]>"]
license = "MIT"
Expand Down

0 comments on commit f938fe6

Please sign in to comment.