package-utils 0.4.2__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {package-utils-0.4.2/src/package_utils.egg-info → package-utils-0.5.0}/PKG-INFO +13 -4
  2. {package-utils-0.4.2 → package-utils-0.5.0}/pyproject.toml +18 -6
  3. {package-utils-0.4.2 → package-utils-0.5.0}/src/package_utils/cli/__init__.py +3 -1
  4. {package-utils-0.4.2 → package-utils-0.5.0}/src/package_utils/cli/convertors/parameter.py +5 -4
  5. package-utils-0.5.0/src/package_utils/context/__init__.py +1 -0
  6. package-utils-0.5.0/src/package_utils/context/context.py +34 -0
  7. package-utils-0.5.0/src/package_utils/context/entry_point.py +21 -0
  8. package-utils-0.5.0/src/package_utils/context/loaders/__init__.py +1 -0
  9. package-utils-0.5.0/src/package_utils/context/loaders/config.py +37 -0
  10. package-utils-0.5.0/src/package_utils/context/loaders/loaders.py +11 -0
  11. package-utils-0.5.0/src/package_utils/context/loaders/options.py +34 -0
  12. package-utils-0.5.0/src/package_utils/context/loaders/secrets_.py +76 -0
  13. package-utils-0.5.0/src/package_utils/context/models/__init__.py +1 -0
  14. package-utils-0.5.0/src/package_utils/context/models/models.py +17 -0
  15. package-utils-0.5.0/src/package_utils/dataclasses/__init__.py +1 -0
  16. package-utils-0.5.0/src/package_utils/dataclasses/mixins.py +25 -0
  17. {package-utils-0.4.2 → package-utils-0.5.0/src/package_utils.egg-info}/PKG-INFO +13 -4
  18. {package-utils-0.4.2 → package-utils-0.5.0}/src/package_utils.egg-info/SOURCES.txt +13 -1
  19. package-utils-0.5.0/src/package_utils.egg-info/requires.txt +17 -0
  20. package-utils-0.4.2/src/package_utils.egg-info/requires.txt +0 -6
  21. {package-utils-0.4.2 → package-utils-0.5.0}/LICENSE +0 -0
  22. {package-utils-0.4.2 → package-utils-0.5.0}/README.md +0 -0
  23. {package-utils-0.4.2 → package-utils-0.5.0}/setup.cfg +0 -0
  24. {package-utils-0.4.2 → package-utils-0.5.0}/src/package_utils/__init__.py +0 -0
  25. {package-utils-0.4.2 → package-utils-0.5.0}/src/package_utils/cli/cli_runner.py +0 -0
  26. {package-utils-0.4.2 → package-utils-0.5.0}/src/package_utils/cli/convertors/__init__.py +0 -0
  27. {package-utils-0.4.2 → package-utils-0.5.0}/src/package_utils/cli/convertors/class_.py +0 -0
  28. {package-utils-0.4.2 → package-utils-0.5.0}/src/package_utils/cli/convertors/dataclass.py +0 -0
  29. {package-utils-0.4.2 → package-utils-0.5.0}/src/package_utils/cli/convertors/method.py +0 -0
  30. {package-utils-0.4.2 → package-utils-0.5.0}/src/package_utils/cli/entry_point.py +0 -0
  31. {package-utils-0.4.2 → package-utils-0.5.0}/src/package_utils/py.typed +0 -0
  32. {package-utils-0.4.2 → package-utils-0.5.0}/src/package_utils.egg-info/dependency_links.txt +0 -0
  33. {package-utils-0.4.2 → package-utils-0.5.0}/src/package_utils.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: package-utils
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: Common utilities for Python packages
5
5
  Author-email: Quinten Roets <qdr2104@columbia.edu>
6
6
  License: MIT
@@ -9,10 +9,19 @@ Requires-Python: >=3.10
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: typer<1,>=0.9
12
+ Provides-Extra: context
13
+ Requires-Dist: dacite<2,>=1.8.1; extra == "context"
14
+ Requires-Dist: PyYaml<7,>=6.0.1; extra == "context"
15
+ Requires-Dist: superpathlib<2,>=1.3.8; extra == "context"
16
+ Provides-Extra: dataclasses
17
+ Requires-Dist: dacite<2,>=1.8.1; extra == "dataclasses"
12
18
  Provides-Extra: dev
13
- Requires-Dist: superpathlib<2,>=1.3.7; extra == "dev"
14
- Requires-Dist: package-dev-tools<1,>=0.3.1; extra == "dev"
15
- Requires-Dist: package-dev-utils<1,>=0.1.3; extra == "dev"
19
+ Requires-Dist: dacite<2,>=1.8.1; extra == "dev"
20
+ Requires-Dist: PyYaml<7,>=6.0.1; extra == "dev"
21
+ Requires-Dist: superpathlib<2,>=1.3.8; extra == "dev"
22
+ Requires-Dist: hypothesis<7,>=6.91.0; extra == "dev"
23
+ Requires-Dist: package-dev-tools<1,>=0.3.6; extra == "dev"
24
+ Requires-Dist: package-dev-utils<1,>=0.1.6; extra == "dev"
16
25
 
17
26
  # Package Utils
18
27
  [![PyPI version](https://badge.fury.io/py/package-utils.svg)](https://badge.fury.io/py/package-utils)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "package-utils"
3
- version = "0.4.2"
3
+ version = "0.5.0"
4
4
  description = "Common utilities for Python packages"
5
5
  authors = [{name = "Quinten Roets", email = "qdr2104@columbia.edu"}]
6
6
  license = {text = "MIT"}
@@ -11,10 +11,23 @@ dependencies = [
11
11
  ]
12
12
 
13
13
  [project.optional-dependencies]
14
+ context = [
15
+ "dacite >=1.8.1, <2",
16
+ "PyYaml >=6.0.1, <7",
17
+ "superpathlib >=1.3.8, <2",
18
+ ]
19
+ dataclasses = [
20
+ "dacite >=1.8.1, <2",
21
+ ]
14
22
  dev = [
15
- "superpathlib >=1.3.7, <2",
16
- "package-dev-tools >=0.3.1, <1",
17
- "package-dev-utils >= 0.1.3, <1",
23
+ # context
24
+ "dacite >=1.8.1, <2",
25
+ "PyYaml >=6.0.1, <7",
26
+ "superpathlib >=1.3.8, <2",
27
+
28
+ "hypothesis >=6.91.0, <7",
29
+ "package-dev-tools >=0.3.6, <1",
30
+ "package-dev-utils >=0.1.6, <1",
18
31
  ]
19
32
 
20
33
  [project.urls]
@@ -34,11 +47,10 @@ fail_under = 100
34
47
  [tool.mypy]
35
48
  strict = true
36
49
  no_implicit_reexport = false
37
- explicit_package_bases = true
38
50
 
39
51
  [tool.pytest.ini_options]
40
52
  pythonpath = [
41
- "src", ".",
53
+ "src", ".",
42
54
  ]
43
55
 
44
56
  [tool.ruff]
@@ -7,6 +7,8 @@ T = TypeVar("T")
7
7
  __all__ = ["create_entry_point", "instantiate_from_cli_args"]
8
8
 
9
9
 
10
- def instantiate_from_cli_args(_class: type[T]) -> T:
10
+ def instantiate_from_cli_args(_class: type[T], documented_object: object = None) -> T:
11
+ if documented_object is not None:
12
+ _class.__doc__ = documented_object.__doc__
11
13
  instantiate_entry_point = create_entry_point(_class)
12
14
  return instantiate_entry_point()
@@ -35,13 +35,14 @@ class CliParameter:
35
35
  if path_class is not None:
36
36
  self.monkey_patch_path_convertor(path_class)
37
37
  self.convert_optional_syntax()
38
- Option = typer.Argument if self.is_argument else typer.Option
39
- return Annotated[self.annotation, Option(path_type=path_class)]
38
+ OptionInfo = typer.Argument if self.is_argument else typer.Option
39
+ option_info = OptionInfo(path_type=path_class) # type: ignore
40
+ return Annotated[self.annotation, option_info]
40
41
 
41
42
  @classmethod
42
43
  def monkey_patch_path_convertor(cls, path_class: type[Path]) -> None:
43
- def convert(path: Path | None) -> Path | None:
44
- return None if path is None else path_class(path)
44
+ def convert(value: str | None = None) -> Path | None:
45
+ return None if value is None else path_class(value)
45
46
 
46
47
  typer.main.param_path_convertor = convert
47
48
 
@@ -0,0 +1 @@
1
+ from .context import Context, Models
@@ -0,0 +1,34 @@
1
+ from typing import Generic
2
+
3
+ from .loaders import Loaders
4
+ from .models import Config as Config_
5
+ from .models import Models
6
+ from .models import Options as Options_
7
+ from .models import Secrets as Secrets_
8
+
9
+
10
+ class Context(Generic[Options_, Config_, Secrets_]):
11
+ def __init__(
12
+ self,
13
+ Options: type[Options_] | None = None,
14
+ Config: type[Config_] | None = None,
15
+ Secrets: type[Secrets_] | None = None,
16
+ ) -> None:
17
+ models = Models[Options_, Config_, Secrets_](Options, Config, Secrets)
18
+ self.loaders = Loaders(models)
19
+
20
+ @property
21
+ def options(self) -> Options_:
22
+ return self.loaders.options.value
23
+
24
+ @options.setter
25
+ def options(self, options: Options_) -> None:
26
+ self.loaders.options.value = options
27
+
28
+ @property
29
+ def config(self) -> Config_:
30
+ return self.loaders.config.value
31
+
32
+ @property
33
+ def secrets(self) -> Secrets_:
34
+ return self.loaders.secrets.value
@@ -0,0 +1,21 @@
1
+ from collections.abc import Callable
2
+ from typing import TypeVar
3
+
4
+ from ..cli import instantiate_from_cli_args
5
+ from .context import Context
6
+ from .models import Config, Options, Secrets
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ def create_entry_point(
12
+ method: Callable[[], T], context: Context[Options, Config, Secrets]
13
+ ) -> Callable[[], T]:
14
+ def entry_point() -> T:
15
+ if context.loaders.options.model is not None:
16
+ context.options = instantiate_from_cli_args(
17
+ context.loaders.options.model, documented_object=method
18
+ )
19
+ return method()
20
+
21
+ return entry_point
@@ -0,0 +1 @@
1
+ from .loaders import Loaders
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from dataclasses import dataclass
5
+ from typing import Any, Generic, TypeVar
6
+
7
+ import dacite
8
+ from plib import Path
9
+
10
+ from ..models import Config, Options
11
+ from .options import Loader as OptionsLoader
12
+
13
+ if typing.TYPE_CHECKING:
14
+ from _typeshed import DataclassInstance # pragma: nocover
15
+
16
+ T = TypeVar("T")
17
+
18
+
19
+ @dataclass
20
+ class Loader(OptionsLoader[Config], Generic[Options, Config]):
21
+ options_loader: OptionsLoader[Options] | None = None
22
+
23
+ def load(self) -> DataclassInstance:
24
+ options = None if self.options_loader is None else self.options_loader.value
25
+ optional_path = options and getattr(options, "config_path", None)
26
+ path = typing.cast(Path | None, optional_path)
27
+ return (
28
+ self.typed_model()
29
+ if path is None
30
+ else self.load_from_file(self.typed_model, path)
31
+ )
32
+
33
+ @classmethod
34
+ def load_from_file(cls, class_type: type[T], path: Path) -> T:
35
+ config = dacite.Config(type_hooks={Path: Path}, strict=True)
36
+ info = typing.cast(dict[str, Any], path.yaml)
37
+ return dacite.from_dict(class_type, info, config=config)
@@ -0,0 +1,11 @@
1
+ from typing import Generic
2
+
3
+ from ..models import Config, Models, Options, Secrets
4
+ from . import config, options, secrets_
5
+
6
+
7
+ class Loaders(Generic[Options, Config, Secrets]):
8
+ def __init__(self, models: Models[Options, Config, Secrets]) -> None:
9
+ self.options = options.Loader(models.Options)
10
+ self.config = config.Loader(models.Config, options_loader=self.options)
11
+ self.secrets = secrets_.Loader(models.Secrets, config_loader=self.config)
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING, Generic, TypeVar
6
+
7
+ if TYPE_CHECKING:
8
+ from _typeshed import DataclassInstance # pragma: nocover
9
+
10
+ Model = TypeVar("Model", bound="DataclassInstance | None")
11
+
12
+
13
+ @dataclass
14
+ class Loader(Generic[Model]):
15
+ model: type[Model] | None
16
+ _value: Model | None = None
17
+
18
+ @property
19
+ def typed_model(self) -> type[DataclassInstance]:
20
+ return typing.cast(type["DataclassInstance"], self.model)
21
+
22
+ @property
23
+ def value(self) -> Model:
24
+ if self._value is None and self.model is not None:
25
+ value = self.load()
26
+ self.value = typing.cast(Model, value)
27
+ return typing.cast(Model, self._value)
28
+
29
+ @value.setter
30
+ def value(self, value: Model | None) -> None:
31
+ self._value = value
32
+
33
+ def load(self) -> DataclassInstance:
34
+ return self.typed_model()
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import os
5
+ import typing
6
+ from dataclasses import dataclass, fields, is_dataclass
7
+ from typing import Generic
8
+
9
+ import cli
10
+ import dacite
11
+ from plib import Path
12
+
13
+ from ..models import Config, Options, Secrets
14
+ from . import options
15
+ from .config import Loader as ConfigLoader
16
+
17
+ if typing.TYPE_CHECKING:
18
+ from _typeshed import DataclassInstance # pragma: nocover
19
+
20
+
21
+ from typing import TypeVar
22
+
23
+ T = TypeVar("T", bound="DataclassInstance")
24
+
25
+
26
+ @dataclass
27
+ class SecretLoader:
28
+ name: str
29
+
30
+ def load(self) -> str:
31
+ result = os.environ.get(self.name) or cli.get("pw", self.name)
32
+ return typing.cast(str, result)
33
+
34
+
35
+ @dataclass
36
+ class DataclassLoader(Generic[T]):
37
+ class_: type[T]
38
+
39
+ def load(self) -> T:
40
+ return dacite.from_dict(self.class_, {})
41
+
42
+
43
+ @dataclass
44
+ class Loader(options.Loader[Secrets], Generic[Options, Config, Secrets]):
45
+ config_loader: ConfigLoader[Options, Config] | None = None
46
+ delimiter: str = "_"
47
+
48
+ def load(self) -> DataclassInstance:
49
+ self.add_defaults(self.typed_model)
50
+ file_secrets = self.load_file_secrets()
51
+ return dacite.from_dict(self.typed_model, file_secrets)
52
+
53
+ def load_file_secrets(self) -> dict[str, str]:
54
+ config = None if self.config_loader is None else self.config_loader.value
55
+ path = config and getattr(config, "secrets_path", None)
56
+ path = typing.cast(Path, path)
57
+ result = {} if path is None else path.yaml
58
+ return typing.cast(dict[str, str], result)
59
+
60
+ def add_defaults(
61
+ self,
62
+ class_type: DataclassInstance | type[DataclassInstance],
63
+ parent_name: str = "",
64
+ ) -> None:
65
+ for field in fields(class_type):
66
+ full_name = (
67
+ f"{parent_name}{self.delimiter}{field.name}"
68
+ if parent_name
69
+ else field.name
70
+ )
71
+ if field.default_factory == dataclasses.MISSING:
72
+ if is_dataclass(field.type):
73
+ self.add_defaults(field.type, full_name)
74
+ field.default_factory = DataclassLoader(field.type).load
75
+ else:
76
+ field.default_factory = SecretLoader(full_name).load
@@ -0,0 +1 @@
1
+ from .models import Config, Models, Options, Secrets
@@ -0,0 +1,17 @@
1
+ import typing
2
+ from dataclasses import dataclass
3
+ from typing import Any, Generic, TypeVar
4
+
5
+ if typing.TYPE_CHECKING:
6
+ from _typeshed import DataclassInstance # pragma: nocover
7
+
8
+ Options = TypeVar("Options", bound=Any | None)
9
+ Config = TypeVar("Config", bound="DataclassInstance | None")
10
+ Secrets = TypeVar("Secrets", bound="DataclassInstance | None")
11
+
12
+
13
+ @dataclass
14
+ class Models(Generic[Options, Config, Secrets]):
15
+ Options: type[Options] | None = None
16
+ Config: type[Config] | None = None
17
+ Secrets: type[Secrets] | None = None
@@ -0,0 +1 @@
1
+ from .mixins import SerializationMixin
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from dataclasses import asdict
5
+ from typing import Any, TypeVar
6
+
7
+ import dacite
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from _typeshed import DataclassInstance # pragma: nocover
11
+
12
+ T = TypeVar("T")
13
+
14
+
15
+ class SerializationMixin:
16
+ @classmethod
17
+ def from_dict(
18
+ cls: type[T], items: dict[str, Any], config: dacite.Config | None = None
19
+ ) -> T:
20
+ if config is None:
21
+ config = dacite.Config(strict=True)
22
+ return dacite.from_dict(cls, items, config=config)
23
+
24
+ def dict(self: DataclassInstance) -> dict[str, Any]:
25
+ return asdict(self)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: package-utils
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: Common utilities for Python packages
5
5
  Author-email: Quinten Roets <qdr2104@columbia.edu>
6
6
  License: MIT
@@ -9,10 +9,19 @@ Requires-Python: >=3.10
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: typer<1,>=0.9
12
+ Provides-Extra: context
13
+ Requires-Dist: dacite<2,>=1.8.1; extra == "context"
14
+ Requires-Dist: PyYaml<7,>=6.0.1; extra == "context"
15
+ Requires-Dist: superpathlib<2,>=1.3.8; extra == "context"
16
+ Provides-Extra: dataclasses
17
+ Requires-Dist: dacite<2,>=1.8.1; extra == "dataclasses"
12
18
  Provides-Extra: dev
13
- Requires-Dist: superpathlib<2,>=1.3.7; extra == "dev"
14
- Requires-Dist: package-dev-tools<1,>=0.3.1; extra == "dev"
15
- Requires-Dist: package-dev-utils<1,>=0.1.3; extra == "dev"
19
+ Requires-Dist: dacite<2,>=1.8.1; extra == "dev"
20
+ Requires-Dist: PyYaml<7,>=6.0.1; extra == "dev"
21
+ Requires-Dist: superpathlib<2,>=1.3.8; extra == "dev"
22
+ Requires-Dist: hypothesis<7,>=6.91.0; extra == "dev"
23
+ Requires-Dist: package-dev-tools<1,>=0.3.6; extra == "dev"
24
+ Requires-Dist: package-dev-utils<1,>=0.1.6; extra == "dev"
16
25
 
17
26
  # Package Utils
18
27
  [![PyPI version](https://badge.fury.io/py/package-utils.svg)](https://badge.fury.io/py/package-utils)
@@ -15,4 +15,16 @@ src/package_utils/cli/convertors/__init__.py
15
15
  src/package_utils/cli/convertors/class_.py
16
16
  src/package_utils/cli/convertors/dataclass.py
17
17
  src/package_utils/cli/convertors/method.py
18
- src/package_utils/cli/convertors/parameter.py
18
+ src/package_utils/cli/convertors/parameter.py
19
+ src/package_utils/context/__init__.py
20
+ src/package_utils/context/context.py
21
+ src/package_utils/context/entry_point.py
22
+ src/package_utils/context/loaders/__init__.py
23
+ src/package_utils/context/loaders/config.py
24
+ src/package_utils/context/loaders/loaders.py
25
+ src/package_utils/context/loaders/options.py
26
+ src/package_utils/context/loaders/secrets_.py
27
+ src/package_utils/context/models/__init__.py
28
+ src/package_utils/context/models/models.py
29
+ src/package_utils/dataclasses/__init__.py
30
+ src/package_utils/dataclasses/mixins.py
@@ -0,0 +1,17 @@
1
+ typer<1,>=0.9
2
+
3
+ [context]
4
+ dacite<2,>=1.8.1
5
+ PyYaml<7,>=6.0.1
6
+ superpathlib<2,>=1.3.8
7
+
8
+ [dataclasses]
9
+ dacite<2,>=1.8.1
10
+
11
+ [dev]
12
+ dacite<2,>=1.8.1
13
+ PyYaml<7,>=6.0.1
14
+ superpathlib<2,>=1.3.8
15
+ hypothesis<7,>=6.91.0
16
+ package-dev-tools<1,>=0.3.6
17
+ package-dev-utils<1,>=0.1.6
@@ -1,6 +0,0 @@
1
- typer<1,>=0.9
2
-
3
- [dev]
4
- superpathlib<2,>=1.3.7
5
- package-dev-tools<1,>=0.3.1
6
- package-dev-utils<1,>=0.1.3
File without changes
File without changes
File without changes