pylaag-core 0.1.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.
- pylaag_core-0.1.0/.gitignore +56 -0
- pylaag_core-0.1.0/LICENSE +21 -0
- pylaag_core-0.1.0/PKG-INFO +206 -0
- pylaag_core-0.1.0/README.md +180 -0
- pylaag_core-0.1.0/pyproject.toml +49 -0
- pylaag_core-0.1.0/src/pylaag_core/__init__.py +18 -0
- pylaag_core-0.1.0/src/pylaag_core/base.py +82 -0
- pylaag_core-0.1.0/src/pylaag_core/errors.py +29 -0
- pylaag_core-0.1.0/src/pylaag_core/py.typed +0 -0
- pylaag_core-0.1.0/src/pylaag_core/utils.py +88 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
venv/
|
|
25
|
+
ENV/
|
|
26
|
+
env/
|
|
27
|
+
.venv
|
|
28
|
+
|
|
29
|
+
# uv
|
|
30
|
+
.uv/
|
|
31
|
+
|
|
32
|
+
# Testing
|
|
33
|
+
.pytest_cache/
|
|
34
|
+
.coverage
|
|
35
|
+
htmlcov/
|
|
36
|
+
.hypothesis/
|
|
37
|
+
|
|
38
|
+
# Type checking
|
|
39
|
+
.mypy_cache/
|
|
40
|
+
.dmypy.json
|
|
41
|
+
dmypy.json
|
|
42
|
+
|
|
43
|
+
# IDEs
|
|
44
|
+
.vscode/
|
|
45
|
+
.idea/
|
|
46
|
+
*.swp
|
|
47
|
+
*.swo
|
|
48
|
+
*~
|
|
49
|
+
|
|
50
|
+
# OS
|
|
51
|
+
.DS_Store
|
|
52
|
+
Thumbs.db
|
|
53
|
+
|
|
54
|
+
# Build artifacts
|
|
55
|
+
*.whl
|
|
56
|
+
*.tar.gz
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 B Schwarz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pylaag-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Core utilities and base classes for the laag Python library
|
|
5
|
+
Project-URL: Homepage, https://github.com/laag/laag-python
|
|
6
|
+
Project-URL: Repository, https://github.com/laag/laag-python
|
|
7
|
+
Project-URL: Documentation, https://github.com/laag/laag-python#readme
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/laag/laag-python/issues
|
|
9
|
+
Author: Laag Contributors
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api,api-design,api-documentation,openapi,raml,smithy,specification,swagger
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# pylaag-core
|
|
28
|
+
|
|
29
|
+
Core utilities and base classes for the laag Python library.
|
|
30
|
+
|
|
31
|
+
## Overview
|
|
32
|
+
|
|
33
|
+
`pylaag-core` provides the foundational components used by all laag packages, including:
|
|
34
|
+
|
|
35
|
+
- Base classes for API document handlers
|
|
36
|
+
- Error handling system
|
|
37
|
+
- Utility functions for nested object navigation
|
|
38
|
+
- Extension property support
|
|
39
|
+
|
|
40
|
+
This package is typically installed as a dependency of other laag packages and is not used directly in most cases.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install pylaag-core
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
- **LaagBase**: Abstract base class for all API document handlers
|
|
51
|
+
- **Error Classes**: Comprehensive exception hierarchy (LaagError, ValidationError, ParseError, NotFoundError)
|
|
52
|
+
- **Utility Functions**: Helper functions for working with nested dictionaries
|
|
53
|
+
- **Extension Properties**: Support for x-* custom properties in API documents
|
|
54
|
+
- **Type Safety**: Full type hints for static analysis
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
### Base Class
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from pylaag_core import LaagBase
|
|
62
|
+
|
|
63
|
+
class MyDocument(LaagBase):
|
|
64
|
+
def validate(self) -> None:
|
|
65
|
+
# Implement validation logic
|
|
66
|
+
if 'required_field' not in self._document:
|
|
67
|
+
raise ValidationError("Missing required field")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Error Handling
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from pylaag_core import ValidationError, ParseError, NotFoundError
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
doc.validate()
|
|
77
|
+
except ValidationError as e:
|
|
78
|
+
print(f"Validation failed: {e}")
|
|
79
|
+
print(f"Context: {e.context}")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Utility Functions
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from pylaag_core import get_nested, set_nested, delete_nested
|
|
86
|
+
|
|
87
|
+
data = {'a': {'b': {'c': 1}}}
|
|
88
|
+
|
|
89
|
+
# Get nested value
|
|
90
|
+
value = get_nested(data, 'a.b.c') # Returns 1
|
|
91
|
+
|
|
92
|
+
# Set nested value
|
|
93
|
+
set_nested(data, 'a.b.d', 2) # data['a']['b']['d'] = 2
|
|
94
|
+
|
|
95
|
+
# Delete nested value
|
|
96
|
+
deleted = delete_nested(data, 'a.b.c') # Returns True
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Extension Properties
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from pylaag_core import LaagBase
|
|
103
|
+
|
|
104
|
+
doc = MyDocument()
|
|
105
|
+
|
|
106
|
+
# Set extension property
|
|
107
|
+
doc.set_extension('x-custom', {'key': 'value'})
|
|
108
|
+
|
|
109
|
+
# Get extension property
|
|
110
|
+
custom = doc.get_extension('x-custom')
|
|
111
|
+
|
|
112
|
+
# Remove extension property
|
|
113
|
+
doc.remove_extension('x-custom')
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## API Reference
|
|
117
|
+
|
|
118
|
+
### Classes
|
|
119
|
+
|
|
120
|
+
#### LaagBase
|
|
121
|
+
|
|
122
|
+
Abstract base class for all API document handlers.
|
|
123
|
+
|
|
124
|
+
**Methods:**
|
|
125
|
+
- `validate() -> None`: Validate the document (must be implemented by subclasses)
|
|
126
|
+
- `get_extension(key: str) -> Optional[Any]`: Get an extension property value
|
|
127
|
+
- `set_extension(key: str, value: Any) -> None`: Set an extension property value
|
|
128
|
+
- `remove_extension(key: str) -> None`: Remove an extension property
|
|
129
|
+
- `to_dict() -> Dict[str, Any]`: Convert to dictionary representation
|
|
130
|
+
|
|
131
|
+
#### LaagError
|
|
132
|
+
|
|
133
|
+
Base exception class for all laag errors.
|
|
134
|
+
|
|
135
|
+
**Attributes:**
|
|
136
|
+
- `context: Dict[str, Any]`: Additional context information about the error
|
|
137
|
+
|
|
138
|
+
#### ValidationError
|
|
139
|
+
|
|
140
|
+
Raised when document validation fails.
|
|
141
|
+
|
|
142
|
+
#### ParseError
|
|
143
|
+
|
|
144
|
+
Raised when document parsing fails.
|
|
145
|
+
|
|
146
|
+
#### NotFoundError
|
|
147
|
+
|
|
148
|
+
Raised when a requested resource is not found.
|
|
149
|
+
|
|
150
|
+
### Functions
|
|
151
|
+
|
|
152
|
+
#### get_nested
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
def get_nested(obj: Dict[str, Any], path: str, default: Any = None) -> Any
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Get a nested value from a dictionary using dot notation.
|
|
159
|
+
|
|
160
|
+
**Parameters:**
|
|
161
|
+
- `obj`: The dictionary to search
|
|
162
|
+
- `path`: Dot-separated path (e.g., 'a.b.c')
|
|
163
|
+
- `default`: Default value if path not found
|
|
164
|
+
|
|
165
|
+
**Returns:** The value at the path, or default if not found
|
|
166
|
+
|
|
167
|
+
#### set_nested
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
def set_nested(obj: Dict[str, Any], path: str, value: Any) -> None
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Set a nested value in a dictionary using dot notation.
|
|
174
|
+
|
|
175
|
+
**Parameters:**
|
|
176
|
+
- `obj`: The dictionary to modify
|
|
177
|
+
- `path`: Dot-separated path (e.g., 'a.b.c')
|
|
178
|
+
- `value`: Value to set
|
|
179
|
+
|
|
180
|
+
#### delete_nested
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
def delete_nested(obj: Dict[str, Any], path: str) -> bool
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Delete a nested value from a dictionary using dot notation.
|
|
187
|
+
|
|
188
|
+
**Parameters:**
|
|
189
|
+
- `obj`: The dictionary to modify
|
|
190
|
+
- `path`: Dot-separated path (e.g., 'a.b.c')
|
|
191
|
+
|
|
192
|
+
**Returns:** True if the value was deleted, False if not found
|
|
193
|
+
|
|
194
|
+
## Requirements
|
|
195
|
+
|
|
196
|
+
- Python 3.10 or higher
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT License - see LICENSE file for details
|
|
201
|
+
|
|
202
|
+
## Related Packages
|
|
203
|
+
|
|
204
|
+
- [pylaag-openapi](https://pypi.org/project/pylaag-openapi/) - OpenAPI/Swagger support
|
|
205
|
+
- [pylaag-raml](https://pypi.org/project/pylaag-raml/) - RAML support
|
|
206
|
+
- [pylaag-smithy](https://pypi.org/project/pylaag-smithy/) - Smithy support
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# pylaag-core
|
|
2
|
+
|
|
3
|
+
Core utilities and base classes for the laag Python library.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`pylaag-core` provides the foundational components used by all laag packages, including:
|
|
8
|
+
|
|
9
|
+
- Base classes for API document handlers
|
|
10
|
+
- Error handling system
|
|
11
|
+
- Utility functions for nested object navigation
|
|
12
|
+
- Extension property support
|
|
13
|
+
|
|
14
|
+
This package is typically installed as a dependency of other laag packages and is not used directly in most cases.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install pylaag-core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **LaagBase**: Abstract base class for all API document handlers
|
|
25
|
+
- **Error Classes**: Comprehensive exception hierarchy (LaagError, ValidationError, ParseError, NotFoundError)
|
|
26
|
+
- **Utility Functions**: Helper functions for working with nested dictionaries
|
|
27
|
+
- **Extension Properties**: Support for x-* custom properties in API documents
|
|
28
|
+
- **Type Safety**: Full type hints for static analysis
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Base Class
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from pylaag_core import LaagBase
|
|
36
|
+
|
|
37
|
+
class MyDocument(LaagBase):
|
|
38
|
+
def validate(self) -> None:
|
|
39
|
+
# Implement validation logic
|
|
40
|
+
if 'required_field' not in self._document:
|
|
41
|
+
raise ValidationError("Missing required field")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Error Handling
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from pylaag_core import ValidationError, ParseError, NotFoundError
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
doc.validate()
|
|
51
|
+
except ValidationError as e:
|
|
52
|
+
print(f"Validation failed: {e}")
|
|
53
|
+
print(f"Context: {e.context}")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Utility Functions
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from pylaag_core import get_nested, set_nested, delete_nested
|
|
60
|
+
|
|
61
|
+
data = {'a': {'b': {'c': 1}}}
|
|
62
|
+
|
|
63
|
+
# Get nested value
|
|
64
|
+
value = get_nested(data, 'a.b.c') # Returns 1
|
|
65
|
+
|
|
66
|
+
# Set nested value
|
|
67
|
+
set_nested(data, 'a.b.d', 2) # data['a']['b']['d'] = 2
|
|
68
|
+
|
|
69
|
+
# Delete nested value
|
|
70
|
+
deleted = delete_nested(data, 'a.b.c') # Returns True
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Extension Properties
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from pylaag_core import LaagBase
|
|
77
|
+
|
|
78
|
+
doc = MyDocument()
|
|
79
|
+
|
|
80
|
+
# Set extension property
|
|
81
|
+
doc.set_extension('x-custom', {'key': 'value'})
|
|
82
|
+
|
|
83
|
+
# Get extension property
|
|
84
|
+
custom = doc.get_extension('x-custom')
|
|
85
|
+
|
|
86
|
+
# Remove extension property
|
|
87
|
+
doc.remove_extension('x-custom')
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## API Reference
|
|
91
|
+
|
|
92
|
+
### Classes
|
|
93
|
+
|
|
94
|
+
#### LaagBase
|
|
95
|
+
|
|
96
|
+
Abstract base class for all API document handlers.
|
|
97
|
+
|
|
98
|
+
**Methods:**
|
|
99
|
+
- `validate() -> None`: Validate the document (must be implemented by subclasses)
|
|
100
|
+
- `get_extension(key: str) -> Optional[Any]`: Get an extension property value
|
|
101
|
+
- `set_extension(key: str, value: Any) -> None`: Set an extension property value
|
|
102
|
+
- `remove_extension(key: str) -> None`: Remove an extension property
|
|
103
|
+
- `to_dict() -> Dict[str, Any]`: Convert to dictionary representation
|
|
104
|
+
|
|
105
|
+
#### LaagError
|
|
106
|
+
|
|
107
|
+
Base exception class for all laag errors.
|
|
108
|
+
|
|
109
|
+
**Attributes:**
|
|
110
|
+
- `context: Dict[str, Any]`: Additional context information about the error
|
|
111
|
+
|
|
112
|
+
#### ValidationError
|
|
113
|
+
|
|
114
|
+
Raised when document validation fails.
|
|
115
|
+
|
|
116
|
+
#### ParseError
|
|
117
|
+
|
|
118
|
+
Raised when document parsing fails.
|
|
119
|
+
|
|
120
|
+
#### NotFoundError
|
|
121
|
+
|
|
122
|
+
Raised when a requested resource is not found.
|
|
123
|
+
|
|
124
|
+
### Functions
|
|
125
|
+
|
|
126
|
+
#### get_nested
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
def get_nested(obj: Dict[str, Any], path: str, default: Any = None) -> Any
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Get a nested value from a dictionary using dot notation.
|
|
133
|
+
|
|
134
|
+
**Parameters:**
|
|
135
|
+
- `obj`: The dictionary to search
|
|
136
|
+
- `path`: Dot-separated path (e.g., 'a.b.c')
|
|
137
|
+
- `default`: Default value if path not found
|
|
138
|
+
|
|
139
|
+
**Returns:** The value at the path, or default if not found
|
|
140
|
+
|
|
141
|
+
#### set_nested
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
def set_nested(obj: Dict[str, Any], path: str, value: Any) -> None
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Set a nested value in a dictionary using dot notation.
|
|
148
|
+
|
|
149
|
+
**Parameters:**
|
|
150
|
+
- `obj`: The dictionary to modify
|
|
151
|
+
- `path`: Dot-separated path (e.g., 'a.b.c')
|
|
152
|
+
- `value`: Value to set
|
|
153
|
+
|
|
154
|
+
#### delete_nested
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
def delete_nested(obj: Dict[str, Any], path: str) -> bool
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Delete a nested value from a dictionary using dot notation.
|
|
161
|
+
|
|
162
|
+
**Parameters:**
|
|
163
|
+
- `obj`: The dictionary to modify
|
|
164
|
+
- `path`: Dot-separated path (e.g., 'a.b.c')
|
|
165
|
+
|
|
166
|
+
**Returns:** True if the value was deleted, False if not found
|
|
167
|
+
|
|
168
|
+
## Requirements
|
|
169
|
+
|
|
170
|
+
- Python 3.10 or higher
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT License - see LICENSE file for details
|
|
175
|
+
|
|
176
|
+
## Related Packages
|
|
177
|
+
|
|
178
|
+
- [pylaag-openapi](https://pypi.org/project/pylaag-openapi/) - OpenAPI/Swagger support
|
|
179
|
+
- [pylaag-raml](https://pypi.org/project/pylaag-raml/) - RAML support
|
|
180
|
+
- [pylaag-smithy](https://pypi.org/project/pylaag-smithy/) - Smithy support
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pylaag-core"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Core utilities and base classes for the laag Python library"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Laag Contributors" }
|
|
10
|
+
]
|
|
11
|
+
keywords = [
|
|
12
|
+
"api",
|
|
13
|
+
"specification",
|
|
14
|
+
"openapi",
|
|
15
|
+
"swagger",
|
|
16
|
+
"raml",
|
|
17
|
+
"smithy",
|
|
18
|
+
"api-design",
|
|
19
|
+
"api-documentation",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
31
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
32
|
+
"Typing :: Typed",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/laag/laag-python"
|
|
37
|
+
Repository = "https://github.com/laag/laag-python"
|
|
38
|
+
Documentation = "https://github.com/laag/laag-python#readme"
|
|
39
|
+
"Bug Tracker" = "https://github.com/laag/laag-python/issues"
|
|
40
|
+
|
|
41
|
+
[build-system]
|
|
42
|
+
requires = ["hatchling"]
|
|
43
|
+
build-backend = "hatchling.build"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/pylaag_core"]
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.sdist]
|
|
49
|
+
exclude = ["tests/"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Core utilities and base classes for the laag Python library."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
from pylaag_core.base import LaagBase
|
|
6
|
+
from pylaag_core.errors import LaagError, NotFoundError, ParseError, ValidationError
|
|
7
|
+
from pylaag_core.utils import delete_nested, get_nested, set_nested
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"LaagBase",
|
|
11
|
+
"LaagError",
|
|
12
|
+
"ValidationError",
|
|
13
|
+
"ParseError",
|
|
14
|
+
"NotFoundError",
|
|
15
|
+
"get_nested",
|
|
16
|
+
"set_nested",
|
|
17
|
+
"delete_nested",
|
|
18
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Base class for all API document handlers."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any, Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LaagBase(ABC, Generic[T]):
|
|
10
|
+
"""Base class for all API document handlers.
|
|
11
|
+
|
|
12
|
+
This class provides common functionality for managing API documents,
|
|
13
|
+
including extension property handling and document serialization.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, document: dict[str, Any] | None = None):
|
|
17
|
+
"""Initialize the base document handler.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
document: The document dictionary to manage. If None, an empty dict is used.
|
|
21
|
+
"""
|
|
22
|
+
self._document: dict[str, Any] = document or {}
|
|
23
|
+
self._extensions: dict[str, Any] = {}
|
|
24
|
+
self._extract_extensions()
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def validate(self) -> None:
|
|
28
|
+
"""Validate the document against its specification.
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
ValidationError: If the document is invalid
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
def _extract_extensions(self) -> None:
|
|
36
|
+
"""Extract x-* properties from document into the extensions dict."""
|
|
37
|
+
for key, value in list(self._document.items()):
|
|
38
|
+
if key.startswith("x-"):
|
|
39
|
+
self._extensions[key] = value
|
|
40
|
+
|
|
41
|
+
def get_extension(self, key: str) -> Any | None:
|
|
42
|
+
"""Get an extension property value.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
key: The extension property key (should start with 'x-')
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The extension property value, or None if not found
|
|
49
|
+
"""
|
|
50
|
+
return self._extensions.get(key)
|
|
51
|
+
|
|
52
|
+
def set_extension(self, key: str, value: Any) -> None:
|
|
53
|
+
"""Set an extension property value.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
key: The extension property key (must start with 'x-')
|
|
57
|
+
value: The value to set
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ValueError: If the key does not start with 'x-'
|
|
61
|
+
"""
|
|
62
|
+
if not key.startswith("x-"):
|
|
63
|
+
raise ValueError(f"Extension property must start with 'x-': {key}")
|
|
64
|
+
self._extensions[key] = value
|
|
65
|
+
self._document[key] = value
|
|
66
|
+
|
|
67
|
+
def remove_extension(self, key: str) -> None:
|
|
68
|
+
"""Remove an extension property.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
key: The extension property key to remove
|
|
72
|
+
"""
|
|
73
|
+
self._extensions.pop(key, None)
|
|
74
|
+
self._document.pop(key, None)
|
|
75
|
+
|
|
76
|
+
def to_dict(self) -> dict[str, Any]:
|
|
77
|
+
"""Convert to dictionary representation.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
A copy of the document dictionary
|
|
81
|
+
"""
|
|
82
|
+
return self._document.copy()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Error classes for the laag Python library."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LaagError(Exception):
|
|
7
|
+
"""Base exception for all laag errors."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str, context: dict[str, Any] | None = None):
|
|
10
|
+
super().__init__(message)
|
|
11
|
+
self.context = context or {}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ValidationError(LaagError):
|
|
15
|
+
"""Raised when document validation fails."""
|
|
16
|
+
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ParseError(LaagError):
|
|
21
|
+
"""Raised when document parsing fails."""
|
|
22
|
+
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NotFoundError(LaagError):
|
|
27
|
+
"""Raised when a requested resource is not found."""
|
|
28
|
+
|
|
29
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Utility functions for the laag Python library."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_nested(obj: dict[str, Any], path: str, default: Any = None) -> Any:
|
|
7
|
+
"""Get a nested value from a dictionary using dot notation.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
obj: The dictionary to search
|
|
11
|
+
path: Dot-separated path to the value (e.g., 'a.b.c')
|
|
12
|
+
default: Default value to return if path not found
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
The value at the specified path, or default if not found
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
>>> get_nested({'a': {'b': {'c': 1}}}, 'a.b.c')
|
|
19
|
+
1
|
|
20
|
+
>>> get_nested({'a': {'b': {}}}, 'a.b.c', 'not found')
|
|
21
|
+
'not found'
|
|
22
|
+
"""
|
|
23
|
+
keys = path.split(".")
|
|
24
|
+
current = obj
|
|
25
|
+
|
|
26
|
+
for key in keys:
|
|
27
|
+
if not isinstance(current, dict) or key not in current:
|
|
28
|
+
return default
|
|
29
|
+
current = current[key]
|
|
30
|
+
|
|
31
|
+
return current
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def set_nested(obj: dict[str, Any], path: str, value: Any) -> None:
|
|
35
|
+
"""Set a nested value in a dictionary using dot notation.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
obj: The dictionary to modify
|
|
39
|
+
path: Dot-separated path to the value (e.g., 'a.b.c')
|
|
40
|
+
value: The value to set
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> d = {}
|
|
44
|
+
>>> set_nested(d, 'a.b.c', 1)
|
|
45
|
+
>>> d
|
|
46
|
+
{'a': {'b': {'c': 1}}}
|
|
47
|
+
"""
|
|
48
|
+
keys = path.split(".")
|
|
49
|
+
current = obj
|
|
50
|
+
|
|
51
|
+
for key in keys[:-1]:
|
|
52
|
+
if key not in current:
|
|
53
|
+
current[key] = {}
|
|
54
|
+
current = current[key]
|
|
55
|
+
|
|
56
|
+
current[keys[-1]] = value
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def delete_nested(obj: dict[str, Any], path: str) -> bool:
|
|
60
|
+
"""Delete a nested value from a dictionary using dot notation.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
obj: The dictionary to modify
|
|
64
|
+
path: Dot-separated path to the value (e.g., 'a.b.c')
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
True if the value was deleted, False if not found
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
>>> d = {'a': {'b': {'c': 1}}}
|
|
71
|
+
>>> delete_nested(d, 'a.b.c')
|
|
72
|
+
True
|
|
73
|
+
>>> delete_nested(d, 'a.b.c')
|
|
74
|
+
False
|
|
75
|
+
"""
|
|
76
|
+
keys = path.split(".")
|
|
77
|
+
current = obj
|
|
78
|
+
|
|
79
|
+
for key in keys[:-1]:
|
|
80
|
+
if not isinstance(current, dict) or key not in current:
|
|
81
|
+
return False
|
|
82
|
+
current = current[key]
|
|
83
|
+
|
|
84
|
+
if keys[-1] in current:
|
|
85
|
+
del current[keys[-1]]
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
return False
|