<div align="center">
# python-newtype
## Documentation
<a href="https://py-nt.asyncmove.com">
<img src="https://img.shields.io/badge/docs-passing-brightgreen.svg" width="100" alt="docs passing">
</a>
### Compatibility and Version
<img src="https://img.shields.io/badge/%3E=python-3.8-blue.svg" alt="Python compat">
<a href="https://pypi.python.org/pypi/python-newtype"><img src="https://img.shields.io/pypi/v/python-newtype.svg" alt="PyPi"></a>
### CI/CD
<a href="https://codecov.io/github/jymchng/python-newtype-dev?branch=main"><img src="https://codecov.io/github/jymchng/python-newtype-dev/coverage.svg?branch=main" alt="Coverage"></a>
### License and Issues
<a href="https://github.com/jymchng/python-newtype-dev/blob/main/LICENSE"><img src="https://img.shields.io/github/license/jymchng/python-newtype-dev" alt="License"></a>
<a href="https://github.com/jymchng/python-newtype-dev/issues"><img src="https://img.shields.io/github/issues/jymchng/python-newtype-dev" alt="Issues"></a>
<a href="https://github.com/jymchng/python-newtype-dev/issues?q=is%3Aissue+is%3Aclosed"><img src="https://img.shields.io/github/issues-closed/jymchng/python-newtype-dev" alt="Closed Issues"></a>
<a href="https://github.com/jymchng/python-newtype-dev/issues?q=is%3Aissue+is%3Aopen"><img src="https://img.shields.io/github/issues-raw/jymchng/python-newtype-dev" alt="Open Issues"></a>
### Development and Quality
<a href="https://github.com/jymchng/python-newtype-dev/network/members"><img src="https://img.shields.io/github/forks/jymchng/python-newtype-dev" alt="Forks"></a>
<a href="https://github.com/jymchng/python-newtype-dev/stargazers"><img src="https://img.shields.io/github/stars/jymchng/python-newtype-dev" alt="Stars"></a>
<a href="https://pypi.python.org/pypi/python-newtype"><img src="https://img.shields.io/pypi/dm/python-newtype" alt="Downloads"></a>
<a href="https://github.com/jymchng/python-newtype-dev/graphs/contributors"><img src="https://img.shields.io/github/contributors/jymchng/python-newtype-dev" alt="Contributors"></a>
<a href="https://github.com/jymchng/python-newtype-dev/commits/main"><img src="https://img.shields.io/github/commit-activity/m/jymchng/python-newtype-dev" alt="Commits"></a>
<a href="https://github.com/jymchng/python-newtype-dev/commits/main"><img src="https://img.shields.io/github/last-commit/jymchng/python-newtype-dev" alt="Last Commit"></a>
<a href="https://github.com/jymchng/python-newtype-dev"><img src="https://img.shields.io/github/languages/code-size/jymchng/python-newtype-dev" alt="Code Size"></a>
<a href="https://github.com/jymchng/python-newtype-dev"><img src="https://img.shields.io/github/repo-size/jymchng/python-newtype-dev" alt="Repo Size"></a>
<a href="https://github.com/jymchng/python-newtype-dev/watchers"><img src="https://img.shields.io/github/watchers/jymchng/python-newtype-dev" alt="Watchers"></a>
<a href="https://github.com/jymchng/python-newtype-dev"><img src="https://img.shields.io/github/commit-activity/y/jymchng/python-newtype-dev" alt="Activity"></a>
<a href="https://github.com/jymchng/python-newtype-dev/pulls"><img src="https://img.shields.io/github/issues-pr/jymchng/python-newtype-dev" alt="PRs"></a>
<a href="https://github.com/jymchng/python-newtype-dev/pulls?q=is%3Apr+is%3Aclosed"><img src="https://img.shields.io/github/issues-pr-closed/jymchng/python-newtype-dev" alt="Merged PRs"></a>
<a href="https://github.com/jymchng/python-newtype-dev/pulls?q=is%3Apr+is%3Aopen"><img src="https://img.shields.io/github/issues-pr/open/jymchng/python-newtype-dev" alt="Open PRs"></a>
</div>
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"<Email - Local Part: {self.local_part}; Domain Part: {self.domain_part}>"
@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 == "<Email - Local Part: test; Domain Part: example.com>"
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
== "<Email - Local Part: classmethod; Domain Part: example.com>"
)
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.
Raw data
{
"_id": null,
"home_page": "https://github.com/jymchng/python-newtype-dev",
"name": "python-newtype",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.8",
"maintainer_email": null,
"keywords": null,
"author": "Jim Chng",
"author_email": "jimchng@outlook.com",
"download_url": "https://files.pythonhosted.org/packages/a6/26/20a94291980cecee2743fc097242321f5bf0ab5c92443342225c0b6c1461/python_newtype-0.1.2.tar.gz",
"platform": null,
"description": "<div align=\"center\">\n\n# python-newtype\n\n## Documentation\n<a href=\"https://py-nt.asyncmove.com\">\n <img src=\"https://img.shields.io/badge/docs-passing-brightgreen.svg\" width=\"100\" alt=\"docs passing\">\n</a>\n\n### Compatibility and Version\n<img src=\"https://img.shields.io/badge/%3E=python-3.8-blue.svg\" alt=\"Python compat\">\n<a href=\"https://pypi.python.org/pypi/python-newtype\"><img src=\"https://img.shields.io/pypi/v/python-newtype.svg\" alt=\"PyPi\"></a>\n\n### CI/CD\n<a href=\"https://codecov.io/github/jymchng/python-newtype-dev?branch=main\"><img src=\"https://codecov.io/github/jymchng/python-newtype-dev/coverage.svg?branch=main\" alt=\"Coverage\"></a>\n\n### License and Issues\n<a href=\"https://github.com/jymchng/python-newtype-dev/blob/main/LICENSE\"><img src=\"https://img.shields.io/github/license/jymchng/python-newtype-dev\" alt=\"License\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev/issues\"><img src=\"https://img.shields.io/github/issues/jymchng/python-newtype-dev\" alt=\"Issues\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev/issues?q=is%3Aissue+is%3Aclosed\"><img src=\"https://img.shields.io/github/issues-closed/jymchng/python-newtype-dev\" alt=\"Closed Issues\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev/issues?q=is%3Aissue+is%3Aopen\"><img src=\"https://img.shields.io/github/issues-raw/jymchng/python-newtype-dev\" alt=\"Open Issues\"></a>\n\n### Development and Quality\n<a href=\"https://github.com/jymchng/python-newtype-dev/network/members\"><img src=\"https://img.shields.io/github/forks/jymchng/python-newtype-dev\" alt=\"Forks\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev/stargazers\"><img src=\"https://img.shields.io/github/stars/jymchng/python-newtype-dev\" alt=\"Stars\"></a>\n<a href=\"https://pypi.python.org/pypi/python-newtype\"><img src=\"https://img.shields.io/pypi/dm/python-newtype\" alt=\"Downloads\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev/graphs/contributors\"><img src=\"https://img.shields.io/github/contributors/jymchng/python-newtype-dev\" alt=\"Contributors\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev/commits/main\"><img src=\"https://img.shields.io/github/commit-activity/m/jymchng/python-newtype-dev\" alt=\"Commits\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev/commits/main\"><img src=\"https://img.shields.io/github/last-commit/jymchng/python-newtype-dev\" alt=\"Last Commit\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev\"><img src=\"https://img.shields.io/github/languages/code-size/jymchng/python-newtype-dev\" alt=\"Code Size\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev\"><img src=\"https://img.shields.io/github/repo-size/jymchng/python-newtype-dev\" alt=\"Repo Size\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev/watchers\"><img src=\"https://img.shields.io/github/watchers/jymchng/python-newtype-dev\" alt=\"Watchers\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev\"><img src=\"https://img.shields.io/github/commit-activity/y/jymchng/python-newtype-dev\" alt=\"Activity\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev/pulls\"><img src=\"https://img.shields.io/github/issues-pr/jymchng/python-newtype-dev\" alt=\"PRs\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev/pulls?q=is%3Apr+is%3Aclosed\"><img src=\"https://img.shields.io/github/issues-pr-closed/jymchng/python-newtype-dev\" alt=\"Merged PRs\"></a>\n<a href=\"https://github.com/jymchng/python-newtype-dev/pulls?q=is%3Apr+is%3Aopen\"><img src=\"https://img.shields.io/github/issues-pr/open/jymchng/python-newtype-dev\" alt=\"Open PRs\"></a>\n\n</div>\n\nA powerful Python library for extending existing types with additional functionality while preserving their original behavior, type information and subtype invariances.\n\n## Features\n\n- **Type Wrapping**: Seamlessly wrap existing Python types with new functionality and preservation of subtype invariances when using methods of supertype\n- **Custom Initialization**: Control object initialization with special handling\n- **Attribute Preservation**: Maintains both `__dict__` and `__slots__` attributes\n- **Memory Efficient**: Uses weak references for caching\n- **Debug Support**: Built-in debug printing capabilities for development\n- **Async Support**: Full support for asynchronous methods and operations\n\n## Quick Start\n\n### Installation\n\n```bash\npip install python-newtype\n```\n\n### Basic Usage\n\n```python\nimport pytest\nimport re\nfrom newtype import NewType, newtype_exclude\n\n\nclass EmailStr(NewType(str)):\n # you can define `__slots__` to save space\n __slots__ = (\n '_local_part',\n '_domain_part',\n )\n\n def __init__(self, value: str):\n super().__init__()\n if \"@\" not in value:\n raise TypeError(\"`EmailStr` requires a '@' symbol within\")\n self._local_part, self._domain_part = value.split(\"@\")\n\n @newtype_exclude\n def __str__(self):\n return f\"<Email - Local Part: {self.local_part}; Domain Part: {self.domain_part}>\"\n\n @property\n def local_part(self):\n \"\"\"Return the local part of the email address.\"\"\"\n return self._local_part\n\n @property\n def domain_part(self):\n \"\"\"Return the domain part of the email address.\"\"\"\n return self._domain_part\n\n @property\n def full_email(self):\n \"\"\"Return the full email address.\"\"\"\n return str(self)\n\n @classmethod\n def from_string(cls, email: str):\n \"\"\"Create an EmailStr instance from a string.\"\"\"\n return cls(email)\n\n @staticmethod\n def is_valid_email(email: str) -> bool:\n \"\"\"Check if the provided string is a valid email format.\"\"\"\n email_regex = r\"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$\"\n return re.match(email_regex, email) is not None\n\n\ndef test_emailstr_replace():\n \"\"\"`EmailStr` uses `str.replace(..)` as its own method, returning an instance of `EmailStr`\n if the resultant `str` instance is a value `EmailStr`.\n \"\"\"\n peter_email = EmailStr(\"peter@gmail.com\")\n smith_email = EmailStr(\"smith@gmail.com\")\n\n with pytest.raises(Exception):\n # this raises because `peter_email` is no longer an instance of `EmailStr`\n peter_email = peter_email.replace(\"peter@gmail.com\", \"petergmail.com\")\n\n # this works because the entire email can be 'replaced'\n james_email = smith_email.replace(\"smith@gmail.com\", \"james@gmail.com\")\n\n # comparison with `str` is built-in\n assert james_email == \"james@gmail.com\"\n\n # `james_email` is still an `EmailStr`\n assert isinstance(james_email, EmailStr)\n\n # this works because the local part can be 'replaced'\n jane_email = james_email.replace(\"james\", \"jane\")\n\n # `jane_email` is still an `EmailStr`\n assert isinstance(jane_email, EmailStr)\n assert jane_email == \"jane@gmail.com\"\n\n\ndef test_emailstr_properties_methods():\n \"\"\"Test the property, class method, and static method of EmailStr.\"\"\"\n # Test property\n email = EmailStr(\"test@example.com\")\n # `property` is not coerced to `EmailStr`\n assert email.full_email == \"<Email - Local Part: test; Domain Part: example.com>\"\n assert isinstance(email.full_email, str)\n # `property` is not coerced to `EmailStr`\n assert not isinstance(email.full_email, EmailStr)\n assert email.local_part == \"test\"\n assert email.domain_part == \"example.com\"\n\n # Test class method\n email_from_string = EmailStr.from_string(\"classmethod@example.com\")\n # `property` is not coerced to `EmailStr`\n assert (\n email_from_string.full_email\n == \"<Email - Local Part: classmethod; Domain Part: example.com>\"\n )\n assert email_from_string.local_part == \"classmethod\"\n assert email_from_string.domain_part == \"example.com\"\n\n # Test static method\n assert EmailStr.is_valid_email(\"valid.email@example.com\") is True\n assert EmailStr.is_valid_email(\"invalid-email.com\") is False\n\n\ndef test_email_str__slots__():\n email = EmailStr(\"test@example.com\")\n\n with pytest.raises(AttributeError):\n email.hi = \"bye\"\n assert email.hi == \"bye\"\n```\n\n## Documentation\n\nFor detailed documentation, visit [py-nt.asyncmove.com](https://py-nt.asyncmove.com/).\n\n### Key Topics:\n- [Installation Guide](https://py-nt.asyncmove.com/getting-started/installation/)\n- [Quick Start Guide](https://py-nt.asyncmove.com/getting-started/quickstart/)\n- [User Guide](https://py-nt.asyncmove.com/user-guide/basic-usage/)\n- [API Reference](https://py-nt.asyncmove.com/api/newtype/)\n\n## Development\n\n### Prerequisites\n\n- Python 3.8 or higher\n- C compiler (for building extensions)\n- Development packages:\n ```bash\n make install-dev-deps\n ```\n\n### Building from Source\n\n```bash\ngit clone https://github.com/jymchng/python-newtype-dev.git\ncd python-newtype-dev\nmake build\n```\n\n### Install from Source\n\n```bash\ngit clone https://github.com/jymchng/python-newtype-dev.git\ncd python-newtype-dev\nmake install\n```\n\n### Running Tests\n\n```bash\n# Run all tests\nmake test\n\n# Run with debug output\nmake test-debug\n\n# Run specific test suite\nmake test-custom\n```\n\n## Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](https://py-nt.asyncmove.com/development/contributing/) for details.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Acknowledgments\n\nSpecial thanks to all contributors who have helped shape this project.\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "A Python library for creating and managing new types with enhanced type safety and flexibility.",
"version": "0.1.2",
"project_urls": {
"Documentation": "https://py-nt.asyncmove.com",
"Homepage": "https://github.com/jymchng/python-newtype-dev",
"Repository": "https://github.com/jymchng/python-newtype-dev"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "520c9a82f2653ed35522fd87562c8b0d8effc8420028d9f04f7d94d244cb9d7f",
"md5": "8f4e88788cfed7aa704800bf3e857648",
"sha256": "81b96b505a0f9164e70bdd954965221ec190e1f12200f0398194da9fc56dcb45"
},
"downloads": -1,
"filename": "python_newtype-0.1.2-cp38-cp38-manylinux_2_35_x86_64.whl",
"has_sig": false,
"md5_digest": "8f4e88788cfed7aa704800bf3e857648",
"packagetype": "bdist_wheel",
"python_version": "cp38",
"requires_python": "<4.0,>=3.8",
"size": 18353,
"upload_time": "2024-12-10T15:29:11",
"upload_time_iso_8601": "2024-12-10T15:29:11.088278Z",
"url": "https://files.pythonhosted.org/packages/52/0c/9a82f2653ed35522fd87562c8b0d8effc8420028d9f04f7d94d244cb9d7f/python_newtype-0.1.2-cp38-cp38-manylinux_2_35_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "a62620a94291980cecee2743fc097242321f5bf0ab5c92443342225c0b6c1461",
"md5": "808780a887e9ee878c011be4f67b3d92",
"sha256": "21e80ee4e7697857e104dcbbdd99acd60e48cece955f7841af9e397cd34d4d10"
},
"downloads": -1,
"filename": "python_newtype-0.1.2.tar.gz",
"has_sig": false,
"md5_digest": "808780a887e9ee878c011be4f67b3d92",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.8",
"size": 21036,
"upload_time": "2024-12-10T15:29:13",
"upload_time_iso_8601": "2024-12-10T15:29:13.461317Z",
"url": "https://files.pythonhosted.org/packages/a6/26/20a94291980cecee2743fc097242321f5bf0ab5c92443342225c0b6c1461/python_newtype-0.1.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-10 15:29:13",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "jymchng",
"github_project": "python-newtype-dev",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "python-newtype"
}