Skip to content

Commit

Permalink
add stub files
Browse files Browse the repository at this point in the history
  • Loading branch information
rnag committed Nov 26, 2024
1 parent c332a9c commit 6504f02
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 84 deletions.
2 changes: 1 addition & 1 deletion dataclass_wizard/environ/dumpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def cls_asdict(obj: T, dict_factory=dict,
"""

# Call the optional hook that runs before we process the subclass
cls_dumper.__pre_as_dict__(obj)
# cls_dumper.__pre_as_dict__(obj)

# This a list that contains a mapping of each `EnvWizard` field to its
# serialized value.
Expand Down
106 changes: 23 additions & 83 deletions dataclass_wizard/environ/lookups.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,35 @@
import os
from dataclasses import MISSING
from os import environ, name
from typing import ClassVar, Dict, Optional, Set

from ..decorators import cached_class_property
from ..lazy_imports import dotenv
from ..type_def import StrCollection, EnvFileType
from ..utils.string_conv import to_snake_case


# Type of `os.environ` or `DotEnv` dict
Environ = Dict[str, Optional[str]]

# Type of (unique) environment variable names
EnvVars = Set[str]
Environ = dict[str, 'str | None']


# noinspection PyMethodParameters
class Env:

__slots__ = ()

_accessed_cleaned_to_env: ClassVar[bool] = False
_accessed_cleaned_to_env = False

@cached_class_property
def var_names(cls) -> EnvVars:
def var_names(cls):
"""
Cached mapping of `os.environ` key names. This can be refreshed with
:meth:`reload` as needed.
"""
return set(environ)

@classmethod
def reload(cls, env: dict = environ):
"""Refresh cached environment variable names."""
env_vars: EnvVars = cls.var_names
def reload(cls, env=environ):

env_vars = cls.var_names
new_vars = set(env) - env_vars

# update names of environment variables
Expand All @@ -47,11 +42,8 @@ def reload(cls, env: dict = environ):
)

@classmethod
def dotenv_values(cls, files: EnvFileType) -> Environ:
"""
Retrieve the values (environment variables) from a dotenv file,
or a list/tuple of dotenv files.
"""
def dotenv_values(cls, files):

if isinstance(files, (str, os.PathLike)):
files = [files]
elif files is True:
Expand All @@ -70,7 +62,8 @@ def dotenv_values(cls, files: EnvFileType) -> Environ:
return env

@classmethod
def update_with_dotenv(cls, files: EnvFileType = '.env', dotenv_values=None):
def update_with_dotenv(cls, files='.env', dotenv_values=None):

if dotenv_values is None:
dotenv_values = cls.dotenv_values(files)

Expand All @@ -79,27 +72,20 @@ def update_with_dotenv(cls, files: EnvFileType = '.env', dotenv_values=None):
# update `os.environ` with new environment variables
environ.update(dotenv_values)

# noinspection PyDunderSlots,PyUnresolvedReferences
# noinspection PyDunderSlots,PyUnresolvedReferences,PyClassVar
@cached_class_property
def cleaned_to_env(cls) -> Environ:
def cleaned_to_env(cls):
cls._accessed_cleaned_to_env = True
return {clean(var): var for var in cls.var_names}


def clean(s: str) -> str:
"""
TODO:
see https://stackoverflow.com/questions/1276764/stripping-everything-but-alphanumeric-chars-from-a-string-in-python
also, see if we can refactor to use something like Rust and `pyo3` for a slight performance improvement.
"""
def clean(s):

return s.replace('-', '').replace('_', '').lower()


def try_cleaned(key: str):
"""
Return the value of the env variable as a *string* if present in
the Environment, or `MISSING` otherwise.
"""
def try_cleaned(key):

key = Env.cleaned_to_env.get(clean(key))

if key is not None:
Expand All @@ -110,11 +96,8 @@ def try_cleaned(key: str):

if name == 'nt':
# Where Env Var Names Must Be UPPERCASE
def lookup_exact(var: StrCollection):
"""
Lookup by variable name(s) with *exact* letter casing, and return
`None` if not found in the environment.
"""
def lookup_exact(var):

if isinstance(var, str):
var = var.upper()

Expand All @@ -132,11 +115,8 @@ def lookup_exact(var: StrCollection):

else:
# Where Env Var Names Can Be Mixed Case
def lookup_exact(var: StrCollection):
"""
Lookup by variable name(s) with *exact* letter casing, and return
`None` if not found in the environment.
"""
def lookup_exact(var):

if isinstance(var, str):
if var in Env.var_names:
return environ[var]
Expand All @@ -149,22 +129,8 @@ def lookup_exact(var: StrCollection):
return MISSING


def with_screaming_snake_case(field_name: str) -> Optional[str]:
"""
Lookup with `SCREAMING_SNAKE_CASE` letter casing first - this is the
default lookup.
This function assumes the dataclass field name is lower-cased.
def with_screaming_snake_case(field_name):

For a field named 'my_env_var', this tries the following lookups in order:
- MY_ENV_VAR (screaming snake-case)
- my_env_var (snake-case)
- Any other variations - i.e. MyEnvVar, myEnvVar, myenvvar, my-env-var
:param field_name: The dataclass field name to lookup in the environment.
:return: The value of the matched environment variable, if one is found in
the environment.
"""
upper_key = field_name.upper()

if upper_key in Env.var_names:
Expand All @@ -176,20 +142,8 @@ def with_screaming_snake_case(field_name: str) -> Optional[str]:
return try_cleaned(field_name)


def with_snake_case(field_name: str) -> Optional[str]:
"""Lookup with `snake_case` letter casing first.
This function assumes the dataclass field name is lower-cased.
For a field named 'my_env_var', this tries the following lookups in order:
- my_env_var (snake-case)
- MY_ENV_VAR (screaming snake-case)
- Any other variations - i.e. MyEnvVar, myEnvVar, myenvvar, my-env-var
def with_snake_case(field_name):

:param field_name: The dataclass field name to lookup in the environment.
:return: The value of the matched environment variable, if one is found in
the environment.
"""
if field_name in Env.var_names:
return environ[field_name]

Expand All @@ -201,22 +155,8 @@ def with_snake_case(field_name: str) -> Optional[str]:
return try_cleaned(field_name)


def with_pascal_or_camel_case(field_name: str) -> Optional[str]:
"""Lookup with `PascalCase` or `camelCase` letter casing first.
This function assumes the dataclass field name is either pascal- or camel-
cased.
For a field named 'myEnvVar', this tries the following lookups in order:
- myEnvVar, MyEnvVar (camel-case, or pascal-case)
- MY_ENV_VAR (screaming snake-case)
- my_env_var (snake-case)
- Any other variations - i.e. my-env-var, myenvvar
def with_pascal_or_camel_case(field_name):

:param field_name: The dataclass field name to lookup in the environment.
:return: The value of the matched environment variable, if one is found in
the environment.
"""
if field_name in Env.var_names:
return environ[field_name]

Expand Down
125 changes: 125 additions & 0 deletions dataclass_wizard/environ/lookups.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from dataclasses import MISSING
from os import environ
from typing import ClassVar

from ..decorators import cached_class_property
from ..type_def import StrCollection, EnvFileType


type _MISSING_TYPE = type(MISSING)
type STR_OR_MISSING = str | _MISSING_TYPE
type STR_OR_NONE = str | None

# Type of `os.environ` or `DotEnv` dict
Environ = dict[str, STR_OR_NONE]

# Type of (unique) environment variable names
EnvVars = set[str]


# noinspection PyMethodParameters
class Env:

__slots__ = ()

_accessed_cleaned_to_env: ClassVar[bool] = False

"""
Cached mapping of `os.environ` key names. This can be refreshed with
:meth:`reload` as needed.
"""
var_names: EnvVars

@classmethod
def reload(cls, env: dict = environ):
"""Refresh cached environment variable names."""

@classmethod
def dotenv_values(cls, files: EnvFileType) -> Environ:
"""
Retrieve the values (environment variables) from a dotenv file,
or a list/tuple of dotenv files.
"""

@classmethod
def update_with_dotenv(cls, files: EnvFileType = '.env', dotenv_values=None):
...

# noinspection PyDunderSlots,PyUnresolvedReferences
@cached_class_property
def cleaned_to_env(cls) -> Environ:
...


def clean(s: str) -> str:
"""
TODO:
see https://stackoverflow.com/questions/1276764/stripping-everything-but-alphanumeric-chars-from-a-string-in-python
also, see if we can refactor to use something like Rust and `pyo3` for a slight performance improvement.
"""


def try_cleaned(key: str) -> STR_OR_MISSING:
"""
Return the value of the env variable as a *string* if present in
the Environment, or `MISSING` otherwise.
"""


def lookup_exact(var: StrCollection) -> STR_OR_MISSING:
"""
Lookup by variable name(s) with *exact* letter casing, and return
`None` if not found in the environment.
"""


def with_screaming_snake_case(field_name: str) -> STR_OR_MISSING:
"""
Lookup with `SCREAMING_SNAKE_CASE` letter casing first - this is the
default lookup.
This function assumes the dataclass field name is lower-cased.
For a field named 'my_env_var', this tries the following lookups in order:
- MY_ENV_VAR (screaming snake-case)
- my_env_var (snake-case)
- Any other variations - i.e. MyEnvVar, myEnvVar, myenvvar, my-env-var
:param field_name: The dataclass field name to lookup in the environment.
:return: The value of the matched environment variable, if one is found in
the environment.
"""


def with_snake_case(field_name: str) -> STR_OR_MISSING:
"""Lookup with `snake_case` letter casing first.
This function assumes the dataclass field name is lower-cased.
For a field named 'my_env_var', this tries the following lookups in order:
- my_env_var (snake-case)
- MY_ENV_VAR (screaming snake-case)
- Any other variations - i.e. MyEnvVar, myEnvVar, myenvvar, my-env-var
:param field_name: The dataclass field name to lookup in the environment.
:return: The value of the matched environment variable, if one is found in
the environment.
"""


def with_pascal_or_camel_case(field_name: str) -> STR_OR_MISSING:
"""Lookup with `PascalCase` or `camelCase` letter casing first.
This function assumes the dataclass field name is either pascal- or camel-
cased.
For a field named 'myEnvVar', this tries the following lookups in order:
- myEnvVar, MyEnvVar (camel-case, or pascal-case)
- MY_ENV_VAR (screaming snake-case)
- my_env_var (snake-case)
- Any other variations - i.e. my-env-var, myenvvar
:param field_name: The dataclass field name to lookup in the environment.
:return: The value of the matched environment variable, if one is found in
the environment.
"""

0 comments on commit 6504f02

Please sign in to comment.