Version¶
A resource version represents the exact state of a resource at a given point in time. The resource is responsible for
defining what a version actually is, and this is defined with Concourse Tools using a Version subclass.
More information on versions can be found in the Concourse documentation.
Version Parent Class¶
- class concoursetools.version.Version[source]¶
A simple wrapper around a Concourse version.
Users should inherit from this class when defining the version schema for their resource. The class is then used by the resource to parse the input and generate the output for the relevant Concourse steps.
Tip
It is usually recommended to inherit from
TypedVersioninstead, to reduce code and enable some more useful type conversion features.- Example:
If your resource is tracking git commits, then the version might only consist of a git hash corresponding to that commit:
class GitCommitVersion(Version): def __init__(self, commit_hash): self.commit_hash = commit_hash
By default, this will correspond to a JSON object which looks like this:
{ "commit_hash": "abcdef..." }
To change this behaviour, overload the
to_flat_dict()andfrom_flat_dict()methods in the class.
- to_flat_dict()[source]¶
Convert the instance to a dictionary with string fields.
Important
Concourse requires the keys and values of this dictionary to be strings. This is subtly enforced by Concourse Tools as both keys and values are cast to strings before the output stage, but you should not rely on this behaviour for any types other than
str.By default, this method outputs a dictionary of public attributes, with each value cast to a string. This is then converted to JSON by the resource.
- Return type:
- Returns:
A key/value dictionary representing the version.
- Example:
Suppose that you wanted to include date/time information in your version for easier comparisons. You could simply add a timestamp, but it’s more Pythonic to use
datetime. In which case, you need to properly convert to and from some flat representation of the date:class GitCommitVersion(Version): def __init__(self, commit_hash, date): self.commit_hash = commit_hash self.date = date def to_flat_dict(self): return { "commit_hash": self.commit_hash, "timestamp": str(int(self.date.timestamp())), }
- classmethod from_flat_dict(version_dict)[source]¶
Load an instance from a dictionary representing the version.
By default, this method feeds the contents of the dictionary directly to the class initialiser. This assumes that all initialisation parameters are strings.
Caution
Simple types such as
intandboolwill be cast to strings automatically by the default behaviour ofto_flat_dict(), but the reverse will not occur automatically.- Parameters:
version_dict (
dict[str,str]) – A string-only key/value dictionary representing the version.- Example:
A user may wish to include whether or not the commit is a “merge commit” within the version. However,
bool("False")will still returnTrue. Instead, the user should do something like this:class GitCommitVersion(Version): def __init__(self, commit_hash, is_merge): self.commit_hash = commit_hash self.is_merge = is_merge @classmethod def from_flat_dict(cls, version_dict): is_merge = (version_dict["is_merge"] == "True") return cls(version_dict["commit_hash"], is_merge)
- Return type:
Tip
It is often useful to overload this method to deal with types such as
set,EnumandPath. However, it is also beneficial to instead inherit fromTypedVersioninstead.
Typed Version Class¶
- class concoursetools.version.TypedVersion[source]¶
A
Versionsubclass with automatic type flattening and un-flattening.Rewriting the logic for flattening and un-flattening version attributes across multiple resource types is frustrating, and results in a lot of boring code. This class allows the user to specify functions for flattening and un-flattening various types, which are then called automatically by
to_flat_dict()andfrom_flat_dict(). These are registered using theflatten()andun_flatten()decorators respectively.Note
This requires the
dataclasses.dataclass()decorator to work.- Example:
>>> from dataclasses import dataclass >>> from datetime import datetime ... >>> @dataclass ... class GitCommitVersion(TypedVersion): ... commit_hash: str ... date: datetime ... >>> version = GitCommitVersion("abcdef", datetime(2020, 1, 1, 12, 30)) >>> version.to_flat_dict() {'commit_hash': 'abcdef', 'date': '1577881800'}
Caution
The full MRO of each object is looked up when calling the flatten and un-flatten functions, so any type which is a subclass of a registered type will still call the same functions, unless explicitly overwritten.
- classmethod flatten(func)[source]¶
Register a function for flattening a specific type.
- Parameters:
func (
Callable[[TypeVar(T)],str]) – A function taking a single object of the given type, and returning a string.- Return type:
Warning
The decorated function must have an input type hint to be registered properly.
- Example:
>>> from datetime import datetime ... >>> @TypedVersion.flatten ... def _(obj: datetime) -> str: ... return str(int(obj.timestamp()))
- classmethod un_flatten(func)[source]¶
Register a function for un-flattening a string to a specific type.
- Parameters:
func (
Callable[[type[TypeVar(T)],str],TypeVar(T)]) – A function taking a a destination type and a flattened string, and returning an instance of that type.- Return type:
Warning
The decorated function must have a valid return type to be registered properly.
- Example:
>>> from datetime import datetime ... >>> @TypedVersion.un_flatten ... def _(type_: Type[datetime], obj: str) -> datetime: ... return type_.fromtimestamp(int(obj))
Supported Types¶
Concourse Tools has out-of-the-box support for a few common types:
Version Comparisons¶
It is often beneficial to directly compare version within your code.
Hashing¶
By default, every version is hashable, and this hash() is determined by the version class and the output of to_flat_dict(). Key/value pairs are sorted and then hashed as a tuple, which is combined with the hash of the class.
Equality¶
By default, two versions are equal if they have the same hash(). However, you may wish to overload this. For example, consider the following version class:
class GitBranch(Version):
def __init__(self, branch_name: str):
self.branch_name = branch_name
Under the default behaviour, GitBranch("main") and GitBranch("origin/main") will be not be considered equal, but this might not be ideal. The fastest way to fix this is to overload __eq__() like so:
class GitBranch(Version):
def __init__(self, branch_name: str):
self.branch_name = branch_name
def __eq__(self, other):
return self.branch_name.split("/")[-1] == other.branch_name.split("/")[-1]
Ordering¶
Sometimes you need to order versions to simplify your scripts (returning the latest version, etc.), but by default versions are not comparable in this way. This can be fixed by also inheriting from SortableVersionMixin, which expects you to implement __lt__(). We want version_a <= version_b if and only if version_a is no older than version_b.
- class concoursetools.version.SortableVersionMixin[source]¶
A mixin for
Versionsubclasses which allows comparisons between instances.Note
Once
__lt__()has been implemented, you will be able to use<,>,<=and>=with your version classes.- Example:
>>> import datetime >>> >>> class MyVersion(Version, SortableVersionMixin): ... ... def __init__(self, commit_hash, date: datetime.datetime): ... self.commit_hash = commit_hash ... self.date = date ... ... def __lt__(self, other: "MyVersion"): ... try: ... return self.date < other.date ... except AttributeError: ... return NotImplemented
Multi Versions¶
- class concoursetools.additional.MultiVersion(versions)[source]¶
Wraps multiple versions into a single class.
Caution
Users shouldn’t invoke this version outside of the
MultiVersionConcourseResourceclass.- Parameters:
versions (
set[Version]) – A (specifically unordered) collection of subversions to be contained in a single version.
Tip
Two multi-versions are equal if their respective set of subversions are also equal.
- property sub_version_data: list[dict[str, str]]¶
Return a list of flattened subversions.
This is created by calling
to_flat_dict()on each subversion.
- to_flat_dict()[source]¶
Convert the instance to a dictionary with string fields.
The resulting version has a single key/value pair, mapping
keyto thesub_version_dataas a JSON-encoded string.
- classmethod from_flat_dict(version_dict)[source]¶
Load an instance from a dictionary representing the version.
This works by extracting the key (given by
key) from the mapping, decoding the corresponding JSON blob and loading each sub-configuration using thesub_version_class.