Display HN: Sections – Straightforward tree records constructions in Python

91
Display HN: Sections – Straightforward tree records constructions in Python

[ s e | c t | i o | n s ]

Coverage Status
Codacy Code Quality Status CodeClimate Quality Status
Requirements Status

PyPI Package latest release
Supported versions
Supported implementations
PyPI Wheel

Documentation Status Commits since latest release
downloads downloads

Python equipment providing versatile tree records constructions for organizing lists and dicts into sections.

Sections is designed to be:

  • Intuitive: Start speedy, verbalize much less time studying the docs.
  • Scalable: Develop arbitrarily advanced bushes as your bid scales.
  • Flexible: Impulsively safe nodes with customized attributes, properties, and techniques on the hover.
  • Instant: Made with performance in solutions – safe admission to lists and sub-lists/dicts in Θ(1) time in many cases. Undercover agent the Efficiency section for the fat shrimp print.
  • Legitimate: Contains an exhaustive check suite and 100% code protection.

Links

Usage

import sections

menu = sections(
    'Breakfast', 'Dinner',
    well-known=['Bacon&Eggs', 'Burger'],
    aspect=['HashBrown', 'Fries'],
)
$ print(menu)
 _________________________
│  _____________________  │
│ │ 'Breakfast'         │ │
│ │ well-known='1st baron beaverbrook&Eggs' │ │
│ │ aspect='HashBrown'  │ │
│  ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯  │
│  _________________      │
│ │ 'Dinner'        │     │
│ │ well-known='Burger' │     │
│ │ aspect='Fries'  │     │
│  ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯      │
 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
# menu's API with the anticipated results: 
train menu.mains == ['Bacon&Eggs', 'Burger']
train menu.aspects == ['HashBrown', 'Fries']
train menu['Breakfast'].well-known == '1st baron beaverbrook&Eggs'
train menu['Breakfast'].aspect == 'HashBrown'
train menu['Dinner'].well-known == 'Burger'
train menu['Dinner'].aspect == 'Fries'
train menu('aspects', checklist) == ['HashBrown', 'Fries']
train menu('aspects', dict) == {'Breakfast': 'HashBrown', 'Dinner': 'Fries'}
# root section/node: 
train isinstance(menu, sections.Half)
# shrimp one sections/nodes: 
train isinstance(menu['Breakfast'], sections.Half)
train isinstance(menu['Dinner'], sections.Half)

Scale in dimension:

library = sections(
    "My Bookshelf",
    sections({'Fiction'},
             'LOTR', 'Harry Potter',
             author=['JRR Tolkien', 'JK Rowling'],
             topic=[{'Fantasy'}, 'Hobbits', 'Wizards'],),
    sections({'Non-Fiction'},
             'Frequent Relativity', 'A Brief History of Time',
             author=['Albert Einstein', 'Steven Hawking'],
             topic=[{'Physics'}, 'Time, Gravity', 'Black Holes'],
             ),
)
$ print(library)
 ________________________________________
│ 'My Bookshelf'                         │
│    ______________________________      │
│   │ 'Fiction'                    │     │
│   │ topic='Delusion'            │     │
│   │    ________________________  │     │
│   │   │ 'LOTR'                 │ │     │
│   │   │ author='JRR Tolkien' │ │     │
│   │   │ topic ='Hobbits'     │ │     │
│   │    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯  │     │
│   │    _______________________   │     │
│   │   │ 'Harry Potter'        │  │     │
│   │   │ author='JK Rowling' │  │     │
│   │   │ topic ='Wizards'    │  │     │
│   │    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯   │     │
│    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯      │
│    __________________________________  │
│   │ 'Non-Fiction'                    │ │
│   │ topic='Physics'                │ │
│   │    ____________________________  │ │
│   │   │ 'Frequent Relativity'       │ │ │
│   │   │ author='Albert Einstein' │ │ │
│   │   │ topic ='Time, Gravity'   │ │ │
│   │    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯  │ │
│   │    ___________________________   │ │
│   │   │ 'A Brief History of Time' │  │ │
│   │   │ author='Steven Hawking' │  │ │
│   │   │ topic ='Murky Holes'    │  │ │
│   │    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯   │ │
│    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯  │
 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

Attrs: Plural/singular hybrid attributes and more

Utilize much less time deciding between the usage of the singular or plural invent for an attribute name:

responsibilities = sections('pay bill', 'tremendous', station=['completed', 'started'])
train responsibilities.statuses == ['completed', 'started']
train responsibilities['pay bill'].station == 'done'
train responsibilities['clean'].station == 'started'

Whenever you don’t like this characteristic, merely turn it off as shown within the Particulars – Plural/singular attributes section.

Properties: Without considerations add on the hover

Properties and techniques are mechanically added to all nodes in a structure returned from a sections() call when handed as key phrase arguments:

time table = sections(
    'Weekdays', 'Weekend',
    hours_per_day=[[8, 8, 6, 10, 8], [4, 6]],
    hours=property(lambda self: sum(self.hours_per_day)),
)
train time table['Weekdays'].hours == 40
train time table['Weekend'].hours == 10
train time table.hours == 50

Along side properties and techniques this model doesn’t impact the class definitions of Sections/nodes from varied constructions. Undercover agent the Particulars – Properties/techniques section for the scheme in which this works.

Construction: Make gradually or all straight away

Make section-by-section, section-wise, attribute-wise, or varied ways:

def demo_different_construction_techniques():
    """Instance enhance ways for producing the identical structure."""
    # Building section-by-section
    books = sections()
    books['LOTR'] = sections(topic='Hobbits', author='JRR Tolkien')
    books['Harry Potter'] = sections(topic='Wizards', author='JK Rowling')
    demo_resulting_object_api(books)

    # Half-wise enhance
    books = sections(
        sections('LOTR', topic='Hobbits', author='JRR Tolkien'),
        sections('Harry Potter', topic='Wizards', author='JK Rowling')
    )
    demo_resulting_object_api(books)

    # Attribute-wise enhance
    books = sections(
        'LOTR', 'Harry Potter',
        issues=['Hobbits', 'Wizards'],
        authors=['JRR Tolkien', 'JK Rowling']
    )
    demo_resulting_object_api(books)

    # setattr put up-enhance
    books = sections(
        'LOTR', 'Harry Potter',
    )
    books.issues = ['Hobbits', 'Wizards']
    books['LOTR'].author = 'JRR Tolkien'
    books['Harry Potter'].author = 'JK Rowling'
    demo_resulting_object_api(books)

def demo_resulting_object_api(books):
    """Instance Half structure API and anticipated results."""
    train books.names == ['LOTR', 'Harry Potter']
    train books.issues == ['Hobbits', 'Wizards']
    train books.authors == ['JRR Tolkien', 'JK Rowling']
    train books['LOTR'].topic == 'Hobbits'
    train books['LOTR'].author == 'JRR Tolkien'
    train books['Harry Potter'].topic == 'Wizards'
    train books['Harry Potter'].author == 'JK Rowling'

demo_different_construction_techniques()

Particulars

Half names

The non-key phrase arguments handed exact into a sections() call interpret the section names and are accessed thru the attribute name. The names are weak like keys in a dict to safe admission to every shrimp one section of the basis section node:

books = sections(
    'LOTR', 'Harry Potter',
    topic=['Hobbits', 'Wizards'],
    author=['JRR Tolkien', 'JK Rowling']
)
train books.names == ['LOTR', 'Harry Potter']
train books['LOTR'].name == 'LOTR'
train books['Harry Potter'].name == 'Harry Potter'

Names are optional, and by default, younger of us names are assigned as integer values equivalent to indices in an array, while a root has a default keyvalue of sections.SectionNone:

sect = sections(x=['a', 'b'])
train sect.sections.names == [0, 1]
train sect.name is sections.SectionNone

# the string representation of sections.SectionNone is 'section': 
train str(sect.name) == 'sections'

Mum or dad names and attributes

A parent section name can optionally be offered as the indispensable argument in a sections() call by defining it in a dilemma (surrounding it with curly brackets). This technique avoids an further level of braces when instantiating Half objects. This belief applies also for outlining parent attributes:

library = sections(
    {"My Bookshelf"},
    [{'Fantasy'}, 'LOTR', 'Harry Potter'],
    [{'Academic'}, 'Advanced Mathematics', 'Physics for Engineers'],
    topic=[{'All my books'},
           [{'Imaginary things'}, 'Hobbits', 'Wizards'],
           [{'School'}, 'Numbers', 'Forces']],
)
train library.name == "My Bookshelf"
train library.sections.names == ['Fantasy', 'Academic']
train library['Fantasy'].sections.names == ['LOTR', 'Harry Potter']
train library['Academic'].sections.names == [
    'Advanced Mathematics', 'Physics for Engineers'
]
train library['Fantasy']['Harry Potter'].name == 'Harry Potter'
train library.topic == 'All my books'
train library['Fantasy'].topic == 'Imaginary issues'
train library['Academic'].topic == 'College'

Return attributes as a checklist, dict, or iterable

Procure entry to the records in varied kinds with the gettype argument in Half.__call__() as follows:

menu = sections('Breakfast', 'Dinner', aspects=['HashBrown', 'Fries'])

# return as checklist always, despite the real fact that a single part is returned
train menu('aspects', checklist) == ['HashBrown', 'Fries']
train menu['Breakfast']('aspect', checklist) == ['HashBrown']

# return as dict
train menu('aspects', dict) == {'Breakfast': 'HashBrown', 'Dinner': 'Fries'}
train menu['Breakfast']('aspect', dict) == {'Breakfast': 'HashBrown'}

# return as iterator over aspects in checklist (quickest scheme, theoretically)
for i, value in enumerate(menu('aspects', iter)):
    train value == ['HashBrown', 'Fries'][i]
for i, value in enumerate(menu['Breakfast']('aspect', iter)):
    train value == ['HashBrown'][i]

Undercover agent the Half.__call__() scheme within the References section of the docs for more alternatives.

Location the default return kind when having access to structure attributes by changing Half.default_gettype as follows:

menu = sections('Breakfast', 'Dinner', aspects=['HashBrown', 'Fries'])

menu['Breakfast'].default_gettype = dict  # dilemma for ultimate 'Breakfast' node
train menu.aspects == ['HashBrown', 'Fries']
train menu['Breakfast']('aspect') == {'Breakfast': 'HashBrown'}

menu.cls.default_gettype = dict           # dilemma for all nodes in `menu`
train menu('aspects') == {'Breakfast': 'HashBrown', 'Dinner': 'Fries'}
train menu['Breakfast']('aspect') == {'Breakfast': 'HashBrown'}

sections.Half.default_gettype = dict   # dilemma for all constructions
tasks1 = sections('pay bill', 'tremendous', station=['completed', 'started'])
tasks2 = sections('pay bill', 'tremendous', station=['completed', 'started'])
train tasks1('statuses') == {'pay bill': 'done', 'tremendous': 'started'}
train tasks2('statuses') == {'pay bill': 'done', 'tremendous': 'started'}

The above will also work for having access to attributes within the invent object.attr nonetheless ultimate if the node doesn’t beget the attribute attr, otherwise this would possibly per chance return the non-iterable raw value for attr. Because of this truth, for consistency, safe admission to attributes the usage of Half.__call__() like above if you happen to would prefer to always receive an iterable invent of the attributes.

Plural/singular attributes

When an attribute is now not stumbled on in a Half node, every the plural and singular
kinds of the observe are then checked to gaze if the node consists of the attribute
beneath those kinds of the observe. In the occasion that they are mild now not stumbled on, the node will
recursively repeat the identical search on every of its younger of us, concatenating the
results exact into a checklist or dict. The marvelous attribute name in every node supplied a
corresponding value is with out reference to name became once given within the predominant phrase argument’s key
(i.e. station within the instance beneath).

Whenever you don’t like this characteristic, merely turn it off the usage of the following:

import pytest
responsibilities = sections('pay bill', 'tremendous', station=['completed', 'started'])
train responsibilities.statuses == ['completed', 'started']
# turn off for all future constructions: 
sections.Half.use_pluralsingular = Fraudulent
responsibilities = sections('pay bill', 'tremendous', station=['completed', 'started'])
with pytest.raises(AttributeError):
    responsibilities.statuses  # this now raises an AttributeError

Display, nonetheless, that this must mild traverse descendant nodes to gaze within the occasion that they
beget the requested attribute. To stop the usage of this characteristic also, safe admission to
attributes the usage of the Half.get_node_attr() scheme as a replacement.

Properties/techniques

Each and every sections() call returns a structure containing nodes of a varied class created in a class factory feature, where the unfamiliar class definition consists of no logic other than that it inherits from the Half class. This enables properties/techniques added to 1 structure’s class definition to now not impact the class definitions of nodes from varied constructions.

Subclassing

Inheriting Half is easy, the ultimate requirement is to call colossal().__init__kwds) at some level in __init__() like beneath if you happen to override that scheme:

class Library(sections.Half):
    """My library class."""

    def __init__(self, stamp="Personalized default value", kwds):
        """Bolt kwds to colossal."""
        colossal().__init__(kwds)
        self.stamp = stamp

    @property
    def genres(self):
        """A synonym for sections."""
        if self.isroot:
            return self.sections
        else:
            lift AttributeError('This library has ultimate 1 level of genres')

    @property
    def books(self):
        """A synonym for leaves."""
        return self.lea

Read More

Charlie Layers
WRITTEN BY

Charlie Layers

Fill your life with experiences so you always have a great story to tell