Skip to main content

Package with const class decoratos and utility

Project description

PyConstClasses

tests examples format coverage


Overview

PyConstClasses is a python package containing const class decorators and utility. It allows for the creation constant and static constant classes by utilizing the annotations of the class definition.



Table of contents



Installation

To install the PyConstClasses package in your python environment run:

pip install pyconstclasses

or

python -m pip install pyconstclasses


Tutorial

After installing the package, you have to import it to your python program:

import constclasses as cc

Basic usage

The core of the PyConstClasses package are the const_class and static_const_class decorators. Both of these decorators override the default behaviour of the __setattr__ magic method for the decorated class so that it thors cc.ConstError when trying to modify the constant attribute of an instance.

  • The const_class decorator allows you to define a class structure and create constant instances of the defined class:

    # const_class_basic.py
    
    @cc.const_class
    class Person:
        first_name: str
        last_name: str
    
        def __repr__(self) -> str:
            return f"{self.first_name} {self.last_name}"
    
    
    if __name__ == "__main__":
        john = Person("John", "Doe")
        print(f"{john = }")
    
        try:
            john.first_name = "Bob"
        except cc.ConstError as err:
            print(f"Error: {err}")
    
        try:
            john.last_name = "Smith"
        except cc.ConstError as err:
            print(f"Error: {err}")
    

    This program will produce the following output:

    john = John Doe
    Error: Cannot modify const attribute `first_name` of class `Person`
    Error: Cannot modify const attribute `last_name` of class `Person`
    
  • The static_const_class deacorator allows you to define a pseudo-static resource with const members (it creates an instance of the decorated class):

    # static_const_class_basic.py
    
    @cc.static_const_class
    class ProjectConfiguration:
        name: str = "MyProject"
        version: str = "alpha"
    
        def __repr__(self):
            return f"Project: {self.name}\nVersion: {self.version}"
    
    
    if __name__ == "__main__":
        print(f"Project configuration:\n{ProjectConfiguration}")
    
        try:
            ProjectConfiguration.name = "NewProjectName"
        except cc.ConstError as err:
            print(f"Error: {err}")
    
        try:
            ProjectConfiguration.version = "beta"
        except cc.ConstError as err:
            print(f"Error: {err}")
    

    This program will produce the following output:

    Project configuration:
    Project: MyProject
    Version: alpha
    Error: Cannot modify const attribute `name` of class `ProjectConfiguration`
    Error: Cannot modify const attribute `version` of class `ProjectConfiguration`
    

    Although the static_const_class decorator prevents "standard" class instantiation, you can create mutable instances of such classes:

    @cc.static_const_class
    class DatabaseConfiguration:
        host: str = "localhost"
        port: int = 5432
        username: str = "admin"
        password: str = "secret"
    
        def __repr__(self):
            return (
                f"DatabaseConfiguration:\n"
                f"Host: {self.host}\n"
                f"Port: {self.port}\n"
                f"Username: {self.username}\n"
                f"Password: {self.password}"
            )
    
    
    if __name__ == "__main__":
        print(f"Database configuration:\n{DatabaseConfiguration}")
    
        try:
            DatabaseConfiguration.host = "remotehost"
        except cc.ConstError as err:
            print(f"Error: {err}")
    
        try:
            DatabaseConfiguration.port = 3306
        except cc.ConstError as err:
            print(f"Error: {err}")
    
        # Create a mutable instance for testing or development
        mutable_config = cc.mutable_instance(DatabaseConfiguration)
        mutable_config.host = "testhost"
        mutable_config.username = "testuser"
        mutable_config.password = "testpassword"
    
        print("\nMutable configuration for testing:")
        print(mutable_config)
    

[!IMPORTANT] In the current version of the package the constant attributes have to be defined using annotations, i.e. the member: type (= value) syntax of the class member declaration is required


Common parameters

Both const decorators - const_class and static_const_class - have the following parameters:

  • with_strict_types: bool

    If this parameter's value is set to

    • False - the decorators will use the attribute_type(given_value) conversion, so as long as the given value's type is convertible to the desired type, the decorators will not raise any errors.
    • True - the decorators will perform an isinstance(given_value, attribute_type) check, the failure of which will result in raising a TypeError

    Example:

    # common_with_strict_types.py
    
    @cc.const_class
    class Person:
        first_name: str
        last_name: str
        age: int
    
        def __repr__(self) -> str:
            return f"{self.first_name} {self.last_name} [age: {self.age}]"
    
    @cc.const_class(with_strict_types=True)
    class PersonStrictTypes:
        first_name: str
        last_name: str
        age: int
    
        def __repr__(self) -> str:
            return f"{self.first_name} {self.last_name} [age: {self.age}]"
    
    
    if __name__ == "__main__":
        john = Person("John", "Doe", 21.5)
        print(john)
    
        try:
            # invalid as 21.5 is not an instance of int
            john_strict = PersonStrictTypes("John", "Doe", 21.5)
        except TypeError as err:
            print(f"Error:\n{err}")
    
        john_strict = PersonStrictTypes("John", "Doe", 21)
        print(john_strict)
    

    This program will produce the following output:

    John Doe [age: 21]
    Error:
    Attribute value does not match the declared type:
            attribute: age, declared type: <class 'int'>, actual type: <class 'float'>
    John Doe [age: 21]
    
  • include: set[str] and exclude: set[str]

    These parameters are used to define which class attributes are supposed to be treated as constant. If they are not set (or explicitly set to None) all attributes will be treaded as constant.

    Example:

    # common_include.py
    
    @cc.const_class(include=["first_name", "last_name"])
    class Person:
        first_name: str
        last_name: str
        age: int
    
        def __repr__(self) -> str:
            return f"{self.first_name} {self.last_name} [age: {self.age}]"
    
    
    if __name__ == "__main__":
        john = Person("John", "Doe", 21)
        print(f"{john = }")
    
        try:
            john.first_name = "Bob"
        except cc.ConstError as err:
            print(f"Error: {err}")
    
        try:
            john.last_name = "Smith"
        except cc.ConstError as err:
            print(f"Error: {err}")
    
        # valid modification as the `age` parameter is not in the include set
        john.age = 22
        print(f"{john = }")
    

    This program will produce the followig output:

    john = John Doe [age: 21]
    Error: Cannot modify const attribute `first_name` of class `Person`
    Error: Cannot modify const attribute `last_name` of class `Person`
    john = John Doe [age: 22]
    

    The same can be achieved using the exclude parameter:

    Example:

    # common_exclude.py
    
    @cc.const_class(exclude=["age"])
    class Person:
        first_name: str
        last_name: str
        age: int
    
        def __repr__(self) -> str:
            return f"{self.first_name} {self.last_name} [age: {self.age}]"
    

    The class defined in this example has the behaviour equivalent to the earlier include example.

[!IMPORTANT] Simultaneous usage of the incldue and exclude parameters will result in raising a configuration error.


Decorator-specific parameters

[!NOTE] In the current version of the package only the const_class decorator has it's own specific parameters.

  • with_kwargs: bool

    By default the const_class decorator adds a constructor which uses positional arguments to create a constant instance of the class. However if this parameter is set to True, the decorator will use the keyword arguments for this purpose.

    Example:

    # const_class_with_kwargs.py
    
    @cc.const_class
    class PersonArgs:
        first_name: str
        last_name: str
    
        def __repr__(self) -> str:
            return f"{self.first_name} {self.last_name}"
    
    
    @cc.const_class(with_kwargs=True)
    class PersonKwargs:
        first_name: str
        last_name: str
    
        def __repr__(self) -> str:
            return f"{self.first_name} {self.last_name}"
    
    
    if __name__ == "__main__":
        john_args = PersonArgs("John", "Doe")
        print(f"{john_args = }")
    
        try:
            john_args = PersonArgs(first_name="John", last_name="Doe")
        except cc.InitializationError as err:
            print(f"Error: {err}")
    
        john_kwargs = PersonKwargs(first_name="John", last_name="Doe")
        print(f"{john_kwargs = }")
    

    This program will produce the following output:

    john_args = John Doe
    Error: Invalid number of arguments: expected 2 - got 0
    john_kwargs = John Doe
    
  • inherit_constructor: bool

    By default the const_class decorator defines a constructor which manually assigns values to attributes. However if this parameter is set to True the class will be initialized using the user defined __init__ function of the decorated class.

    Example:

    # const_class_inherit_constructor.py
    
    @cc.static_const_class
    class ProjectConfiguration:
        name: str = "MyProject"
        version: str = "alpha"
    
        def __repr__(self):
            return f"Project: {self.name}\nVersion: {self.version}"
    
    
    if __name__ == "__main__":
        print(f"Project configuration:\n{ProjectConfiguration}")
    
        try:
            ProjectConfiguration.name = "NewProjectName"
        except cc.ConstError as err:
            print(f"Error: {err}")
    
        try:
            ProjectConfiguration.version = "beta"
        except cc.ConstError as err:
            print(f"Error: {err}")
    

    This program will produce the following output:

    john = John Doe
    Error: Cannot modify const attribute `first_name` of class `Person`
    Error: Cannot modify const attribute `last_name` of class `Person`
    


Examples

The project examples shown in the Tutorial section can be found in the examples directory.

To run the example programs you need to install the PyConstClasses package into your python environment. You can install it via pip of using a local distribution build (the process is described in the Dev notes section).



Dev notes

Environment setup:

To be able to build or test the project create a python virtual environment

python -m venv cc_venv
source cc_venv/bin/activate
pip install -r requirements-dev.txt

[!NOTE] If any package listed in requirements-dev.txt is no longer required or if there is a new required package, update the requirements file using: pip freeze > requirements-dev.txt

Building

To build the package, run:

python -m build

This will generate the dist directory with the .whl file. To locally test if the distribution is correct, you can run:

pip install dist/pyconstclasses-<version>-py3-none-any.whl --force-reinstall

[!NOTE] To test the package build locally it is recommended to use a clean virtual environment.

Testing:

The project uses pytest and tox for testing purposes.

  • To run the tests for the current python interpreter run: pytest -v
  • To run tests for all supported python versions run: tox

Coverage

To generate a test coverage report you can run tox which will automatically generate json and xml reports (the types of coverage reports can be adjusted in tox.ini).

You can also generate the coverage reports manually with pytest, e.g.:

pytest -v --cov=constclasses --cov-report=xml --cov-report=html

[!NOTE] When testing the project or generating coverate reports, python (or it's packages) will generate additional files (cache file, etc.). To easily clean those files from the working directory run bash scripts/cleanup.sh

Formatting:

The project uses black and isort for formatting purposes. To format the source code use the prepared script:

bash scripts/format.sh

You can also use the black and isort packages directly, e.g.:

python -m <black/isort> <path> (--check)


Licence

The PyConstClasses project uses the MIT Licence

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyconstclasses-1.0.tar.gz (14.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyconstclasses-1.0-py3-none-any.whl (9.8 kB view details)

Uploaded Python 3

File details

Details for the file pyconstclasses-1.0.tar.gz.

File metadata

  • Download URL: pyconstclasses-1.0.tar.gz
  • Upload date:
  • Size: 14.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.9.19

File hashes

Hashes for pyconstclasses-1.0.tar.gz
Algorithm Hash digest
SHA256 3de403d79e2edecce65154544338bf8557a94579ec10e3dbbabef0172bec51e5
MD5 e44535a075d79e00e4e814dce4b8d2e2
BLAKE2b-256 a29c688d802072f9387d94df9d91ba27ae732b675d23631ffafbcb8df7d05c80

See more details on using hashes here.

File details

Details for the file pyconstclasses-1.0-py3-none-any.whl.

File metadata

  • Download URL: pyconstclasses-1.0-py3-none-any.whl
  • Upload date:
  • Size: 9.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.9.19

File hashes

Hashes for pyconstclasses-1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 77b49f0c0379adbc2de3b669f607d17cc606738551efa6f2fae6e155e6370407
MD5 83cd0de06c695ad197014f439cc95c22
BLAKE2b-256 271c22182d77d5438186a7db94690564bd35958fdbbb0f4818129aa5de32a2c5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page