facetkit 0.1.0__tar.gz → 0.2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: facetkit
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A composable dependency container with passive UI and service facets.
5
5
  Author-email: "Daniel R. Vásquez Montes" <dev.DanielR@gmail.composable>
6
6
  License: MIT
@@ -143,7 +143,7 @@ app.unmount_facet("cli")
143
143
  - **Duplicate keys** — registering the same name again overwrites the previous entry (last wins).
144
144
  - **Unmount** — `unmount_facet` clears that facet's registries. Attached components are not automatically detached; call `remove_component` first if you need a full teardown.
145
145
  - **Facet names** — the mount name is arbitrary (`app.mount_facet("cli", CliFacet())`). The library does not enforce that the name matches the facet type.
146
- - **Missing facets** — `app.facets["cli"]` raises `KeyError`; `app.get("facets.cli")` returns `None`.
146
+ - **Missing facets** — `app.facets["cli"]` raises `KeyError`; `app.get("facets.cli")` raises `PathAccessError`; `app.get("facets.cli", default=None)` returns `None`.
147
147
 
148
148
  ## Introspection with `get()`
149
149
 
@@ -158,14 +158,19 @@ app.get("facets.cli.commands") # command registry
158
158
  app.get("facets.web.routes.users") # a single route entry
159
159
  ```
160
160
 
161
- Missing paths return `default` (or `None`):
161
+ Without `default`, resolution errors propagate (missing paths, glom failures, etc.):
162
162
 
163
163
  ```python
164
- app.get("facets.missing") # None
165
- app.get("facets.missing", default={}) # {}
164
+ app.get("facets.cli.commands") # works when the cli facet is mounted
165
+ app.get("facets.missing") # raises glom.PathAccessError
166
166
  ```
167
167
 
168
- `get()` also returns `default` on any internal error while resolving a path, not only missing keys. That keeps introspection safe but can hide bugs prefer direct attribute access when you want exceptions to surface.
168
+ Pass `default` for optional lookupsany resolution error returns that value:
169
+
170
+ ```python
171
+ app.get("facets.missing", default=None) # None
172
+ app.get("facets.missing", default={}) # {}
173
+ ```
169
174
 
170
175
  Direct dict access also works when you know a facet is mounted:
171
176
 
@@ -117,7 +117,7 @@ app.unmount_facet("cli")
117
117
  - **Duplicate keys** — registering the same name again overwrites the previous entry (last wins).
118
118
  - **Unmount** — `unmount_facet` clears that facet's registries. Attached components are not automatically detached; call `remove_component` first if you need a full teardown.
119
119
  - **Facet names** — the mount name is arbitrary (`app.mount_facet("cli", CliFacet())`). The library does not enforce that the name matches the facet type.
120
- - **Missing facets** — `app.facets["cli"]` raises `KeyError`; `app.get("facets.cli")` returns `None`.
120
+ - **Missing facets** — `app.facets["cli"]` raises `KeyError`; `app.get("facets.cli")` raises `PathAccessError`; `app.get("facets.cli", default=None)` returns `None`.
121
121
 
122
122
  ## Introspection with `get()`
123
123
 
@@ -132,14 +132,19 @@ app.get("facets.cli.commands") # command registry
132
132
  app.get("facets.web.routes.users") # a single route entry
133
133
  ```
134
134
 
135
- Missing paths return `default` (or `None`):
135
+ Without `default`, resolution errors propagate (missing paths, glom failures, etc.):
136
136
 
137
137
  ```python
138
- app.get("facets.missing") # None
139
- app.get("facets.missing", default={}) # {}
138
+ app.get("facets.cli.commands") # works when the cli facet is mounted
139
+ app.get("facets.missing") # raises glom.PathAccessError
140
140
  ```
141
141
 
142
- `get()` also returns `default` on any internal error while resolving a path, not only missing keys. That keeps introspection safe but can hide bugs prefer direct attribute access when you want exceptions to surface.
142
+ Pass `default` for optional lookupsany resolution error returns that value:
143
+
144
+ ```python
145
+ app.get("facets.missing", default=None) # None
146
+ app.get("facets.missing", default={}) # {}
147
+ ```
143
148
 
144
149
  Direct dict access also works when you know a facet is mounted:
145
150
 
@@ -38,4 +38,4 @@ __all__ = [
38
38
  "WebFacet",
39
39
  ]
40
40
 
41
- __version__ = "0.1.0"
41
+ __version__ = "0.2.0"
@@ -3,13 +3,15 @@
3
3
 
4
4
  import glom
5
5
 
6
- from typing import Dict, Any, Optional
7
- from facetkit.types import Component, Facet
8
-
9
- #===============================================================================
10
- # DEFINITIONS
11
-
12
- class Container:
6
+ from typing import Dict, Any
7
+ from facetkit.types import Component, Facet
8
+
9
+ #===============================================================================
10
+ # DEFINITIONS
11
+
12
+ _UNSET = object()
13
+
14
+ class Container:
13
15
 
14
16
  def __init__(self, config: Dict[str, Any]):
15
17
  self.config : Dict[str, Any] = config
@@ -18,16 +20,22 @@ class Container:
18
20
 
19
21
  # Public API ===============================================================
20
22
 
21
- def get(self, path: str, default: Any = None) -> Any:
22
- """Retrieve from container using glom."""
23
-
24
- if not path: return self
25
- try:
26
- return glom.glom(self.__dict__, path)
27
- except (glom.PathAccessError, glom.GlomError):
28
- return default
29
- except Exception:
30
- return default
23
+ def get(self, path: str, default: Any = _UNSET) -> Any:
24
+ """Retrieve from container using glom.
25
+
26
+ Raises glom errors when *default* is omitted. Pass *default* to
27
+ return that value instead when the path cannot be resolved.
28
+ """
29
+
30
+ if not path: return self
31
+ try:
32
+ return glom.glom(self.__dict__, path)
33
+ except (glom.PathAccessError, glom.GlomError):
34
+ if default is _UNSET: raise
35
+ return default
36
+ except Exception:
37
+ if default is _UNSET: raise
38
+ return default
31
39
 
32
40
  def mount_facet(self, name: str, facet: Facet) -> None:
33
41
  if name in self.facets: self.unmount_facet(name)
@@ -44,8 +44,28 @@ class TestContainerGet:
44
44
  def test_missing_path_returns_default(self, container):
45
45
  assert container.get("config.missing", default="fallback") == "fallback"
46
46
 
47
- def test_missing_path_returns_none_by_default(self, container):
48
- assert container.get("config.missing") is None
47
+ def test_missing_path_returns_none_when_default_is_none(self, container):
48
+ assert container.get("config.missing", default=None) is None
49
+
50
+ def test_missing_path_raises_without_default(self, container):
51
+ with pytest.raises(glom.PathAccessError):
52
+ container.get("config.missing")
53
+
54
+ def test_glom_error_raises_without_default(self, container, monkeypatch):
55
+ def raise_glom_error(*_args, **_kwargs):
56
+ raise glom.GlomError("boom")
57
+
58
+ monkeypatch.setattr(container_module.glom, "glom", raise_glom_error)
59
+ with pytest.raises(glom.GlomError):
60
+ container.get("config.app.name")
61
+
62
+ def test_unexpected_exception_raises_without_default(self, container, monkeypatch):
63
+ def raise_runtime_error(*_args, **_kwargs):
64
+ raise RuntimeError("unexpected")
65
+
66
+ monkeypatch.setattr(container_module.glom, "glom", raise_runtime_error)
67
+ with pytest.raises(RuntimeError):
68
+ container.get("config.app.name")
49
69
 
50
70
  def test_glom_error_returns_default(self, container, monkeypatch):
51
71
  def raise_glom_error(*_args, **_kwargs):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes