kapro 0.0.1__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.
- kapro-0.0.1/PKG-INFO +167 -0
- kapro-0.0.1/README.md +150 -0
- kapro-0.0.1/pyproject.toml +193 -0
- kapro-0.0.1/setup.cfg +4 -0
- kapro-0.0.1/src/kapro/__init__.py +72 -0
- kapro-0.0.1/src/kapro/cached/__init__.py +29 -0
- kapro-0.0.1/src/kapro/cached/instance.py +108 -0
- kapro-0.0.1/src/kapro/cached/instance.pyi +53 -0
- kapro-0.0.1/src/kapro/cached/klass.py +240 -0
- kapro-0.0.1/src/kapro/cached/klass.pyi +72 -0
- kapro-0.0.1/src/kapro/cached/mixed.py +146 -0
- kapro-0.0.1/src/kapro/cached/mixed.pyi +59 -0
- kapro-0.0.1/src/kapro/cached/post.py +70 -0
- kapro-0.0.1/src/kapro/cached/post.pyi +29 -0
- kapro-0.0.1/src/kapro/cached/pre.py +70 -0
- kapro-0.0.1/src/kapro/cached/pre.pyi +29 -0
- kapro-0.0.1/src/kapro/class_property.py +204 -0
- kapro-0.0.1/src/kapro/class_property.pyi +96 -0
- kapro-0.0.1/src/kapro/primitives.py +331 -0
- kapro-0.0.1/src/kapro/primitives.pyi +79 -0
- kapro-0.0.1/src/kapro/py.typed +0 -0
- kapro-0.0.1/src/kapro.egg-info/PKG-INFO +167 -0
- kapro-0.0.1/src/kapro.egg-info/SOURCES.txt +32 -0
- kapro-0.0.1/src/kapro.egg-info/dependency_links.txt +1 -0
- kapro-0.0.1/src/kapro.egg-info/requires.txt +1 -0
- kapro-0.0.1/src/kapro.egg-info/top_level.txt +1 -0
- kapro-0.0.1/tests/test_cached.py +836 -0
- kapro-0.0.1/tests/test_cached_types.py +160 -0
- kapro-0.0.1/tests/test_class_property.py +402 -0
- kapro-0.0.1/tests/test_class_property_types.py +77 -0
- kapro-0.0.1/tests/test_pin_here.py +425 -0
- kapro-0.0.1/tests/test_pin_types.py +81 -0
- kapro-0.0.1/tests/test_primitives.py +588 -0
- kapro-0.0.1/tests/test_properties.py +1193 -0
kapro-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kapro
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: kapro
|
|
5
|
+
Author-email: Alex Kalaverin <alex@kalaver.in>
|
|
6
|
+
Project-URL: Homepage, https://kalaver.in
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: Programming Language :: Python
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: <3.14,>=3.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: kain>=1.1.11
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
title: kapro
|
|
20
|
+
description: Class-level, mixed, and pinned descriptors for Python
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
[ref: #kapro]
|
|
24
|
+
|
|
25
|
+
# kapro
|
|
26
|
+
|
|
27
|
+
`kapro` is a small Python library of property descriptors.
|
|
28
|
+
|
|
29
|
+
It gives you three public entry points:
|
|
30
|
+
|
|
31
|
+
- `class_property` — a descriptor that lives on the class and is shared by every instance.
|
|
32
|
+
- `mixed_property` — a descriptor that can be defined on the class but resolved per instance.
|
|
33
|
+
- `pin` — a cached instance property that stores its value in the instance dictionary.
|
|
34
|
+
|
|
35
|
+
[ref: #installation]
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
Install with `uv`:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv add kapro
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or with any PEP 517-compatible tool:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install kapro
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
[ref: #class-property]
|
|
52
|
+
|
|
53
|
+
## Class-level properties with `class_property`
|
|
54
|
+
|
|
55
|
+
`class_property` behaves like a class-level attribute, but it is computed lazily and cached on the class.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from kapro import class_property
|
|
59
|
+
|
|
60
|
+
class Config:
|
|
61
|
+
@class_property
|
|
62
|
+
def name(cls) -> str:
|
|
63
|
+
return "default"
|
|
64
|
+
|
|
65
|
+
assert Config.name == "default"
|
|
66
|
+
assert Config().name == "default"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The value is stored on the class, so every instance sees the same value.
|
|
70
|
+
|
|
71
|
+
[ref: #mixed-property]
|
|
72
|
+
|
|
73
|
+
## Mixed properties with `mixed_property`
|
|
74
|
+
|
|
75
|
+
`mixed_property` calls the same function with the class when accessed on the class and with the instance when accessed on an instance.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from kapro import mixed_property
|
|
79
|
+
|
|
80
|
+
class User:
|
|
81
|
+
def __init__(self, id: int) -> None:
|
|
82
|
+
self.id = id
|
|
83
|
+
|
|
84
|
+
@mixed_property
|
|
85
|
+
def label(node) -> str:
|
|
86
|
+
if isinstance(node, type):
|
|
87
|
+
return "User model"
|
|
88
|
+
return f"user: {node.id}"
|
|
89
|
+
|
|
90
|
+
assert User.label == "User model"
|
|
91
|
+
assert User(id=7).label == "user: 7"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
[ref: #pin]
|
|
95
|
+
|
|
96
|
+
## Cached instance properties with `pin`
|
|
97
|
+
|
|
98
|
+
`pin` is the cached equivalent of a plain method-based property.
|
|
99
|
+
The first access computes the value and stores it in the instance dictionary.
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from kapro import pin
|
|
103
|
+
|
|
104
|
+
class Expensive:
|
|
105
|
+
@pin
|
|
106
|
+
def payload(self) -> dict:
|
|
107
|
+
return {"loaded": True}
|
|
108
|
+
|
|
109
|
+
obj = Expensive()
|
|
110
|
+
assert obj.payload is obj.payload
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
[ref: #pin-flavors]
|
|
114
|
+
|
|
115
|
+
### `pin` flavors
|
|
116
|
+
|
|
117
|
+
`pin` exposes several variants through class attributes.
|
|
118
|
+
|
|
119
|
+
| Flavor | Decorator | Caches on |
|
|
120
|
+
| --- | --- | --- |
|
|
121
|
+
| `native` | `@pin` | instance (default) |
|
|
122
|
+
| `cls` | `@pin.cls` | class |
|
|
123
|
+
| `any` | `@pin.any` | class or instance |
|
|
124
|
+
| `pre` | `@pin.pre` | instance, runs before the underlying call |
|
|
125
|
+
| `post` | `@pin.post` | instance, runs after the underlying call |
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from kapro import pin
|
|
129
|
+
|
|
130
|
+
class Service:
|
|
131
|
+
@pin.cls
|
|
132
|
+
def version(cls) -> str:
|
|
133
|
+
return "1.0"
|
|
134
|
+
|
|
135
|
+
assert Service.version == "1.0"
|
|
136
|
+
assert Service().version == "1.0"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
[ref: #full-example]
|
|
140
|
+
|
|
141
|
+
## Full example
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from kapro import class_property, mixed_property, pin
|
|
145
|
+
|
|
146
|
+
class App:
|
|
147
|
+
@class_property
|
|
148
|
+
def name(cls) -> str:
|
|
149
|
+
return "MyApp"
|
|
150
|
+
|
|
151
|
+
@mixed_property
|
|
152
|
+
def greeting(node) -> str:
|
|
153
|
+
return f"Hello from {node.name}"
|
|
154
|
+
|
|
155
|
+
@pin
|
|
156
|
+
def config(self) -> dict:
|
|
157
|
+
return {"debug": True}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
assert App.name == "MyApp"
|
|
161
|
+
assert App.greeting == "Hello from MyApp"
|
|
162
|
+
assert App().greeting == "Hello from MyApp"
|
|
163
|
+
|
|
164
|
+
app = App()
|
|
165
|
+
assert app.config == {"debug": True}
|
|
166
|
+
assert app.config is app.config
|
|
167
|
+
```
|
kapro-0.0.1/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: kapro
|
|
3
|
+
description: Class-level, mixed, and pinned descriptors for Python
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
[ref: #kapro]
|
|
7
|
+
|
|
8
|
+
# kapro
|
|
9
|
+
|
|
10
|
+
`kapro` is a small Python library of property descriptors.
|
|
11
|
+
|
|
12
|
+
It gives you three public entry points:
|
|
13
|
+
|
|
14
|
+
- `class_property` — a descriptor that lives on the class and is shared by every instance.
|
|
15
|
+
- `mixed_property` — a descriptor that can be defined on the class but resolved per instance.
|
|
16
|
+
- `pin` — a cached instance property that stores its value in the instance dictionary.
|
|
17
|
+
|
|
18
|
+
[ref: #installation]
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Install with `uv`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv add kapro
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or with any PEP 517-compatible tool:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install kapro
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
[ref: #class-property]
|
|
35
|
+
|
|
36
|
+
## Class-level properties with `class_property`
|
|
37
|
+
|
|
38
|
+
`class_property` behaves like a class-level attribute, but it is computed lazily and cached on the class.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from kapro import class_property
|
|
42
|
+
|
|
43
|
+
class Config:
|
|
44
|
+
@class_property
|
|
45
|
+
def name(cls) -> str:
|
|
46
|
+
return "default"
|
|
47
|
+
|
|
48
|
+
assert Config.name == "default"
|
|
49
|
+
assert Config().name == "default"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The value is stored on the class, so every instance sees the same value.
|
|
53
|
+
|
|
54
|
+
[ref: #mixed-property]
|
|
55
|
+
|
|
56
|
+
## Mixed properties with `mixed_property`
|
|
57
|
+
|
|
58
|
+
`mixed_property` calls the same function with the class when accessed on the class and with the instance when accessed on an instance.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from kapro import mixed_property
|
|
62
|
+
|
|
63
|
+
class User:
|
|
64
|
+
def __init__(self, id: int) -> None:
|
|
65
|
+
self.id = id
|
|
66
|
+
|
|
67
|
+
@mixed_property
|
|
68
|
+
def label(node) -> str:
|
|
69
|
+
if isinstance(node, type):
|
|
70
|
+
return "User model"
|
|
71
|
+
return f"user: {node.id}"
|
|
72
|
+
|
|
73
|
+
assert User.label == "User model"
|
|
74
|
+
assert User(id=7).label == "user: 7"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
[ref: #pin]
|
|
78
|
+
|
|
79
|
+
## Cached instance properties with `pin`
|
|
80
|
+
|
|
81
|
+
`pin` is the cached equivalent of a plain method-based property.
|
|
82
|
+
The first access computes the value and stores it in the instance dictionary.
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from kapro import pin
|
|
86
|
+
|
|
87
|
+
class Expensive:
|
|
88
|
+
@pin
|
|
89
|
+
def payload(self) -> dict:
|
|
90
|
+
return {"loaded": True}
|
|
91
|
+
|
|
92
|
+
obj = Expensive()
|
|
93
|
+
assert obj.payload is obj.payload
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
[ref: #pin-flavors]
|
|
97
|
+
|
|
98
|
+
### `pin` flavors
|
|
99
|
+
|
|
100
|
+
`pin` exposes several variants through class attributes.
|
|
101
|
+
|
|
102
|
+
| Flavor | Decorator | Caches on |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| `native` | `@pin` | instance (default) |
|
|
105
|
+
| `cls` | `@pin.cls` | class |
|
|
106
|
+
| `any` | `@pin.any` | class or instance |
|
|
107
|
+
| `pre` | `@pin.pre` | instance, runs before the underlying call |
|
|
108
|
+
| `post` | `@pin.post` | instance, runs after the underlying call |
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from kapro import pin
|
|
112
|
+
|
|
113
|
+
class Service:
|
|
114
|
+
@pin.cls
|
|
115
|
+
def version(cls) -> str:
|
|
116
|
+
return "1.0"
|
|
117
|
+
|
|
118
|
+
assert Service.version == "1.0"
|
|
119
|
+
assert Service().version == "1.0"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
[ref: #full-example]
|
|
123
|
+
|
|
124
|
+
## Full example
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from kapro import class_property, mixed_property, pin
|
|
128
|
+
|
|
129
|
+
class App:
|
|
130
|
+
@class_property
|
|
131
|
+
def name(cls) -> str:
|
|
132
|
+
return "MyApp"
|
|
133
|
+
|
|
134
|
+
@mixed_property
|
|
135
|
+
def greeting(node) -> str:
|
|
136
|
+
return f"Hello from {node.name}"
|
|
137
|
+
|
|
138
|
+
@pin
|
|
139
|
+
def config(self) -> dict:
|
|
140
|
+
return {"debug": True}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
assert App.name == "MyApp"
|
|
144
|
+
assert App.greeting == "Hello from MyApp"
|
|
145
|
+
assert App().greeting == "Hello from MyApp"
|
|
146
|
+
|
|
147
|
+
app = App()
|
|
148
|
+
assert app.config == {"debug": True}
|
|
149
|
+
assert app.config is app.config
|
|
150
|
+
```
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "setuptools.build_meta"
|
|
3
|
+
requires = [
|
|
4
|
+
"setuptools"
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
[dependency-groups]
|
|
8
|
+
build = [
|
|
9
|
+
"setuptools"
|
|
10
|
+
]
|
|
11
|
+
dev = [
|
|
12
|
+
"async-asgi-testclient>=1.4.11",
|
|
13
|
+
"bandit[toml]>=1.9.4",
|
|
14
|
+
"basedmypy>=2.10.0",
|
|
15
|
+
"basedpyright>=1.39.4",
|
|
16
|
+
"better-exceptions>=0.3.3",
|
|
17
|
+
"black>=26.3.1",
|
|
18
|
+
"colorama>=0.4.6",
|
|
19
|
+
"faker>=40.18.0",
|
|
20
|
+
"hexora>=0.2.4",
|
|
21
|
+
"pre-commit>=4.6.0",
|
|
22
|
+
"pyrefly>=1.0.0",
|
|
23
|
+
"pytest-asyncio>=1.3.0",
|
|
24
|
+
"pytest-check>=2.8.0",
|
|
25
|
+
"pytest-clarity>=1.0.1",
|
|
26
|
+
"pytest-cov>=7.1.0",
|
|
27
|
+
"pytest-dotenv>=0.5.2",
|
|
28
|
+
"pytest-icdiff>=0.9",
|
|
29
|
+
"pytest-mock>=3.15.1",
|
|
30
|
+
"pytest-randomly>=4.1.0",
|
|
31
|
+
"pytest-timeout>=2.4.0",
|
|
32
|
+
"pytest-xdist>=3.8.0",
|
|
33
|
+
"pytest>=9.0.3",
|
|
34
|
+
"python-lsp-server[all]>=1.14.0",
|
|
35
|
+
"refurb>=2.3.1",
|
|
36
|
+
"righttyper>=0.1.0",
|
|
37
|
+
"ruff>=0.15.13",
|
|
38
|
+
"structlog>=25.5.0",
|
|
39
|
+
"time-machine>=3.2.0",
|
|
40
|
+
"ty>=0.0.36",
|
|
41
|
+
"typing-extensions>=4.15.0",
|
|
42
|
+
"typing-inspection>=0.4.2",
|
|
43
|
+
"uv-publish>=1.4",
|
|
44
|
+
"vulture>=2.16",
|
|
45
|
+
"yamlfix>=1.19.1",
|
|
46
|
+
"yamllint>=1.38.0"
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[project]
|
|
50
|
+
authors = [
|
|
51
|
+
{name = "Alex Kalaverin", email = "alex@kalaver.in"}
|
|
52
|
+
]
|
|
53
|
+
classifiers = [
|
|
54
|
+
"Intended Audience :: Developers",
|
|
55
|
+
"Programming Language :: Python",
|
|
56
|
+
"Programming Language :: Python :: 3",
|
|
57
|
+
"Programming Language :: Python :: 3.12",
|
|
58
|
+
"Programming Language :: Python :: 3.13",
|
|
59
|
+
"License :: OSI Approved :: BSD License",
|
|
60
|
+
"Operating System :: OS Independent"
|
|
61
|
+
]
|
|
62
|
+
dependencies = [
|
|
63
|
+
"kain>=1.1.11"
|
|
64
|
+
]
|
|
65
|
+
description = "kapro"
|
|
66
|
+
name = "kapro"
|
|
67
|
+
readme = "README.md"
|
|
68
|
+
requires-python = ">=3.12,<3.14"
|
|
69
|
+
version = "0.0.1"
|
|
70
|
+
|
|
71
|
+
[project.urls]
|
|
72
|
+
Homepage = "https://kalaver.in"
|
|
73
|
+
|
|
74
|
+
[tool.bandit]
|
|
75
|
+
exclude_dirs = [".venv", ".git"]
|
|
76
|
+
|
|
77
|
+
[tool.black]
|
|
78
|
+
line-length = 79
|
|
79
|
+
target-version = ["py312"]
|
|
80
|
+
|
|
81
|
+
[tool.coverage.report]
|
|
82
|
+
exclude_lines = [
|
|
83
|
+
"pragma: no cover",
|
|
84
|
+
"if __name__ == .__main__.:",
|
|
85
|
+
"^\\s+\\.{3}",
|
|
86
|
+
"if TYPE_CHECKING:",
|
|
87
|
+
"raise NotImplementedError",
|
|
88
|
+
"pass"
|
|
89
|
+
]
|
|
90
|
+
fail_under = 90
|
|
91
|
+
show_missing = true
|
|
92
|
+
|
|
93
|
+
[tool.coverage.run]
|
|
94
|
+
branch = true
|
|
95
|
+
omit = [
|
|
96
|
+
"*/migrations/*",
|
|
97
|
+
"*/__init__.py",
|
|
98
|
+
"*/if TYPE_CHECKING:*",
|
|
99
|
+
"tests/*"
|
|
100
|
+
]
|
|
101
|
+
source = ["src"]
|
|
102
|
+
|
|
103
|
+
[tool.pylint.messages_control]
|
|
104
|
+
enable = "all"
|
|
105
|
+
|
|
106
|
+
[tool.pytest.ini_options]
|
|
107
|
+
addopts = [
|
|
108
|
+
"-ra",
|
|
109
|
+
"--strict-config",
|
|
110
|
+
"--strict-markers",
|
|
111
|
+
"--tb=short",
|
|
112
|
+
"--timeout=60"
|
|
113
|
+
]
|
|
114
|
+
faulthandler_timeout = 60
|
|
115
|
+
filterwarnings = [
|
|
116
|
+
"default"
|
|
117
|
+
]
|
|
118
|
+
markers = [
|
|
119
|
+
]
|
|
120
|
+
minversion = "8.0"
|
|
121
|
+
testpaths = ["tests"]
|
|
122
|
+
|
|
123
|
+
[tool.refurb]
|
|
124
|
+
ignore = [
|
|
125
|
+
123,
|
|
126
|
+
149,
|
|
127
|
+
176,
|
|
128
|
+
191
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
[tool.ruff]
|
|
132
|
+
exclude = []
|
|
133
|
+
extend = "etc/lint/ruff.toml"
|
|
134
|
+
|
|
135
|
+
[tool.ruff.lint.isort]
|
|
136
|
+
section-order = [
|
|
137
|
+
"future",
|
|
138
|
+
"standard-library",
|
|
139
|
+
"third-party",
|
|
140
|
+
"first-party",
|
|
141
|
+
"local-project",
|
|
142
|
+
"local-folder"
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
[tool.ruff.lint.isort.sections]
|
|
146
|
+
local-project = ["kapro"]
|
|
147
|
+
|
|
148
|
+
[tool.ruff.lint.per-file-ignores]
|
|
149
|
+
"*.pyi" = [
|
|
150
|
+
"ANN",
|
|
151
|
+
"N",
|
|
152
|
+
"PYI"
|
|
153
|
+
]
|
|
154
|
+
"contrib/*" = []
|
|
155
|
+
"docs/*" = [
|
|
156
|
+
"INP001", # File `docs/conf.py` is part of an implicit namespace package. Add an `__init__.py`.
|
|
157
|
+
"I001", # [*] Import block is un-sorted or un-formatted
|
|
158
|
+
"PTH100", # `os.path.abspath()` should be replaced by `Path.resolve()`
|
|
159
|
+
"A001" # Variable `copyright` is shadowing a Python builtin
|
|
160
|
+
]
|
|
161
|
+
"src/*" = []
|
|
162
|
+
"tests/*" = [
|
|
163
|
+
"ANN001", # Missing type annotation for function argument `mock_workflow_is_replaying`
|
|
164
|
+
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed in `key`
|
|
165
|
+
"ARG001", # Unused function argument: `node`
|
|
166
|
+
"ARG002", # Unused method argument: `mock_workflow_info`
|
|
167
|
+
"ARG005", # Unused lambda argument: `cls`
|
|
168
|
+
"B018", # Found useless expression. Either assign it to a variable or remove it.
|
|
169
|
+
"FBT003", # Boolean positional value in function call
|
|
170
|
+
"INP001", # File `tests/workflows/test_turnover_workflows.py` is part of an implicit namespace package. Add an `__init__.py`.
|
|
171
|
+
"N805", # First argument of a method should be named `self`
|
|
172
|
+
"N805", # First argument of a method should be named `self`
|
|
173
|
+
"PLC0415", # `import` should be at the top-level of a file
|
|
174
|
+
"PLR0124", # Name compared with itself, consider replacing `Nothing == Nothing`
|
|
175
|
+
"PLR2004", # Magic value used in comparison, consider replacing `3` with a constant variable
|
|
176
|
+
"PYI024", # Use `typing.NamedTuple` instead of `collections.namedtuple`
|
|
177
|
+
"S101", # Use of `assert` detected
|
|
178
|
+
"SLF001", # Private member accessed: `_proxy`
|
|
179
|
+
"TC002", # Move third-party import `faker.Faker` into a type-checking block
|
|
180
|
+
"TC003", # Move standard library import `collections.abc.Generator` into a type-checking block
|
|
181
|
+
"TRY301" # Abstract `raise` to an inner function
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
[tool.setuptools.packages.find]
|
|
185
|
+
where = ["src"]
|
|
186
|
+
|
|
187
|
+
[tool.ty.rules]
|
|
188
|
+
possibly-missing-attribute = "ignore"
|
|
189
|
+
|
|
190
|
+
[tool.uv]
|
|
191
|
+
default-groups = [
|
|
192
|
+
"dev"
|
|
193
|
+
]
|
kapro-0.0.1/setup.cfg
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, TypeVar, final, overload, override
|
|
4
|
+
|
|
5
|
+
from kapro.cached import (
|
|
6
|
+
cached_property,
|
|
7
|
+
class_cached_property,
|
|
8
|
+
mixed_cached_property,
|
|
9
|
+
post_cached_property,
|
|
10
|
+
pre_cached_property,
|
|
11
|
+
)
|
|
12
|
+
from kapro.class_property import class_property, mixed_property
|
|
13
|
+
from kapro.primitives import (
|
|
14
|
+
ContextFaultError,
|
|
15
|
+
Nothing,
|
|
16
|
+
bound_property,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
T_co = TypeVar("T_co", covariant=True)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@final
|
|
23
|
+
class pin[T_co](bound_property[T_co]): # noqa: N801
|
|
24
|
+
native = cached_property
|
|
25
|
+
cls = class_cached_property
|
|
26
|
+
any = mixed_cached_property
|
|
27
|
+
pre = pre_cached_property
|
|
28
|
+
post = post_cached_property
|
|
29
|
+
|
|
30
|
+
@overload
|
|
31
|
+
def __get__(
|
|
32
|
+
self,
|
|
33
|
+
node: None,
|
|
34
|
+
klass: Any = ..., # noqa: ANN401
|
|
35
|
+
) -> pin[T_co]: ...
|
|
36
|
+
|
|
37
|
+
@overload
|
|
38
|
+
def __get__(
|
|
39
|
+
self,
|
|
40
|
+
node: object,
|
|
41
|
+
klass: Any = ..., # noqa: ANN401
|
|
42
|
+
) -> T_co: ...
|
|
43
|
+
|
|
44
|
+
@override
|
|
45
|
+
def __get__(
|
|
46
|
+
self,
|
|
47
|
+
node: object | None,
|
|
48
|
+
klass: Any = Nothing,
|
|
49
|
+
) -> pin[T_co] | T_co:
|
|
50
|
+
if node is None:
|
|
51
|
+
raise ContextFaultError(self.header_with_context(klass))
|
|
52
|
+
|
|
53
|
+
cache = getattr(node, "__dict__", None)
|
|
54
|
+
if cache is None:
|
|
55
|
+
raise TypeError(
|
|
56
|
+
f"{self.header_with_context(node)} has no __dict__",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
return cache[self.name] # type: ignore[no-any-return, index]
|
|
61
|
+
|
|
62
|
+
except KeyError:
|
|
63
|
+
value = self.function(node)
|
|
64
|
+
cache[self.name] = value # type: ignore[index]
|
|
65
|
+
return value # type: ignore[no-any-return]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
__all__ = (
|
|
69
|
+
"class_property",
|
|
70
|
+
"mixed_property",
|
|
71
|
+
"pin",
|
|
72
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from kapro.cached.instance import cached_property
|
|
2
|
+
from kapro.cached.klass import (
|
|
3
|
+
class_cached_property,
|
|
4
|
+
class_parent_cached_property,
|
|
5
|
+
)
|
|
6
|
+
from kapro.cached.mixed import (
|
|
7
|
+
mixed_cached_property,
|
|
8
|
+
mixed_parent_cached_property,
|
|
9
|
+
)
|
|
10
|
+
from kapro.cached.post import (
|
|
11
|
+
post_cached_property,
|
|
12
|
+
post_parent_cached_property,
|
|
13
|
+
)
|
|
14
|
+
from kapro.cached.pre import (
|
|
15
|
+
pre_cached_property,
|
|
16
|
+
pre_parent_cached_property,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = (
|
|
20
|
+
"cached_property",
|
|
21
|
+
"class_cached_property",
|
|
22
|
+
"class_parent_cached_property",
|
|
23
|
+
"mixed_cached_property",
|
|
24
|
+
"mixed_parent_cached_property",
|
|
25
|
+
"post_cached_property",
|
|
26
|
+
"post_parent_cached_property",
|
|
27
|
+
"pre_cached_property",
|
|
28
|
+
"pre_parent_cached_property",
|
|
29
|
+
)
|