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.
@@ -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