Types that make coding in Python quick and safe.

Overview

Type[T]

PyPI Python Versions Build Status Coverage Status Code Quality

Types that make coding in Python quick and safe.

Type[T] works best with Python 3.6 or later. Prior to 3.6, object types must use comment type hint syntax.

Installation

Install it using pip:

pip install typet

Features

  • An Object base class that eliminates boilerplate code and verifies and coerces types when possible.
  • Validation types that, when instantiated, create an instance of a specific type and verify that they are within the user defined boundaries for the type.

Quick Start: Creating a Person

Import the Type[T] types that you will use.

from typet import Bounded, Object, String
  • Object, for composing complex objects
  • Bound to describe a type that validates its value is of the correct type and within bounds upon instantiation
  • String, which will validate that it is instantiated with a string with a length within the defined bounds.

Create Type Aliases That Describe the Intent of the Type

Age = Bounded[int, 0:150]
Name = String[1:50]
Hobby = String[1:300]

In this example, a Person has an Age, which is an integer between 0 and 150, inclusive; a Name which must be a non-empty string with no more than 50 characters; and finally, a Hobby, which is a non-empty string with no more than 300 characters.

Compose a Person object Using Type Aliases

class Person(Object):
    name: Name
    surname: Name
    age: Age
    hobby: Hobby = None

Assigning a class attribute sets that value as the default value for instances of the Object. In this instance, hobby is assigned a default value of None; by convention, this tells Python that the type is Optional[Hobby], and Type[T] will allow None in addition to strings of the correct length.

Put It All Together

from typet import Bounded, Object, String

Age = Bounded[int, 0:150]
Name = String[1:50]
Hobby = String[1:300]

class Person(Object):
    name: Name
    surname: Name
    age: Age
    hobby: Hobby = None

Person is now a clearly defined and typed object with an intuitive constructor, hash method, comparison operators and bounds checking.

Positional arguments will be in the order of the definition of class attributes, and keyword arguments are also acceptable.

jim = Person('Jim', 'Coder', 23, 'Python')
bob = Person('Robert', 'Coder', hobby='C++', age=51)

Python 2.7 to 3.5

Type[T] supports PEP 484 class comment type hints for defining an Object.

from typing import Optional

from typet import Bounded, Object, String

Age = Bounded[int, 0:150]
Name = String[1:50]
Hobby = String[1:300]

class Person(Object):
    name = None  # type: Name
    surname = None  # type: Name
    age = None  # type: Age
    hobby = None  # type: Optional[Hobby]

Note that, because Python prior to 3.6 cannot annotate an attribute without defining it, by convention, assigning the attribute to None will not imply that it is optional; it must be specified explicitly in the type hint comment.

Object Types

Object

One of the cooler features of Type[T] is the ability to create complex objects with very little code. The following code creates an object that generates properties from the annotated class attributes that will ensure that only values of int or that can be coerced into int can be set. It also generates a full suite of common comparison methods.

from typet import Object

class Point(Object):
    x: int
    y: int

Point objects can be used intuitively because they generate a standard __init__ method that will allow positional and keyword arguments.

p1 = Point(0, 0)      # Point(x=0, y=0)
p2 = Point('2', 2.5)  # Point(x=2, y=2)
p3 = Point(y=5, x=2)  # Point(x=2, y=5)
assert p1 < p2        # True
assert p2 < p1        # AssertionError

A close equivalent traditional class would be much larger, would have to be updated for any new attributes, and wouldn't support more advanced casting, such as to types annotated using the typing module:

class Point(object):

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return 'Point(x={x}, y={y})'.format(x=self.x, y=self.y)

    def __setattr__(self, name, value):
        if name in ('x', 'y'):
            value = int(value)
        super(Point, self).__setattr__(name, value)

    def __eq__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.x, self.y) == (other.x, other.y)

    def __ne__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.x, self.y) != (other.x, other.y)

    def __lt__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.x, self.y) < (other.x, other.y)

    def __le__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.x, self.y) <= (other.x, other.y)

    def __gt__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.x, self.y) > (other.x, other.y)

    def __ge__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.x, self.y) >= (other.x, other.y)

    def __hash__(self):
        return hash((self.x, self.y))

Attributes can be declared optional either manually, by using typing.Optional or by using the PEP 484 implicit optional of a default value of None.

from typing import Optional

from typet import Object

class Point(Object):
    x: Optional[int]
    y: int = None

p1 = Point()   # Point(x=None, y=None)
p2 = Point(5)  # Point(x=5, y=None)

StrictObject

By default, Object will use cast from typingplus to attempt to coerce any values supplied to attributes to the annotated type. In some cases, it may be preferred to disallow casting and only allow types that are already of the correct type. StrictObject has all of the features of Object, but will not coerce values into the annotated type.

from typet import StrictObject

class Point(StrictObject):
    x: int
    y: int

Point(0, 0)      # Okay
Point('2', 2.5)  # Raises TypeError

StrictObject uses is_instance from typingplus to check types, so it's possible to use types from the typing library for stricter checking.

from typing import List

from typet import StrictObject

class IntegerContainer(StrictObject):
    integers: List[int]

IntegerContainer([0, 1, 2, 3])          # Okay
IntegerContainer(['a', 'b', 'c', 'd'])  # Raises TypeError

Validation Types

Type[T] contains a suite of sliceable classes that will create bounded, or validated, versions of those types that always assert their values are within bounds; however, when an instance of a bounded type is instantiated, the instance will be of the original type.

Bounded

Bounded can be sliced with either two arguments or three. The first argument is the type being bound. The second is a slice containing the upper and lower bounds used for comparison during instantiation.

from typet import Bounded

BoundedInt = Bounded[int, 10:20]

BoundedInt(15)  # Okay
type(x)         # <class 'int'>
BoundedInt(5)   # Raises ValueError

Optionally, a third argument, a function, may be supplied that will be run on the value before the comparison.

from typet import Bounded

LengthBoundedString = Bounded[str, 1:3, len]

LengthBoundedString('ab')    # Okay
LengthBoundedString('')      # Raises ValueError
LengthBoundedString('abcd')  # Raises ValueError

Length

Because len is a common comparison method, there is a shortcut type, Length that takes two arguments and uses len as the comparison method.

from typing import List

from typet import Length

LengthBoundedList = Length[List[int], 1:3]

LengthBoundedList([1, 2])        # Okay
LengthBoundedList([])            # Raises ValueError
LengthBoundedList([1, 2, 3, 4])  # Raises ValueError

String

str and len are commonly used together so a special type, String, has been added to simplify binding strings to specific lengths.

from typet import String

ShortString = String[1:3]

ShortString('ab')    # Okay
ShortString('')      # Raises ValueError
ShortString('abcd')  # Raises ValueError

Note that, on Python 2, String instantiates unicode objects and not str.

Metaclasses and Utilities

Singleton

Singleton will cause a class to allow only one instance.

from typet import Singleton

class Config(metaclass=Singleton):
    pass

c1 = Config()
c2 = Config()
assert c1 is c2  # Okay

Singleton supports an optional __singleton__ method on the class that will allow the instance to update if given new parameters.

from typet import Singleton

class Config(metaclass=Singleton):

    def __init__(self, x):
        self.x = x

    def __singleton__(self, x=None):
        if x:
            self.x = x

c1 = Config(1)
c1.x                   # 1
c2 = Config()          # Okay because __init__ is not called.
c2.x                   # 1
c3 = Config(3)         # Calls __singleton__ if it exists.
c1.x                   # 3
c2.x                   # 3
c3.x                   # 3
assert c1 is c2 is c3  # Okay

@singleton

Additionally, there is a decorator, @singleton that can be used make a class a singleton, even if it already uses another metaclass. This is convenient for creating singleton Objects.

from typet import Object, singleton

@singleton
class Config(Object):
    x: int

c1 = Config(1)
c2 = Config()    # Okay because __init__ is not called.
assert c1 is c2  # Okay

@metaclass

Type[T] contains a class decorator, @metaclass, that will create a derivative metaclass from the given metaclasses and the metaclass used by the decorated class and recreate the class with the derived metaclass.

Most metaclasses are not designed to be used in such a way, so careful testing must be performed when this decorator is to be used. It is primarily intended to ease use of additional metaclasses with Objects.

from typet import metaclass, Object, Singleton

@metaclass(Singleton)
class Config(Object):
    x: int

c1 = Config(1)
c2 = Config()    # Okay because __init__ is not called.
assert c1 is c2  # Okay
Comments
  • The Singleton meta class unexpectedly raises a TypeError when we thought __instance__ existed

    The Singleton meta class unexpectedly raises a TypeError when we thought __instance__ existed

    In [4]: _RcliConfig.call()

    TypeError Traceback (most recent call last) in () ----> 1 _RcliConfig.call()

    ~/rcli/venv/lib/python3.6/site-packages/typet/meta.py in call(cls, args, **kwargs) 78 else: 79 try: ---> 80 cls.instance.singleton(args, **kwargs) # type: ignore 81 except AttributeError: 82 pass

    TypeError: 'NoneType' object is not callable

    The code in question is here:

    https://github.com/contains-io/typet/blob/master/typet/meta.py#L64

    opened by zancas 4
  • Add base classes SingletonObject and StrictSingletonObject

    Add base classes SingletonObject and StrictSingletonObject

    Create a SingletonObject by creating a private metaclass that inherits from Singleton and _ObjectMeta and a metaclass for a StrictSingletonObject that inherits from Singleton and _StrictObjectMeta.

    The proposed use case would be a global settings object:

    from typet import SingletonObject, File
    
    class _Configuration(SingletonObject):
        config: File = '~/.my_config'
    
    settings = _Configuration()
    
    enhancement 
    opened by dangle 1
  • Add support for type comments.

    Add support for type comments.

    Because the annotations are read during the metaclass before the class is created, it may be necessary to add support for reading the type hint comments in the metaclass.

    enhancement 
    opened by dangle 1
  • Register validation types as the sliced type.

    Register validation types as the sliced type.

    It should be possible to run this test:

    from typingplus import is_instance
    from typet import Bounded
    assert is_instance(5, Bounded[int, 0:10])
    

    I believe this causes an issue when using validation types with StrictObject.

    This can be done by setting __instancecheck__ and __subclasscheck__ in BoundedMeta._BoundedSubclass.

    bug 
    opened by dangle 1
  • Break package into multiple modules.

    Break package into multiple modules.

    typet/__init__.py is becoming unwieldy. It should be broken into multiple packages and imported into the package with wildcards.

    Initial proposed packages:

    • typet
    • typet.path
    • typet.validation
    • typet.object
    refactor 
    opened by dangle 0
  • Path validation objects should use pathlib.

    Path validation objects should use pathlib.

    The path validation objects, File, Dir, Path, and ExistingPath should instantiate pathlib objects instead of strings. They should also accept pathlib objects.

    A check to import pathlib2 will need to be added to setup.py.

    bug 
    opened by dangle 0
  • Using the @singleton errors on default metaclass.

    Using the @singleton errors on default metaclass.

    When using @singleton on a class that does not inherit from Object (no other metaclasses defined) throws an exception.

    Traceback (most recent call last):
      File "<input>", line 1, in <module>
        @singleton
      File "/home/dangle/Projects/contains.io/containment/.tox/py36/lib/python3.6/site-packages/typet/meta.py", line 58, in _inner
        class _Meta(base, _Meta):  # pylint: disable=function-redefined
    TypeError: Cannot create a consistent method resolution
    order (MRO) for bases type, Singleton
    
    bug 
    opened by dangle 0
  • Add support for Generics in Object and StrictObject

    Add support for Generics in Object and StrictObject

    Currently, using Generics with Object is a pain. Initial support for casting and type validation exist, but creating the class itself is awkward as it requires creating a metaclass.

    from typet import Object
    from typing import Generic, GenericMeta, TypeVar
    
    T = TypeVar('T')
    
    class Meta(type(Object), GenericMeta): ...
    
    class MyObject(Object, Generic[T], meta=Meta):
        value: T
    
    enhancement 
    opened by dangle 0
  • Non Object attributes violate the expectations of the __hash__ function.

    Non Object attributes violate the expectations of the __hash__ function.

    If I make a proper typet Object, I can still assign to its attributes at run time, subsequent comparison or hash operations on the resulting instance will now return unexpected results. I believe the solution is to add logic to set_attr to prevent non-Object, or StrictObject assignment.

    opened by zancas 1
  • Validation types do not work with mypy.

    Validation types do not work with mypy.

    mypy reports validation objects as invalid type aliases.

    Additionally, if a validation type is used as a class annotation without being aliased, mypy ends analysis with an invalid syntax error.

    See python/mypy#4285.

    blocked 
    opened by dangle 0
Owner
Contains
Contains
The source code that powers readthedocs.org

Welcome to Read the Docs Purpose Read the Docs hosts documentation for the open source community. It supports Sphinx docs written with reStructuredTex

Read the Docs 7.4k Dec 25, 2022
Generate modern Python clients from OpenAPI

openapi-python-client Generate modern Python clients from OpenAPI 3.x documents. This generator does not support OpenAPI 2.x FKA Swagger. If you need

555 Jan 02, 2023
Create docsets for Dash.app-compatible API browser.

doc2dash: Create Docsets for Dash.app and Clones doc2dash is an MIT-licensed extensible Documentation Set generator intended to be used with the Dash.

Hynek Schlawack 498 Dec 30, 2022
Toolchain for project structure and documents optimisation

ritocco Toolchain for project structure and documents optimisation

Harvey Wu 1 Jan 12, 2022
EasyModerationKit is an open-source framework designed to moderate and filter inappropriate content.

EasyModerationKit is a public transparency statement. It declares any repositories and legalities used in the EasyModeration system. It allows for implementing EasyModeration into an advanced charact

Aarav 1 Jan 16, 2022
DeltaPy - Tabular Data Augmentation (by @firmai)

DeltaPy⁠⁠ — Tabular Data Augmentation & Feature Engineering Finance Quant Machine Learning ML-Quant.com - Automated Research Repository Introduction T

Derek Snow 470 Dec 28, 2022
Loudchecker - Python script to check files for earrape

loudchecker python script to check files for earrape automatically installs depe

1 Jan 22, 2022
A hack to run custom shell commands when building documentation on Read the Docs.

readthedocs-custom-steps A hack to run custom steps when building documentation on Read the Docs. Important: This module should not be installed outsi

Niklas Rosenstein 5 Feb 22, 2022
Python code for working with NFL play by play data.

nfl_data_py nfl_data_py is a Python library for interacting with NFL data sourced from nflfastR, nfldata, dynastyprocess, and Draft Scout. Includes im

82 Jan 05, 2023
Flask-Rebar combines flask, marshmallow, and swagger for robust REST services.

Flask-Rebar Flask-Rebar combines flask, marshmallow, and swagger for robust REST services. Features Request and Response Validation - Flask-Rebar reli

PlanGrid 223 Dec 19, 2022
A complete kickstart devcontainer repository for python3

A complete kickstart devcontainer repository for python3

Viktor Freiman 3 Dec 23, 2022
A Sublime Text plugin to select a default syntax dialect

Default Syntax Chooser This Sublime Text 4 plugin provides the set_default_syntax_dialect command. This command manipulates a syntax file (e.g.: SQL.s

3 Jan 14, 2022
✨ Real-life Data Analysis and Model Training Workshop by Global AI Hub.

🎓 Data Analysis and Model Training Course by Global AI Hub Syllabus: Day 1 What is Data? Multimedia Structured and Unstructured Data Data Types Data

Global AI Hub 71 Oct 28, 2022
A next-generation curated knowledge sharing platform for data scientists and other technical professions.

Knowledge Repo The Knowledge Repo project is focused on facilitating the sharing of knowledge between data scientists and other technical roles using

Airbnb 5.2k Dec 27, 2022
A markdown wiki and dashboarding system for Datasette

datasette-notebook A markdown wiki and dashboarding system for Datasette This is an experimental alpha and everything about it is likely to change. In

Simon Willison 19 Apr 20, 2022
An awesome Data Science repository to learn and apply for real world problems.

AWESOME DATA SCIENCE An open source Data Science repository to learn and apply towards solving real world problems. This is a shortcut path to start s

Academic.io 20.3k Jan 09, 2023
A curated list of awesome tools for Sphinx Python Documentation Generator

Awesome Sphinx (Python Documentation Generator) A curated list of awesome extra libraries, software and resources for Sphinx (Python Documentation Gen

Hyunjun Kim 831 Dec 27, 2022
Python For Finance Cookbook - Code Repository

Python For Finance Cookbook - Code Repository

Packt 544 Dec 25, 2022
Variable Transformer Calculator

✠ VASCO - VAriable tranSformer CalculatOr Software que calcula informações de transformadores feita para a matéria de "Conversão Eletromecânica de Ene

Arthur Cordeiro Andrade 2 Feb 12, 2022
Poetry plugin to export the dependencies to various formats

Poetry export plugin This package is a plugin that allows the export of locked packages to various formats. Note: For now, only the requirements.txt f

Poetry 90 Jan 05, 2023