diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6caa301 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 + +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: yearly diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..4006689 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,25 @@ +name: CD + +on: + release: + types: + - prereleased + - released + +jobs: + package-name: + uses: ComPWA/actions/.github/workflows/get-pypi-name.yml@99e91bcc486218c7dbbb4f2e0319bfcb028562aa # 4.0.1 + pypi: + environment: + name: PyPI + url: https://pypi.org/p/${{ needs.package-name.outputs.name }} + if: startsWith(github.ref, 'refs/tags') + name: Publish to PyPI + needs: + - package-name + permissions: + id-token: write + runs-on: ubuntu-24.04 + steps: + - uses: ComPWA/actions/build-pypi-distribution@99e91bcc486218c7dbbb4f2e0319bfcb028562aa # 4.0.1 + - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e0dd542 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: CI + +env: + FORCE_COLOR: yes + +on: + pull_request: + branches: + - 1.4.x + push: + branches: + - 1.4.x + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) }} + +jobs: + test: + name: Test Python ${{ matrix.python-version }} + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + python-version: ${{ matrix.python-version }} + - name: Run tests + run: uv run --no-project --with pytest python -m pytest -q + + distribution: + name: Build and inspect distributions + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + python-version: "3.14" + - name: Build wheel and source distribution + run: uv build + - name: Check distribution metadata + run: uvx twine check dist/* + - name: Prepare isolated wheel tests + run: | + mkdir -p "${{ runner.temp }}/wheel-test" + cp -R tests "${{ runner.temp }}/wheel-test/" + - name: Test the installed wheel + working-directory: ${{ runner.temp }}/wheel-test + run: >- + uv run + --isolated + --no-project + --with "${{ github.workspace }}"/dist/*.whl + --with pytest + python -m pytest -q diff --git a/.gitignore b/.gitignore index b102207..3870980 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,14 @@ target/ # PyCharm / Intellij .idea/ + +# Virtual environments +.venv/ +uv.lock + +# Agent configuration +/.agents/ +/.claude/ +/.codex/ +/AGENTS.md +/CLAUDE.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..55c6b28 --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ +[![CI](https://github.com/ComPWA/python-constraint/actions/workflows/ci.yml/badge.svg)](https://github.com/ComPWA/python-constraint/actions/workflows/ci.yml) + +# compwa-python-constraint + +> [!NOTE] +> This is a packaging fork of [python-constraint 1.4.0](https://github.com/python-constraint/python-constraint/tree/1.4.0), maintained by the [ComPWA project](https://compwa.github.io). It is published as [compwa-python-constraint](https://pypi.org/project/compwa-python-constraint), but preserves the public `constraint` import package and the behavior of the upstream 1.4.0 solver. + +## Why this fork exists + +The upstream 1.4.0 release on +[PyPI](https://pypi.org/project/python-constraint/1.4.0) provides only a +`.tar.bz2` source distribution. +[PEP 625](https://peps.python.org/pep-0625/) standardized source distributions +as gzip-compressed `.tar.gz` archives. In version 0.12.0, +[uv stopped accepting legacy source-distribution formats](https://github.com/astral-sh/uv/releases/tag/0.12.0#breaking-changes), +including `.tar.bz2`, and recommends rebuilding affected packages as `.tar.gz` +archives. + +This fork makes that packaging-only update so projects can continue using the +1.4.0 implementation with current Python packaging tools. Its declarative +`pyproject.toml` build produces: + +- a pure-Python `py3-none-any` wheel, which installers can use without a local + build step; and +- a standards-compliant `.tar.gz` source distribution as a fallback. + +The distribution is versioned as `1.4.0.post1` to identify it as a downstream +packaging revision. Solver changes continue to belong in the +[upstream project](https://github.com/python-constraint/python-constraint). + +## Introduction + +The Python constraint module offers solvers for +[Constraint Satisfaction Problems (CSPs)](https://en.wikipedia.org/wiki/Constraint_satisfaction_problem) +over finite domains in simple and pure Python. A CSP can be represented in +terms of variables, their possible domains, and constraints between them. + +## Examples + +### Basics + +```python +>>> from constraint import * +>>> problem = Problem() +>>> problem.addVariable("a", [1, 2, 3]) +>>> problem.addVariable("b", [4, 5, 6]) +>>> problem.getSolutions() +[{'a': 3, 'b': 6}, {'a': 3, 'b': 5}, {'a': 3, 'b': 4}, + {'a': 2, 'b': 6}, {'a': 2, 'b': 5}, {'a': 2, 'b': 4}, + {'a': 1, 'b': 6}, {'a': 1, 'b': 5}, {'a': 1, 'b': 4}] + +>>> problem.addConstraint(lambda a, b: a * 2 == b, ("a", "b")) +>>> problem.getSolutions() +[{'a': 3, 'b': 6}, {'a': 2, 'b': 4}] + +>>> problem = Problem() +>>> problem.addVariables(["a", "b"], [1, 2, 3]) +>>> problem.addConstraint(AllDifferentConstraint()) +>>> problem.getSolutions() +[{'a': 3, 'b': 2}, {'a': 3, 'b': 1}, {'a': 2, 'b': 3}, + {'a': 2, 'b': 1}, {'a': 1, 'b': 2}, {'a': 1, 'b': 3}] +``` + +### Rooks problem + +```python +>>> problem = Problem() +>>> numpieces = 8 +>>> cols = range(numpieces) +>>> rows = range(numpieces) +>>> problem.addVariables(cols, rows) +>>> for col1 in cols: +... for col2 in cols: +... if col1 < col2: +... problem.addConstraint( +... lambda row1, row2: row1 != row2, +... (col1, col2), +... ) +>>> solutions = problem.getSolutions() +``` + +### Magic squares + +```python +>>> problem = Problem() +>>> problem.addVariables(range(16), range(1, 17)) +>>> problem.addConstraint(AllDifferentConstraint(), range(16)) +>>> problem.addConstraint(ExactSumConstraint(34), [0, 5, 10, 15]) +>>> problem.addConstraint(ExactSumConstraint(34), [3, 6, 9, 12]) +>>> for row in range(4): +... problem.addConstraint( +... ExactSumConstraint(34), +... [row * 4 + i for i in range(4)], +... ) +>>> for col in range(4): +... problem.addConstraint( +... ExactSumConstraint(34), +... [col + 4 * i for i in range(4)], +... ) +>>> solutions = problem.getSolutions() +``` + +## Features + +The following solvers are available: + +- Backtracking solver +- Recursive backtracking solver +- Minimum conflicts solver + +Predefined constraint types include: + +- `FunctionConstraint` +- `AllDifferentConstraint` +- `AllEqualConstraint` +- `ExactSumConstraint` +- `MaxSumConstraint` +- `MinSumConstraint` +- `InSetConstraint` +- `NotInSetConstraint` +- `SomeInSetConstraint` +- `SomeNotInSetConstraint` + +## Installation + +```shell +python -m pip install compwa-python-constraint +``` + +The distribution name is `compwa-python-constraint`, while the import remains +unchanged: + +```python +from constraint import Problem +``` + +## Upstream project + +`python-constraint` was written by Gustavo Niemeyer and is maintained at +[python-constraint/python-constraint](https://github.com/python-constraint/python-constraint). +Please report solver bugs and propose behavioral changes +[upstream](https://github.com/python-constraint/python-constraint/issues). + +For packaging issues specific to this fork, use the +[ComPWA issue tracker](https://github.com/ComPWA/python-constraint/issues). diff --git a/README.rst b/README.rst deleted file mode 100644 index 564e46a..0000000 --- a/README.rst +++ /dev/null @@ -1,159 +0,0 @@ -|Build Status| |Code Health| |Code Coverage| - -python-constraint -================= - -Introduction ------------- -The Python constraint module offers solvers for `Constraint Satisfaction Problems (CSPs) `_ over finite domains in simple and pure Python. CSP is class of problems which may be represented in terms of variables (a, b, ...), domains (a in [1, 2, 3], ...), and constraints (a < b, ...). - -Examples --------- - -Basics -~~~~~~ - -This interactive Python session demonstrates the module basic operation: - -.. code-block:: python - - >>> from constraint import * - >>> problem = Problem() - >>> problem.addVariable("a", [1,2,3]) - >>> problem.addVariable("b", [4,5,6]) - >>> problem.getSolutions() - [{'a': 3, 'b': 6}, {'a': 3, 'b': 5}, {'a': 3, 'b': 4}, - {'a': 2, 'b': 6}, {'a': 2, 'b': 5}, {'a': 2, 'b': 4}, - {'a': 1, 'b': 6}, {'a': 1, 'b': 5}, {'a': 1, 'b': 4}] - - >>> problem.addConstraint(lambda a, b: a*2 == b, - ("a", "b")) - >>> problem.getSolutions() - [{'a': 3, 'b': 6}, {'a': 2, 'b': 4}] - - >>> problem = Problem() - >>> problem.addVariables(["a", "b"], [1, 2, 3]) - >>> problem.addConstraint(AllDifferentConstraint()) - >>> problem.getSolutions() - [{'a': 3, 'b': 2}, {'a': 3, 'b': 1}, {'a': 2, 'b': 3}, - {'a': 2, 'b': 1}, {'a': 1, 'b': 2}, {'a': 1, 'b': 3}] - -Rooks problem -~~~~~~~~~~~~~ - -The following example solves the classical Eight Rooks problem: - -.. code-block:: python - - >>> problem = Problem() - >>> numpieces = 8 - >>> cols = range(numpieces) - >>> rows = range(numpieces) - >>> problem.addVariables(cols, rows) - >>> for col1 in cols: - ... for col2 in cols: - ... if col1 < col2: - ... problem.addConstraint(lambda row1, row2: row1 != row2, - ... (col1, col2)) - >>> solutions = problem.getSolutions() - >>> solutions - >>> solutions - [{0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 2, 6: 1, 7: 0}, - {0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 2, 6: 0, 7: 1}, - {0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 1, 6: 2, 7: 0}, - {0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 1, 6: 0, 7: 2}, - ... - {0: 7, 1: 5, 2: 3, 3: 6, 4: 2, 5: 1, 6: 4, 7: 0}, - {0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 2, 6: 0, 7: 4}, - {0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 2, 6: 4, 7: 0}, - {0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 4, 6: 2, 7: 0}, - {0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 4, 6: 0, 7: 2}, - ...] - - -Magic squares -~~~~~~~~~~~~~ - -This example solves a 4x4 magic square: - -.. code-block:: python - - >>> problem = Problem() - >>> problem.addVariables(range(0, 16), range(1, 16 + 1)) - >>> problem.addConstraint(AllDifferentConstraint(), range(0, 16)) - >>> problem.addConstraint(ExactSumConstraint(34), [0, 5, 10, 15]) - >>> problem.addConstraint(ExactSumConstraint(34), [3, 6, 9, 12]) - >>> for row in range(4): - ... problem.addConstraint(ExactSumConstraint(34), - [row * 4 + i for i in range(4)]) - >>> for col in range(4): - ... problem.addConstraint(ExactSumConstraint(34), - [col + 4 * i for i in range(4)]) - >>> solutions = problem.getSolutions() - -Features --------- - -The following solvers are available: - -- Backtracking solver -- Recursive backtracking solver -- Minimum conflicts solver - - -.. role:: python(code) - :language: python - -Predefined constraint types currently available: - -- :python:`FunctionConstraint` -- :python:`AllDifferentConstraint` -- :python:`AllEqualConstraint` -- :python:`ExactSumConstraint` -- :python:`MaxSumConstraint` -- :python:`MinSumConstraint` -- :python:`InSetConstraint` -- :python:`NotInSetConstraint` -- :python:`SomeInSetConstraint` -- :python:`SomeNotInSetConstraint` - -API documentation ------------------ -Documentation for the module is available at: http://labix.org/doc/constraint/ - -Download and install --------------------- - -.. code-block:: shell - - $ pip install python-constraint - -Roadmap -------- - -This GitHub organization and repository is a global effort to help to -maintain python-constraint which was written by Gustavo Niemeyer -and originaly located at https://labix.org/python-constraint - -- Create some unit tests - DONE -- Enable continuous integration - DONE -- Port to Python 3 (Python 2 being also supported) - DONE -- Respect Style Guide for Python Code (PEP8) - DONE -- Improve code coverage writting more unit tests - ToDo -- Move doc to Sphinx or MkDocs - https://readthedocs.org/ - ToDo - -Contact -------- -- `Gustavo Niemeyer `_ -- `Sébastien Celles `_ - -But it's probably better to `open an issue `_. - - -.. |Build Status| image:: https://travis-ci.org/python-constraint/python-constraint.svg?branch=master - :target: https://travis-ci.org/python-constraint/python-constraint -.. |Code Health| image:: https://landscape.io/github/python-constraint/python-constraint/master/landscape.svg?style=flat - :target: https://landscape.io/github/python-constraint/python-constraint/master - :alt: Code Health -.. |Code Coverage| image:: https://coveralls.io/repos/github/python-constraint/python-constraint/badge.svg - :target: https://coveralls.io/github/python-constraint/python-constraint diff --git a/constraint/version.py b/constraint/version.py index b3dddc1..dc4315e 100644 --- a/constraint/version.py +++ b/constraint/version.py @@ -2,7 +2,7 @@ __copyright__ = "Copyright (c) 2005-2018 - Gustavo Niemeyer " __credits__ = ["Sebastien Celles"] __license__ = "" -__version__ = "1.4.0" +__version__ = "1.4.0.post1" __email__ = "gustavo@niemeyer.net" __status__ = "Development" -__url__ = "https://github.com/python-constraint/python-constraint" +__url__ = "https://github.com/ComPWA/python-constraint" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1713df6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,65 @@ +[project] +name = "compwa-python-constraint" +description = "Support for handling constraint-solving problems over finite domains" +readme = "README.md" +license = "BSD-2-Clause" +license-files = ["LICENSE"] +authors = [{ name = "Gustavo Niemeyer", email = "gustavo@niemeyer.net" }] +keywords = [ + "CSP", + "constraint solving", + "problem solver", +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Intended Audience :: Science/Research", + "Operating System :: OS Independent", + "Programming Language :: Cython", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Topic :: Scientific/Engineering", +] +dynamic = ["version"] + +[project.urls] +Documentation = "https://python-constraint.github.io/python-constraint" +Issues = "https://github.com/ComPWA/python-constraint/issues" +Repository = "https://github.com/ComPWA/python-constraint" + +[project.optional-dependencies] +dev = [ + "check-manifest", + "nose", +] +test = [ + "coverage", + "nose", +] + +[build-system] +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +exclude = [ + "contrib", + "docs", + "tests*", +] + +[tool.setuptools.dynamic] +version = { attr = "constraint.version.__version__" } + +[tool.tombi.files] +exclude = ["**/uv.lock"] + +[tool.tombi.format.rules] +indent-width = 4 +line-width = 88 + +[tool.tombi.lint.rules] +key-empty = "off" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 5bf0452..0000000 --- a/setup.cfg +++ /dev/null @@ -1,9 +0,0 @@ -[bdist_wheel] -universal = 1 - -[bdist_rpm] -doc_files = README.rst -use_bzip2 = 1 - -[sdist] -formats = bztar diff --git a/setup.py b/setup.py deleted file mode 100755 index 77b0253..0000000 --- a/setup.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from setuptools import setup, find_packages # Always prefer setuptools over distutils -from codecs import open # To use a consistent encoding -from os import path -import io - -NAME = "python-constraint" -filename = "%s/version.py" % "constraint" -with open(filename) as f: - exec(f.read()) - -here = path.abspath(path.dirname(__file__)) - - -def readme(): - filename = path.join(here, "README.rst") - with io.open(filename, "rt", encoding="UTF-8") as f: - return f.read() - - -setup( - name=NAME, - # Versions should comply with PEP440. For a discussion on single-sourcing - # the version across setup.py and the project code, see - # https://packaging.python.org/en/latest/development.html#single-sourcing-the-version - # version='0.0.1', - version=__version__, - description="python-constraint is a module implementing support " - "for handling CSPs (Constraint Solving Problems) over finite domain", - long_description=readme(), - # The project's main homepage. - url=__url__, - # Author details - author=__author__, - author_email=__email__, - # Choose your license - license=__license__, - # See https://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ - # How mature is this project? Common values are - # 3 - Alpha - # 4 - Beta - # 5 - Production/Stable - "Development Status :: 3 - Alpha", - # Indicate who your project is intended for - "Environment :: Console", - # 'Topic :: Software Development :: Build Tools', - "Intended Audience :: Science/Research", - "Operating System :: OS Independent", - # Specify the Python versions you support here. In particular, ensure - # that you indicate whether you support Python 2, Python 3 or both. - "Programming Language :: Cython", - "Programming Language :: Python", - # 'Programming Language :: Python :: 2', - # 'Programming Language :: Python :: 2.6', - "Programming Language :: Python :: 2.7", - # 'Programming Language :: Python :: 3', - # 'Programming Language :: Python :: 3.2', - # "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Topic :: Scientific/Engineering", - # Pick your license as you wish (should match "license" above) - "License :: OSI Approved :: BSD License", - ], - # What does your project relate to? - keywords="csp constraint solving problems problem solver", - # You can just specify the packages manually here if your project is - # simple. Or you can use find_packages(). - packages=find_packages(exclude=["contrib", "docs", "tests*"]), - # List run-time dependencies here. These will be installed by pip when your - # project is installed. For an analysis of "install_requires" vs pip's - # requirements files see: - # https://packaging.python.org/en/latest/technical.html#install-requires-vs-requirements-files - install_requires=[], - # List additional groups of dependencies here (e.g. development dependencies). - # You can install these using the following syntax, for example: - # $ pip install -e .[dev,test] - extras_require={"dev": ["check-manifest", "nose"], "test": ["coverage", "nose"]}, - # If there are data files included in your packages that need to be - # installed, specify them here. If using Python 2.6 or less, then these - # have to be included in MANIFEST.in as well. - # package_data={ - # 'sample': ['logging.conf'], - # }, - # Although 'package_data' is the preferred approach, in some case you may - # need to place data files outside of your packages. - # see http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files - # In this case, 'data_file' will be installed into '/my_data' - # data_files=[('my_data', ['data/data_file'])], - # To provide executable scripts, use entry points in preference to the - # "scripts" keyword. Entry points provide cross-platform support and allow - # pip to create the appropriate form of executable for the target platform. - # entry_points={ - # 'console_scripts': [ - # 'sample=sample:main', - # ], - # }, -)