python-library-registry 0.1.3__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.
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ .env
9
+ .pytest_cache/
10
+ config.yaml
11
+ logs/
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-library-registry
3
+ Version: 0.1.3
4
+ Requires-Python: >=3.10
5
+ Requires-Dist: python-library-tree-model
@@ -0,0 +1,17 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "python-library-registry"
7
+ version = "0.1.3"
8
+ requires-python = ">=3.10"
9
+ dependencies = [
10
+ "python-library-tree-model",
11
+ ]
12
+
13
+ [tool.hatch.metadata]
14
+ allow-direct-references = true
15
+
16
+ [tool.hatch.build.targets.wheel]
17
+ packages = ["registry"]
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+
3
+ from difflib import get_close_matches
4
+ from typing import Any
5
+
6
+ from pydantic import Field
7
+ from tree_model import TreeModel
8
+
9
+
10
+ class EntryNode(TreeModel):
11
+ obj: Any | None = Field(default=None, description="注册对象")
12
+
13
+
14
+ class Registry:
15
+ def __init__(
16
+ self,
17
+ namespace: str = "",
18
+ *,
19
+ _root: EntryNode | None = None,
20
+ ) -> None:
21
+ """
22
+ Args:
23
+ namespace: 命名空间
24
+ Attributes:
25
+ _namespace: 命名空间
26
+ _root: 根节点
27
+ """
28
+ self._namespace = namespace.strip(".")
29
+ self._root = EntryNode(name="") if _root is None else _root
30
+
31
+ def _full_name(self, name: str) -> str:
32
+ if not self._namespace:
33
+ return name
34
+ return f"{self._namespace}.{name}"
35
+
36
+ def _get_or_create_node(self, full_name: str) -> EntryNode:
37
+ node = self._root
38
+ for part in filter(None, full_name.split(".")):
39
+ child = node.find_child(lambda item: item.name == part)
40
+ if child is None:
41
+ child = EntryNode(name=part)
42
+ node.add_child(child)
43
+ node = child
44
+ return node
45
+
46
+ def _find_node(self, full_name: str) -> EntryNode | None:
47
+ node: EntryNode = self._root
48
+ for part in filter(None, full_name.split(".")):
49
+ found = node.find_child(lambda item: item.name == part)
50
+ if found is None:
51
+ return None
52
+ node = found
53
+ return node
54
+
55
+ def get_registered_names(self) -> list[str]:
56
+ """获取已注册的名称列表"""
57
+ names: list[str] = []
58
+
59
+ def walk(node: EntryNode) -> None:
60
+ if node.obj is not None and node.full_name:
61
+ names.append(node.full_name)
62
+ for child in node:
63
+ walk(child)
64
+
65
+ for child in self._root:
66
+ walk(child)
67
+
68
+ return names
69
+
70
+ def _suggest_name(self, full_name: str) -> str | None:
71
+ matches = get_close_matches(
72
+ full_name,
73
+ self.get_registered_names(),
74
+ n=1,
75
+ cutoff=0.6,
76
+ )
77
+ return matches[0] if matches else None
78
+
79
+ def register(self, name: str, obj: Any) -> Any:
80
+ """注册对象"""
81
+ full_name = self._full_name(name)
82
+
83
+ node = self._get_or_create_node(full_name)
84
+ if node.obj is not None:
85
+ raise ValueError(f"重复注册 {full_name!r}")
86
+ node.obj = obj
87
+ return obj
88
+
89
+ def __call__(self, name: str, obj: Any | None = None) -> Any:
90
+ if obj is not None:
91
+ return self.register(name=name, obj=obj)
92
+ def decorator(target: Any) -> Any:
93
+ return self.register(name=name, obj=target)
94
+ return decorator
95
+
96
+ def namespace(self, full_name: str) -> "Registry":
97
+ """命名空间"""
98
+ full_name = full_name.strip(".")
99
+ if not full_name:
100
+ return self
101
+ namespace = f"{self._namespace}.{full_name}" if self._namespace else full_name
102
+ return Registry(namespace=namespace, _root=self._root)
103
+
104
+ def get(self, name: str) -> Any:
105
+ """获取注册对象"""
106
+ full_name = self._full_name(name)
107
+ node = self._find_node(full_name)
108
+ if node is None or node.obj is None:
109
+ suggested_name = self._suggest_name(full_name)
110
+ message = f"未找到 {full_name!r}"
111
+ if suggested_name is not None:
112
+ message += f",是否想找 {suggested_name!r}"
113
+ raise ValueError(message)
114
+ return node.obj
@@ -0,0 +1,10 @@
1
+ @echo off
2
+ cd /d %~dp0
3
+
4
+ if not exist .venv (
5
+ python -m venv .venv
6
+ )
7
+
8
+ call .venv\Scripts\activate.bat
9
+ python -m pip install -e .
10
+ python -m unittest discover -s tests -p "test_*.py"
@@ -0,0 +1,40 @@
1
+ import unittest
2
+
3
+ from registry import Registry
4
+
5
+
6
+ class RegistryTests(unittest.TestCase):
7
+ def test_decorator_registration(self) -> None:
8
+ registry = Registry()
9
+ event = registry.namespace("event")
10
+ condition = registry.namespace("condition")
11
+
12
+ @event("startup")
13
+ class StartupEvent:
14
+ pass
15
+
16
+ @condition("is_admin")
17
+ class IsAdmin:
18
+ pass
19
+
20
+ @registry("send_mail")
21
+ class SendMail:
22
+ pass
23
+
24
+ self.assertIs(registry.get("event.startup"), StartupEvent)
25
+ self.assertIs(registry.get("condition.is_admin"), IsAdmin)
26
+ self.assertIs(registry.get("send_mail"), SendMail)
27
+
28
+ def test_namespaced_registry_get_local_name(self) -> None:
29
+ registry = Registry()
30
+ event = registry.namespace("event")
31
+ @event("startup")
32
+ class StartupEvent:
33
+ pass
34
+
35
+ self.assertIs(event.get("startup"), StartupEvent)
36
+ self.assertIs(registry.get("event.startup"), StartupEvent)
37
+
38
+
39
+ if __name__ == "__main__":
40
+ unittest.main()