cdxcore.version#
Framework to track code versions of functions, classes, and their members via a simple decorating mechanism
implemented with cdxcore.verson.version().
Overview#
A main application is the use in caching results of computational intensive tasks such as data pipelines in machine learning. The version
framework allows updating dynamically only those parts of the data dependency graph whose code generation logic has changed.
Correspondingly, cdxcore.subdir.SubDir supports fast light-weight file based read/write support for versioned files.
A full caching logic is implemented using cdxcore.subdir.SubDir.cache().
Versioned Functions#
For versioning, basic use is straight forward and self-explanatory:
from cdxbasics.version import version
@version("0.0.1")
def f(x):
return x
print( f.version.full ) # -> 0.0.1
Dependencies are declared with the dependencies keyword:
@version("0.0.2", dependencies=[f])
def g(x):
return f(x)
print( g.version.input ) # -> 0.0.2
print( g.version.full ) # -> 0.0.2 { f: 0.0.01 }
You have access to version from within the function:
@version("0.0.2", dependencies=[f])
def g(x):
print(g.version.full) # -> 0.0.2 { f: 0.0.01 }
return f(x)
g(1)
You can also use strings to refer to dependencies.
This functionality depends on visibility of the referred dependencies by the function in the
function’s __global__ scope:
@version("0.0.4", dependencies=['f'])
def r(x):
return x
print( r.version.full ) # -> 0.0.4 { f: 0.0.01 }
Versioned Classes#
This works with classes, too:
@version("0.0.3", dependencies=[f] )
class A(object):
def h(self, x):
return f(x)
print( A.version.input ) # -> 0.0.3
print( A.version.full ) # -> 0.0.3 { f: 0.0.01 }
a = A()
print( a.version.input ) # -> 0.0.3
print( a.version.full ) # -> 0.0.3 { f: 0.0.01 }
Dependencies on base classes are automatic:
@version("0.0.1")
class A(object):
pass
@version("0.0.2")
class B(A):
pass
print( B.version.full ) # -> 0.0.2 { A: 0.0.1 }
Member functions are automatically dependent on their defining class:
from cdxcore.version import version
class A(object):
def __init__(self, x=2):
self.x = x
@version(version="0.4.1")
def h(self, y):
return self.x*y
@version(version="0.3.0")
def h(x,y):
return x+y
@version(version="0.0.2", dependencies=[h])
def f(x,y):
return h(y,x)
@version(version="0.0.1", dependencies=["f", A.h])
def g(x,z):
a = A()
return f(x*2,z)+a.h(z)
g(1,2)
print("version", g.version.input) # -> version 0.0.1
print("full version", g.version.full ) # -> full version 0.0.1 { f: 0.0.2 { h: 0.3.0 }, A.h: 0.4.1 }
print("full version ID",g.version.unique_id48 ) # -> full version ID 0.0.1 { f: 0.0.2 { h: 0.3.0 }, A.h: 0.4.1 }
print("full version ID",g.version.unique_id32 ) # -> full version ID 0.0.1 { f: 0.0.2 { h: 0.3.0 }, A.h: 0.4.1 }
print("depedencies",g.version.dependencies ) # -> depedencies ('0.0.1', {'f': ('0.0.2', {'h': '0.3.0'}), 'A.h': '0.4.1'})
Decorated Function Information#
A decorated function or class has a member version of type cdxcore.version.Version which has the following
key properties:
cdxcore.version.Version.input: the input version as defined withcdxcore.version.version().cdxcore.version.Version.full: a fully qualified version with all dependent functions and classes in human readable form.cdxcore.version.Version.unique_id48,cdxcore.version.Version.unique_id64: unique hashes ofcdxcore.version.Version.fullof 48 or 64 characters, respectively. You can use the functioncdxcore.version.Version.unique_id()to compute hash IDs of any length.cdxcore.version.Version.dependencies: a hierarchical list of dependencies for systematic inspection.
Import#
from cdxcore.version import version
Documentation#
Functions
|
Decorator to "version" a function or class, which may depend on other versioned functions or classes. |
Classes
|
Class to track version dependencies for a given function or class. |
Exceptions
|
Error raised if an error occurred during version definition. |
|
Standardized error type to be raised by applications if a version found did not match an expected version. |
- class cdxcore.version.Version(original, version, dependencies, auto_class)[source]#
Bases:
objectClass to track version dependencies for a given function or class.
This class is used by
cdxcore.subdir.version(). Developers will typically access it via a decorated function’sversionproperty.Key Properties
cdxcore.version.Version.input: input version string as provided by the user.cdxcore.version.Version.full: qualified full version including versions of dependent functions or classes, as a string.cdxcore.version.Version.unique_id48: 48 character unique ID. Versions for 60 and 64 characters are also pre-defined.cdxcore.version.Version.dependencies: hierarchy of version dependencies as a list.
Dependency Resolution
Dependency resolution is lazy to allow creating dependencies on Python elements which are defined later / elsewhere. If an error occurs during dependency resoution an exception of type
cdxcore.version.VersionDefinitionErroris raised.- Parameters:
- originalCallable
Orignal Python element: a function, class, or member function.
- versionstr, optional
User version string for
original.- dependencieslist[type], optional
List of dependencies as types (preferably) or string names.
- auto_classbool, optional
Whether to automatically make classes dependent on their (versioned) base classes, and member functions on their (versioned) containing classes.
- property dependencies#
Returns information on the version of
selfand all dependent elements.For a given function the format is as follows:
If the element has no dependents:
"version"If the function has dependencies, return recursively:
( "version", { dependency: dependency.version_full() } )
Example:
from cdxcore.version import version class A(object): def __init__(self, x=2): self.x = x @version(version="0.4.1") def h(self, y): return self.x*y @version(version="0.3.0") def h(x,y): return x+y @version(version="0.0.2", dependencies=[h]) def f(x,y): return h(y,x) @version(version="0.0.1", dependencies=["f", A.h]) def g(x,z): a = A() return f(x*2,z)+a.h(z) g(1,2) print("version", g.version.input) # -> version 0.0.1 print("full version", g.version.full ) # -> full version 0.0.1 { f: 0.0.2 { h: 0.3.0 }, A.h: 0.4.1 } print("depedencies",g.version.dependencies ) # -> depedencies ('0.0.1', {'f': ('0.0.2', {'h': '0.3.0'}), 'A.h': '0.4.1'})
- property full: str#
Returns information on the version of
selfand all dependent elements in human readable form.Elements are sorted by name, hence this representation can be used to test equality between two versions (
cdxcore.version.Versionimplements==and!=based onfull).
- is_dependent(other)[source]#
Determines whether the current element is dependent on another element.
The parameter
othercan be qualified name, a function, or a class.- Returns:
- Versionstr
This function returns
Noneif there is no dependency onother, or the direct user-specified version of theotherit is dependent on.
- unique_id(max_len=64)[source]#
Returns a unique version string for this version of at most the specified number of characters.
Returns either the simple readable version or the current version plus a unique hash if the simple version exceeds
max_lencharacters.
- property unique_id48: str#
Returns a unique version string for this version of at most 48 characters.
Returns either the simple readable version or the current version plus a unique hash if the simple version exceeds 48 characters.
- exception cdxcore.version.VersionDefinitionError(context, message)[source]#
Bases:
RuntimeErrorError raised if an error occurred during version definition.
- exception cdxcore.version.VersionError(*args, version_found, version_expected)[source]#
Bases:
RuntimeErrorStandardized error type to be raised by applications if a version found did not match an expected version.
- version_expected#
The version expected.
- version_found#
The version found.
- cdxcore.version.version(version='0.0.1', dependencies=[], *, auto_class=True, raise_if_has_version=True)[source]#
Decorator to “version” a function or class, which may depend on other versioned functions or classes. The point of this decorator is being able to find out the code version of a sequence of function calls, and be able to update cached or otherwise stored results accordingly.
You can
cdxcore.version.version()functions, classes, and their member functions.When a class is versioned it will automatically be dependent on the versions of any versioned base classes. The same is true for versioned member functions: by default they will be dependent on the version of the defining class. Sometimes this behaviour is not helpful. In this case set
auto_classtoFalsewhen defining thecdxcore.version.version()for a member function or derived class.Simple function example:
from cdxcore.version import version class A(object): def __init__(self, x=2): self.x = x @version(version="0.4.1") def h(self, y): return self.x*y @version(version="0.3.0") def h(x,y): return x+y @version(version="0.0.2", dependencies=[h]) def f(x,y): return h(y,x) @version(version="0.0.1", dependencies=["f", A.h]) def g(x,z): a = A() return f(x*2,z)+a.h(z) g(1,2) print("version", g.version.input) # -> version 0.0.1 print("full version", g.version.full ) # -> full version 0.0.1 { f: 0.0.2 { h: 0.3.0 }, A.h: 0.4.1 } print("full version ID",g.version.unique_id48 ) # -> full version ID 0.0.1 { f: 0.0.2 { h: 0.3.0 }, A.h: 0.4.1 } print("depedencies",g.version.dependencies ) # -> depedencies ('0.0.1', {'f': ('0.0.2', {'h': '0.3.0'}), 'A.h': '0.4.1'})
Example for classes:
@version("0.1") class A(object): @version("0.2") # automatically depends on A def f(self, x): return x @version("0.3", auto_class=False ) # does not depend on A def g(self, x): return x @version("0.4") # automatically depends on A class B(A): pass @version("0.4", auto_class=False ) # does not depend on A class C(A): pass b = B() c = C() print( "B", b.version.full ) # -> B 0.4 { A: 0.1 } print( "C", c.version.full ) # -> C 0.4
See Also
cdxcore.subdir.SubDir.cache()implements a caching mechanism which uses versions to decide whether a cached result can still be used.- Parameters:
- versionstr
Version string for this function or class.
- dependencieslist[type], optional
List of elements this function depends on. Usually the list contains the actual other element by Python reference. If this is not suitable (for example if the name cannot be resolved in order), a string can be used to identify the dependency. If strings are used, then the function’s global context and, if appliable, the associated
selfwill be searched for the respective element.- auto_classbool, optional
If
True, the default, then the version of member function or an inherited class is automatically dependent on the version of the defining/base class. Set toFalseto turn off. The default isTrue.- raise_if_has_versionbool, optional
Whether to throw an exception of version are already present. This is usually the desired behaviour except if used in another wrapper, see for example
cdxcore.subdir.SubDir.cache(). The default isTrue.
- Returns:
- WrapperCallable
The returned decorated function or class will have a version property of type
cdxcore.version.Versionwith the following key properties:cdxcore.version.Version.input: input version string as provided by the user.cdxcore.version.Version.full: qualified full version including versions of dependent functions or classes, as a stringcdxcore.version.Version.unique_id48: a 48 character unique ID. Versions for 60 and 64 characters are also pre-defined.cdxcore.version.Version.dependencies: hierarchy of version dependencies as a list. The recursive definition is as follows: if the function has no dependencies, return:"version"If the function has dependencies, return recursively:
( "version", { dependency: dependency.version_full() } )