python-newtype


Namepython-newtype JSON
Version 0.1.2 PyPI version JSON
download
home_pagehttps://github.com/jymchng/python-newtype-dev
SummaryA Python library for creating and managing new types with enhanced type safety and flexibility.
upload_time2024-12-10 15:29:13
maintainerNone
docs_urlNone
authorJim Chng
requires_python<4.0,>=3.8
licenseApache-2.0
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            <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"
}
        
Elapsed time: 2.01289s