Package: redictum
Python redictum lib/service (redictum)ConceptImagine some state (a set of key value pairs; a dictionary), the state has a start time and
optionally an end time (like a "valid from" and "valid to" criteria), and some of the state
is just information, and some of it defines the state's uniqueness.Imagine now that you can just throw state information at the wall, and it sticks, meaning
that the state that is not unique is taken as an update, and the state which is unique, is
new state.That's what redictum does, as briefly as I can make it!In case the idea of uniqueness isn't quite clear, imagine a type of state which represents
people, and we are interested in the first name, last name, age and thirst and hunger of
these people.For example imagine these four bits of state:{"first_name":"Fred","last_name":"Flint","age":10,"thirst":2,"hunger":80}{"first_name":"Jane","last_name":"Oster","age":20,"thirst":30,"hunger":30}{"first_name":"John","last_name":"Medium","age":40,"thirst":76,"hunger":60}{"first_name":"John","last_name":"Winter","age":30,"thirst":40,"hunger":10}Let's say, in our contrived world, that no two people have the same first and last names
and that is the extent of our consideration for identifying the uniqueness of a person.In that case all four states above are unique, but, if we add this state:{"first_name":"John","last_name":"Winter","age":31,"thirst":55,"hunger":10}...John Winter has become a year older, and a bit more thirsty (that could happen in
a year).The total state of the system is now:{"first_name":"Fred","last_name":"Flint","age":10,"thirst":2,"hunger":80}{"first_name":"Jane","last_name":"Oster","age":20,"thirst":30,"hunger":30}{"first_name":"John","last_name":"Medium","age":40,"thirst":76,"hunger":60}{"first_name":"John","last_name":"Winter","age":31,"thirst":55,"hunger":10}Hadagebeen considered part of what made people unique, then the state would have been
this instead:{"first_name":"Fred","last_name":"Flint","age":10,"thirst":2,"hunger":80}{"first_name":"Jane","last_name":"Oster","age":20,"thirst":30,"hunger":30}{"first_name":"John","last_name":"Medium","age":40,"thirst":76,"hunger":60}{"first_name":"John","last_name":"Winter","age":30,"thirst":40,"hunger":10}{"first_name":"John","last_name":"Winter","age":31,"thirst":55,"hunger":10}You can also set the start (valid from time) and optionally end (valid to time) by way of
a TTL (time to live), and get the "valid" states, or expired or future states.This simple set of rules means you can throw state at the system that will go away if not
renewed / updated regularly (as govered by the TTL), and you can throw state at the system
which will become "valid" after some time, if it's not cancelled, or otherwise updated.
Being able to get the "valid" states then becomes a powerful tool.That's more or less the entire concept, it just remains to be said that there are types of
state, with which code can be associated, and the back end storage can be whatever there
is a storage adapter for.In codeThe above explanation and data is shown here in code now.First import redictum, create a repository, create a class for people with a (default) time
to live of 200 (seconds), then get the valid dictums, and there will of course be one item:>>>importredictum>>>>>>people=redictum.Repo(redictum.store.RdbmsStore("sqlite:///:memory:"))>>>>>>classPerson(redictum.Dictum):...unique_by=("first_name","last_name")...ttl=200...>>>people.apply(Person({"first_name":"Fred","last_name":"Flint","age":10,"thirst":2,"hunger":80}))>>>>>>people.valid_dictums()[__main__.Person{'valid_from_ts':1654451266.453832,'valid_to_ts':1654451466.453832,'first_name':'Fred','last_name':'Flint','age':10,'thirst':2,'hunger':80}]>>>Now add more people:>>>people.apply(Person({"first_name":"Jane","last_name":"Oster","age":20,"thirst":30,"hunger":30}))>>>people.apply(Person({"first_name":"John","last_name":"Medium","age":40,"thirst":76,"hunger":60}))>>>people.apply(Person({"first_name":"John","last_name":"Winter","age":30,"thirst":40,"hunger":10}))>>>The display of the dictum objects is a bit inconvenient, so something a little easier on the eye:>>>forpin[f"{d.data['first_name']}{d.data['last_name']}{d.data['age']}{d.data['thirst']}{d.data['hunger']}"fordinpeople.valid_dictums()]:print(p)...FredFlint10280JaneOster203030JohnMedium407660JohnWinter304010>>>Now add the new person, which is not a new person, because the first and last names are already in
use (so it updates the existing John Winter record):>>>people.apply(Person({"first_name":"John","last_name":"Winter","age":31,"thirst":55,"hunger":10}))>>>>>>forpin[f"{d.data['first_name']}{d.data['last_name']}{d.data['age']}{d.data['thirst']}{d.data['hunger']}"fordinpeople.valid_dictums()]:print(p)...FredFlint10280JaneOster203030JohnMedium407660JohnWinter315510>>>The valid from and valid to timestamps are also available:>>>forpin[f"{d.data['first_name']}{d.data['last_name']}{d.meta_data['valid_from_ts']}{d.meta_data['valid_to_ts']}"fordinpeople.valid_dictums()]:print(p)...FredFlint1654451266.4538321654451466.453832JaneOster1654451279.8880881654451479.888088JohnMedium1654451282.2650071654451482.265007JohnWinter1654451347.8735711654451547.873571>>>A short while later, the first people "applied" have expired, sopeople.valid_dictums()no
longer returns them, though the later record is still valid:>>>forpin[f"{d.data['first_name']}{d.data['last_name']}{d.meta_data['valid_from_ts']}{d.meta_data['valid_to_ts']}"fordinpeople.valid_dictums()]:print(p)...JohnWinter1654451347.8735711654451547.873571>>>The expired records can be retrieved:>>>forpin[f"{d.data['first_name']}{d.data['last_name']}{d.meta_data['valid_from_ts']}{d.meta_data['valid_to_ts']}"fordinpeople.expired_dictums()]:print(p)...FredFlint1654451266.4538321654451466.453832JaneOster1654451279.8880881654451479.888088JohnMedium1654451282.2650071654451482.265007>>>If these records are updated, they will disappear from expired and be valid again.dictum APITheDictumclass API is as follows.constructorThere is one required positional parameter, the data. Optionally atskwarg may be given which
changes the valid from time from now, to whatever you set. A floating point timestamp value should
be given. For example this will create a record which will not appear in the list of valid dictums
until 20 seconds has elapsed:Dictum({"first_name":"John","last_name":"Winter"},ts=datetime.now().timestamp()+20))The valid to time (if there is a TTL set) will be adjusted as well, so the TTL will remain whatever
it was.slide_ts_windowThis slides the validity window, forwards or backwards. A single positional parameter specifying
the offset to apply in seconds must be given. The dictum object it self is returned. Thus this:Dictum({"first_name":"John","last_name":"Winter"}).slide_ts_window(20)...will be the same as this:Dictum({"first_name":"John","last_name":"Winter"},ts=datetime.now().timestamp()+20))extend_ts_windowThis is likeslide_ts_windowbut only the "to" time is shifted. Though "extend" implies
a positive offset, as withslide_ts_windowa negative number may be given to shrink the
validity window.deleteDelete the dictum.dataThispropertyreturns the dictum's data.meta_dataThispropertyreturns the dictum's meta data (a dictionary withvalid_from_tsandvalid_to_tskeys).all_dataThispropertyreturns data and meta data as one dict.idThispropertyreturns the store ID which can uniquely identify the dict. It will
beNoneif the dictum is not committed to a store.dictum callbacksTheDictclass has two callback methods which do nothing by default, but which yourDictsubclass may implement. Only the instance being worked on will have it's callback called.addedCalled when a dictum is added and committed to storage.updatedCalled when a change to an existing dictum is committed.deletedFor completeness of the API, this is called when a dictum is deleted.the tracer dictumThe Tracer class is a Dictum subclass or mix in which automatically adds "tracer" data.To use it, add it to the super classes of yourDictumsubclass:classDogDictum(redictum.Tracer,redictum.Dictum):ttl=3600*24*365unique_by=("owner","address","name")Now data will contain an "inst_at" key, and the value will be a structure
describing the module and function where the dictum was instantiated.It you instantiate another dog, which is the same dog, then the trace data will be updated.NotesYou may have as many different types (Dictumsubclasses) as you like, in the same store
or different ones. If you create two or moreRdbmsStoreobjects with the same DSN for this
purpose, it will be the same object, which should be fine. This per DSN singleton behaviour
is specific to theRdbmsStoreclass, other stores behaviour may vary.