Skip to content

Instantly share code, notes, and snippets.

@akaszynski
Last active April 26, 2021 17:45
Show Gist options
  • Save akaszynski/0d3c240d086af5e8da403d8371d9ed1c to your computer and use it in GitHub Desktop.
Save akaszynski/0d3c240d086af5e8da403d8371d9ed1c to your computer and use it in GitHub Desktop.
Implements a simple version check for a client/server pair.
"""
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