A Quick Tour¶
This tour takes just a few minutes to cover the full cycle of application persistence - from application data object to file storage and back to the application.
Object Declaration¶
The first step is to declare a collection of values that will be stored and then recovered at some later point. A simple example appears below:
import uuid
import ansar as ar
class ReceivedJob(ar.Message):
def __init__(self, unique_id=None, title='watchdog', priority=10, service='noop', body=b''):
ar.Message.__init__(self)
self.unique_id = unique_id or uuid.uuid4()
self.title = title
self.priority = priority
self.service = service
self.body = body
ar.bind(ReceivedJob)
The ReceivedJob
class inherits from the Message
base
class, all class members are given default values using named arguments and lastly, the class
is registered using the bind
function.
After default creation - j = ReceivedJob()
- all members of j
contain valid
data. The library relies on this behaviour during recovery of data and also at
registration time, to determine the data types of individual members.
Note
Classes can be declared with much more than a few int
and str
members.
Refer to More About Types for an introduction into the deeper topic of types.
Write An Object To A File¶
Writing an object into file storage is most conveniently carried out using the
File
class:
f = ar.File('job', ReceivedJob)
j = ReceivedJob()
f.store(j)
The call to store()
creates or overwrites the job.json
file in the current folder. The contents of the file look like this:
{
"value": {
"body": "",
"priority": 10,
"service": "noop",
"title": "watchdog",
"unique_id": "8eab5e5d-6b74-49ad-994f-d436dc4cbf39"
}
}
The file contains an instance of a JSON object. The values for the ReceivedJob
appear as the member value
.
Note
The ReceivedJob
class being passed to the File
object
is an example of a type expression. Refer to Type Expressions
for the full scope of what that parameter can be.
XML is supported as an alternative encoding. The major motivation for providing this alternative is for those projects with significant commitment to XML in their literature and tooling. There may also be relevant external requirements. Adopting XML for storing and recovering of jobs requires a single additional parameter:
f = ar.File('job', ReceivedJob, encoding=ar.CodecXml)
j = ReceivedJob()
f.store(j)
The contents of the new file look like:
<?xml version="1.0" ?>
<message>
<message name="value">
<string name="unique_id">1d719661-af98-47be-9af0-a550b85770fe</string>
<string name="title">watchdog</string>
<integer name="priority" value="10"/>
<string name="service">noop</string>
<string name="body"/>
</message>
</message>
Use of the XML encoding comes at the cost of increased consumption of resources. XML can consume 2-4 times as much file space as JSON and the increase in consumption of CPU cycles is typically worse. For these reasons the JSON encoding is defined as the default.
Warning
The XML encoding does not deliver the same capabilities as the JSON
encoding, specifically in the area of representation of non-printing
values within byte
values. Look here
for details.
Note
By default the store()
method - and its
sibling recover()
- auto-append an extension to
the supplied file name. This behaviour is consistent throughout the library
and especially significant when dealing with collections of files inside folders.
The behaviour can be disabled using decorate_names=False
which passes complete
responsibility for file names back to the caller.
Reading An Object From A File¶
Reading an object from file storage is also carried out using the
File
class. In fact, we can re-use the same instance from the
previous sample:
j, _ = f.recover()
This results in assignment of a fully formed instance of the ReceivedJob
class, to the j
variable. Details like the filename and expected object
type were retained in the f
variable and re-applied here.
The recover()
method specifically returns a 2-tuple. The
first element is the recovered instance and the second element (i.e. the underscore)
is a version tag. Use of the underscore above obviously discards whatever that
version information might have been. This is fine in prototyping code and is
also fine in the first version of any software - everything initially defaults
to a “no-op” or “nothing to see here”.
Note
Support for version handling is hard-wired into the library. An entire
section is devoted to the proper use of the version information
(Versions, Upgrading And Migration) returned by the recover()
method. For the moment it is enough to know that nothing in the above code
fragments is “wrong” or destined to be discarded when the “proper” code is
written.
A Few Details¶
The operational behaviour of the File
class can be influenced
by passing additional named parameters. The full set of parameters are:
name
te
encoding
create_default
pretty_format
decorate_names
The first two are required and often all that is needed - the remainder have
default values satisfying most expectations. The create_default parameter
affects the behaviour of the recover()
method, where a
named file does not exist. If set to True
the method will return a default instance
of the expected type, rather than raising an exception. By default, file contents
are “pretty-printed” for readability and to assist direct editing. Efficiency can be
improved by setting this parameter to False
. Lastly, setting the decorate_names
parameter to False
disables the auto-append of an encoding-dependent file
extension, e.g. .xml
.
Summary¶
That is a complete introduction into how the library should be used to implement application persistence. The signficant steps were:
registration,
storing
and recovering.
From a developer’s point of view this represents a low intellectual overhead. It also quietly delivers:
type sophistication,
reading of fully-formed application types
and production-ready version support.