Coverage for src/lexigram/admin/resources/namespace.py: 0%
17 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 23:18 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 23:18 +0800
1"""Runtime namespace helper for contributor-supplied Resource classes."""
3from __future__ import annotations
5import re
7_CACHE: dict[tuple[type, str], type] = {}
10def _sanitize_package_segment(namespaced_name: str) -> str:
11 """Return *namespaced_name* with non-slug characters replaced by underscores.
13 Contributor package sources are distribution names, where hyphens are
14 legal ("lexigram-template"); resource slugs only allow ``[a-z0-9_]``.
15 The package portion is sanitized so the namespaced name passes
16 :func:`lexigram.admin.resources.base._validate_resource_name`.
18 Args:
19 namespaced_name: Dotted provider-package name.
21 Returns:
22 The sanitized dotted name.
23 """
24 package, _, slug = namespaced_name.partition(".")
25 safe_package = re.sub(r"[^a-z0-9_]", "_", package.lower())
26 return f"{safe_package}.{slug}" if slug else safe_package
29def apply_namespace(resource_cls: type, namespaced_name: str) -> type:
30 """Return a subclass of *resource_cls* with its name set to *namespaced_name*.
32 The returned class inherits all attributes from the original and adds a
33 ``route_prefix`` derived from the dotted name:
34 ``"fake_pkg.users"`` → ``route_prefix = "/fake_pkg/users"``.
36 Idempotent: calling twice with the same arguments returns the same class.
37 """
38 safe_name = _sanitize_package_segment(namespaced_name)
39 key = (resource_cls, safe_name)
40 if key in _CACHE:
41 return _CACHE[key]
43 package, _, slug = safe_name.partition(".")
44 route_prefix = f"/{package}/{slug}"
46 wrapped = type(
47 f"Namespaced_{resource_cls.__name__}",
48 (resource_cls,),
49 {
50 "name": safe_name,
51 "route_prefix": route_prefix,
52 },
53 )
54 _CACHE[key] = wrapped
55 return wrapped