yaafcli 2026.2.4.180929__py3-none-any.whl → 2026.2.6.22353__py3-none-any.whl

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.
yaaf/cli.py CHANGED
@@ -23,7 +23,7 @@ def main() -> None:
23
23
  serve_parser.add_argument("--consumers-dir", default="consumers")
24
24
  serve_parser.set_defaults(command="serve")
25
25
 
26
- gen_parser = subparsers.add_parser("gen-services", help="Generate consumers/services.py")
26
+ gen_parser = subparsers.add_parser("gen-services", help="Generate consumers/api/__init__.py")
27
27
  gen_parser.add_argument("--consumers-dir", default="consumers")
28
28
  gen_parser.add_argument("--output", default=None)
29
29
  gen_parser.set_defaults(command="gen-services")
yaaf/di.py CHANGED
@@ -29,9 +29,22 @@ class ServiceRegistry:
29
29
  """Resolve a service by type annotation."""
30
30
  if isinstance(annotation, str):
31
31
  return self.by_alias.get(annotation)
32
- if annotation and annotation in self.by_type:
33
- return self.by_type[annotation]
34
32
  if annotation is not None:
33
+ # Direct type match
34
+ if annotation in self.by_type:
35
+ return self.by_type[annotation]
36
+
37
+ # Check for protocol/base class relationships
38
+ for registered_type, instance in self.by_type.items():
39
+ try:
40
+ # Check if registered type is a subclass of the annotation
41
+ if issubclass(registered_type, annotation):
42
+ return instance
43
+ except (TypeError, AttributeError):
44
+ # Handle cases where issubclass fails (e.g., for Protocol types)
45
+ pass
46
+
47
+ # Try to resolve by name as fallback
35
48
  alias = getattr(annotation, "__name__", "")
36
49
  if alias and alias in self.by_alias:
37
50
  return self.by_alias[alias]
yaaf/gen_services.py CHANGED
@@ -1,4 +1,4 @@
1
- """Generate a consumers/services.py typing module from filesystem routes."""
1
+ """Generate a consumers/api/__init__.py typing module from filesystem routes."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -14,7 +14,9 @@ def _is_identifier(segment: str) -> bool:
14
14
 
15
15
 
16
16
  def _camel_case(parts: Iterable[str]) -> str:
17
- return "".join(part[:1].upper() + part[1:] for part in parts if part)
17
+ return "".join(
18
+ sub[:1].upper() + sub[1:] for part in parts if part for sub in part.split("_") if sub
19
+ )
18
20
 
19
21
 
20
22
  def _strip_dynamic(segment: str) -> str:
@@ -32,9 +34,11 @@ def _service_alias(route_parts: list[str]) -> str:
32
34
 
33
35
  def generate_services(consumers_dir: str = "consumers", output_path: str | None = None) -> Path:
34
36
  base = Path(consumers_dir)
35
- out_path = Path(output_path) if output_path else base / "services.py"
37
+ out_path = Path(output_path) if output_path else base / "api" / "__init__.py"
36
38
 
37
39
  if not base.exists():
40
+ if not out_path.parent.exists():
41
+ out_path.parent.mkdir(parents=True, exist_ok=True)
38
42
  out_path.write_text(HEADER + "\n__all__ = []\n")
39
43
  return out_path
40
44
 
@@ -76,5 +80,7 @@ def generate_services(consumers_dir: str = "consumers", output_path: str | None
76
80
  if dynamic_aliases:
77
81
  lines.append("\n# Dynamic routes use Protocol stubs (invalid import paths).\n")
78
82
 
83
+ if not out_path.parent.exists():
84
+ out_path.parent.mkdir(parents=True, exist_ok=True)
79
85
  out_path.write_text("".join(lines))
80
86
  return out_path
yaaf/loader.py CHANGED
@@ -72,7 +72,7 @@ def discover_routes(consumers_dir: str) -> tuple[list[RouteTarget], ServiceRegis
72
72
  service_aliases: dict[Path, list[str]] = {}
73
73
 
74
74
  for root, _dirs, files in os.walk(base):
75
- if "_server.py" not in files or "_service.py" not in files:
75
+ if "_server.py" not in files:
76
76
  continue
77
77
 
78
78
  root_path = Path(root)
@@ -90,7 +90,8 @@ def discover_routes(consumers_dir: str) -> tuple[list[RouteTarget], ServiceRegis
90
90
  aliases.append(route_parts[-1])
91
91
  service_aliases[root_path] = [alias for alias in aliases if alias]
92
92
 
93
- service_modules[root_path] = _load_module(root_path / "_service.py", "service")
93
+ if "_service.py" in files:
94
+ service_modules[root_path] = _load_module(root_path / "_service.py", "service")
94
95
  server_modules[root_path] = _load_module(root_path / "_server.py", "server")
95
96
 
96
97
  registry = ServiceRegistry(by_type={}, by_alias={})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yaafcli
3
- Version: 2026.2.4.180929
3
+ Version: 2026.2.6.22353
4
4
  Summary: Minimal ASGI app scaffold
5
5
  License: MIT License
6
6
 
@@ -105,7 +105,7 @@ In `_server.py`, export functions named after HTTP methods (lowercase): `get`, `
105
105
  Example `_server.py`:
106
106
 
107
107
  ```python
108
- from consumers.services import HelloService
108
+ from consumers.api import HelloService
109
109
  from yaaf import Request
110
110
  from yaaf.types import Params
111
111
 
@@ -117,7 +117,7 @@ async def get(request: Request, service: HelloService, params: Params):
117
117
  In `_service.py`, expose a module-level `service` instance (or a callable like `Service` or `get_service`). Services are registered and can be injected into other services or handlers:
118
118
 
119
119
  ```python
120
- from consumers.services import UsersService
120
+ from consumers.api import UsersService
121
121
 
122
122
 
123
123
  class Service:
@@ -157,7 +157,7 @@ service = Service()
157
157
 
158
158
  `consumers/api/hello/_service.py`
159
159
  ```python
160
- from yaaf.services import UsersService
160
+ from consumers.api import UsersService
161
161
 
162
162
 
163
163
  class Service:
@@ -173,7 +173,7 @@ service = Service
173
173
 
174
174
  `consumers/api/hello/_server.py`
175
175
  ```python
176
- from consumers.services import HelloService
176
+ from consumers.api import HelloService
177
177
  from yaaf import Request
178
178
 
179
179
 
@@ -197,7 +197,7 @@ python scripts/bump_version.py
197
197
 
198
198
  ## Service Type Generation
199
199
 
200
- Every `yaaf` command regenerates `consumers/services.py` for type-checking. You can also run it explicitly:
200
+ Every `yaaf` command regenerates `consumers/api/__init__.py` for type-checking. You can also run it explicitly:
201
201
 
202
202
  ```bash
203
203
  yaaf gen-services
@@ -0,0 +1,16 @@
1
+ yaaf/__init__.py,sha256=mH1F6chTA4kRIaYK-yA1E7pBMOyReKi3rbrMiqKTxUY,602
2
+ yaaf/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
3
+ yaaf/app.py,sha256=7YrPl2_OZ-8NUy1RvUEBAbSntcqlP_in-2TLrKmGooE,3339
4
+ yaaf/cli.py,sha256=I8KlridkkikWwOO2BPwbqXoATP_RXZ0aHtCE26wMnao,1676
5
+ yaaf/di.py,sha256=yoyTuL0Vc4MmGj_71M04JpLAzqBRREniZk1f2opEuEE,3039
6
+ yaaf/gen_services.py,sha256=f7OHHhvoPtcD_09p08Lf6i_N0KkM7wlrUsDOaoCHckg,3127
7
+ yaaf/loader.py,sha256=q8BV5kAeYCtX5sBRy7KYy9CfPShlJyfrv2TfjT18Zu0,7224
8
+ yaaf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ yaaf/responses.py,sha256=iXZ0vDYfGO-BEUBBsobCR4JfWLRx8zhwYD9taQ8Sat4,2616
10
+ yaaf/types.py,sha256=BAD3Wkjqi4NKxNz2bT1xggPx0jU5Sz_hOvsHXmGszig,1262
11
+ yaafcli-2026.2.6.22353.dist-info/licenses/LICENSE,sha256=ESYyLizI0WWtxMeS7rGVcX3ivMezm-HOd5WdeOh-9oU,1056
12
+ yaafcli-2026.2.6.22353.dist-info/METADATA,sha256=ef4rWld98zs5mlfFT3ygcEygxIwlIhsRh4hTmRufAcg,6236
13
+ yaafcli-2026.2.6.22353.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
+ yaafcli-2026.2.6.22353.dist-info/entry_points.txt,sha256=dh9ijq3gnFIr46RFjPjGR48PHQMUxwHopj3b9JipCRQ,39
15
+ yaafcli-2026.2.6.22353.dist-info/top_level.txt,sha256=6WqEmcTdZROTeK4-7HzkB5h9JMBgs51otonH6vnjcAc,5
16
+ yaafcli-2026.2.6.22353.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- yaaf/__init__.py,sha256=mH1F6chTA4kRIaYK-yA1E7pBMOyReKi3rbrMiqKTxUY,602
2
- yaaf/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
3
- yaaf/app.py,sha256=7YrPl2_OZ-8NUy1RvUEBAbSntcqlP_in-2TLrKmGooE,3339
4
- yaaf/cli.py,sha256=X8YmeozhWjNtbEiyg_wTK75f_d-5LPKYkQmkU6v8dm0,1672
5
- yaaf/di.py,sha256=ZrGNBswJT4a3shYLgqd6hhqWii1UmiVmZ93B-G2sHWE,2448
6
- yaaf/gen_services.py,sha256=Gov7IlSLMG5Oo8RoRGHCbxHSsb_qecoh7IBZPWZKEe8,2869
7
- yaaf/loader.py,sha256=OzN2J5xl_sWGyNEaPz2LcuSEGfRPWHAGAs7QoK2JUdY,7215
8
- yaaf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- yaaf/responses.py,sha256=iXZ0vDYfGO-BEUBBsobCR4JfWLRx8zhwYD9taQ8Sat4,2616
10
- yaaf/types.py,sha256=BAD3Wkjqi4NKxNz2bT1xggPx0jU5Sz_hOvsHXmGszig,1262
11
- yaafcli-2026.2.4.180929.dist-info/licenses/LICENSE,sha256=ESYyLizI0WWtxMeS7rGVcX3ivMezm-HOd5WdeOh-9oU,1056
12
- yaafcli-2026.2.4.180929.dist-info/METADATA,sha256=weCYQZBxNrbDD9H7gKcP0nGkBW2F84DN3aIiwpQ43iQ,6248
13
- yaafcli-2026.2.4.180929.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
- yaafcli-2026.2.4.180929.dist-info/entry_points.txt,sha256=dh9ijq3gnFIr46RFjPjGR48PHQMUxwHopj3b9JipCRQ,39
15
- yaafcli-2026.2.4.180929.dist-info/top_level.txt,sha256=6WqEmcTdZROTeK4-7HzkB5h9JMBgs51otonH6vnjcAc,5
16
- yaafcli-2026.2.4.180929.dist-info/RECORD,,