morphic 0.1.0__py3-none-any.whl
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.
- morphic/__init__.py +7 -0
- morphic/autoenum.py +397 -0
- morphic/registry.py +955 -0
- morphic/typed.py +1327 -0
- morphic-0.1.0.dist-info/METADATA +333 -0
- morphic-0.1.0.dist-info/RECORD +8 -0
- morphic-0.1.0.dist-info/WHEEL +4 -0
- morphic-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: morphic
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dynamic Python utilities for class registration, creation, and type checking
|
|
5
|
+
Author-email: Abhishek Divekar <adivekar@utexas.edu>
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Topic :: Utilities
|
|
17
|
+
Requires-Python: >=3.10.9
|
|
18
|
+
Requires-Dist: typing-extensions>=4.0.0
|
|
19
|
+
Provides-Extra: all
|
|
20
|
+
Requires-Dist: black>=23.0.0; extra == 'all'
|
|
21
|
+
Requires-Dist: mike>=2.0.0; extra == 'all'
|
|
22
|
+
Requires-Dist: mkdocs-material>=9.5.0; extra == 'all'
|
|
23
|
+
Requires-Dist: mkdocs>=1.6.0; extra == 'all'
|
|
24
|
+
Requires-Dist: mkdocstrings[python]>=0.24.0; extra == 'all'
|
|
25
|
+
Requires-Dist: mypy>=1.0.0; extra == 'all'
|
|
26
|
+
Requires-Dist: pydantic>=2.0.0; extra == 'all'
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'all'
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == 'all'
|
|
29
|
+
Requires-Dist: ruff>=0.1.0; extra == 'all'
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pydantic>=2.0.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
37
|
+
Provides-Extra: docs
|
|
38
|
+
Requires-Dist: mike>=2.0.0; extra == 'docs'
|
|
39
|
+
Requires-Dist: mkdocs-material>=9.5.0; extra == 'docs'
|
|
40
|
+
Requires-Dist: mkdocs>=1.6.0; extra == 'docs'
|
|
41
|
+
Requires-Dist: mkdocstrings[python]>=0.24.0; extra == 'docs'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# Morphic
|
|
45
|
+
|
|
46
|
+
[](https://adivekar-utexas.github.io/morphic/)
|
|
47
|
+
[](https://www.python.org/downloads/)
|
|
48
|
+
[](https://opensource.org/licenses/MIT)
|
|
49
|
+
|
|
50
|
+
Dynamic Python utilities for class registration, creation, and type checking.
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- **Registry**: Dynamic class registration and factory pattern for building extensible architectures
|
|
55
|
+
- **AutoEnum**: Automatic enumeration creation from class hierarchies with type safety
|
|
56
|
+
- **Typed**: Enhanced data modeling with validation and serialization capabilities
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
### From PyPI
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install morphic
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### From Source
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install git+https://github.com/adivekar-utexas/morphic.git
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Development Installation
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
git clone https://github.com/adivekar-utexas/morphic.git
|
|
76
|
+
cd morphic
|
|
77
|
+
|
|
78
|
+
# Install with all dependencies (dev + docs)
|
|
79
|
+
pip install -e ".[all]"
|
|
80
|
+
|
|
81
|
+
# Or install specific dependency groups
|
|
82
|
+
pip install -e ".[dev]" # Development dependencies only
|
|
83
|
+
pip install -e ".[docs]" # Documentation dependencies only
|
|
84
|
+
pip install -e ".[dev,docs]" # Both dev and docs
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Quick Start
|
|
88
|
+
|
|
89
|
+
### Registry System: Inheritance-Based Class Registration
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from morphic import Registry
|
|
93
|
+
from abc import ABC, abstractmethod
|
|
94
|
+
|
|
95
|
+
# Create base registry class
|
|
96
|
+
class NotificationService(Registry, ABC):
|
|
97
|
+
@abstractmethod
|
|
98
|
+
def send(self, to: str, message: str) -> bool:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
# Classes automatically register when inheriting
|
|
102
|
+
class EmailService(NotificationService):
|
|
103
|
+
aliases = ["email", "mail"] # Multiple aliases supported
|
|
104
|
+
|
|
105
|
+
def __init__(self, smtp_server: str = "localhost"):
|
|
106
|
+
self.smtp_server = smtp_server
|
|
107
|
+
|
|
108
|
+
def send(self, to: str, message: str) -> bool:
|
|
109
|
+
print(f"Sending email to {to} via {self.smtp_server}")
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
class SMSService(NotificationService):
|
|
113
|
+
aliases = ["sms", "text"]
|
|
114
|
+
|
|
115
|
+
def send(self, to: str, message: str) -> bool:
|
|
116
|
+
print(f"Sending SMS to {to}")
|
|
117
|
+
return True
|
|
118
|
+
|
|
119
|
+
# Hierarchical factory pattern - create instances through base class
|
|
120
|
+
email_service = NotificationService.of("EmailService", smtp_server="mail.example.com")
|
|
121
|
+
sms_service = NotificationService.of("sms") # Works with aliases too!
|
|
122
|
+
|
|
123
|
+
# Direct instantiation works for concrete classes
|
|
124
|
+
email_direct = EmailService.of(smtp_server="direct.mail.com")
|
|
125
|
+
|
|
126
|
+
# Use the services
|
|
127
|
+
email_service.send("user@example.com", "Hello!")
|
|
128
|
+
sms_service.send("+1234567890", "Hello!")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### AutoEnum: Ultra-Fast Fuzzy-Matching Enums
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from morphic import AutoEnum, alias, auto
|
|
135
|
+
import json
|
|
136
|
+
|
|
137
|
+
# Create enum with fuzzy matching and aliases
|
|
138
|
+
class TaskStatus(AutoEnum):
|
|
139
|
+
PENDING = alias("waiting", "queued", "not_started")
|
|
140
|
+
RUNNING = alias("active", "in_progress", "executing")
|
|
141
|
+
COMPLETE = alias("done", "finished", "success")
|
|
142
|
+
FAILED = alias("error", "failure", "crashed")
|
|
143
|
+
|
|
144
|
+
# Fuzzy matching works with various formats
|
|
145
|
+
status1 = TaskStatus("pending") # Direct match
|
|
146
|
+
status2 = TaskStatus("IN PROGRESS") # Case insensitive + space handling
|
|
147
|
+
status3 = TaskStatus("not-started") # Alias with different formatting
|
|
148
|
+
status4 = TaskStatus("Done") # Alias with different case
|
|
149
|
+
|
|
150
|
+
# JSON compatibility (unlike standard enums!)
|
|
151
|
+
data = [TaskStatus.PENDING, TaskStatus.RUNNING]
|
|
152
|
+
json_str = json.dumps(data) # Works! -> '["PENDING", "RUNNING"]'
|
|
153
|
+
recovered = TaskStatus.convert_list(json.loads(json_str)) # Back to enums!
|
|
154
|
+
|
|
155
|
+
# Dynamic enum creation
|
|
156
|
+
Priority = AutoEnum.create("Priority", ["low", "medium", "high", "urgent"])
|
|
157
|
+
print(Priority.Low) # Low
|
|
158
|
+
print(Priority("MEDIUM")) # Medium (fuzzy matched)
|
|
159
|
+
|
|
160
|
+
# Perfect for configuration and user input
|
|
161
|
+
config_status = TaskStatus(user_input) # Handles "In Progress", "IN_PROGRESS", etc.
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Typed
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from morphic import Typed
|
|
168
|
+
from typing import Optional, List
|
|
169
|
+
|
|
170
|
+
class User(Typed):
|
|
171
|
+
name: str
|
|
172
|
+
email: str
|
|
173
|
+
age: int
|
|
174
|
+
is_active: bool = True
|
|
175
|
+
bio: Optional[str] = None
|
|
176
|
+
tags: List[str] = [] # Default empty list
|
|
177
|
+
|
|
178
|
+
def validate(self):
|
|
179
|
+
if self.age < 0:
|
|
180
|
+
raise ValueError("Age must be non-negative")
|
|
181
|
+
if "@" not in self.email:
|
|
182
|
+
raise ValueError("Invalid email format")
|
|
183
|
+
|
|
184
|
+
# Instantiate like any class with auto type-conversion:
|
|
185
|
+
user = User(
|
|
186
|
+
name="Alice Johnson",
|
|
187
|
+
email="alice@example.com",
|
|
188
|
+
age="30", # String automatically converts to int
|
|
189
|
+
is_active="true", # String automatically converts to bool
|
|
190
|
+
tags=("python", "ai", "data"), # Tuple of strings converted to list of strings.
|
|
191
|
+
)
|
|
192
|
+
print(f"User: {user.name}, Age: {user.age} ({type(user.age)}), Tags: {user.tags}")
|
|
193
|
+
# Output:
|
|
194
|
+
# User: Alice Johnson, Age: 30 (<class 'int'>), Tags: ['python', 'ai', 'data']
|
|
195
|
+
|
|
196
|
+
# Create from dict:
|
|
197
|
+
user = User.from_dict({
|
|
198
|
+
"name": "Alice Johnson",
|
|
199
|
+
"email": "alice@example.com",
|
|
200
|
+
"age": "30", # String automatically converts to int
|
|
201
|
+
"is_active": "true", # String automatically converts to bool
|
|
202
|
+
"tags": ["python", "ai", "data"] # List of strings
|
|
203
|
+
})
|
|
204
|
+
print(f"User: {user.name}, Age: {user.age} ({type(user.age)}), Tags: {user.tags}")
|
|
205
|
+
# Output:
|
|
206
|
+
# Same as above.
|
|
207
|
+
|
|
208
|
+
# Hierarchical field handling for nested data
|
|
209
|
+
class Company(Typed):
|
|
210
|
+
name: str
|
|
211
|
+
employees: List[User] # Automatically converts dicts to User instances
|
|
212
|
+
|
|
213
|
+
company_data = {
|
|
214
|
+
"name": "TechCorp",
|
|
215
|
+
"employees": [
|
|
216
|
+
{"name": "Alice", "email": "alice@tech.com", "age": "30"},
|
|
217
|
+
{"name": "Bob", "email": "bob@tech.com", "age": "25"}
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
company = Company.from_dict(company_data)
|
|
222
|
+
print(f"Company: {company.name}, Employees: {len(company.employees)}")
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Documentation
|
|
226
|
+
|
|
227
|
+
Comprehensive documentation is available at [https://adivekar.github.io/morphic/](https://adivekar.github.io/morphic/)
|
|
228
|
+
|
|
229
|
+
### Building Documentation Locally
|
|
230
|
+
|
|
231
|
+
To build and serve the documentation locally:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# Install documentation dependencies
|
|
235
|
+
pip install -e ".[docs]"
|
|
236
|
+
|
|
237
|
+
# Serve documentation locally
|
|
238
|
+
mkdocs serve
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The documentation will be available at http://localhost:8000
|
|
242
|
+
|
|
243
|
+
### Documentation Structure
|
|
244
|
+
|
|
245
|
+
- **[User Guide](https://adivekar.github.io/morphic/user-guide/getting-started/)**: Comprehensive tutorials and examples
|
|
246
|
+
- **[API Reference](https://adivekar.github.io/morphic/api/)**: Detailed API documentation generated from docstrings
|
|
247
|
+
- **[Examples](https://adivekar.github.io/morphic/examples/)**: Real-world usage examples and patterns
|
|
248
|
+
- **[Contributing](https://adivekar.github.io/morphic/contributing/)**: Guidelines for contributors
|
|
249
|
+
|
|
250
|
+
## Development
|
|
251
|
+
|
|
252
|
+
### Setting Up Development Environment
|
|
253
|
+
|
|
254
|
+
1. **Clone the repository**:
|
|
255
|
+
```bash
|
|
256
|
+
git clone https://github.com/adivekar/morphic.git
|
|
257
|
+
cd morphic
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
2. **Create virtual environment**:
|
|
261
|
+
```bash
|
|
262
|
+
python -m venv venv
|
|
263
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
3. **Install in development mode**:
|
|
267
|
+
```bash
|
|
268
|
+
# Install all dependencies (recommended for contributors)
|
|
269
|
+
pip install -e ".[all]"
|
|
270
|
+
|
|
271
|
+
# Or install specific groups
|
|
272
|
+
pip install -e ".[dev]" # Just development tools
|
|
273
|
+
pip install -e ".[docs]" # Just documentation tools
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Running Tests
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# Run all tests
|
|
280
|
+
pytest
|
|
281
|
+
|
|
282
|
+
# Run with coverage
|
|
283
|
+
pytest --cov=morphic --cov-report=html
|
|
284
|
+
|
|
285
|
+
# Run specific test file
|
|
286
|
+
pytest tests/test_registry.py
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Code Quality
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# Format code
|
|
293
|
+
black src/ tests/
|
|
294
|
+
|
|
295
|
+
# Lint code
|
|
296
|
+
ruff check src/ tests/
|
|
297
|
+
|
|
298
|
+
# Type checking
|
|
299
|
+
mypy src/
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Documentation Deployment
|
|
303
|
+
|
|
304
|
+
Documentation is automatically deployed to GitHub Pages when changes are pushed to the main branch via GitHub Actions. The deployment workflow:
|
|
305
|
+
|
|
306
|
+
1. **Builds documentation** using MkDocs with Material theme
|
|
307
|
+
2. **Generates API documentation** automatically from docstrings using mkdocstrings
|
|
308
|
+
3. **Deploys to GitHub Pages** at https://adivekar.github.io/morphic/
|
|
309
|
+
|
|
310
|
+
## Performance
|
|
311
|
+
|
|
312
|
+
Morphic is optimized for production use:
|
|
313
|
+
- **AutoEnum**: 5.7M+ lookups/second with fuzzy matching
|
|
314
|
+
- **Registry**: O(1) class lookup with hierarchical inheritance
|
|
315
|
+
- **Typed**: Efficient type conversion with caching
|
|
316
|
+
|
|
317
|
+
## Requirements
|
|
318
|
+
|
|
319
|
+
- Python 3.10.9 or higher
|
|
320
|
+
- typing-extensions >= 4.0.0
|
|
321
|
+
|
|
322
|
+
## Contributing
|
|
323
|
+
|
|
324
|
+
We welcome contributions! Please see our [Contributing Guide](https://adivekar.github.io/morphic/contributing/) for details on:
|
|
325
|
+
|
|
326
|
+
- Setting up the development environment
|
|
327
|
+
- Running tests and quality checks
|
|
328
|
+
- Code style and documentation standards
|
|
329
|
+
- Pull request process
|
|
330
|
+
|
|
331
|
+
## License
|
|
332
|
+
|
|
333
|
+
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
morphic/__init__.py,sha256=HzrUZ6uJDk6chMhHf9TxQcex_LPJ32a7rfAwFqxvf1Y,313
|
|
2
|
+
morphic/autoenum.py,sha256=1v31PMEyWkdVygsNkwSjpLZdHE99b3tVQDB371-k428,14887
|
|
3
|
+
morphic/registry.py,sha256=hT01NBkAYLVCvnU9A_NsrMFoc29q1zoICgh7PB6nmBE,38767
|
|
4
|
+
morphic/typed.py,sha256=b50sriDINA0Gtn5Pdd5-O1527PFNVDsWRzpYfEvUy2c,56218
|
|
5
|
+
morphic-0.1.0.dist-info/METADATA,sha256=-MPOE5oD7dV4_R4U9yDZ-Xmd00Kx9mgaHcgZEM0kiEs,10323
|
|
6
|
+
morphic-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
morphic-0.1.0.dist-info/licenses/LICENSE,sha256=Fu83sfpznMNp6N5-PYx62oGz7AhwUOgI7dWuRL-Hh3M,1072
|
|
8
|
+
morphic-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Abhishek Divekar
|
|
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.
|