Common developer utilities and base classes to support other dewloosh packages.
Project description
dewloosh.core
This package contains common developer utilities to support other dewloosh
solutions. Everything is pure Python, the package requires no extra dependencies and should run on a minimal setup.
The most important features:
-
Various dictionary classes that enhance the core behaviour of the built-in
dict
type. The top of the cake is theDeepDict
class, which offers a different behaviour for nested dictionaries by applying a self replicating defalt factory. -
A set of tools for metaprogramming. The use cases include declaring custom abstract class properties, using metaclasses to avoid unwanted code conflicts, assuring the implementation of abstract methods at design time, etc.
-
Decorators, wrappers and other handy developer tools.
Documentation
Click here to read the documentation.
Installation
This is optional, but we suggest you to create a dedicated virtual enviroment at all times to avoid conflicts with your other projects. Create a folder, open a command shell in that folder and use the following command
>>> python -m venv venv_name
Once the enviroment is created, activate it via typing
>>> .\venv_name\Scripts\activate
dewloosh.core
can be installed (either in a virtual enviroment or globally) from PyPI using pip
on Python >= 3.6:
>>> pip install dewloosh.core
Crash Course
Dictionaries of dictionaries of diactionaries of ...
In every case where you'd want to use a dict
, you can use a Deepdict
as a drop-in replacement, but on top of what a simple dictionary provides, a Deepdict
is more capable, as it provides a machinery to handle nested layouts. It is basically an ordered defaultdict
with a self replicating default factory.
>>> from dewloosh.core import Deepdict
>>> data = Deepdict()
A Deepdict
is essentially a nested default dictionary. Being nested refers to the fact that you can do this:
>>> data['a']['b']['c']['e'] = 1
>>> data['a']['b']['d'] = 2
Notice that the object carves a way up until the last key, without needing to create each level explicitly. What happens is that every time a key is missing in a data
, the object creates a new instance, which then is also ready to handle missing keys or data. Accessing nested subdictionaries works in a similar fashion:
>>> data['a']['b']['c']['e']
1
To allow for a more Pythonic feel, it also supports array-like indexing, so that the following operations are valid:
>>> data['a', 'b', 'c', 'e'] = 3
>>> data['a', 'b', 'c', 'e']
3
Of course, this is something that we can easily replicate using pure Python in one line, without the need for fancy stuff:
>>> data = {'a' : {'b' : {'c' : {'e' : 3}, 'd' : 2}}}
The key point is that we loop over a pure dict
instance, we get
>>> [k for k in data.keys()]
['a']
But if we use a Deepdict
class and the option deep=True
when accessing
keys, values or items of dictionaries, the following happens:
>>> [k for k in Deepdict(data).keys(deep=True)]
['e', 'd']
We can see, that in this case, iteration goes over keys, that actually hold on to some data, and does not return the containers themselves. If we do the same experiment with the values, it shows that the Deepdict
only returns the leafs of the data-tree and the behaviour is fundamentally different:
>>> [k for k in data.values()]
[{'b': {'c': {'e': 3}, 'd': 2}}]
>>> [k for k in Deepdict(data).values(deep=True)]
[3, 2]
It is important, that the call obj.values(deep=True)
still returns a generator object, which makes it memory efficient when looping over large datasets.
>>> Deepdict(data).values(deep=True)
<generator object OrderedDefaultDict.values at 0x0000028F209D54A0>
Wrapping
Wrapping may not be the most elegant solutions to inherit properties of a different class, but there are certain situations when it might save your life. One such a scenario is when you want to write an interface to a Deepdict that gets dinamically generated runtime, meaning, that the classes are simply not present at the time of writing your own code. This is when a wrapper comes handy. To wrap a dictionary, do the following:
>>> from dewloosh.core import Wrapper
>>> data = {'a' : {'b' : {'c' : {'e' : 3}, 'd' : 2}}}
>>> wrapper = Wrapper(wrap=data)
The Wrapper
class channels down every call to the wrapped object (in this case a dictionary), if the object that the call is made upon is unable to answer the call by itself (because it misses the wanted attribute or method). The wrapped object is accessible through the wrapped
property of the wrapper.
>>> wrapper.wrapped
{'a': {'b': {'c': {'e': 3}, 'd': 2}}}
Note, that if for some reason we accidentally shadow a method in a base class like this:
>>> class CustomWrapper(Wrapper):
>>>
>>> def values(self, *args, **kwargs):
>>> return None
Id we tried to wrap a dictionary now, the implementation would alter the bahaviour of the wrapper, leaving the behaviour of the wrapped object preserved and still accessible as CustomWrapper(wrap=data).wrapped.values()
.
>>> CustomWrapper(wrap=data).wrapped.values()
dict_values([{'b': {'c': {'e': 3}, 'd': 2}}])
Abstract Classes and Metaprogramming
The submodule dewloosh.core.abc
provides simple classes to alleviate some of the unwanted consequences of the dynamically typed nature of Python. One of such a scenarios is when we subclass another class from a third-party Deepdict, because we want to inherit the functionality therein. But the stuff is complicated, and we probably woundn't want to go through all of it. Nevertheless, we want to make sure, that we don't brake the inner flow of the object at runtime, by overriding some essential methods, shadowing the original behaviour. Not like it wouldn't show up runtime sooner or later, but this leaves the door opened for bad code. Luckily, the problem can be solved fairly easily with some metaprogramming, and the meta submodule provides an abstract class ABC_Safe
that can be used as a base class further down the line.
Running the following code throws an error at design time, because foo
is already implemented in the parent class:
>>> from dewloosh.core.abc import ABC_Safe
>>>
>>> class Parent(ABC_Safe):
>>> def foo(self):
>>> pass
>>>
>>> class Child(Parent):
>>> def foo(self):
>>> pass
Another important situation arises with abstract methods. Python provides a decorator for this out of the box, but again, not implemented abstract methods only show up at rumtime, which can easily be no time in the world of interpreted languages. The meta submodul is equipped with another abstract class called ABC_Strong
, that makes you able to be informed about missing function implementations of a class right at design time. Here 'Strong' refers to the stronger requirement imposed on abstract methods. An abstract method in a child class is either implemented, or decorated with the abstractmethod
decorator, which passes the ball to the next child. Obviously, you don't get to runtime, unless you implement all the required abstract classes. This is also useful if we want to create a template object, that provides instructions on how to complete a skeleton to have a working solution. A simple example to illustrate what happens if you break the rules is the following:
>>> from dewloosh.core.abc import ABC_Strong
>>>
>>> class Parent(ABC_Strong):
>>> @abstractmethod
>>> def foo(self):
>>> pass
>>>
>>> class Child(Parent):
>>> ...
Abstract Class Properties
Along the same thoughts, sometimes we want to ensure the existence of some class properties when building complex objects with multiple base classes. This can be done using a special decorator:
>>> from dewloosh.core.acp import abstract_class_property
>>> from abc import ABC
>>>
>>> @abstract_class_property(prop1=int, prop2=float})
>>> class BaseClassA(ABC):
>>>
>>> prop1: int
>>> prop2: list
>>>
>>> def __init__(self):
>>> self.prop2 = [3, 4]
>>> super().__init__()
>>> return
Infix Operators
Infix operators allow for a fancy way of defining binary operations using the operators '<<', '>>' and '|'.
>>> from dewloosh.core.infix import Infix
>>>
>>> mul = Infix(lambda x, y: x * y)
>>> 2 | mul | 4
8
>>> add = Infix(lambda x, y: x + y)
>>> 2 << add >> 4
6
Testing
To run all tests, open up a console in the root directory of the project and type the following
>>> python -m unittest
Dependencies
The only dependency is six
, to provide basic continuity between major Python versions 2 and 3.
License
This package is licensed under the MIT license.
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 dewloosh.core-1.0.10.tar.gz
.
File metadata
- Download URL: dewloosh.core-1.0.10.tar.gz
- Upload date:
- Size: 18.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.8.10
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | c55e9e16c838edfe96056e9a69e143bf691406db894cd7e632857ea87b0ca32f |
|
MD5 | 93ce66e8f7398769f68954e799d3a88b |
|
BLAKE2b-256 | d52a5c2b04f8dee705746ca831f200feb27308434127bb6563f2e0000f45d8ce |
Provenance
File details
Details for the file dewloosh.core-1.0.10-py3-none-any.whl
.
File metadata
- Download URL: dewloosh.core-1.0.10-py3-none-any.whl
- Upload date:
- Size: 16.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.8.10
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 16b70ea0f5dc57171594a883a1fe0e2591b709eedca80dab2329d14ec8170f01 |
|
MD5 | 447ba904d9d658590c7f2e2bdf020766 |
|
BLAKE2b-256 | 49370bbb7a21bc8e812e653b0607ff7bcb75563c541dcf089b929c79393ec5db |