Skip to main content

A kit containing tools for better programming.

Project description

PyPI Downloads

Overview

modkit is a collection of tools that can be used for better programming experience and automation.

Features

  • @override decorator for python3.8+
  • New Property class that automates property definition and takes less number of lines of code to define.
  • New Possibility class that helps extend return types with custom methods/properties which in turn helps in better readability of code.

Usage

  1. @override decorator and functionality.

    The @override decorator was introduced in python3.12+ under the typing module. However, for python<3.12, it is not available.

    The override class under modkit supports python3.8+ and brings additional features rather than just helping in maintaining an error free codebase.

    The override class contains two static methods that can be used as decorators: cls and mtd. The @override.mtd acts as a decorator for the child class methods which are being over-ridden from the parent class. It sets the __override__ attribute of the methods, it has been decorated with, as True.

    The @override.cls decorator on top of a child class, checks for all methods that have their __override__ attribute set as True and it generates errors accordingly. It will raise MethodOverrideError if the method set as override is not present in the parent class. Additionally, it will also check if the current class is a child class or not (inheritance is implemented or not).

    Apart from methods, the properties need not be decorated with mtd, the @override.cls over the class will automatically check if the property is over-ridden or not. If a over-ridden property does not include a setter or deleter or both, the parent property's setter and deleter will be copied to the child class.

    Use case

    1. Import the override class from modkit.typing

      from modkit.typing import override
      
    2. Define a Base Class.

      class Base:
          def __init__(self) -> None:
              self.value = 10
          
          def base_method(self) -> None:
              self.value += 1
      
          @property
          def base_property(self) -> int:
              return self.value
          
          @base_property.setter
          def base_property(self, value) -> None:
              self.value = value
          
          @base_property.deleter
          def base_property(self) -> None:
              self.value = 0
      
    3. Define the Child Class with override class.

      @override.cls # valid
      class Child(Base):
          @override.mtd # valid
          def base_method(self, incrementor: int) -> None
              self.value += incrementor
          
          @property # valid
          def base_property(self) -> int:
              return self.value + 10
          
          # since no setter, deleter are set,
          # it will use parent's setter and deleter.
      
          @override.mtd # invalid, not present in base class.
          def child_method(self) -> str:
              return str(self.value)
      
    4. The look_in classmethod of override class.

      The override class checks for methods and properties present in the child class with respect to the recent parent of the child. However, this behavior can be changed to check for the topmost base class (not object).

      Example:

      override.look_in('topmost') # other values: `recent`
      
      class Top:
          def top_method(self) -> None:
              pass
      
      @override.cls
      class Mid(Top):
          @override.mtd # valid
          def top_method(self) -> str:
              pass
      
          def mid_method(self) -> None:
              pass
      
      @override.cls
      class Bottom(Mid):
          @override.mtd 
          # invalid, mid_method is present in mid,
          # and not in the top most base class that is Top
          def mid_method(self) -> str:
              pass
      
          @override.mtd # valid
          def top_method(self) -> int:
              pass
      

      To avoid conflicts in most cases, use the default setting, or go back to the default setting using override.look_in('recent').

  2. Property class for automating property creation.

    Usage Warning: Property class internally uses property to maintain, create and dispatch property getter, setter and deleter, however, it does not support static type checking as this an external class.

    The Property class can be used to quickly and neatly define properties of a class.

    1. By binding an attribute.

      The property class can be used to create a property that is bound to an attribute, with easy setter and deleter creation or blocking.

      from modkit.classtools import Property
      
      class Example:
          def __init__(self, value: int) -> None:
              self._value = value
          
          # note that, this is the only way the Property can be defined,
          # it cannot be defined inside a method, or another property.
          value = Property(
              attribute='_value', # bind to _value,
              setter=True, # enable setter,
              deleter=True, # enable deleter,
              deleter_deletes_attribute=False,
              # this prevents the deleter from actually deleting the
              # attribute (del self._value). Instead, it sets self._value
              # to None.
              # However, if deleter_deletes_attribute is set to True,
              # the _value attribute will be deleted.
          )
      

      It also supports blocking setter or deleter or both.

      from modkit.classtools import Property
      
      class Example2:
          def __init__(self, value: int) -> None:
              self._value = value
          
          value = Property(
              attribute='_value', # bind to _value,
              setter=False, # block setting it,
              deleter=False, # block deletion,
              error=AttributeError, # choose any exception to raise,
              # any custom exception can also be used.
              # as long as it is a child class of Exception or Exception
              # itself,
              setter_error_arguments=("Cannot set _value",),
              # choose setter error arguments for the exception.
              deleter_error_arguments-("Cannot delete _value",),
              # choose deleter error arguments for the exception.
          )
      
          # This property can only be accessed and cannot be set or
          # deleted. Trying to do the same will raise AttributeError.
      
    2. By explicitly defining getter, setter, and deleter.

      from modkit.classtools import Property
      
      class Example3:
          def __init__(self, value: int) -> None:
              self._value = value
          
          value_is_none = Property(
              getter=lambda cls: cls._value is None, # returns bool.
              setter=None, # blocking setter, or define it just like getter.
              deleter=None, # blocking deleter.
              error=ValueError, # set, delete will raise ValueError,
              setter_error_arguments=("Read-only property.",),
              deleter_error_arguments=("Read-only property.",),
          )
      
          # This will create a property named value_is_none which returns
          # bool, based on _value.
      
          # using previously defined property.
          value_is_not_none = Property(
              getter=lambda cls: not cls.value_is_none,
              setter=None,
              deleter=None,
              error=AttributeError,
              setter_error_arguments=("Read-only.",),
              deleter_error_arguments=("Read-only.",),
          )
      
          # creating with setter and deleter.
          value = Property(
              getter=lambda cls: cls._value,
              setter=lambda cls, value: setattr(cls, '_value', value),
              deleter=lambda cls: setattr(cls, '_value', None),
          )
      
  3. The Possibility class to extend return types functionality.

    Suppose there is a function that returns an int and that is it for that, it cannot do anything else.

    I wanted to check if the int value returned is a positive number or negative. A normal code will do it like this:

    # a function that returns an int
    def func() -> int:
        return 10
    
    # checking positive or negative
    >>> func() > 0
    True
    

    The Possibility way:

    1. Import the Possibility abstract class.

      from modkit.classtools import Possibility
      
    2. Create a new return type, say, Extra.

      class Extra(Possibility):
          # leave the __init__ method untouched.
          # The Possibility class has three properties
          # parent, attribute (attribute_name) 
          # and value (attribute's value).
      
          # __bool__ method needs to be defined (as it is an
          # abstract method.), we will do it later.
      
          # let us create a method named, is_positive.
          def is_positive(self) -> bool:
              return self.value >= 0
          
          # the __bool__ method
          def __bool__(self) -> bool:
              return self.value is not None
              # this implements the `if <class-here>` statement
              # so u can set any logic here.
      
    3. Modify the Function.

      # The __init__ method of Extra will take 3 parameters.
      # parent: the parent class.
      # attribute: The attribute name
      # value: The value.
      
      # for now, we will leave, parent, and attribute.
      def func() -> Extra[int]:
          return Extra(parent=None, attribute='', value=10)
      
    4. Assess the result.

      >>> func() # will return Extra
      <Extra object at XXXXXXX>
      
      >>> func().is_positive() # if the value is positive.
      True
      
      >>> func()() # get the value.
      10
      
    5. Using in a class.

      We will use the Extra class we created above.

      class Example:
          def __init__(self, value: int) -> None:
              self._value = value
          
          @property
          def value(self) -> Extra[int]:
              return Extra(
                  parent=self,
                  attribute='_value',
                  value=self._value
              )
      

      Let us see the results.

      >>> obj = Example(100)
      >>> obj.value # simply using this will return the Extra class object
      <Extra object at XXXXXX>
      >>> obj.value() # even though it is a property, () calling it will return the value
      100
      # if it was a method, you have to call it twice, like value()().
      >>> obj.value.is_positive() # better readability.
      True
      

Contributing

Contributions are welcome, create and issue here and create a pull request here.

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

modkit-1.3.0.tar.gz (15.7 kB view details)

Uploaded Source

Built Distribution

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

modkit-1.3.0-py3-none-any.whl (15.5 kB view details)

Uploaded Python 3

File details

Details for the file modkit-1.3.0.tar.gz.

File metadata

  • Download URL: modkit-1.3.0.tar.gz
  • Upload date:
  • Size: 15.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.13.1

File hashes

Hashes for modkit-1.3.0.tar.gz
Algorithm Hash digest
SHA256 8504f56a1b2f83d83dd00ed44f86eab86ba89d38354bbc71d7d664f7f8b8c00b
MD5 e3c835f0bad8e5e9d7c06ebd9b32dceb
BLAKE2b-256 fdba3f480e76dbf1b958953a84dd5cc5d2be76e25f88bc219f48df6592e157d3

See more details on using hashes here.

File details

Details for the file modkit-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: modkit-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 15.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.13.1

File hashes

Hashes for modkit-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c197f34d9b9bb50fd4904b52ed58ed7a87e04822a5dc987562e709475875c911
MD5 f4927e4cccc863e957353c2df85f5cfb
BLAKE2b-256 09e68662ae89bd424d805771d56ed83c70d61e592cb90332730e3ec1573c5aaf

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