https://github.com/jymchng/python-newtype-dev
Beautiful way to create new types for your Python classes!
https://github.com/jymchng/python-newtype-dev
classes newtype python python3 types
Last synced: 8 months ago
JSON representation
Beautiful way to create new types for your Python classes!
- Host: GitHub
- URL: https://github.com/jymchng/python-newtype-dev
- Owner: jymchng
- License: mit
- Created: 2024-08-04T16:30:29.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-02-20T08:20:53.000Z (about 1 year ago)
- Last Synced: 2025-07-03T04:08:34.001Z (9 months ago)
- Topics: classes, newtype, python, python3, types
- Language: Python
- Homepage: https://py-nt.asyncmove.com/
- Size: 54.6 MB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# python-newtype
A powerful Python library for extending existing types with additional functionality while preserving their original behavior, type information and subtype invariances.
## Features
- **Type Wrapping**: Seamlessly wrap existing Python types with new functionality and preservation of subtype invariances when using methods of supertype
- **Custom Initialization**: Control object initialization with special handling
- **Attribute Preservation**: Maintains both `__dict__` and `__slots__` attributes
- **Memory Efficient**: Uses weak references for caching
- **Debug Support**: Built-in debug printing capabilities for development
- **Async Support**: Full support for asynchronous methods and operations
## Quick Start
### Installation
```bash
pip install python-newtype
```
### Basic Usage
```python
import pytest
import re
from newtype import NewType, newtype_exclude
class EmailStr(NewType(str)):
# you can define `__slots__` to save space
__slots__ = (
'_local_part',
'_domain_part',
)
def __init__(self, value: str):
super().__init__()
if "@" not in value:
raise TypeError("`EmailStr` requires a '@' symbol within")
self._local_part, self._domain_part = value.split("@")
@newtype_exclude
def __str__(self):
return f""
@property
def local_part(self):
"""Return the local part of the email address."""
return self._local_part
@property
def domain_part(self):
"""Return the domain part of the email address."""
return self._domain_part
@property
def full_email(self):
"""Return the full email address."""
return str(self)
@classmethod
def from_string(cls, email: str):
"""Create an EmailStr instance from a string."""
return cls(email)
@staticmethod
def is_valid_email(email: str) -> bool:
"""Check if the provided string is a valid email format."""
email_regex = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
return re.match(email_regex, email) is not None
def test_emailstr_replace():
"""`EmailStr` uses `str.replace(..)` as its own method, returning an instance of `EmailStr`
if the resultant `str` instance is a value `EmailStr`.
"""
peter_email = EmailStr("peter@gmail.com")
smith_email = EmailStr("smith@gmail.com")
with pytest.raises(Exception):
# this raises because `peter_email` is no longer an instance of `EmailStr`
peter_email = peter_email.replace("peter@gmail.com", "petergmail.com")
# this works because the entire email can be 'replaced'
james_email = smith_email.replace("smith@gmail.com", "james@gmail.com")
# comparison with `str` is built-in
assert james_email == "james@gmail.com"
# `james_email` is still an `EmailStr`
assert isinstance(james_email, EmailStr)
# this works because the local part can be 'replaced'
jane_email = james_email.replace("james", "jane")
# `jane_email` is still an `EmailStr`
assert isinstance(jane_email, EmailStr)
assert jane_email == "jane@gmail.com"
def test_emailstr_properties_methods():
"""Test the property, class method, and static method of EmailStr."""
# Test property
email = EmailStr("test@example.com")
# `property` is not coerced to `EmailStr`
assert email.full_email == ""
assert isinstance(email.full_email, str)
# `property` is not coerced to `EmailStr`
assert not isinstance(email.full_email, EmailStr)
assert email.local_part == "test"
assert email.domain_part == "example.com"
# Test class method
email_from_string = EmailStr.from_string("classmethod@example.com")
# `property` is not coerced to `EmailStr`
assert (
email_from_string.full_email
== ""
)
assert email_from_string.local_part == "classmethod"
assert email_from_string.domain_part == "example.com"
# Test static method
assert EmailStr.is_valid_email("valid.email@example.com") is True
assert EmailStr.is_valid_email("invalid-email.com") is False
def test_email_str__slots__():
email = EmailStr("test@example.com")
with pytest.raises(AttributeError):
email.hi = "bye"
assert email.hi == "bye"
```
## Documentation
For detailed documentation, visit [py-nt.asyncmove.com](https://py-nt.asyncmove.com/).
### Key Topics:
- [Installation Guide](https://py-nt.asyncmove.com/getting-started/installation/)
- [Quick Start Guide](https://py-nt.asyncmove.com/getting-started/quickstart/)
- [User Guide](https://py-nt.asyncmove.com/user-guide/basic-usage/)
- [API Reference](https://py-nt.asyncmove.com/api/newtype/)
## Development
### Prerequisites
- Python 3.8 or higher
- C compiler (for building extensions)
- Development packages:
```bash
make install-dev-deps
```
### Building from Source
```bash
git clone https://github.com/jymchng/python-newtype-dev.git
cd python-newtype-dev
make build
```
### Install from Source
```bash
git clone https://github.com/jymchng/python-newtype-dev.git
cd python-newtype-dev
make install
```
### Running Tests
```bash
# Run all tests
make test
# Run with debug output
make test-debug
# Run specific test suite
make test-custom
```
## Contributing
We welcome contributions! Please see our [Contributing Guide](https://py-nt.asyncmove.com/development/contributing/) for details.
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Acknowledgments
Special thanks to all contributors who have helped shape this project.