diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..58423b9 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + # manually triggered + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + env: + UV_PYTHON: ${{ matrix.python-version }} + + steps: + - name: Checkout the repository + uses: actions/checkout@main + - name: Install the default version of uv + id: setup-uv + uses: astral-sh/setup-uv@v3 + - name: Print the installed version + run: echo "Installed uv version is ${{ steps.setup-uv.outputs.uv-version }}" + - name: Install Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + + - name: Tests + run: | + uv venv + uv pip install ".[testing]" + uv run ruff format . --check + uv run ruff check . + .venv/bin/pytest -x tests --cov=confetti --cov-report=html diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9f71c79..0000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: python -sudo: false -python: -- 2.7 -- 3.5 -- 3.6 -install: -- pip install -e . -script: pytest tests -deploy: - provider: pypi - user: vmalloc - password: - secure: LB2D3QUQYomZ7yIwtTQSikuDNFrBLU9RFQ/9bwXxBiMBeMyrT4rpL5XOon8z0LvrsHlzmEAezf25qXmAqUO0LpsbhVXqAEcGfFrxGjOFZxl1eIj3HjK/8pBgR0VRiNYB/zbe/ll//4dy30qMqvRQJPnLKGOz3MkDbdZrdJDDKic= - on: - tags: true - repo: getslash/confetti - python: '3.6' diff --git a/Makefile b/Makefile index 24a4b17..ec27fff 100644 --- a/Makefile +++ b/Makefile @@ -4,21 +4,12 @@ detox-test: detox test: env - .env/bin/py.test tests + .venv/bin/pytest -x tests --cov=confetti --cov-report=html -coverage-test: env - .env/bin/coverage run .env/bin/py.test -w tests - -env: .env/.up-to-date - -.env/.up-to-date: setup.py Makefile - virtualenv .env - .env/bin/pip install -e . - .env/bin/pip install Sphinx==1.1.3 releases pytest - touch .env/.up-to-date +env: + uv venv + uv pip install -e ".[testing]" doc: env - .env/bin/python setup.py build_sphinx - -.PHONY: doc + .venv/bin/sphinx-build -a -W -E doc build/sphinx/html diff --git a/confetti/__version__.py b/confetti/__version__.py index 43b48b6..a9686e0 100644 --- a/confetti/__version__.py +++ b/confetti/__version__.py @@ -1 +1,3 @@ -__version__ = "2.5.3" +from importlib.metadata import distribution + +__version__ = distribution("confetti").version diff --git a/confetti/config.py b/confetti/config.py index c7966fc..b7d9d8d 100644 --- a/confetti/config.py +++ b/confetti/config.py @@ -5,7 +5,6 @@ from sentinels import NOTHING from . import exceptions -from .python3_compat import iteritems, string_types, itervalues from .ref import Ref from .utils import coerce_leaf_value @@ -45,7 +44,7 @@ def mark_clean(self): while stack: root = stack.pop() if isinstance(root, dict): - stack.extend(itervalues(root)) + stack.extend(iter(root.values())) elif isinstance(root, Config): stack.extend(root.itervalues()) @@ -64,7 +63,7 @@ def _init_value(self, value): def _fix_dictionary_value(self): to_replace = [] - for k, v in iteritems(self._value): + for k, v in self._value.items(): if isinstance(v, dict): to_replace.append((k, Config(v, parent=self))) for k, v in to_replace: @@ -199,10 +198,10 @@ def extend(self, conf=None, **kw): def _extend_from_conf(self, conf): conf = dict((key, conf.get_config(key)) for key in conf.keys()) - for key, value in iteritems(conf): + for key, value in conf.items(): if key in self._value: self.get_config(key)._verify_config_paths(value) - for key, value in iteritems(conf): + for key, value in conf.items(): self._value[key] = value def _verify_config_paths(self, conf): @@ -233,7 +232,7 @@ def _verify_config_paths(self, conf): self.get_config(k)._verify_config_paths(conf._value[k]) def _extend_from_dict(self, d): - for key, value in iteritems(d): + for key, value in d.items(): if isinstance(value, dict): if key not in self._value: self._value[key] = {} @@ -243,7 +242,7 @@ def _extend_from_dict(self, d): def update(self, conf): conf = dict((key, conf.get_config(key)) for key in conf.keys()) - for key, value in iteritems(conf): + for key, value in conf.items(): if not value.is_leaf(): if key not in self._value: self._value[key] = {} @@ -258,7 +257,7 @@ def keys(self): return self._value.keys() def itervalues(self): - return itervalues(self._value) + return iter(self._value.values()) @classmethod def from_filename(cls, filename, namespace=None): @@ -350,7 +349,7 @@ def assign_path(self, path, value, deduce_type=False, default_type=None): 3 """ config = self.get_config(path) - if deduce_type and isinstance(value, string_types): + if deduce_type and isinstance(value, str): leaf = self.get_path(path) value = coerce_leaf_value(path, value, leaf, default_type) @@ -375,7 +374,6 @@ def __repr__(self): class ConfigProxy(object): - def __init__(self, conf): super(ConfigProxy, self).__init__() self._conf = conf @@ -427,7 +425,7 @@ def _set_state(config, state): assert isinstance(config, Config) for key in set(config.keys()) - set(state): config.pop(key) - for key, value in iteritems(state): + for key, value in state.items(): if isinstance(value, dict): _set_state(config[key], value) else: diff --git a/confetti/metadata.py b/confetti/metadata.py index db78204..f2b01ed 100644 --- a/confetti/metadata.py +++ b/confetti/metadata.py @@ -2,7 +2,6 @@ class Metadata(object): - def __init__(self, **kwargs): super(Metadata, self).__init__() self.metadata = kwargs diff --git a/confetti/python3_compat.py b/confetti/python3_compat.py deleted file mode 100644 index c167447..0000000 --- a/confetti/python3_compat.py +++ /dev/null @@ -1,27 +0,0 @@ -import platform -from types import MethodType - -IS_PY3 = platform.python_version() >= "3" - -if IS_PY3: - iteritems = lambda d: iter(d.items()) # not dict.items!!! See above - itervalues = lambda d: iter(d.values()) - - create_instance_method = MethodType - basestring = str - string_types = (basestring,) - from functools import reduce -else: - iteritems = ( - lambda d: d.iteritems() - ) # not dict.iteritems!!! we support ordered dicts as well - itervalues = lambda d: d.itervalues() - - from __builtin__ import basestring - - string_types = (str,) - from __builtin__ import reduce - - -def items_list(dictionary): - return list(iteritems(dictionary)) diff --git a/confetti/ref.py b/confetti/ref.py index 98dbccc..c708ac3 100644 --- a/confetti/ref.py +++ b/confetti/ref.py @@ -2,7 +2,6 @@ class Ref(object): - def __init__(self, target, filter=None): super(Ref, self).__init__() self._target = target diff --git a/doc/conf.py b/doc/conf.py index dd0eb4e..7441d6c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -11,7 +11,7 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +# import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..00d0786 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,37 @@ +[build-system] +requires = ["hatchling>=0.25.1", "hatch-vcs"] +build-backend = "hatchling.build" + +[project] +name = "confetti" +description = "Generic configuration mechanism" +readme = "README.rst" +requires-python = ">=3.8" +license = { text = "BSD 3-Clause License" } + +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +dependencies = ["sentinels>=0.0.5"] +dynamic = ["version"] + +authors = [{ name = "Rotem Yaari", email = "vmalloc@gmail.com" }] + +[project.urls] +"Homepage" = "https://github.com/getslash/confetti" + +[project.optional-dependencies] +testing = ["pytest", "pytest-cov", "ruff"] + +[tool.hatch.version] +source = "vcs" + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F401"] diff --git a/setup.py b/setup.py deleted file mode 100644 index 6bf1516..0000000 --- a/setup.py +++ /dev/null @@ -1,35 +0,0 @@ -import os -import platform -import itertools -from setuptools import setup, find_packages - -with open( - os.path.join(os.path.dirname(__file__), "confetti", "__version__.py") -) as version_file: - exec(version_file.read()) - -_REQUIREMENTS = ["sentinels>=0.0.5", "six"] -if platform.python_version() < "2.7": - _REQUIREMENTS.append("unittest2") - -setup( - name="confetti", - classifiers=[ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - ], - description="Generic configuration mechanism", - license="BSD", - author="Rotem Yaari", - author_email="vmalloc@gmail.com", - url="https://github.com/vmalloc/confetti", - version=__version__, - packages=find_packages(exclude=["tests"]), - install_requires=_REQUIREMENTS, - scripts=[], -) diff --git a/tests/conftest.py b/tests/conftest.py index 91c7d17..64f8f0c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,7 +32,6 @@ def checkpoint(): class Checkpoint(object): - called = False args = kwargs = timestamp = None diff --git a/tests/test_config.py b/tests/test_config.py index 521b8c4..0490df5 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,7 +1,7 @@ import os import tempfile -from .test_utils import TestCase +from unittest import TestCase from confetti import Config from confetti import get_config_object_from_proxy from confetti import exceptions @@ -10,7 +10,6 @@ class BasicUsageTest(TestCase): - def setUp(self): super(BasicUsageTest, self).setUp() self.conf = Config(dict(a=dict(b=2))) @@ -84,7 +83,6 @@ def test_keys(self): class ExtendingTest(TestCase): - def setUp(self): super(ExtendingTest, self).setUp() self.conf = Config({"a": 1}) @@ -151,7 +149,6 @@ def test_update_config_preserves_nodes(self): class HelperMethodsTest(TestCase): - def setUp(self): super(HelperMethodsTest, self).setUp() self.config = Config( @@ -175,7 +172,6 @@ def test_traverse_leaves(self): class CopyingTest(TestCase): - def test_copying_nested_dictionaries(self): raw_conf = {"a": {"b": 2}} conf1 = Config(raw_conf) @@ -185,7 +181,6 @@ def test_copying_nested_dictionaries(self): class LinkedConfigurationTest(TestCase): - def setUp(self): super(LinkedConfigurationTest, self).setUp() self.conf1 = Config(dict(a=1)) @@ -217,7 +212,6 @@ def test_linked_backups_restore_parent_then_child(self): class BackupTest(TestCase): - def setUp(self): super(BackupTest, self).setUp() self.conf = Config(dict(a=1, b=2, c=[])) @@ -250,7 +244,6 @@ def test_backup_copy(self): class SerializationTest(TestCase): - def setUp(self): super(SerializationTest, self).setUp() self.dict = dict(a=dict(b=dict(c=8))) diff --git a/tests/test_cross_references.py b/tests/test_cross_references.py index 558e0b2..92a91ed 100644 --- a/tests/test_cross_references.py +++ b/tests/test_cross_references.py @@ -1,4 +1,4 @@ -from .test_utils import TestCase +from unittest import TestCase from confetti import Config, Ref diff --git a/tests/test_documentation.py b/tests/test_documentation.py index 138b967..93920f6 100644 --- a/tests/test_documentation.py +++ b/tests/test_documentation.py @@ -4,7 +4,6 @@ class DocumentationTest(TestCase): - def test_doctests(self): for p, _, filenames in os.walk( os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "doc")) diff --git a/tests/test_path_assignments.py b/tests/test_path_assignments.py index c9dc5a8..425d666 100644 --- a/tests/test_path_assignments.py +++ b/tests/test_path_assignments.py @@ -1,10 +1,9 @@ -from .test_utils import TestCase +from unittest import TestCase from confetti import Config from confetti import exceptions class PathAssignmentTest(TestCase): - def setUp(self): super(PathAssignmentTest, self).setUp() self.conf = Config(dict(a=dict(b=dict(c=3)), d=4, e=None)) diff --git a/tests/test_utils.py b/tests/test_utils.py deleted file mode 100644 index 2558595..0000000 --- a/tests/test_utils.py +++ /dev/null @@ -1,6 +0,0 @@ -import platform - -if platform.python_version() < "2.7": - from unittest2 import TestCase -else: - from unittest import TestCase diff --git a/tox.ini b/tox.ini index 5131fea..1c9e698 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,6 @@ [tox] -envlist = py26,py27,py33,py34,py35,pypy +envlist = pypy,py38,py39,py310,py311,py312,py313 [testenv] deps=pytest -commands=py.test tests - -[testenv:py26] -deps=unittest2 - pytest +commands=pytest tests