From 8b6f54e33a74dfe8581f3c9caf6438cf96237393 Mon Sep 17 00:00:00 2001 From: Alex Pizarro <80284459+apizarro-paypal@users.noreply.github.com> Date: Fri, 18 Mar 2022 12:03:53 -0400 Subject: [PATCH 01/21] Modernize and add Python 3.5-3.7 support (#76) 1. Switch tests from Travis to GitHub Actions. 2. Switch test runner from Nose to Pytest alongside Pytest-Cov for coverage reporting. 3. Require `six` as an installation requirement instead of using it vendored below `business_rules/six.py`. 4. Drop support for Python 2.6 and references to `unittest2`. 5. Use Tox to test library against Python 2.7 and 3.5-3.7. 6. Fix Python 3 deprecation warning related to usage of `assertRaisesRegexp`. 7. Update README "Contributing" instructions to mention Pytest and Tox. --- .github/workflows/tests.yaml | 31 ++ .gitignore | 5 +- .travis.yml | 12 - README.md | 14 +- business_rules/operators.py | 2 +- business_rules/six.py | 577 ---------------------------------- dev-requirements.txt | 11 +- pytest.ini | 5 + setup.cfg | 13 + setup.py | 33 +- tests/__init__.py | 9 +- tests/test_actions_class.py | 6 +- tests/test_engine_logic.py | 4 +- tests/test_integration.py | 4 +- tests/test_operators.py | 10 +- tests/test_operators_class.py | 2 +- tests/test_variables.py | 4 +- tests/test_variables_class.py | 2 +- tox.ini | 28 ++ 19 files changed, 142 insertions(+), 630 deletions(-) create mode 100644 .github/workflows/tests.yaml delete mode 100644 .travis.yml delete mode 100644 business_rules/six.py create mode 100644 pytest.ini create mode 100644 setup.cfg create mode 100644 tox.ini diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 00000000..d0f5df00 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,31 @@ +name: Tests + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + tests: + # https://help.github.com/articles/virtual-environments-for-github-actions + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["2.7", "3.5", "3.6", "3.7"] + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install "tox<4" "tox-gh-actions~=2.8" + + - name: Test with tox + run: tox -vv diff --git a/.gitignore b/.gitignore index 8898a0ae..04a74307 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,7 @@ pip-log.txt # Unit test / coverage reports .coverage .tox -nosetests.xml +.pytest_cache # Translations *.mo @@ -36,3 +36,6 @@ nosetests.xml # Vim *.sw[po] + +# Virtual environments +venv*/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2d254379..00000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: python -python: - - "2.6" - - "2.7" - - "pypy" - - "3.2" - - "3.3" -install: - - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then pip install unittest2; fi - - "pip install -e ." -script: - - "nosetests tests" diff --git a/README.md b/README.md index ef69af88..bfb2c04a 100644 --- a/README.md +++ b/README.md @@ -286,7 +286,15 @@ Note: to compare floating point equality we just check that the difference is le Open up a pull request, making sure to add tests for any new functionality. To set up the dev environment (assuming you're using [virtualenvwrapper](http://docs.python-guide.org/en/latest/dev/virtualenvs/#virtualenvwrapper)): ```bash -$ mkvirtualenv business-rules -$ pip install -r dev-requirements.txt -$ nosetests +$ python -m virtualenv venv +$ source ./venv/bin/activate +$ pip install -r dev-requirements.txt -e . +$ pytest +``` + +Alternatively, you can also use Tox: + +```bash +$ pip install "tox<4" +$ tox -p auto --skip-missing-interpreters ``` diff --git a/business_rules/operators.py b/business_rules/operators.py index 817a9356..d43e5b1f 100644 --- a/business_rules/operators.py +++ b/business_rules/operators.py @@ -1,7 +1,7 @@ import inspect import re from functools import wraps -from .six import string_types, integer_types +from six import string_types, integer_types from .fields import (FIELD_TEXT, FIELD_NUMERIC, FIELD_NO_INPUT, FIELD_SELECT, FIELD_SELECT_MULTIPLE) diff --git a/business_rules/six.py b/business_rules/six.py deleted file mode 100644 index 85898ec7..00000000 --- a/business_rules/six.py +++ /dev/null @@ -1,577 +0,0 @@ -"""Utilities for writing code that runs on Python 2 and 3""" - -# Copyright (c) 2010-2013 Benjamin Peterson -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import operator -import sys -import types - -__author__ = "Benjamin Peterson " -__version__ = "1.4.1" - - -# Useful for very coarse version differentiation. -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - -if PY3: - string_types = str, - integer_types = int, - class_types = type, - text_type = str - binary_type = bytes - - MAXSIZE = sys.maxsize -else: - string_types = basestring, - integer_types = (int, long) - class_types = (type, types.ClassType) - text_type = unicode - binary_type = str - - if sys.platform.startswith("java"): - # Jython always uses 32 bits. - MAXSIZE = int((1 << 31) - 1) - else: - # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): - def __len__(self): - return 1 << 31 - try: - len(X()) - except OverflowError: - # 32-bit - MAXSIZE = int((1 << 31) - 1) - else: - # 64-bit - MAXSIZE = int((1 << 63) - 1) - del X - - -def _add_doc(func, doc): - """Add documentation to a function.""" - func.__doc__ = doc - - -def _import_module(name): - """Import module, returning the module after the last dot.""" - __import__(name) - return sys.modules[name] - - -class _LazyDescr(object): - - def __init__(self, name): - self.name = name - - def __get__(self, obj, tp): - result = self._resolve() - setattr(obj, self.name, result) - # This is a bit ugly, but it avoids running this again. - delattr(tp, self.name) - return result - - -class MovedModule(_LazyDescr): - - def __init__(self, name, old, new=None): - super(MovedModule, self).__init__(name) - if PY3: - if new is None: - new = name - self.mod = new - else: - self.mod = old - - def _resolve(self): - return _import_module(self.mod) - - -class MovedAttribute(_LazyDescr): - - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): - super(MovedAttribute, self).__init__(name) - if PY3: - if new_mod is None: - new_mod = name - self.mod = new_mod - if new_attr is None: - if old_attr is None: - new_attr = name - else: - new_attr = old_attr - self.attr = new_attr - else: - self.mod = old_mod - if old_attr is None: - old_attr = name - self.attr = old_attr - - def _resolve(self): - module = _import_module(self.mod) - return getattr(module, self.attr) - - - -class _MovedItems(types.ModuleType): - """Lazy loading of moved objects""" - - -_moved_attributes = [ - MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), - MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), - MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), - MovedAttribute("map", "itertools", "builtins", "imap", "map"), - MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("reload_module", "__builtin__", "imp", "reload"), - MovedAttribute("reduce", "__builtin__", "functools"), - MovedAttribute("StringIO", "StringIO", "io"), - MovedAttribute("UserString", "UserString", "collections"), - MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), - - MovedModule("builtins", "__builtin__"), - MovedModule("configparser", "ConfigParser"), - MovedModule("copyreg", "copy_reg"), - MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), - MovedModule("http_cookies", "Cookie", "http.cookies"), - MovedModule("html_entities", "htmlentitydefs", "html.entities"), - MovedModule("html_parser", "HTMLParser", "html.parser"), - MovedModule("http_client", "httplib", "http.client"), - MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), - MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), - MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), - MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), - MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), - MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), - MovedModule("cPickle", "cPickle", "pickle"), - MovedModule("queue", "Queue"), - MovedModule("reprlib", "repr"), - MovedModule("socketserver", "SocketServer"), - MovedModule("tkinter", "Tkinter"), - MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), - MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), - MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), - MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), - MovedModule("tkinter_tix", "Tix", "tkinter.tix"), - MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), - MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), - MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), - MovedModule("tkinter_font", "tkFont", "tkinter.font"), - MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), - MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), - MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), - MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), - MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), - MovedModule("winreg", "_winreg"), -] -for attr in _moved_attributes: - setattr(_MovedItems, attr.name, attr) -del attr - -moves = sys.modules[__name__ + ".moves"] = _MovedItems(__name__ + ".moves") - - - -class Module_six_moves_urllib_parse(types.ModuleType): - """Lazy loading of moved objects in six.moves.urllib_parse""" - - -_urllib_parse_moved_attributes = [ - MovedAttribute("ParseResult", "urlparse", "urllib.parse"), - MovedAttribute("parse_qs", "urlparse", "urllib.parse"), - MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), - MovedAttribute("urldefrag", "urlparse", "urllib.parse"), - MovedAttribute("urljoin", "urlparse", "urllib.parse"), - MovedAttribute("urlparse", "urlparse", "urllib.parse"), - MovedAttribute("urlsplit", "urlparse", "urllib.parse"), - MovedAttribute("urlunparse", "urlparse", "urllib.parse"), - MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), - MovedAttribute("quote", "urllib", "urllib.parse"), - MovedAttribute("quote_plus", "urllib", "urllib.parse"), - MovedAttribute("unquote", "urllib", "urllib.parse"), - MovedAttribute("unquote_plus", "urllib", "urllib.parse"), - MovedAttribute("urlencode", "urllib", "urllib.parse"), -] -for attr in _urllib_parse_moved_attributes: - setattr(Module_six_moves_urllib_parse, attr.name, attr) -del attr - -sys.modules[__name__ + ".moves.urllib_parse"] = Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse") -sys.modules[__name__ + ".moves.urllib.parse"] = Module_six_moves_urllib_parse(__name__ + ".moves.urllib.parse") - - -class Module_six_moves_urllib_error(types.ModuleType): - """Lazy loading of moved objects in six.moves.urllib_error""" - - -_urllib_error_moved_attributes = [ - MovedAttribute("URLError", "urllib2", "urllib.error"), - MovedAttribute("HTTPError", "urllib2", "urllib.error"), - MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), -] -for attr in _urllib_error_moved_attributes: - setattr(Module_six_moves_urllib_error, attr.name, attr) -del attr - -sys.modules[__name__ + ".moves.urllib_error"] = Module_six_moves_urllib_error(__name__ + ".moves.urllib_error") -sys.modules[__name__ + ".moves.urllib.error"] = Module_six_moves_urllib_error(__name__ + ".moves.urllib.error") - - -class Module_six_moves_urllib_request(types.ModuleType): - """Lazy loading of moved objects in six.moves.urllib_request""" - - -_urllib_request_moved_attributes = [ - MovedAttribute("urlopen", "urllib2", "urllib.request"), - MovedAttribute("install_opener", "urllib2", "urllib.request"), - MovedAttribute("build_opener", "urllib2", "urllib.request"), - MovedAttribute("pathname2url", "urllib", "urllib.request"), - MovedAttribute("url2pathname", "urllib", "urllib.request"), - MovedAttribute("getproxies", "urllib", "urllib.request"), - MovedAttribute("Request", "urllib2", "urllib.request"), - MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), - MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), - MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), - MovedAttribute("BaseHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), - MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), - MovedAttribute("FileHandler", "urllib2", "urllib.request"), - MovedAttribute("FTPHandler", "urllib2", "urllib.request"), - MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), - MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), - MovedAttribute("urlretrieve", "urllib", "urllib.request"), - MovedAttribute("urlcleanup", "urllib", "urllib.request"), - MovedAttribute("URLopener", "urllib", "urllib.request"), - MovedAttribute("FancyURLopener", "urllib", "urllib.request"), -] -for attr in _urllib_request_moved_attributes: - setattr(Module_six_moves_urllib_request, attr.name, attr) -del attr - -sys.modules[__name__ + ".moves.urllib_request"] = Module_six_moves_urllib_request(__name__ + ".moves.urllib_request") -sys.modules[__name__ + ".moves.urllib.request"] = Module_six_moves_urllib_request(__name__ + ".moves.urllib.request") - - -class Module_six_moves_urllib_response(types.ModuleType): - """Lazy loading of moved objects in six.moves.urllib_response""" - - -_urllib_response_moved_attributes = [ - MovedAttribute("addbase", "urllib", "urllib.response"), - MovedAttribute("addclosehook", "urllib", "urllib.response"), - MovedAttribute("addinfo", "urllib", "urllib.response"), - MovedAttribute("addinfourl", "urllib", "urllib.response"), -] -for attr in _urllib_response_moved_attributes: - setattr(Module_six_moves_urllib_response, attr.name, attr) -del attr - -sys.modules[__name__ + ".moves.urllib_response"] = Module_six_moves_urllib_response(__name__ + ".moves.urllib_response") -sys.modules[__name__ + ".moves.urllib.response"] = Module_six_moves_urllib_response(__name__ + ".moves.urllib.response") - - -class Module_six_moves_urllib_robotparser(types.ModuleType): - """Lazy loading of moved objects in six.moves.urllib_robotparser""" - - -_urllib_robotparser_moved_attributes = [ - MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), -] -for attr in _urllib_robotparser_moved_attributes: - setattr(Module_six_moves_urllib_robotparser, attr.name, attr) -del attr - -sys.modules[__name__ + ".moves.urllib_robotparser"] = Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib_robotparser") -sys.modules[__name__ + ".moves.urllib.robotparser"] = Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser") - - -class Module_six_moves_urllib(types.ModuleType): - """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" - parse = sys.modules[__name__ + ".moves.urllib_parse"] - error = sys.modules[__name__ + ".moves.urllib_error"] - request = sys.modules[__name__ + ".moves.urllib_request"] - response = sys.modules[__name__ + ".moves.urllib_response"] - robotparser = sys.modules[__name__ + ".moves.urllib_robotparser"] - - -sys.modules[__name__ + ".moves.urllib"] = Module_six_moves_urllib(__name__ + ".moves.urllib") - - -def add_move(move): - """Add an item to six.moves.""" - setattr(_MovedItems, move.name, move) - - -def remove_move(name): - """Remove item from six.moves.""" - try: - delattr(_MovedItems, name) - except AttributeError: - try: - del moves.__dict__[name] - except KeyError: - raise AttributeError("no such move, %r" % (name,)) - - -if PY3: - _meth_func = "__func__" - _meth_self = "__self__" - - _func_closure = "__closure__" - _func_code = "__code__" - _func_defaults = "__defaults__" - _func_globals = "__globals__" - - _iterkeys = "keys" - _itervalues = "values" - _iteritems = "items" - _iterlists = "lists" -else: - _meth_func = "im_func" - _meth_self = "im_self" - - _func_closure = "func_closure" - _func_code = "func_code" - _func_defaults = "func_defaults" - _func_globals = "func_globals" - - _iterkeys = "iterkeys" - _itervalues = "itervalues" - _iteritems = "iteritems" - _iterlists = "iterlists" - - -try: - advance_iterator = next -except NameError: - def advance_iterator(it): - return it.next() -next = advance_iterator - - -try: - callable = callable -except NameError: - def callable(obj): - return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) - - -if PY3: - def get_unbound_function(unbound): - return unbound - - create_bound_method = types.MethodType - - Iterator = object -else: - def get_unbound_function(unbound): - return unbound.im_func - - def create_bound_method(func, obj): - return types.MethodType(func, obj, obj.__class__) - - class Iterator(object): - - def next(self): - return type(self).__next__(self) - - callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") - - -get_method_function = operator.attrgetter(_meth_func) -get_method_self = operator.attrgetter(_meth_self) -get_function_closure = operator.attrgetter(_func_closure) -get_function_code = operator.attrgetter(_func_code) -get_function_defaults = operator.attrgetter(_func_defaults) -get_function_globals = operator.attrgetter(_func_globals) - - -def iterkeys(d, **kw): - """Return an iterator over the keys of a dictionary.""" - return iter(getattr(d, _iterkeys)(**kw)) - -def itervalues(d, **kw): - """Return an iterator over the values of a dictionary.""" - return iter(getattr(d, _itervalues)(**kw)) - -def iteritems(d, **kw): - """Return an iterator over the (key, value) pairs of a dictionary.""" - return iter(getattr(d, _iteritems)(**kw)) - -def iterlists(d, **kw): - """Return an iterator over the (key, [values]) pairs of a dictionary.""" - return iter(getattr(d, _iterlists)(**kw)) - - -if PY3: - def b(s): - return s.encode("latin-1") - def u(s): - return s - unichr = chr - if sys.version_info[1] <= 1: - def int2byte(i): - return bytes((i,)) - else: - # This is about 2x faster than the implementation above on 3.2+ - int2byte = operator.methodcaller("to_bytes", 1, "big") - byte2int = operator.itemgetter(0) - indexbytes = operator.getitem - iterbytes = iter - import io - StringIO = io.StringIO - BytesIO = io.BytesIO -else: - def b(s): - return s - def u(s): - return unicode(s, "unicode_escape") - unichr = unichr - int2byte = chr - def byte2int(bs): - return ord(bs[0]) - def indexbytes(buf, i): - return ord(buf[i]) - def iterbytes(buf): - return (ord(byte) for byte in buf) - import StringIO - StringIO = BytesIO = StringIO.StringIO -_add_doc(b, """Byte literal""") -_add_doc(u, """Text literal""") - - -if PY3: - import builtins - exec_ = getattr(builtins, "exec") - - - def reraise(tp, value, tb=None): - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - - - print_ = getattr(builtins, "print") - del builtins - -else: - def exec_(_code_, _globs_=None, _locs_=None): - """Execute code in a namespace.""" - if _globs_ is None: - frame = sys._getframe(1) - _globs_ = frame.f_globals - if _locs_ is None: - _locs_ = frame.f_locals - del frame - elif _locs_ is None: - _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") - - - exec_("""def reraise(tp, value, tb=None): - raise tp, value, tb -""") - - - def print_(*args, **kwargs): - """The new-style print function.""" - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - def write(data): - if not isinstance(data, basestring): - data = str(data) - fp.write(data) - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) - -_add_doc(reraise, """Reraise an exception.""") - - -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - return meta("NewBase", bases, {}) - -def add_metaclass(metaclass): - """Class decorator for creating a class with a metaclass.""" - def wrapper(cls): - orig_vars = cls.__dict__.copy() - orig_vars.pop('__dict__', None) - orig_vars.pop('__weakref__', None) - for slots_var in orig_vars.get('__slots__', ()): - orig_vars.pop(slots_var) - return metaclass(cls.__name__, cls.__bases__, orig_vars) - return wrapper diff --git a/dev-requirements.txt b/dev-requirements.txt index 589fe11d..25bbe50a 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,5 +1,6 @@ -ipdb==0.8 -ipython==1.2.1 -mock==1.0.1 -nose==1.3.1 -nose-run-line-number==0.0.1 +flake8<4 # Version 4 drops support for Python 2 +ipdb==0.13.9 +ipython +mock<4 # Version 4 drops support for Python 2 +pytest +pytest-cov diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..1548117a --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +testpaths = tests +addopts = + --cov=business_rules + --cov-fail-under=95 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..c694025f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,13 @@ +[bdist_wheel] +# Signal that this project has no C extensions and supports Python 2 and 3 +# https://packaging.python.org/guides/distributing-packages-using-setuptools/#wheels +universal=1 + +[flake8] +exclude = + __pycache__ + .eggs + .git + .tox + *.egg + venv/ diff --git a/setup.py b/setup.py index c092b85e..1124e1f7 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ #! /usr/bin/env python -import setuptools +from setuptools import setup from business_rules import __version__ as version @@ -9,13 +9,26 @@ description = 'Python DSL for setting up business intelligence rules that can be configured without code' -setuptools.setup( - name='business-rules', - version=version, - description='{0}\n\n{1}'.format(description, history), - author='Venmo', - author_email='open-source@venmo.com', - url='https://github.com/venmo/business-rules', - packages=['business_rules'], - license='MIT' +setup( + name='business-rules', + version=version, + description='{0}\n\n{1}'.format(description, history), + author='Venmo', + author_email='open-source@venmo.com', + url='https://github.com/venmo/business-rules', + packages=['business_rules'], + license='MIT', + classifiers=[ + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + ], + python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", + install_requires=[ + "six>=1.16.0", + ], ) diff --git a/tests/__init__.py b/tests/__init__.py index 9e449e63..91a437d0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1,5 @@ -try: - from unittest2 import TestCase -except ImportError: - from unittest import TestCase +from unittest import TestCase -assert TestCase +# Allow us to use Python 3's `assertRaisesRegex` to avoid "DeprecationWarning: Please use assertRaisesRegex instead." +if not hasattr(TestCase, 'assertRaisesRegex'): + TestCase.assertRaisesRegex = TestCase.assertRaisesRegexp diff --git a/tests/test_actions_class.py b/tests/test_actions_class.py index 248247c1..d57a427b 100644 --- a/tests/test_actions_class.py +++ b/tests/test_actions_class.py @@ -1,6 +1,6 @@ from business_rules.actions import BaseActions, rule_action from business_rules.fields import FIELD_TEXT -from . import TestCase +from unittest import TestCase class ActionsClassTests(TestCase): """ Test methods on classes that inherit from BaseActions. @@ -33,14 +33,14 @@ def non_action(self): def test_rule_action_doesnt_allow_unknown_field_types(self): err_string = "Unknown field type blah specified for action some_action"\ " param foo" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): @rule_action(params={'foo': 'blah'}) def some_action(self, foo): pass def test_rule_action_doesnt_allow_unknown_parameter_name(self): err_string = "Unknown parameter name foo specified for action some_action" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): @rule_action(params={'foo': 'blah'}) def some_action(self): pass diff --git a/tests/test_engine_logic.py b/tests/test_engine_logic.py index 095a1b5a..70642fe5 100644 --- a/tests/test_engine_logic.py +++ b/tests/test_engine_logic.py @@ -4,7 +4,7 @@ from business_rules.actions import BaseActions from mock import patch, MagicMock -from . import TestCase +from unittest import TestCase class EngineTests(TestCase): @@ -191,5 +191,5 @@ def test_do_actions(self): def test_do_with_invalid_action(self): actions = [{'name': 'fakeone'}] err_string = "Action fakeone is not defined in class BaseActions" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): engine.do_actions(actions, BaseActions()) diff --git a/tests/test_integration.py b/tests/test_integration.py index 6dbf1ace..008228cf 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -4,7 +4,7 @@ from business_rules.variables import BaseVariables, string_rule_variable, numeric_rule_variable, boolean_rule_variable from business_rules.fields import FIELD_TEXT, FIELD_NUMERIC, FIELD_SELECT -from . import TestCase +from unittest import TestCase class SomeVariables(BaseVariables): @@ -76,7 +76,7 @@ def test_check_incorrect_method_name(self): 'operator': 'equal_to', 'value': 'm'} err_string = 'Variable food is not defined in class SomeVariables' - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): check_condition(condition, SomeVariables()) def test_check_incorrect_operator_name(self): diff --git a/tests/test_operators.py b/tests/test_operators.py index 079369bb..824d4a69 100644 --- a/tests/test_operators.py +++ b/tests/test_operators.py @@ -2,7 +2,7 @@ NumericType, BooleanType, SelectType, SelectMultipleType) -from . import TestCase +from unittest import TestCase from decimal import Decimal import sys @@ -51,7 +51,7 @@ class NumericOperatorTests(TestCase): def test_instantiate(self): err_string = "foo is not a valid numeric type" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): NumericType("foo") def test_numeric_type_validates_and_casts_decimal(self): @@ -83,7 +83,7 @@ def test_numeric_equal_to(self): def test_other_value_not_numeric(self): error_string = "10 is not a valid numeric type" - with self.assertRaisesRegexp(AssertionError, error_string): + with self.assertRaisesRegex(AssertionError, error_string): NumericType(10).equal_to("10") def test_numeric_greater_than(self): @@ -121,10 +121,10 @@ class BooleanOperatorTests(TestCase): def test_instantiate(self): err_string = "foo is not a valid boolean type" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): BooleanType("foo") err_string = "None is not a valid boolean type" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): BooleanType(None) def test_boolean_is_true_and_is_false(self): diff --git a/tests/test_operators_class.py b/tests/test_operators_class.py index 9391a3a3..3509aa8c 100644 --- a/tests/test_operators_class.py +++ b/tests/test_operators_class.py @@ -1,5 +1,5 @@ from business_rules.operators import BaseType, type_operator -from . import TestCase +from unittest import TestCase from mock import MagicMock class OperatorsClassTests(TestCase): diff --git a/tests/test_variables.py b/tests/test_variables.py index 97a9386e..3234e740 100644 --- a/tests/test_variables.py +++ b/tests/test_variables.py @@ -1,4 +1,4 @@ -from . import TestCase +from unittest import TestCase from business_rules.utils import fn_name_to_pretty_label from business_rules.variables import (rule_variable, numeric_rule_variable, @@ -26,7 +26,7 @@ def test_pretty_label(self): def test_rule_variable_requires_instance_of_base_type(self): err_string = "a_string is not instance of BaseType in rule_variable "\ "field_type" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): @rule_variable('a_string') def some_test_function(self): pass diff --git a/tests/test_variables_class.py b/tests/test_variables_class.py index 1a4bf7da..23628dc9 100644 --- a/tests/test_variables_class.py +++ b/tests/test_variables_class.py @@ -1,6 +1,6 @@ from business_rules.variables import BaseVariables, rule_variable from business_rules.operators import StringType -from . import TestCase +from unittest import TestCase class VariablesClassTests(TestCase): """ Test methods on classes that inherit from BaseVariables diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..e5d7ae10 --- /dev/null +++ b/tox.ini @@ -0,0 +1,28 @@ +# Tox (https://tox.readthedocs.io/) is a tool for running tests in multiple virtualenvs. This configuration file helps +# to run the test suite against different combinations of libraries and Python versions. +# To use it locally, "pip install tox" and then run "tox --skip-missing-interpreters" from this directory. + +[tox] +isolated_build = true +minversion = 3.24.3 +envlist = py{27,35,36,37} + +[gh-actions] +# Mapping of Python versions (MAJOR.MINOR) to Tox factors. +# When running Tox inside GitHub Actions, the `tox-gh-actions` plugin automatically: +# 1. Identifies the Python version used to run Tox. +# 2. Determines the corresponding Tox factor for that Python version, based on the `python` mapping below. +# 3. Narrows down the Tox `envlist` to environments that match the factor. +# For more details, please see the `tox-gh-actions` README [0] and architecture documentation [1]. +# [0] https://github.com/ymyzk/tox-gh-actions/tree/v2.8.1 +# [1] https://github.com/ymyzk/tox-gh-actions/blob/v2.8.1/ARCHITECTURE.md +python = + 2.7: py27 + 3.5: py35 + 3.6: py36 + 3.7: py37 + +[testenv] +usedevelop = true +deps = -rdev-requirements.txt +commands = pytest -vv {posargs} From fb426200669dc8da359c1e478853c026f89d96a7 Mon Sep 17 00:00:00 2001 From: Alex Pizarro <80284459+apizarro-paypal@users.noreply.github.com> Date: Fri, 18 Mar 2022 12:09:33 -0400 Subject: [PATCH 02/21] Bump version to 1.1.0 (#77) --- business_rules/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/business_rules/__init__.py b/business_rules/__init__.py index 747404df..1c4af5a6 100644 --- a/business_rules/__init__.py +++ b/business_rules/__init__.py @@ -1,4 +1,4 @@ -__version__ = '1.0.1' +__version__ = '1.1.0' from .engine import run_all from .utils import export_rule_data From b5c1a3cee725ae5cebcd64c19b6165dd2cb4e59e Mon Sep 17 00:00:00 2001 From: Alex Pizarro <80284459+apizarro-paypal@users.noreply.github.com> Date: Fri, 18 Mar 2022 12:35:43 -0400 Subject: [PATCH 03/21] Fix long description and bump to v1.1.1 (#78) --- .gitignore | 1 + HISTORY.md | 26 ++++++++++++++++++++++++++ HISTORY.rst | 14 -------------- README.md | 6 ------ business_rules/__init__.py | 2 +- setup.py | 12 +++++++----- 6 files changed, 35 insertions(+), 26 deletions(-) create mode 100644 HISTORY.md delete mode 100644 HISTORY.rst diff --git a/.gitignore b/.gitignore index 04a74307..8bc7b869 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.py[cod] +.DS_Store # C extensions *.so diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 00000000..13d32dfb --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,26 @@ +History +========= + +## 1.1.1 + +Release date: 2022-3-18 + +- Fix package description and long description + +## 1.1.0 + +Release date: 2022-3-18 + +- Add support for Python 3.5-3.7 + +## 1.0.1 + +Release date: 2016-3-16 + +- Fixes a packaging bug preventing 1.0.0 from being installed on some platforms. + +## 1.0.0 + +Release date: 2016-3-16 + +- Removes caching layer on rule decorator diff --git a/HISTORY.rst b/HISTORY.rst deleted file mode 100644 index 9a682cb6..00000000 --- a/HISTORY.rst +++ /dev/null @@ -1,14 +0,0 @@ -History -------- - -1.0.1 -+++++ -released 2016-3-16 - -- Fixes a packaging bug preventing 1.0.0 from being installed on some platforms. - -1.0.0 -+++++ -released 2016-3-16 - -- Removes caching layer on rule decorator diff --git a/README.md b/README.md index bfb2c04a..f0e95427 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ business-rules ============== -[![Build Status](https://travis-ci.org/venmo/business-rules.svg?branch=master)](https://travis-ci.org/venmo/business-rules) - As a software system grows in complexity and usage, it can become burdensome if every change to the logic/behavior of the system also requires you to write and deploy new code. The goal of this business rules engine is to provide a simple @@ -14,10 +12,6 @@ marketing logic around when certain customers or items are eligible for a discount or to automate emails after users enter a certain state or go through a particular sequence of events. -

- -

- ## Usage ### 1. Define Your set of variables diff --git a/business_rules/__init__.py b/business_rules/__init__.py index 1c4af5a6..348801bb 100644 --- a/business_rules/__init__.py +++ b/business_rules/__init__.py @@ -1,4 +1,4 @@ -__version__ = '1.1.0' +__version__ = '1.1.1' from .engine import run_all from .utils import export_rule_data diff --git a/setup.py b/setup.py index 1124e1f7..68dde442 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,20 @@ #! /usr/bin/env python - from setuptools import setup from business_rules import __version__ as version -with open('HISTORY.rst') as f: - history = f.read() +with open('README.md') as f: + readme = f.read() -description = 'Python DSL for setting up business intelligence rules that can be configured without code' +with open('HISTORY.md') as f: + history = f.read() setup( name='business-rules', version=version, - description='{0}\n\n{1}'.format(description, history), + description='Python DSL for setting up business intelligence rules that can be configured without code', + long_description='{}\n\n{}'.format(readme, history), + long_description_content_type='text/markdown', author='Venmo', author_email='open-source@venmo.com', url='https://github.com/venmo/business-rules', From eb0674d91e9a7ca63c41b17beb5a348c5e1ff70d Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 09:09:31 +0100 Subject: [PATCH 04/21] Switch to poetry --- .circleci/config.yml | 7 +- .travis.yml | 12 -- HISTORY.rst | 6 + business_rules/__init__.py | 2 +- makefile | 8 +- poetry.lock | 259 ++++++++++++++++++++++++++ pyproject.toml | 24 +++ requirements-dev.txt | 8 - requirements.txt | 3 - setup.py | 30 --- tests/operators/test_operators.py | 16 +- tests/operators/test_time_operator.py | 4 +- tests/test_actions_class.py | 4 +- tests/test_engine_logic.py | 2 +- tests/test_integration.py | 6 +- tests/test_variables.py | 2 +- 16 files changed, 314 insertions(+), 79 deletions(-) delete mode 100644 .travis.yml create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements-dev.txt delete mode 100644 requirements.txt delete mode 100644 setup.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 4b5db2b4..f9ea898b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,18 +3,15 @@ version: 2 jobs: build: docker: - - image: circleci/python:2.7 + - image: cimg/python:3.8 steps: - checkout - run: name: Install Dependencies command: | - sudo pip install virtualenv - virtualenv venv - . venv/bin/activate + make install make deps - run: name: Run Tests command: | - . venv/bin/activate make coverage diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2d254379..00000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: python -python: - - "2.6" - - "2.7" - - "pypy" - - "3.2" - - "3.3" -install: - - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then pip install unittest2; fi - - "pip install -e ." -script: - - "nosetests tests" diff --git a/HISTORY.rst b/HISTORY.rst index 9a682cb6..4b7a5e61 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,12 @@ History ------- +2.0.0 ++++++ +Move to Poetry package manager +Support Django 4.2 and Python 3.11 + + 1.0.1 +++++ released 2016-3-16 diff --git a/business_rules/__init__.py b/business_rules/__init__.py index e64d144d..13e1ee63 100644 --- a/business_rules/__init__.py +++ b/business_rules/__init__.py @@ -1,4 +1,4 @@ -__version__ = '1.5.0' +__version__ = '2.0.0' from .engine import run_all, check_conditions_recursively from .utils import export_rule_data, validate_rule_data diff --git a/makefile b/makefile index 0a3847ce..b19b5fd7 100644 --- a/makefile +++ b/makefile @@ -2,12 +2,14 @@ clean: -find . -type f -name "*.pyc" -delete + poetry env remove 3.8 || true + poetry env use 3.8 deps: - pip install -r requirements-dev.txt + poetry install test: - py.test $(pytest_args) + poetry run py.test $(pytest_args) coverage: - py.test --cov-report term-missing --cov=./business_rules $(pytest_args) + poetry run py.test --cov-report term-missing --cov=./business_rules $(pytest_args) diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..9e8d6f1a --- /dev/null +++ b/poetry.lock @@ -0,0 +1,259 @@ +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.2.3" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coverage-7.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e58c0d41d336569d63d1b113bd573db8363bc4146f39444125b7f8060e4e04f5"}, + {file = "coverage-7.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:344e714bd0fe921fc72d97404ebbdbf9127bac0ca1ff66d7b79efc143cf7c0c4"}, + {file = "coverage-7.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974bc90d6f6c1e59ceb1516ab00cf1cdfbb2e555795d49fa9571d611f449bcb2"}, + {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0743b0035d4b0e32bc1df5de70fba3059662ace5b9a2a86a9f894cfe66569013"}, + {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d0391fb4cfc171ce40437f67eb050a340fdbd0f9f49d6353a387f1b7f9dd4fa"}, + {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a42e1eff0ca9a7cb7dc9ecda41dfc7cbc17cb1d02117214be0561bd1134772b"}, + {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be19931a8dcbe6ab464f3339966856996b12a00f9fe53f346ab3be872d03e257"}, + {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72fcae5bcac3333a4cf3b8f34eec99cea1187acd55af723bcbd559adfdcb5535"}, + {file = "coverage-7.2.3-cp310-cp310-win32.whl", hash = "sha256:aeae2aa38395b18106e552833f2a50c27ea0000122bde421c31d11ed7e6f9c91"}, + {file = "coverage-7.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:83957d349838a636e768251c7e9979e899a569794b44c3728eaebd11d848e58e"}, + {file = "coverage-7.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfd393094cd82ceb9b40df4c77976015a314b267d498268a076e940fe7be6b79"}, + {file = "coverage-7.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182eb9ac3f2b4874a1f41b78b87db20b66da6b9cdc32737fbbf4fea0c35b23fc"}, + {file = "coverage-7.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb1e77a9a311346294621be905ea8a2c30d3ad371fc15bb72e98bfcfae532df"}, + {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0f34363e2634deffd390a0fef1aa99168ae9ed2af01af4a1f5865e362f8623"}, + {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55416d7385774285b6e2a5feca0af9652f7f444a4fa3d29d8ab052fafef9d00d"}, + {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06ddd9c0249a0546997fdda5a30fbcb40f23926df0a874a60a8a185bc3a87d93"}, + {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312"}, + {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ea53151d87c52e98133eb8ac78f1206498c015849662ca8dc246255265d9c3c4"}, + {file = "coverage-7.2.3-cp311-cp311-win32.whl", hash = "sha256:8f6c930fd70d91ddee53194e93029e3ef2aabe26725aa3c2753df057e296b925"}, + {file = "coverage-7.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910"}, + {file = "coverage-7.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2317d5ed777bf5a033e83d4f1389fd4ef045763141d8f10eb09a7035cee774c"}, + {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be9824c1c874b73b96288c6d3de793bf7f3a597770205068c6163ea1f326e8b9"}, + {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3b2803e730dc2797a017335827e9da6da0e84c745ce0f552e66400abdfb9a1"}, + {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f69770f5ca1994cb32c38965e95f57504d3aea96b6c024624fdd5bb1aa494a1"}, + {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1127b16220f7bfb3f1049ed4a62d26d81970a723544e8252db0efde853268e21"}, + {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa784405f0c640940595fa0f14064d8e84aff0b0f762fa18393e2760a2cf5841"}, + {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3146b8e16fa60427e03884301bf8209221f5761ac754ee6b267642a2fd354c48"}, + {file = "coverage-7.2.3-cp37-cp37m-win32.whl", hash = "sha256:1fd78b911aea9cec3b7e1e2622c8018d51c0d2bbcf8faaf53c2497eb114911c1"}, + {file = "coverage-7.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f3736a5d34e091b0a611964c6262fd68ca4363df56185902528f0b75dbb9c1f"}, + {file = "coverage-7.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:981b4df72c93e3bc04478153df516d385317628bd9c10be699c93c26ddcca8ab"}, + {file = "coverage-7.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0045f8f23a5fb30b2eb3b8a83664d8dc4fb58faddf8155d7109166adb9f2040"}, + {file = "coverage-7.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f760073fcf8f3d6933178d67754f4f2d4e924e321f4bb0dcef0424ca0215eba1"}, + {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c86bd45d1659b1ae3d0ba1909326b03598affbc9ed71520e0ff8c31a993ad911"}, + {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:172db976ae6327ed4728e2507daf8a4de73c7cc89796483e0a9198fd2e47b462"}, + {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2a3a6146fe9319926e1d477842ca2a63fe99af5ae690b1f5c11e6af074a6b5c"}, + {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f649dd53833b495c3ebd04d6eec58479454a1784987af8afb77540d6c1767abd"}, + {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c4ed4e9f3b123aa403ab424430b426a1992e6f4c8fd3cb56ea520446e04d152"}, + {file = "coverage-7.2.3-cp38-cp38-win32.whl", hash = "sha256:eb0edc3ce9760d2f21637766c3aa04822030e7451981ce569a1b3456b7053f22"}, + {file = "coverage-7.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:63cdeaac4ae85a179a8d6bc09b77b564c096250d759eed343a89d91bce8b6367"}, + {file = "coverage-7.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20d1a2a76bb4eb00e4d36b9699f9b7aba93271c9c29220ad4c6a9581a0320235"}, + {file = "coverage-7.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ea748802cc0de4de92ef8244dd84ffd793bd2e7be784cd8394d557a3c751e21"}, + {file = "coverage-7.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b154aba06df42e4b96fc915512ab39595105f6c483991287021ed95776d934"}, + {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859"}, + {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2e58e45fe53fab81f85474e5d4d226eeab0f27b45aa062856c89389da2f0d9"}, + {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:87ecc7c9a1a9f912e306997ffee020297ccb5ea388421fe62a2a02747e4d5539"}, + {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:387065e420aed3c71b61af7e82c7b6bc1c592f7e3c7a66e9f78dd178699da4fe"}, + {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ea3f5bc91d7d457da7d48c7a732beaf79d0c8131df3ab278e6bba6297e23c6c4"}, + {file = "coverage-7.2.3-cp39-cp39-win32.whl", hash = "sha256:ae7863a1d8db6a014b6f2ff9c1582ab1aad55a6d25bac19710a8df68921b6e30"}, + {file = "coverage-7.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:3f04becd4fcda03c0160d0da9c8f0c246bc78f2f7af0feea1ec0930e7c93fa4a"}, + {file = "coverage-7.2.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:965ee3e782c7892befc25575fa171b521d33798132692df428a09efacaffe8d0"}, + {file = "coverage-7.2.3.tar.gz", hash = "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "exceptiongroup" +version = "1.1.1" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "mock" +version = "5.0.2" +description = "Rolling backport of unittest.mock for all Pythons" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mock-5.0.2-py3-none-any.whl", hash = "sha256:0e0bc5ba78b8db3667ad636d964eb963dc97a59f04c6f6214c5f0e4a8f726c56"}, + {file = "mock-5.0.2.tar.gz", hash = "sha256:06f18d7d65b44428202b145a9a36e99c2ee00d1eb992df0caf881d4664377891"}, +] + +[package.extras] +build = ["blurb", "twine", "wheel"] +docs = ["sphinx"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "nose" +version = "1.3.7" +description = "nose extends unittest to make testing easier" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "nose-1.3.7-py2-none-any.whl", hash = "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a"}, + {file = "nose-1.3.7-py3-none-any.whl", hash = "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac"}, + {file = "nose-1.3.7.tar.gz", hash = "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"}, +] + +[[package]] +name = "nose-run-line-number" +version = "0.0.2" +description = "Nose plugin to run tests by line number" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "nose-run-line-number-0.0.2.tar.gz", hash = "sha256:521ed2d1c4259d7cc0cab84225e63b1d6f7c7582b580263a2b7c19f2591cb1d4"}, +] + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "7.3.1" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, + {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytz" +version = "2023.3" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "e5ecf198eeaaf99a3b645c1d5bfa6b8cbd51d74dc6f51f7333e81a5ad959b0e6" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..3731d5a4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[tool.poetry] +name = "business-rules" +version = "2.0.0" +description = "Python DSL for setting up business intelligence rules that can be configured without code [https://github.com/venmo/business-rules]" +authors = ["venmo "] +readme = "README.md" +packages = [{include = "business_rules"}] +license='MIT' + +[tool.poetry.dependencies] +python = "^3.8" +pytz = "^2023.3" +six = "^1.16.0" + +[tool.poetry.group.dev.dependencies] +mock = "*" +nose = "*" +nose-run-line-number = "*" +pytest = "*" +pytest-cov = "*" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index b20dfcff..00000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,8 +0,0 @@ --r requirements.txt -ipdb==0.10.1 -ipython==5.1 -mock==1.0.1 -nose==1.3.1 -nose-run-line-number==0.0.1 -pytest>=3.0.5 -pytest-cov==2.4.0 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 37161970..00000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -pytz>=2016.10 -typing>=3.6.1 -six diff --git a/setup.py b/setup.py deleted file mode 100644 index 029f3859..00000000 --- a/setup.py +++ /dev/null @@ -1,30 +0,0 @@ -#! /usr/bin/env python - -from __future__ import absolute_import -import setuptools -from setuptools import find_packages - -from business_rules import __version__ as version - -with open('HISTORY.rst') as f: - history = f.read() - -description = 'Python DSL for setting up business intelligence rules that can be configured without code' - -install_requires = [ - 'pytz>=2016.10', - 'typing', - 'six', -] - -setuptools.setup( - name='business-rules', - version=version, - description='{0}\n\n{1}'.format(description, history), - author='Venmo', - author_email='open-source@venmo.com', - url='https://github.com/venmo/business-rules', - packages=find_packages(exclude=['tests']), - license='MIT', - install_requires=install_requires -) diff --git a/tests/operators/test_operators.py b/tests/operators/test_operators.py index 0a9d914a..23960ad0 100644 --- a/tests/operators/test_operators.py +++ b/tests/operators/test_operators.py @@ -71,7 +71,7 @@ def test_non_empty(self): class NumericOperatorTests(TestCase): def test_instantiate(self): err_string = "foo is not a valid numeric type" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): NumericType("foo") def test_numeric_type_validates_and_casts_decimal(self): @@ -103,7 +103,7 @@ def test_numeric_equal_to(self): def test_other_value_not_numeric(self): error_string = "10 is not a valid numeric type" - with self.assertRaisesRegexp(AssertionError, error_string): + with self.assertRaisesRegex(AssertionError, error_string): NumericType(10).equal_to("10") def test_numeric_greater_than(self): @@ -140,10 +140,10 @@ def test_numeric_less_than_or_equal_to(self): class BooleanOperatorTests(TestCase): def test_instantiate(self): err_string = "foo is not a valid boolean type" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): BooleanType("foo") err_string = "None is not a valid boolean type" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): BooleanType(None) def test_boolean_is_true_and_is_false(self): @@ -247,7 +247,7 @@ def setUp(self): def test_instantiate(self): err_string = "foo is not a valid datetime type" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): DateTimeType("foo") def test_datetime_type_validates_and_cast_datetime(self): @@ -281,7 +281,7 @@ def test_datetime_equal_to(self): def test_other_value_not_datetime(self): error_string = "2016-10 is not a valid datetime type" - with self.assertRaisesRegexp(AssertionError, error_string): + with self.assertRaisesRegex(AssertionError, error_string): DateTimeType(self.TEST_DATE).equal_to("2016-10") def datetime_after_than_asserts(self, datetime_type): @@ -379,7 +379,7 @@ def setUp(self): def test_instantiate(self): err_string = "foo is not a valid time type" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): TimeType("foo") def test_time_type_validates_and_cast_time(self): @@ -403,7 +403,7 @@ def test_time_equal_to(self): def test_other_value_not_time(self): error_string = "2016-10 is not a valid time type" - with self.assertRaisesRegexp(AssertionError, error_string): + with self.assertRaisesRegex(AssertionError, error_string): TimeType(self.TEST_TIME_NO_SECONDS).equal_to("2016-10") def time_after_than_asserts(self, time_type): diff --git a/tests/operators/test_time_operator.py b/tests/operators/test_time_operator.py index 67193cce..f34c8a60 100644 --- a/tests/operators/test_time_operator.py +++ b/tests/operators/test_time_operator.py @@ -20,7 +20,7 @@ def setUp(self): def test_instantiate(self): err_string = "foo is not a valid time type" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): TimeType("foo") def test_time_type_validates_and_cast_time(self): @@ -38,7 +38,7 @@ def test_time_equal_to(self): def test_other_value_not_time(self): error_string = "2016-10 is not a valid time type" - with self.assertRaisesRegexp(AssertionError, error_string): + with self.assertRaisesRegex(AssertionError, error_string): TimeType(self.TEST_TIME).equal_to("2016-10") def test_time_after_than(self): diff --git a/tests/test_actions_class.py b/tests/test_actions_class.py index a7a52941..5c66a98e 100644 --- a/tests/test_actions_class.py +++ b/tests/test_actions_class.py @@ -38,14 +38,14 @@ def non_action(self): def test_rule_action_doesnt_allow_unknown_field_types(self): err_string = "Unknown field type blah specified for action some_action" \ " param foo" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): @rule_action(params={'foo': 'blah'}) def some_action(self, foo): pass def test_rule_action_doesnt_allow_unknown_parameter_name(self): err_string = "Unknown parameter name foo specified for action some_action" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): @rule_action(params={'foo': 'blah'}) def some_action(self): pass diff --git a/tests/test_engine_logic.py b/tests/test_engine_logic.py index 627dc423..02ca6886 100644 --- a/tests/test_engine_logic.py +++ b/tests/test_engine_logic.py @@ -292,7 +292,7 @@ def test_do_with_invalid_action(self): checked_conditions_results = [(True, 'condition_name', 'operator_name', 'condition_value')] - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): engine.do_actions(actions, BaseActions(), checked_conditions_results, rule) def test_do_with_parameter_with_default_value(self): diff --git a/tests/test_integration.py b/tests/test_integration.py index 56aea91b..8540863f 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -146,7 +146,7 @@ def test_check_incorrect_method_name(self): err_string = 'Variable food is not defined in class SomeVariables' - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): check_condition(condition, SomeVariables(), rule) def test_check_incorrect_operator_name(self): @@ -177,7 +177,7 @@ def test_check_missing_params(self): err_string = 'Missing parameters x for variable x_plus_one' - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): check_condition(condition, SomeVariables(), rule) def test_check_invalid_params(self): @@ -194,7 +194,7 @@ def test_check_invalid_params(self): err_string = 'Invalid parameters y for variable x_plus_one' - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): check_condition(condition, SomeVariables(), rule) def test_variable_received_rules(self): diff --git a/tests/test_variables.py b/tests/test_variables.py index 0e7425ab..c99ca30d 100644 --- a/tests/test_variables.py +++ b/tests/test_variables.py @@ -36,7 +36,7 @@ def test_pretty_label(self): def test_rule_variable_requires_instance_of_base_type(self): err_string = "a_string is not instance of BaseType in rule_variable " \ "field_type" - with self.assertRaisesRegexp(AssertionError, err_string): + with self.assertRaisesRegex(AssertionError, err_string): @rule_variable('a_string') def some_test_function(self): pass From d67b327d6788d00edcdf8916da98580eef3610bc Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 09:25:54 +0100 Subject: [PATCH 05/21] fix CircleCI issue --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f9ea898b..b1c11d99 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ jobs: - run: name: Install Dependencies command: | - make install + make clean make deps - run: name: Run Tests From 42c387ec91c021233a9f2b4f698222553dd44d01 Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 09:30:05 +0100 Subject: [PATCH 06/21] fix CircleCI issue --- .circleci/config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b1c11d99..2772f79d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,8 +10,10 @@ jobs: name: Install Dependencies command: | make clean - make deps + poetry install --no-ansi --no-interaction - run: name: Run Tests command: | make coverage + - store_test_results: + path: . From 9e134573e0205e922d10b7abb247958e09058589 Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 09:34:26 +0100 Subject: [PATCH 07/21] Add test results for circleci --- .gitignore | 1 + makefile | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d5f52038..c3840a5b 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ pip-log.txt .coverage .tox nosetests.xml +test-results/ # Translations *.mo diff --git a/makefile b/makefile index b19b5fd7..f957d922 100644 --- a/makefile +++ b/makefile @@ -12,4 +12,5 @@ test: poetry run py.test $(pytest_args) coverage: - poetry run py.test --cov-report term-missing --cov=./business_rules $(pytest_args) + mkdir -p test-results + poetry run py.test --junitxml=test-results/junit.xml --cov-report term-missing --cov=./business_rules $(pytest_args) From 5c09838ba7d7165bef59b4be44daef42dcf9010a Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 09:37:43 +0100 Subject: [PATCH 08/21] set small resource class --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2772f79d..ce05184f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,6 +2,7 @@ version: 2 jobs: build: + resource_class: small docker: - image: cimg/python:3.8 steps: From 7cb3c3365e3f87eefa0497936d02e2588c4df0de Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 09:47:42 +0100 Subject: [PATCH 09/21] generate code coverage html and store as artifact --- .circleci/config.yml | 4 +++- makefile | 1 + poetry.lock | 4 ++-- pyproject.toml | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ce05184f..0c1fffdb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,5 +16,7 @@ jobs: name: Run Tests command: | make coverage + - store_artifacts: + path: htmlcov - store_test_results: - path: . + path: ./test-results diff --git a/makefile b/makefile index f957d922..b89a96fb 100644 --- a/makefile +++ b/makefile @@ -14,3 +14,4 @@ test: coverage: mkdir -p test-results poetry run py.test --junitxml=test-results/junit.xml --cov-report term-missing --cov=./business_rules $(pytest_args) + poetry run coverage html # open htmlcov/index.html in a browser diff --git a/poetry.lock b/poetry.lock index 9e8d6f1a..c9397f06 100644 --- a/poetry.lock +++ b/poetry.lock @@ -16,7 +16,7 @@ files = [ name = "coverage" version = "7.2.3" description = "Code coverage measurement for Python" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -256,4 +256,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "e5ecf198eeaaf99a3b645c1d5bfa6b8cbd51d74dc6f51f7333e81a5ad959b0e6" +content-hash = "7dbd69208e7912f84419dc422c864fa5187888230f248231a34720636dbdbd6b" diff --git a/pyproject.toml b/pyproject.toml index 3731d5a4..e8f537c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ license='MIT' python = "^3.8" pytz = "^2023.3" six = "^1.16.0" +coverage = "^7.2.3" [tool.poetry.group.dev.dependencies] mock = "*" From 7c8d4afc30f791c8e3499bd5def685cac0b4d580 Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 09:48:50 +0100 Subject: [PATCH 10/21] fix indentation --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0c1fffdb..c3ad3b26 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,6 +17,6 @@ jobs: command: | make coverage - store_artifacts: - path: htmlcov + path: htmlcov - store_test_results: path: ./test-results From 6d31b239d59b33ee38d3e91a901b6e249bd18c05 Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 10:02:04 +0100 Subject: [PATCH 11/21] remove "six" package dependency Move to python 3.12. Oooh shiny! --- .circleci/config.yml | 2 +- business_rules/operators.py | 11 +++--- business_rules/utils.py | 4 +-- poetry.lock | 50 ++------------------------- pyproject.toml | 5 ++- tests/operators/test_time_operator.py | 2 +- 6 files changed, 12 insertions(+), 62 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c3ad3b26..190b6c5e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ jobs: build: resource_class: small docker: - - image: cimg/python:3.8 + - image: cimg/python:3.12 steps: - checkout - run: diff --git a/business_rules/operators.py b/business_rules/operators.py index 17de63b2..0c5d5a94 100644 --- a/business_rules/operators.py +++ b/business_rules/operators.py @@ -1,13 +1,10 @@ from __future__ import absolute_import -import calendar import inspect import re from datetime import date, datetime, time from decimal import Decimal from functools import wraps -from six import integer_types, string_types - from .fields import (FIELD_DATETIME, FIELD_NO_INPUT, FIELD_NUMERIC, FIELD_SELECT, FIELD_SELECT_MULTIPLE, FIELD_TEXT, FIELD_TIME) @@ -70,7 +67,7 @@ class StringType(BaseType): def _assert_valid_value_and_cast(self, value): value = value or "" - if not isinstance(value, string_types): + if not isinstance(value, str): raise AssertionError("{0} is not a valid string type.". format(value)) return value @@ -115,7 +112,7 @@ def _assert_valid_value_and_cast(value): if isinstance(value, float): # In python 2.6, casting float to Decimal doesn't work return float_to_decimal(value) - if isinstance(value, integer_types): + if isinstance(value, int): return Decimal(value) if isinstance(value, Decimal): return value @@ -175,8 +172,8 @@ def _assert_valid_value_and_cast(self, value): @staticmethod def _case_insensitive_equal_to(value_from_list, other_value): - if isinstance(value_from_list, string_types) and \ - isinstance(other_value, string_types): + if isinstance(value_from_list, str) and \ + isinstance(other_value, str): return value_from_list.lower() == other_value.lower() else: return value_from_list == other_value diff --git a/business_rules/utils.py b/business_rules/utils.py index e18eb385..e4806f89 100644 --- a/business_rules/utils.py +++ b/business_rules/utils.py @@ -208,8 +208,6 @@ def validate_conditions(input_conditions, rule_schema): """ Recursively check all levels of input conditions """ - import six - if isinstance(input_conditions, list): for condition in input_conditions: validate_conditions(condition, rule_schema) @@ -219,7 +217,7 @@ def validate_conditions(input_conditions, rule_schema): if len(keys) > 1: raise AssertionError('Expected ONE of "any" or "all" but found {}'.format(keys)) else: - for _, v in six.iteritems(input_conditions): + for _, v in input_conditions.items(): validate_conditions(v, rule_schema) else: validate_condition(input_conditions, variables, rule_schema) diff --git a/poetry.lock b/poetry.lock index c9397f06..950964c8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -16,7 +16,7 @@ files = [ name = "coverage" version = "7.2.3" description = "Code coverage measurement for Python" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -73,27 +73,9 @@ files = [ {file = "coverage-7.2.3.tar.gz", hash = "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259"}, ] -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - [package.extras] toml = ["tomli"] -[[package]] -name = "exceptiongroup" -version = "1.1.1" -description = "Backport of PEP 654 (exception groups)" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, - {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, -] - -[package.extras] -test = ["pytest (>=6)"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -189,11 +171,9 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] @@ -229,31 +209,7 @@ files = [ {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - [metadata] lock-version = "2.0" -python-versions = "^3.8" -content-hash = "7dbd69208e7912f84419dc422c864fa5187888230f248231a34720636dbdbd6b" +python-versions = "^3.12" +content-hash = "72cce9a6b44dc20a20e8a10bf2f814620f35678613f78f7557a088cad15662ba" diff --git a/pyproject.toml b/pyproject.toml index e8f537c1..24380ce3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,10 +8,8 @@ packages = [{include = "business_rules"}] license='MIT' [tool.poetry.dependencies] -python = "^3.8" +python = "^3.12" pytz = "^2023.3" -six = "^1.16.0" -coverage = "^7.2.3" [tool.poetry.group.dev.dependencies] mock = "*" @@ -19,6 +17,7 @@ nose = "*" nose-run-line-number = "*" pytest = "*" pytest-cov = "*" +coverage = "^7.2.3" [build-system] requires = ["poetry-core"] diff --git a/tests/operators/test_time_operator.py b/tests/operators/test_time_operator.py index f34c8a60..df0a43ae 100644 --- a/tests/operators/test_time_operator.py +++ b/tests/operators/test_time_operator.py @@ -1,5 +1,5 @@ from __future__ import absolute_import -from datetime import time, datetime, timedelta +from datetime import time, datetime from business_rules.operators import TimeType from .. import TestCase From 80690b6d45834e37a80a97088cb06279c1710e71 Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 10:03:13 +0100 Subject: [PATCH 12/21] update makefile to use python 3.12 poetry environment --- makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/makefile b/makefile index b89a96fb..b89b4b4b 100644 --- a/makefile +++ b/makefile @@ -2,8 +2,8 @@ clean: -find . -type f -name "*.pyc" -delete - poetry env remove 3.8 || true - poetry env use 3.8 + poetry env remove 3.12 || true + poetry env use 3.12 deps: poetry install From b6d125614a1eb3dc13fa6490f528dc93bf170bb8 Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 10:05:52 +0100 Subject: [PATCH 13/21] CircleCI doesn't support python 3.12 yet :-( --- .circleci/config.yml | 2 +- makefile | 4 ++-- poetry.lock | 4 ++-- pyproject.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 190b6c5e..2dc85f37 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ jobs: build: resource_class: small docker: - - image: cimg/python:3.12 + - image: cimg/python:3.11 steps: - checkout - run: diff --git a/makefile b/makefile index b89b4b4b..05e8da40 100644 --- a/makefile +++ b/makefile @@ -2,8 +2,8 @@ clean: -find . -type f -name "*.pyc" -delete - poetry env remove 3.12 || true - poetry env use 3.12 + poetry env remove 3.11 || true + poetry env use 3.11 deps: poetry install diff --git a/poetry.lock b/poetry.lock index 950964c8..c65ced70 100644 --- a/poetry.lock +++ b/poetry.lock @@ -211,5 +211,5 @@ files = [ [metadata] lock-version = "2.0" -python-versions = "^3.12" -content-hash = "72cce9a6b44dc20a20e8a10bf2f814620f35678613f78f7557a088cad15662ba" +python-versions = ">=3.11" +content-hash = "3cced204d1e5f9b2ffc60f97d15de47811b249456a87f9f09bd07b8d65828324" diff --git a/pyproject.toml b/pyproject.toml index 24380ce3..6b01a71d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ packages = [{include = "business_rules"}] license='MIT' [tool.poetry.dependencies] -python = "^3.12" +python = ">=3.11" pytz = "^2023.3" [tool.poetry.group.dev.dependencies] From a6beaa8bec5f9c7ad6bcf3ebe2c51fca51a5c78b Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 15:17:50 +0100 Subject: [PATCH 14/21] fix tox.ini --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1d123a5a..41b6002a 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] isolated_build = true minversion = 3.24.3 -envlist = py{27,35,36,37, 38, 39, 310, 311} +envlist = py{37,38,39,310,311} [gh-actions] # Mapping of Python versions (MAJOR.MINOR) to Tox factors. From 4d8a8ce02f30dc4ad100e4a0d351483248e334ce Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 15:22:38 +0100 Subject: [PATCH 15/21] update github action for running tox. --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index d0f5df00..434d4154 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["2.7", "3.5", "3.6", "3.7"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v2 From d425823c7a880f03b4f6b5371aeace7587981966 Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 15:25:07 +0100 Subject: [PATCH 16/21] fix supported python version so we can run tox under github properly. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 76df3b9b..32d33a9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ packages = [{include = "business_rules"}] license='MIT' [tool.poetry.dependencies] -python = ">=3.11" +python = ">=3.7" pytz = "^2023.3" [tool.poetry.group.dev.dependencies] From 6026875666ea8fd3654a1646786671f3330314f5 Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 15:30:14 +0100 Subject: [PATCH 17/21] fix poetry environment in tox.ini when calling pytest --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 41b6002a..8e65f1c0 100644 --- a/tox.ini +++ b/tox.ini @@ -26,4 +26,4 @@ python = [testenv] usedevelop = true deps = pytest -commands = pytest -vv {posargs} +commands = poetry run pytest -vv {posargs} From 17748f3b7812b258f1220ad5055d88419eb07ad1 Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 15:43:04 +0100 Subject: [PATCH 18/21] fix tox in github --- .github/workflows/tests.yaml | 8 +- poetry.lock | 232 ++++++++++++++++++++++++++++++++++- pyproject.toml | 6 +- tox.ini | 11 +- 4 files changed, 247 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 434d4154..0af2bf77 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v2 @@ -24,8 +24,8 @@ jobs: - name: Install dependencies run: | - python -m pip install --upgrade setuptools pip wheel - python -m pip install "tox<4" "tox-gh-actions~=2.8" + curl -sSL https://install.python-poetry.org | python3 - + poetry install -v --no-root --with dev - name: Test with tox - run: tox -vv + run: poetry run tox -vv diff --git a/poetry.lock b/poetry.lock index c65ced70..9c290cf0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,29 @@ # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. +[[package]] +name = "cachetools" +version = "5.3.0" +description = "Extensible memoizing collections and decorators" +category = "dev" +optional = false +python-versions = "~=3.7" +files = [ + {file = "cachetools-5.3.0-py3-none-any.whl", hash = "sha256:429e1a1e845c008ea6c85aa35d4b98b65d6a9763eeef3e37e92728a12d1de9d4"}, + {file = "cachetools-5.3.0.tar.gz", hash = "sha256:13dfddc7b8df938c21a940dfa6557ce6e94a2f1cdfa58eb90c805721d58f2c14"}, +] + +[[package]] +name = "chardet" +version = "5.1.0" +description = "Universal encoding detector for Python 3" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "chardet-5.1.0-py3-none-any.whl", hash = "sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9"}, + {file = "chardet-5.1.0.tar.gz", hash = "sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5"}, +] + [[package]] name = "colorama" version = "0.4.6" @@ -73,9 +97,76 @@ files = [ {file = "coverage-7.2.3.tar.gz", hash = "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259"}, ] +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + [package.extras] toml = ["tomli"] +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.1" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "filelock" +version = "3.12.0" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "filelock-3.12.0-py3-none-any.whl", hash = "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9"}, + {file = "filelock-3.12.0.tar.gz", hash = "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718"}, +] + +[package.extras] +docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "importlib-metadata" +version = "6.6.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"}, + {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"}, +] + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -141,6 +232,25 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "platformdirs" +version = "3.4.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.4.0-py3-none-any.whl", hash = "sha256:01437886022decaf285d8972f9526397bfae2ac55480ed372ed6d9eca048870a"}, + {file = "platformdirs-3.4.0.tar.gz", hash = "sha256:a5e1536e5ea4b1c238a1364da17ff2993d5bd28e15600c2c8224008aff6bbcad"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.5", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + [[package]] name = "pluggy" version = "1.0.0" @@ -153,10 +263,33 @@ files = [ {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pyproject-api" +version = "1.5.1" +description = "API to interact with the python pyproject.toml based projects" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyproject_api-1.5.1-py3-none-any.whl", hash = "sha256:4698a3777c2e0f6b624f8a4599131e2a25376d90fe8d146d7ac74c67c6f97c43"}, + {file = "pyproject_api-1.5.1.tar.gz", hash = "sha256:435f46547a9ff22cf4208ee274fca3e2869aeb062a4834adfc99a4dd64af3cf9"}, +] + +[package.dependencies] +packaging = ">=23" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["furo (>=2022.12.7)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] +testing = ["covdefaults (>=2.2.2)", "importlib-metadata (>=6)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "virtualenv (>=20.17.1)", "wheel (>=0.38.4)"] + [[package]] name = "pytest" version = "7.3.1" @@ -171,9 +304,12 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] @@ -209,7 +345,99 @@ files = [ {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tox" +version = "4.5.1" +description = "tox is a generic virtualenv management and test command line tool" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tox-4.5.1-py3-none-any.whl", hash = "sha256:d25a2e6cb261adc489604fafd76cd689efeadfa79709965e965668d6d3f63046"}, + {file = "tox-4.5.1.tar.gz", hash = "sha256:5a2eac5fb816779dfdf5cb00fecbc27eb0524e4626626bb1de84747b24cacc56"}, +] + +[package.dependencies] +cachetools = ">=5.3" +chardet = ">=5.1" +colorama = ">=0.4.6" +filelock = ">=3.11" +importlib-metadata = {version = ">=6.4.1", markers = "python_version < \"3.8\""} +packaging = ">=23.1" +platformdirs = ">=3.2" +pluggy = ">=1" +pyproject-api = ">=1.5.1" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.5", markers = "python_version < \"3.8\""} +virtualenv = ">=20.21" + +[package.extras] +docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-argparse-cli (>=1.11)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2022.1.2b11)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] +testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "devpi-process (>=0.3)", "diff-cover (>=7.5)", "distlib (>=0.3.6)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.14)", "psutil (>=5.9.4)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-xdist (>=3.2.1)", "re-assert (>=1.1)", "time-machine (>=2.9)", "wheel (>=0.40)"] + +[[package]] +name = "typing-extensions" +version = "4.5.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, +] + +[[package]] +name = "virtualenv" +version = "20.22.0" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.22.0-py3-none-any.whl", hash = "sha256:48fd3b907b5149c5aab7c23d9790bea4cac6bc6b150af8635febc4cfeab1275a"}, + {file = "virtualenv-20.22.0.tar.gz", hash = "sha256:278753c47aaef1a0f14e6db8a4c5e1e040e90aea654d0fc1dc7e0d8a42616cc3"}, +] + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.11,<4" +importlib-metadata = {version = ">=6.4.1", markers = "python_version < \"3.8\""} +platformdirs = ">=3.2,<4" + +[package.extras] +docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + [metadata] lock-version = "2.0" -python-versions = ">=3.11" -content-hash = "3cced204d1e5f9b2ffc60f97d15de47811b249456a87f9f09bd07b8d65828324" +python-versions = ">=3.7,<4.0" +content-hash = "c36a1673d6d698c7ad45f256b4a254427861286c1a400d49f20b937c857f65f8" diff --git a/pyproject.toml b/pyproject.toml index 32d33a9b..13e97655 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,8 +22,11 @@ readme = "README.md" packages = [{include = "business_rules"}] license='MIT' +[tool.poetry.extras] +test = ["pytest", "pytest-cov"] + [tool.poetry.dependencies] -python = ">=3.7" +python = ">=3.7,<4.0" pytz = "^2023.3" [tool.poetry.group.dev.dependencies] @@ -33,6 +36,7 @@ nose-run-line-number = "*" pytest = "*" pytest-cov = "*" coverage = "^7.2.3" +tox = "^4.5.1" [build-system] requires = ["poetry-core"] diff --git a/tox.ini b/tox.ini index 8e65f1c0..a79479d8 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] isolated_build = true minversion = 3.24.3 -envlist = py{37,38,39,310,311} +envlist = py{37,38,39,310,311,312} [gh-actions] # Mapping of Python versions (MAJOR.MINOR) to Tox factors. @@ -22,8 +22,13 @@ python = 3.9: py39 3.10: py310 3.11: py311 + 3.12: py312 [testenv] +allowlist_externals = + pytest usedevelop = true -deps = pytest -commands = poetry run pytest -vv {posargs} +extras = + test +commands = + pytest -vv {posargs} From 5a8c7f3293d9c0da1b876393379e33b8a1a75019 Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 16:27:54 +0100 Subject: [PATCH 19/21] update actions to V3 --- .github/workflows/tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 0af2bf77..15f512fb 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -15,10 +15,10 @@ jobs: matrix: python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} From 842450132976b151001912683fee38ce7c120432 Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 16:29:53 +0100 Subject: [PATCH 20/21] update setup-python to V4 --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 15f512fb..6ab2fd5c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} From 4d0ba95c7c29be2661670440bc6706dcd71b5675 Mon Sep 17 00:00:00 2001 From: Del Hyman-Jones Date: Thu, 27 Apr 2023 16:32:56 +0100 Subject: [PATCH 21/21] Python 3.12 not supported by setup-python action it seems --- .github/workflows/tests.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 6ab2fd5c..e1a70dfc 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -13,7 +13,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + # Python 3.12 is not working yet at time of writing this. + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3