Bases and utilities for compatibility, features and validation.
Project description
Overview
Base Classes and Utilities for compatibility, features and validation.
Motivation
While developing Python software for Visual Effects pipelines, I found myself having to write the same boiler-plate code over and over again, as well as struggling with compatibility issues and feature gaps between Python 2.7 and Python 3.7+.
So I decided to implement solutions for those issues at the Base, and basicco was born.
Base Classes
CompatBase
The goal with the CompatBaseMeta metaclass and the CompatBase class is to bridge some of the feature gaps between Python 2.7 and Python 3.7+.
- This includes adding Python 2.7 workarounds for:
Abstract properties: Better abstractmethod decorator support for property-like descriptors. See also abstract_class.
PEP 487: Support for __init_subclass__ and __set_name__. See also init_subclass and set_name.
object.__dir__: Base __dir__ method. See also default_dir.
__eq__ override: Overriding __eq__ will set __hash__ to None. See also implicit_hash.
PEP 307: Support for pickling objects with __slots__. See also obj_state.
PEP 3155: Qualified name __qualname__ for nested classes. See also qualname.
__ne__ behavior: By default, __ne__ should negate the result of __eq__. See also safe_not_equals
PEP 0560: Better handling of Generic classes. See also tippo.
Base
In addition to the compatibility solutions, the goal with the BaseMeta metaclass and the Base class is to add useful low-level features that hopefully yield better code readability and validation.
- This includes:
__weakref__ slot: Added by default.
locked_class: Public class attributes are read-only by default.
explicit_hash: Overriding __eq__ without overriding __hash__ will error.
namespace: Adds a protected __namespace unique to each class.
runtime_final: Runtime checking for classes and methods decorated with final.
SlottedBase
The SlottedBase class and the SlottedBaseMeta metaclass offer all features from Base and BaseMeta plus implicit __slots__ declaration. See slotted for more information.
Utilities
Apart from the features integrated into the base classes, basicco provides many general utility modules.
abstract_class
Better support for abstract classes.
Provides abstract decorators that can be used directly on methods but also on property getters, classmethods, and staticmethods (even in Python 2.7).
>>> from six import with_metaclass
>>> from basicco.abstract_class import AbstractMeta, abstract
>>> class Asset(with_metaclass(AbstractMeta, object)):
... @abstract
... def method(self):
... pass
...
... @property
... @abstract
... def prop(self):
... return None
...
>>> Asset()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class Asset...
caller_module
Retrieve the caller’s module name.
>>> from basicco.caller_module import caller_module
>>> def do_something():
... return "I was called by {}".format(caller_module())
...
>>> do_something()
'I was called by __main__'
context_vars
Backport of the contextvars module for Python 2.7, based on MagicStack/contextvars.
When imported from Python 3, it simply redirects to the native contextvars module.
>>> from basicco.context_vars import ContextVar
>>> my_var = ContextVar("my_var", default="bar")
>>> token = my_var.set("foo")
>>> my_var.get()
'foo'
>>> my_var.reset(token)
>>> my_var.get()
'bar'
custom_repr
Custom representation functions for mappings, items, and iterables.
>>> from basicco.custom_repr import mapping_repr
>>> dct = {"a": 1, "b": 2}
>>> mapping_repr(
... dct,
... prefix="<",
... suffix=">",
... template="{key}={value}",
... sorting=True
... )
"<'a'=1, 'b'=2>"
>>> from basicco.custom_repr import mapping_repr
>>> items = [("a", 1), ("b", 2)]
>>> mapping_repr(
... items,
... prefix="[", suffix="]",
... template=(lambda i, key, value: key + " -> " + value),
... )
"['a' -> 1, 'b' -> 2]"
>>> from basicco.custom_repr import iterable_repr
>>> tup = ("a", "b", "c", 1, 2, 3)
>>> iterable_repr(tup, prefix="<", suffix=">", value_repr=str)
'<a, b, c, 1, 2, 3>'
default_dir
Backport of Python 3’s implementation of object.__dir__.
This allows for calling super().__dir__() from a subclass to leverage the default implementation.
>>> from six import with_metaclass
>>> from basicco.default_dir import DefaultDir
>>> class Class(DefaultDir):
... def __dir__(self):
... return super(Class, self).__dir__()
...
>>> obj = Class()
>>> dir(obj)
[...]
descriptors
Configurable descriptors.
>>> from six import with_metaclass
>>> from basicco.descriptors import REMOVE, Descriptor, Owner
>>> class SlotDescriptor(Descriptor):
... def __get_required_slots__(self):
... return (self.name,) # request a slot with the same name as this
... def __get_replacement__(self):
... return REMOVE # remove this descriptor from the class body
...
>>> class PropDescriptor(Descriptor):
... __slots__ = ("_slot_desc",)
... def __init__(self, slot_desc):
... super(PropDescriptor, self).__init__()
... self._slot_desc = slot_desc
... def __get__(self, instance, owner):
... if instance is not None:
... return getattr(instance, self._slot_desc.name)
... return self
... def __set__(self, instance, value):
... setattr(instance, self._slot_desc.name, value)
...
>>> class Stuff(Owner):
... _foo = SlotDescriptor()
... _bar = SlotDescriptor()
... foo = PropDescriptor(_foo)
... bar = PropDescriptor(_bar)
...
>>> stuff = Stuff()
>>> stuff.foo = "foo"
>>> stuff.bar = "bar"
>>> stuff.foo
'foo'
>>> stuff.bar
'bar'
dynamic_class
Easily generate classes on the fly. This works best with a Base class. If provided a valid qualified name and module (uses caller_module by default), the class will be pickable/importable.
>>> from basicco import Base
>>> from basicco.dynamic_class import make_cls
>>> class MyClass(object):
... DynClass = make_cls("MyClass.DynClass", bases=(Base,), dct={"foo": "bar"})
...
>>> repr(MyClass.DynClass)
"<class '__main__.MyClass.DynClass'>"
dynamic_code
Generate debuggable code on the fly that supports line numbers on tracebacks.
>>> from basicco.dynamic_code import make_function, generate_unique_filename
>>> class MyClass(object):
... pass
...
>>> bar = 'bar'
>>> # Prepare the script and necessary data.
>>> script = "\n".join(
... (
... "def __init__(self):",
... " self.foo = 'bar'",
... )
... )
>>> # Gather information.
>>> name = "__init__"
>>> owner_name = MyClass.__name__
>>> module = MyClass.__module__
>>> filename = generate_unique_filename(name, module, owner_name)
>>> globs = {"bar": bar}
>>> # Make function and attach it as a method.
>>> MyClass.__init__ = make_function(name, script, globs, filename, module)
>>> obj = MyClass()
>>> obj.foo
'bar'
explicit_hash
Metaclass that forces __hash__ to be declared whenever __eq__ is declared.
>>> from six import with_metaclass
>>> from basicco.explicit_hash import ExplicitHashMeta
>>> class Asset(with_metaclass(ExplicitHashMeta, object)):
... def __eq__(self, other):
... pass
...
Traceback (most recent call last):
TypeError: declared '__eq__' in 'Asset' but didn't declare '__hash__'
fabricate_value
Run a value through a callable factory (or None).
>>> from basicco.fabricate_value import fabricate_value
>>> fabricate_value(None, 3) # no factory, value passthrough
3
>>> fabricate_value(str, 3) # callable factory
'3'
>>> fabricate_value("str", 3) # use an import path
'3'
>>> fabricate_value(int) # no input value, just the factory itself
0
func_tools
Backport of functools.cache, functools.lru_cache, and functools.update_wrapper for Python 2.7.
>>> from basicco.func_tools import cache
>>> @cache
... def calculate(a, b):
... print("calculating...")
... return a ** b
...
>>> calculate(2, 2)
calculating...
4
>>> calculate(2, 2)
4
get_mro
Get consistent MRO amongst different python versions. This works even with generic classes in Python 2.7.
>>> from six import with_metaclass
>>> from tippo import Generic, TypeVar
>>> from basicco.get_mro import get_mro
>>> T = TypeVar("T")
>>> class MyGeneric(Generic[T]):
... pass
...
>>> class SubClass(MyGeneric[T]):
... pass
...
>>> class Mixed(SubClass[T], MyGeneric[T]):
... pass
...
>>> [c.__name__ for c in get_mro(Mixed)]
['Mixed', 'SubClass', 'MyGeneric', 'Generic', 'object']
hash_cache_wrapper
An integer subclass that pickles/copies as None. This can be used to avoid serializing a cached hash value.
>>> from copy import copy
>>> from basicco.hash_cache_wrapper import HashCacheWrapper
>>> hash_cache = HashCacheWrapper(12345)
>>> print(hash_cache)
12345
>>> print(copy(hash_cache))
None
implicit_hash
Metaclass that forces __hash__ to None when __eq__ is declared. This is a backport of the default behavior in Python 3.
>>> from six import with_metaclass
>>> from basicco.implicit_hash import ImplicitHashMeta
>>> class Asset(with_metaclass(ImplicitHashMeta, object)):
... def __eq__(self, other):
... pass
...
>>> Asset.__hash__ is None
True
import_path
Generate importable dot paths and import from them.
>>> import itertools
>>> from basicco.import_path import get_path, import_path
>>> get_path(itertools.chain)
'itertools.chain'
>>> import_path("itertools.chain")
<... 'itertools.chain'>
>>> from basicco.import_path import extract_generic_paths
>>> extract_generic_paths("Tuple[int, str]")
('Tuple', ('int', 'str'))
init_subclass
Backport of the functionality of __init_subclass__ from PEP 487 to Python 2.7. This works for both Python 2 (using __kwargs__) and 3 (using the new class parameters).
>>> from basicco.init_subclass import InitSubclass
>>> class Foo(InitSubclass):
... def __init_subclass__(cls, foo=None, **kwargs):
... cls.foo = foo
...
>>> class Bar(Foo):
... __kwargs__ = {"foo": "bar"} # you can specify cls kwargs on py2 like this
...
>>> Bar.foo
'bar'
lazy_tuple
Lazily-evaluated tuple-like structure.
>>> from basicco.lazy_tuple import LazyTuple
>>> def expensive_generator():
... for i in range(100):
... yield i
...
>>> lazy_tuple = LazyTuple(expensive_generator())
>>> lazy_tuple[4]
4
locked_class
Prevents changing public class attributes.
>>> from six import with_metaclass
>>> from basicco.locked_class import LockedClassMeta
>>> class Foo(with_metaclass(LockedClassMeta, object)):
... bar = "foo"
...
>>> Foo.bar = "bar"
Traceback (most recent call last):
AttributeError: can't set read-only class attribute 'bar'
mangling
Functions to mangle/unmangle/extract private names.
>>> from basicco.mangling import mangle, unmangle, extract
>>> mangle("__member", "Foo")
'_Foo__member'
>>> unmangle("_Foo__member", "Foo")
'__member'
>>> extract("_Foo__member")
('__member', 'Foo')
mapping_proxy
Mapping Proxy type (read-only dictionary) for older Python versions.
>>> from basicco.mapping_proxy import MappingProxyType
>>> internal_dict = {"foo": "bar"}
>>> proxy_dict = MappingProxyType(internal_dict)
>>> proxy_dict["foo"]
'bar'
namespace
Wraps a dictionary/mapping and provides attribute-style access to it.
>>> from basicco.namespace import Namespace
>>> ns = Namespace({"bar": "foo"})
>>> ns.bar
'foo'
>>> from basicco.namespace import MutableNamespace
>>> ns = MutableNamespace({"bar": "foo"})
>>> ns.foo = "bar"
>>> ns.foo
'bar'
>>> ns.bar
'foo'
Also provides a NamespacedMeta metaclass that adds a __namespace protected class attribute that is unique to each class.
>>> from six import with_metaclass
>>> from basicco.namespace import NamespacedMeta
>>> class Asset(with_metaclass(NamespacedMeta, object)):
... @classmethod
... def set_class_value(cls, value):
... cls.__namespace.value = value
...
... @classmethod
... def get_class_value(cls):
... return cls.__namespace.value
...
>>> Asset.set_class_value("foobar")
>>> Asset.get_class_value()
'foobar'
null_context
Backport of contextlib.nullcontext for Python 2.7.
>>> from basicco.null_context import null_context
>>> from basicco.suppress_exception import suppress_exception
>>> def myfunction(arg, ignore_exceptions=False):
... if ignore_exceptions:
... # Use suppress_exception to ignore all exceptions.
... cm = suppress_exception(Exception)
... else:
... # Do not ignore any exceptions, cm has no effect.
... cm = null_context()
... with cm:
... pass # Do something
...
obj_state
Get/update the state of an object, slotted or not (works even in Python 2.7).
>>> from basicco.obj_state import get_state
>>> class Slotted(object):
... __slots__ = ("foo", "bar")
... def __init__(self, foo, bar):
... self.foo = foo
... self.bar = bar
...
>>> slotted = Slotted("a", "b")
>>> sorted(get_state(slotted).items())
[('bar', 'b'), ('foo', 'a')]
Also provides a ReducibleMeta metaclass that allows for pickling instances of slotted classes in Python 2.7.
qualname
Python 2.7 compatible way of getting the qualified name. Based on wbolster/qualname. Also provides a QualnamedMeta metaclass with a __qualname__ class property for Python 2.7.
recursive_repr
Decorator that prevents infinite recursion for __repr__ methods.
>>> from basicco.recursive_repr import recursive_repr
>>> class MyClass(object):
...
... @recursive_repr
... def __repr__(self):
... return "MyClass<{!r}>".format(self)
...
>>> my_obj = MyClass()
>>> repr(my_obj)
'MyClass<...>'
runtime_final
Runtime-checked version of the typing.final decorator.
Can be used on methods, properties, classmethods, staticmethods, and classes that have RuntimeFinalMeta as a metaclass. It is also recognized by static type checkers and prevents subclassing and/or member overriding during runtime:
>>> from six import with_metaclass
>>> from basicco.runtime_final import RuntimeFinalMeta, final
>>> @final
... class Asset(with_metaclass(RuntimeFinalMeta, object)):
... pass
...
>>> class SubAsset(Asset):
... pass
...
Traceback (most recent call last):
TypeError: can't subclass final class 'Asset'
>>> from six import with_metaclass
>>> from basicco.runtime_final import RuntimeFinalMeta, final
>>> class Asset(with_metaclass(RuntimeFinalMeta, object)):
... @final
... def method(self):
... pass
...
>>> class SubAsset(Asset):
... def method(self):
... pass
Traceback (most recent call last):
TypeError: 'SubAsset' overrides final member 'method' defined by 'Asset'
>>> from six import with_metaclass
>>> from basicco.runtime_final import RuntimeFinalMeta, final
>>> class Asset(with_metaclass(RuntimeFinalMeta, object)):
... @property
... @final
... def prop(self):
... pass
...
>>> class SubAsset(Asset):
... @property
... def prop(self):
... pass
Traceback (most recent call last):
TypeError: 'SubAsset' overrides final member 'prop' defined by 'Asset'
safe_not_equals
Backport of the default Python 3 behavior of __ne__ behavior for Python 2.7.
>>> from six import with_metaclass
>>> from basicco.safe_not_equals import SafeNotEqualsMeta
>>> class Class(with_metaclass(SafeNotEqualsMeta, object)):
... pass
...
>>> obj_a = Class()
>>> obj_b = Class()
>>> assert (obj_a == obj_a) is not (obj_a != obj_a)
>>> assert (obj_b == obj_b) is not (obj_b != obj_b)
>>> assert (obj_a == obj_b) is not (obj_a != obj_b)
safe_repr
Decorator that prevents __repr__ methods from raising exceptions and return a default representation instead.
>>> from basicco.safe_repr import safe_repr
>>> class Class(object):
... @safe_repr
... def __repr__(self):
... raise RuntimeError("oh oh")
...
>>> obj = Class()
>>> repr(obj)
"<__main__.Class object at ...; repr failed due to 'RuntimeError: oh oh'>"
sentinel
Easily define singleton sentinel values and their type (for type hinting).
>>> from basicco.sentinel import SentinelType
>>> class MissingType(SentinelType):
... def __repr__(self):
... return "MISSING"
...
>>> MISSING = MissingType()
>>> MISSING
MISSING
>>> MissingType() is MISSING
True
>>> isinstance(MISSING, MissingType)
True
set_name
Backport of the functionality of __set_name__ from PEP 487 to Python 2.7.
>>> from basicco.set_name import SetName
>>> class Attribute(object):
... def __set_name__(self, owner, name):
... self.owner = owner
... self.name = name
...
>>> class Collection(SetName):
... foo = Attribute()
...
>>> Collection.foo.owner is Collection
True
>>> Collection.foo.name
'foo'
suppress_exception
Backport of contextlib.suppress for Python 2.7. See null_context for an example usage.
type_checking
Runtime type checking with support for import paths and type hints.
>>> from tippo import Mapping, Literal
>>> from itertools import chain
>>> from basicco.type_checking import is_instance
>>> class SubChain(chain):
... pass
...
>>> is_instance(3, int)
True
>>> is_instance(3, (chain, int))
True
>>> is_instance(3, ())
False
>>> is_instance(SubChain(), "itertools.chain")
True
>>> is_instance(chain(), "itertools.chain", subtypes=False)
True
>>> is_instance(SubChain(), "itertools.chain", subtypes=False)
False
>>> is_instance({"a": 1, "b": 2}, Mapping[str, int])
True
>>> is_instance("PRE", Literal["PRE", "POST"])
True
unique_iterator
Iterator that yields unique values.
>>> from basicco.unique_iterator import unique_iterator
>>> list(unique_iterator([1, 2, 3, 3, 4, 4, 5]))
[1, 2, 3, 4, 5]
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file basicco-10.5.8.tar.gz
.
File metadata
- Download URL: basicco-10.5.8.tar.gz
- Upload date:
- Size: 68.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.0.0 CPython/3.12.2
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | e3dfe40087ab59b4a5e792b8bb7e0c5959666a18a222ace9c2d27509961a425b |
|
MD5 | 8b7e98d6db753e811b76488ea587ca8d |
|
BLAKE2b-256 | 3119e6ea563eaf0f105bdb5e865edc4447ad955922b4ffd0c9959a9037b66377 |
File details
Details for the file basicco-10.5.8-py2.py3-none-any.whl
.
File metadata
- Download URL: basicco-10.5.8-py2.py3-none-any.whl
- Upload date:
- Size: 55.2 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.0.0 CPython/3.12.2
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 2eb0cc4092a477ff5e93269370faaeb1936110e93c81b15ff54f84c7a2b4e234 |
|
MD5 | 7e3d10beecafb162829853d11812aa79 |
|
BLAKE2b-256 | 1c5008d03ea8a0af98b795e96dcc4794d10fb9ff7983db553aadadb5ffce26f2 |