Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: 2

updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: yearly
25 changes: 25 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -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
64 changes: 64 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,14 @@ target/

# PyCharm / Intellij
.idea/

# Virtual environments
.venv/
uv.lock

# Agent configuration
/.agents/
/.claude/
/.codex/
/AGENTS.md
/CLAUDE.md
145 changes: 145 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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).
Loading