Objconfig (Python): Config Class

Zend-Config

I decided that, for future projects, I needed to port asherwunk/phabstractic to the Python programming language (pyabstractic).  Well, phabstractic relies on a couple packages itself that are in the PHP domain.  One of these packages is zend-config, which is part of the Zend Framework programming library.  The nice thing about the Zend Framework is that each piece is generally modular, in that, you don’t need the entire framework to use a piece of it.  In phabstractic I particularly used Zend Framework version 2’s zend-config.  You can learn more about zend-config at it’s version 2 documentation page, or by browsing zend-config on github.  I shall quote its github README:

zend-config is designed to simplify access to configuration data within applications. It provides a nested object property-based user interface for accessing this configuration data within application code. The configuration data may come from a variety of media supporting hierarchical data storage.

What The Heck Did I Use This For?

For better or worse I used zend-config to configure all my objects, particularly my abstract data type objects, whether they needed a juggernaut of configuration like zend-config or not.  Really, the core of the library, being the Config object, is what I wanted: something that I could set properties on like a stdClass and have it be able to be save-able or at least hash-able in some form (like XML or JSON).  I wanted the basic Config functionality, the fact that you could write it out to a file or string in any given format was just icing on the cake.  When I decided to use zend-config as well, I had no eye towards porting the phabstractic library to any other language, so I thought it really wasn’t a big deal.

Well, come ’round to the idea of porting phabstractic to Python (pyabstractic), and possibly JavaScript (abstractic.js) and I have to implement or find something similar in those languages.  So, I just decided to build my own zend-config port in Python.

The Config Object

The PHP Config object is pretty simple.  It defines a protected array which it then accesses using magic functions, as well as normal functions.  This array can be modified, but only if modifications are allowed, which is tracked by another protected property.  It also implements the \Countable, \Iterable, and \ArrayAccess interfaces enabling some specific functionality in PHP.  That is, being able to process count(), being able to be used in a foreach loop, and being able to be accessed like an array (such as this[key]):

When I decided to port to Python I wanted to keep the ‘interface’ or the usability, as in how you use the library, of PHP.  So for instance, if a class implements an interface, such as \Countable, then I would implement such an “interface” in Python.  Now, Python doesn’t have interfaces like PHP does, but that’s okay because we can ameliorate by being able to inherit from multiple classes.  So, just build an “interface” as a parent object with some specific functions, and if the child class doesn’t implement those functions raise an exception.

Exceptions

However, likewise for exceptions, I wanted to port it so that people who are familiar with the PHP code could be instantly familiar with the Python implementation.  Zend, as well as I, seems to like to define an “interface” again for an exception local to where it’s used in the code.  Then they define a set of exceptions local to where they are used int he code that implement that given interface.  This is handy because then you just have to reference that interface if you want to catch any exceptions that happen only in this particular section of code.  Thus, I also defined the following exceptions in the Python module.

These are exactly the same exceptions defined in zend-config at the time of this writing (as you can see in their github).

Built-In Interfaces

So zend-config’s Config object implements \Countable, \Iterator, and \ArrayAccess.  What do these PHP built-in interfaces entail?  Well, \Countable is about revealing how many of something exist, and in this case is really the length of the protected property data in the Config object.  PHP counts the number of elements in an array with the count() function, while Python implements the len() function.  In Python we are able tap into the len() built-in function using the operator overloader __len__:

Config also implements \Iterator, which is PHP’s way of enabling the object to be used in foreach loops and other such iteration contexts.  Python is able to do the same thing by using operator overloaders __iter__, and __next__.  In Python, __iter__ returns what is known as an iterator that implements __next__, and __next__ returns the next object in the list being iterated.  Next() is a little different in Python version 2, but we’re targeting this library for version 3 so I don’t particularly well, care.  As well, you’ll notice the PHP \Iterator interface defines more methods, but I felt that including those methods (which don’t really do anything in Python) would just clutter up the namespace.  Below is our implementation of PHP’s \Iterator as an “interface” in Python:

And lastly, Config implements \ArrayAccess, which is PHP’s way of enabling the object to be indexed like an array, such as this[key].  Python can do something similar as well using operator overloading.  Like the \Iterator interface, I decided that we are more going for functionality than exact method names, so I excluded the original method names from the PHP interface.  So, my implementation of PHP’s \ArrayAccess as an “interface” in Python reads:

Constructing a Config Object

Constructing a Config object is pretty straightforward in PHP, you just set the values sent in the single argument to the protected data property.  If the value is another array, then we recursively construct another Config object and attach it to this one:

We do something similar in Python, however, in Python arrays aren’t as straightforward as they are in PHP.  In PHP an array can be associative or not, and it’s all considered an array.  In Python an ‘array’ might be a tuple, a list (without keys), a dictionary (with keys), or even another iterable object.  So, we have to take a few things into consideration in this constructor with regards to the given data type.  I tried to support “duck typing” as best I could for these purposes, and I came up with this constructor for my Python Config object:

The toArray Method

As an aside you’ll see that I test for the existence of a ‘toArray’ method in case ‘items’ doesn’t exist (the case for non-dictionaries).  I’m assuming any object that implements toArray will return a dictionary, or something that implements ‘items’.  In fact, in Config I implement the toArray function (this converts Config objects passed into Config objects to dictionaries) after being inspired by the PHP counterpart:

In Python:

Interfaces

As written before I implement three built-in interfaces explicitly in Python, in order to mirror the PHP code in a way.  These include \Countable, \Iterator, and \ArrayAccess.  I did not mirror their method names, but instead their functionality as it exists in Python (with operator overloading).

\Countable

This one’s pretty easy.  Python uses len() instead of count(), so… just use that:

\Iterator

I outlined above the interface I used for the iterator interface.  Python implements iterators as objects that utilize the __iter__ and __next__ overloadable methods.  In PHP you implement a set of methods that rewind, current, advance, etc.  In Python __iter__ returns an object that implements __next__ and __next__ returns the next item in the sequence.  For whatever reason, probably to support multiple iterators in one loop, I made my own iteration object in the __iter__ function and pass in the given self.  I did not implement __next__ at all (the generator function returned implements that)… I’m not sure why I did that:

\ArrayAccess

I outlined above the interface I used for the array access interface.  Python implements array access as objects that utilize the __(get/set/del)item__ operator overloaders.  An object calls set on an indexed assignment, get on an indexed retrieval, and del on an indexed deletion (del array[key]).  Rather than pass these on straight to the __data dictionary functionality, I actually proxy them over to the attribute setting/deleting of the Config object.  I do this because that’s what zend-config PHP does in it’s ArrayAccess methods, calling the magic methods (below):

Magic Methods

In PHP there are magic methods that are called when you try to set and get a property on an object that is inaccessible (or doesn’t exist).  Magic methods in PHP are like Python operator overloaders, but they are much more precise in scope.  If you’ve ever programmed OOP in PHP, don’t worry, you’ve already used magic methods… __construct() is a magic method!

zend-config implements the magic methods __set and __get to access the protected data property of the object:

Note here how __get calls ->get() without a default.  This means any access to any property not defined on the Config object will return as null rather than raise an exception.  Also note that __set checks if modifications are allowed using the ->allowModifications protected property of the Config object

We implement the same functionality in python by utilizing __getattr__ and __setattr_:

Note that in Python __getattr__ is only called on accesses of properties that don’t exist yet.  If we wanted to hook into every property access we would need to use the newer __getattribute__ operator overloader.  For our purposes, properties are never actually added to the Config object, just it’s protected __data property, so __getattr__ is always called on property access.  Also note how in __setattr__ we make exceptions for the defined properties we’ve made in Config.

In PHP you can see if the value is an array we automatically convert it into a Config object, in Python I tried to duplicate much the same by testing to see if the value was a dictionary.  However, this means that you can assign lists and tuples to Config objects without them getting converted to more Config objects (which makes sense).

PHP also defines isset and unset magic methods (because a variable might not be “set” in PHP but can still be polled for, unlike Python), and Python gives us one of those: a del operator overloader.  So we implement:

in Python as:

Copying

One thing we do implement in Python is the __deepcopy__ method, implemented by the copy module.  I did this because the zend-config PHP class specifies particular __clone behavior (similar functionality):

So in Python:

Note how this uses the attribute setting functionality, and also note that it produces a true copy object.

Merging

In PHP merging two arrays is a pretty straightforward process.  This is taken advantage of in zend-config when we want to merge two Config objects together (the second one replacing the same keys as the first).  In Python, things aren’t necessarily as straightforward.

First, we cycle through the merging data, and compare it against the existing data in the Config object.  If it exists, does it implement toArray() and does our own data support Merge?  Then Merge this beast!  If not, create a new Config if it implements toArray(), if not, just assign the value as usual.  The test for this and results look like this:

The Rest Of It

The rest of the methods, such as those testing for modifiability, and such are pretty straightforward:

Note here that if we set the Config object to ReadOnly we recursively set all its children Config objects as read only.  This is important as currently there is no setModifiable function.  Don’t lock yourself out of your own Config!

Conclusion

Configuring objects is an important feature.  I wanted there to be some kind of uniform and exportable way to configure objects in my phabstractic framework so I employed zend-config.  Porting it to Python wasn’t as difficult as I anticipated, however, there are more parts to this story.  How do we import and export the configuration information to different formats, such as XML, INI, YAML, and JSON?  Future posts will cover…

This is part of the Objconfig (Python) Library.

If you appreciate my programming please help support me through my Patreon.

photo credit: OpalMirror Digital Radio Comm Setup for Linux via photopin (license)

Liked it? Take a second to support kadar on Patreon!

kadar

I'm just a wunk, trying to enjoy life. I am a cofounder of http//originalpursuitssoc.com/ and I like computers, code, creativity, and friends.

You may also like...

Leave a Reply

%d bloggers like this: