Last active
April 26, 2021 17:45
-
-
Save akaszynski/0d3c240d086af5e8da403d8371d9ed1c to your computer and use it in GitHub Desktop.
Implements a simple version check for a client/server pair.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Implements a simple version check for a client/server pair. | |
Used when you want to verify if your server version is a minimum | |
value. | |
The decorator allows arguments within the decorator itself. | |
""" | |
def meets_version(version, meets): | |
"""Check if a version string meets a minimum version. | |
This is a simplified way to compare version strings. For a more robust | |
tool, please check out the ``packaging`` library: | |
https://github.com/pypa/packaging | |
Parameters | |
---------- | |
version : str | |
Version string. For example ``'0.25.1'``. | |
meets : str | |
Version string. For example ``'0.25.2'``. | |
Returns | |
------- | |
newer : bool | |
True if version ``version`` is greater or equal to version ``meets``. | |
Examples | |
-------- | |
>>> meets_version('0.25.1', '0.25.2') | |
False | |
>>> meets_version('0.26.0', '0.25.2') | |
True | |
""" | |
if not isinstance(version, tuple): | |
va = version_tuple(version) | |
else: | |
va = version | |
if not isinstance(meets, tuple): | |
vb = version_tuple(meets) | |
else: | |
vb = meets | |
if len(va) != len(vb): | |
raise ValueError("Versions are not comparable.") | |
for i in range(len(va)): | |
if va[i] > vb[i]: | |
return True | |
elif va[i] < vb[i]: | |
return False | |
# Arrived here if same version | |
return True | |
def version_tuple(v): | |
"""Convert a version string to a tuple containing ints. | |
Non-numeric version strings will be converted to 0. For example: | |
``'0.28.0dev0'`` will be converted to ``'0.28.0'`` | |
Returns | |
------- | |
ver_tuple : tuple | |
Length 3 tuple representing the major, minor, and patch | |
version. | |
""" | |
split_v = v.split(".") | |
while len(split_v) < 3: | |
split_v.append('0') | |
if len(split_v) > 3: | |
raise ValueError('Version strings containing more than three parts ' | |
'cannot be parsed') | |
vals = [] | |
for item in split_v: | |
if item.isnumeric(): | |
vals.append(int(item)) | |
else: | |
vals.append(0) | |
return tuple(vals) | |
class VersionError(ValueError): | |
"""Raised when the Server is the wrong version""" | |
def __init__(self, msg='Invalid Server version'): | |
ValueError.__init__(self, msg) | |
class FakeServer: | |
"""Fake server""" | |
def __init__(self): | |
self._version = 0, 2, 0 | |
def meth_a(self): | |
return 'a' | |
def meth_b(self): | |
return 'b' | |
@property | |
def version(self): | |
"""version of the server""" | |
return self._version | |
def version_requires(min_version): | |
"""Ensure the method called matches a certain version""" | |
def decorator(func): | |
# first arg *must* be a tuple containing the version | |
if not isinstance(min_version, tuple): | |
raise TypeError('version_requires decorator must include a version ' | |
'tuple. For example:\n' | |
'``@_version_requires((0, 1, 3))``') | |
if not len(min_version) == 3: | |
raise TypeError('version_requires decorator must include a version ' | |
'tuple. For example:\n' | |
'``@_version_requires((0, 1, 3))``') | |
def wrapper(self, *args, **kwargs): | |
"""Call the original function""" | |
# must be called from a "Client" instance containing a server attribute | |
server_version = self._server.version | |
if not meets_version(server_version, min_version): | |
ver_str = '.'.join(map(str, min_version)) | |
raise VersionError(f'``{func.__name__}`` requires server version >= {ver_str}') | |
return func(self, *args, **kwargs) | |
return wrapper | |
return decorator | |
class Client(): | |
def __init__(self): | |
"""Connects to a fake server""" | |
self._server = FakeServer() | |
@version_requires((0, 1, 3)) # require 0.1.3 | |
def meth_a(self): | |
"""calls method a on the 'server'""" | |
return self._server.meth_a() | |
@version_requires((0, 2, 3)) # require 0.2.3 | |
def meth_b(self): | |
"""calls method b on the 'server'""" | |
return self._server.meth_b() | |
if __name__ == '__main__': | |
client = Client() | |
client.meth_a() | |
# this will fail | |
client.meth_b() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment