Skip to main content

No project description provided

Project description

A compact, fast object system that can serve as the basis for a DAO model.

To that end, instruct uses __slots__ to prevent new attribute addition, properties to control types, event listeners and historical changes, and a Jinja2-driven codegen to keep a pure-Python implementation as fast and as light as possible.

Attempt to serve multiple masters:

  • Support multiple inheritance, chained fields and __slots__ [Done]

  • Support type coercions (via _coerce__) [Done]

  • Strictly-typed ability to define fixed data objects [Done]

  • Ability to drop all of the above type checks [Done]

  • Track changes made to the object as well as reset [Done]

  • Fast __iter__ [Done]

  • Native support of pickle [Done]/json [Partial]

  • Support List[type] declarations and initializations

  • CStruct-Base class that operates on an _cvalue cffi struct.

  • Cython compatibility

Design Goal

This comes out of my experience of doing multiple object systems mean to represent database relations and business rules. One thing that has proven an issue is the requirements for using as little memory as possible, as little CPU as possible yet prevent the developer from trying to stick a string where a integer belongs.

Further complicating this model is that desire to “correct” data as it comes in. Done correctly, it is possible to feed an instruct.Base-derived class fields that are not of the correct data type but are eligible for being coerced (converted) into the right type with a function. With some work, it’ll be possible to inline a lambda val: ... expression directly into the setter function code.

Finally, multiple inheritance is a must. Sooner or later, you end up making a single source implementation for a common behavior shared between objects. Being able to share business logic between related implementations is a wonderful thing.

Wouldn’t it be nice to define a heirachy like this:

class Member(Base):
    __slots__ = {
        'first_name': str,
        'last_name': str,
        'id': str,
    }
    def __init__(self, **kwargs):
        self.first_name = self.last_name = ''
        self.id = -1
        super().__init__(**kwargs)

class Organization(Base, history=True):
    __slots__ = {
        'name': str,
        'id': int,
        'members': List[Member],
        'created_date': datetime.datetime,
    }

    __coerce__ = {
        'created_date': (str, lambda obj: datetime.datetime.strptime('%Y-%m-%d', obj))
    }

    def __init__(self, **kwargs):
        self.name = ''
        self.id = -1
        self.members = []
        self.created_date = datetime.datetime.utcnow()
        super().__init__(**kwargs)

And have it work like this?

data = {
    "name": "An Org",
    "id": 123,
    "members": [
        {
            "id": 551,
            "first_name": "Jinja",
            "last_name": "Ninja",
        }
    ]
}
org = Organization(**data)
assert org.members[0].first_name == 'Jinja'
org.name = "New Name"
org.history()

Design

Solving the multiple-inheritance and __slots__ problem

Consider the following graph:

Base1    Base2
     \  /
   Class A

If both defined __slots__ = (), Class A would be able to declare __slots__ to hold variables. For now on, we shall consider both Base’s to have __slots__ = () for simplicity.

However, consider this case:

Base1    Base2
     \  /
   Class A     Class B
          \    /
          Class C

Now this isn’t possible if Class A has non-empty __slots__.

But what if we could change the rules. What if, somehow, when you __new__ ed a class, it really gave you a specialized form of the class with non-empty __slots__?

Such a graph may look like this:

Base1    Base2
     \  /
   Class A     Class B
      |  \    /     |
Class _A  Class C  Class _B
            |
          Class _C

Now it is possible for any valid multiple-inheritance chain to proceed, provided it respects the above constraints - there are either support classes or data classes (denoted with an underscore in front of their class name). Support classes may be inherited from, data classes cannot.

Solving the Slowness issue

I’ve noticed that there are constant patterns of writing setters/getters and other related functions. Using Jinja2, we can rely on unhygenic macros while preserving some semblance of approachability. It’s more likely a less experienced developer could handle blocks of Jinja-fied Python than AST synthesis/traversal.

Callgraph Performance

Callgraph of project

Benchmark

Before additions of coercion, event-listeners, multiple-inheritance

$ python -m instruct benchmark
Overhead of allocation, one field, safeties on: 6.52us
Overhead of allocation, one field, safeties off: 6.13us
Overhead of setting a field:
Test with safeties: 0.40 us
Test without safeties: 0.22 us
Overhead of clearing/setting
Test with safeties: 1.34 us
Test without safeties: 1.25 us

After additions of those. Safety is expensive.

$ python -m instruct benchmark
Overhead of allocation, one field, safeties on: 19.25us
Overhead of allocation, one field, safeties off: 18.98us
Overhead of setting a field:
Test with safeties: 0.36 us
Test without safeties: 0.22 us
Overhead of clearing/setting
Test with safeties: 1.29 us
Test without safeties: 1.14 us

Project details


Release history Release notifications | RSS feed

This version

0.3.6

Download files

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

Source Distribution

instruct-0.3.6.tar.gz (19.3 kB view details)

Uploaded Source

Built Distribution

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

instruct-0.3.6-py3-none-any.whl (20.4 kB view details)

Uploaded Python 3

File details

Details for the file instruct-0.3.6.tar.gz.

File metadata

  • Download URL: instruct-0.3.6.tar.gz
  • Upload date:
  • Size: 19.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.12.1 pkginfo/1.4.2 requests/2.20.1 setuptools/40.8.0 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.7.3

File hashes

Hashes for instruct-0.3.6.tar.gz
Algorithm Hash digest
SHA256 d320512d6b04e8f07c78249ca8ee4385480f7dfea548d3058c0e8363d7282b75
MD5 ed806578895e9d88efd4bd33fd1a9ca1
BLAKE2b-256 7cd73fc13417b19dc6420fa939f78ffff394e54cb23c94af08d5f7b63a5f004d

See more details on using hashes here.

File details

Details for the file instruct-0.3.6-py3-none-any.whl.

File metadata

  • Download URL: instruct-0.3.6-py3-none-any.whl
  • Upload date:
  • Size: 20.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.12.1 pkginfo/1.4.2 requests/2.20.1 setuptools/40.8.0 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.7.3

File hashes

Hashes for instruct-0.3.6-py3-none-any.whl
Algorithm Hash digest
SHA256 b81b065c83aeedbfea841f7d82c6b2686441129ca4d4ef0cef521b4ec5f0ad88
MD5 8e4d5d0d211e4cf8d69bca4d54c204d3
BLAKE2b-256 83e2ffbe81f4a4dd4746c5e1d144dd9423d0617f226d7a31816e4f5e2124a71e

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