# python-constraint: a CSP solver you install under a different name

> python-constraint solves constraint satisfaction problems over finite domains in Python, with five solvers, twenty predefined constraint types and a beta format that lets you write constraints as strings. The package you install today is python-constraint2, because the original pip release stopped being updated.

**python-constraint/python-constraint** — Constraint Solving Problem resolver for Python

- Repository: https://github.com/python-constraint/python-constraint
- Website: https://python-constraint.github.io/python-constraint/
- Stars: 520 · Forks: 80
- Language: Python
- License: BSD-2-Clause
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/python-constraint-python-constraint

## Variables, domains and constraints over finite sets

A constraint satisfaction problem is a class of problems represented in terms of variables, domains and constraints. The README gives the shape directly: a in [1, 2, 3] is a variable with a domain, and a < b is a constraint. The module offers solvers for that class over finite domains, in a package with no external dependencies and no service to run.

The basic session looks like this:

```python
    >>> from constraint import *
    >>> problem = Problem()
    >>> problem.addVariable("a", [1,2,3])
    >>> problem.addVariable("b", [4,5,6])
    >>> problem.getSolutions()  # doctest: +NORMALIZE_WHITESPACE
```

With no constraint between the two variables, every combination is a solution, and getSolutions returns them all. Adding a constraint prunes the space:

```python
    >>> problem.addConstraint("a*2 == b") # string constraints are preferable over the black-box problem.addConstraint(lambda a, b: a*2 == b, ("a", "b"))
    >>> problem.getSolutions()
    [{'a': 3, 'b': 6}, {'a': 2, 'b': 4}]
```

The comment in that line is the project making a point about its own direction. The black-box form, a lambda plus a tuple of variable names, is what the library has always accepted and what a lot of existing code uses. The string form is the recommended one going forward.

The problem class also supports adding several variables over one shared domain with addVariables, and the built-in constraint types cover the common cases, including AllDifferentConstraint, which is the constraint that makes puzzle style problems work at all.

## String constraints parsed into built-in constraint objects

The most interesting piece of the library is the new string format, and the README marks it as beta and subject to possible change, with feedback invited.

The claim is that writing constraints in the new string format is preferable over functions and lambdas, because these strings, even as compound statements, are automatically parsed to faster built-in constraints, are more concise, and do not require constraint solving familiarity by the user to be efficient.

The examples show the translation. A compound inequality:

The compound inequality `problem.addConstraint(["50 <= x * y < 100"])` is parsed to MinProdConstraint(50, ["x", "y"]) and MaxProdConstraint(100, ["x", "y"]). A division equality, `problem.addConstraint(["x / y == z"])`, is parsed to ExactProdConstraint("x", ["z", "y"]). The second case is the interesting one, because the surface expression is not obviously a product constraint at all. Parsing "x / y == z" into a product form on x, y and z means the solver can prune using the same machinery as a plain multiplication constraint instead of calling back into Python code for every candidate.

The practical gain is that the hot path stops being a Python callable. A lambda constraint has to be evaluated for each candidate assignment, in Python, which dominates the runtime once the search space is large. A parsed built-in constraint can be evaluated in the library's own code and can participate in the solver's own pruning. The cost is that the parser has to understand your expression, so the syntax is limited to what it can parse, and the whole feature is still labelled beta.

If you adopt it, adopt it deliberately. The parser turns a readable expression into something with specific semantics, and a constraint that parses into the wrong built-in type would be a silent correctness problem rather than a loud one.

## Five solvers and twenty constraint types

The solver list is short and each entry is a different search strategy. OptimizedBacktrackingSolver is the default. BacktrackingSolver is the plain version. RecursiveBacktrackingSolver does the same search with a recursive implementation. MinConflictsSolver is a local search method. ParallelSolver is there for using multiple cores.

The default choice is worth a second look, because it is not the plain backtracking solver. The README lists OptimizedBacktrackingSolver first and marks it as the default, so the library is doing something with the default beyond the textbook algorithm, and the comment in the string constraint example calls the lambda form a black box, which suggests the project has spent effort making the built-in path cheaper than the generic one.

The predefined constraint types cover a wide range of arithmetic and set relations. FunctionConstraint is the generic wrapper. AllDifferentConstraint and AllEqualConstraint handle the pairwise relations. The sum family is ExactSumConstraint, MinSumConstraint and MaxSumConstraint, and the product family mirrors it with ExactProdConstraint, MinProdConstraint and MaxProdConstraint. There is then a variable keyed version of each: VariableExactSumConstraint, VariableMinSumConstraint, VariableMaxSumConstraint, VariableExactProdConstraint, VariableMinProdConstraint and VariableMaxProdConstraint. The set family finishes it with InSetConstraint, NotInSetConstraint, SomeInSetConstraint and SomeNotInSetConstraint.

The size of that list is the point of the string format. Writing "50 <= x * y < 100" gets you a MinProdConstraint and a MaxProdConstraint without importing or instantiating anything, and the README says to use the parsing for automatic conversion to these types.

## Rooks, queens and sudoku show what the search costs

The examples directory is a better guide to the library's behaviour than the feature list. It holds abc, coins, crosswords, einstein, queens, rooks, studentdesks, sudoku, wordmath and xsum, which is close to a catalogue of classic puzzle and constraint domains.

The README walks through two of them. The Eight Rooks problem adds eight column variables and eight row variables, then adds a constraint for every pair of columns asserting that the rows differ, using the lambda form with a tuple of variable names. The magic square example uses the built-in constraints instead:

```python
    >>> 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])
```

That is AllDifferentConstraint over all sixteen cells and ExactSumConstraint(34) applied to each row, column and diagonal. The sums are expressed as constraints over lists of variables rather than as lambdas, which is where ExactSumConstraint earns its place.

Between them, the two examples show the cost model of the library. The rooks formulation needs one constraint per pair of columns, so the number of constraints grows with the square of the number of pieces. The magic square formulation needs a fixed number of constraints regardless of size, because each row and column is a constraint over a list. A library that solves the second efficiently and the first slowly is still worth having, since the shape of the constraint set is usually what determines whether a problem is practical.

The magic square example is marked with a skip in the doctest, which is a hint that the full enumeration is not something to run casually in a documentation build.

## Install python-constraint2, not python-constraint

The first thing to know is that the package you install has a different name from the project. The README states it as important: this software must be installed with pip install python-constraint2, as the original pip release will not be updated. The project metadata carries the same note in the project name field, with a comment reminding a maintainer to remove the trailing 2 in other places if the name is ever set back.

For pip users that is the whole install:

```shell
    $ pip install python-constraint2
```

Conda and Mamba users have a channel to configure first, which the README spells out:

```shell
    $ conda config --add channels conda-forge
    $ conda config --set channel_priority strict
    $ conda install python-constraint2
    $ mamba install python-constraint2
```

The strict channel priority is there for a reason. With a Conda environment that already has python-constraint installed from another channel, a loose priority lets the old package win and you end up debugging against a version that is no longer updated.

The import name is unaffected. The examples all do from constraint import *, so the code inside your project does not change, only the thing you install. That split is convenient but confusing for anyone auditing dependencies, and it is worth recording in your own notes which package the constraint module in your environment actually came from.

## Optional Cython acceleration, and a test matrix run by nox

The build is not plain Python. The pyproject file requires poetry-core, setuptools and Cython in the build system, and the repository has a cythonize_build.py script at the root, so the C extension is part of the packaging rather than an afterthought. The documentation classifiers list Python 3.11 through 3.14, and the project requires Python 3.11 or newer.

The recent releases explain why the build system changed. Version 2.7.3 is titled Expanded Linux wheels, and it was preceded on the same day by 2.7.3b1 and 2.7.3b2, both with expanded Linux wheels in their titles. Prebuilt wheels for Linux are the difference between a pip install that just works and a build step that needs a compiler on the target machine, so that release cycle was about packaging reach rather than solver features.

Testing has two entry points. nox runs the tests for all supported Python versions in their own virtual environment, which is the matrix run. For a local Python version, the development dependencies have to be installed first, and then pytest does the work. The README adds a detail that matters: you can pass --no-cov if you have the C extensions enabled, which is a hint that the coverage measurement interacts with the compiled path.

The documentation is a Sphinx project in the docs folder and can be built locally with make clean html, and the project recommends restview for reading the RST sources. The CHANGELOG.md at the root is the place to look for what changed between versions, and RELEASE_PROCEDURE.md documents how a release is cut.

## What the solver will not do for you

The boundaries of this library follow from its design, and the README is honest about most of them.

The first is domain size. Backtracking over finite domains is exponential in the worst case, and the rooks example, which adds a constraint for every pair of columns, is a demonstration of how the constraint count grows. OptimizedBacktrackingSolver, MinConflictsSolver and ParallelSolver are all ways of making the same search cheaper, not ways of avoiding the growth. If your variables have thousands of values each, no solver in the list makes that tractable.

The second is the scope of the answer. getSolutions enumerates solutions, and the solver is a search procedure, so it finds solutions rather than proving that a domain is empty in the way an SMT solver would. The project keywords list SMT, satisfiability modulo theory and SAT alongside CSP, which tells you where the library sits in that space: it is a domain propagation solver for finite domains, not an encoder that hands your problem to a different class of solver. The practical difference shows up when a problem has no finite domains, or when you need a proof of unsatisfiability rather than a list of answers.

The third is the beta string format. The README says outright that the feature is in beta and subject to possible change, so an expression that parses today may not parse the same way after an upgrade. If you adopt it, pin the version.

Finally, the naming split means the ecosystem around this library is fragmented: two PyPI names, a Conda package called python-constraint2, a repository named python-constraint, and an import name of constraint. Nothing here is broken, but a support question is harder when four names refer to one thing.

## Maintenance, licensing and the state of the 2.x line

The licence is BSD-2-Clause, stated in the pyproject metadata and carried in the LICENSE file at the root of the repository. It is a permissive licence with no copyleft obligation on code that imports the library, which is what most people expect from a solver of this kind, though a distribution that vendors the library should read the LICENSE file itself rather than take the summary from a README.

On maintenance, the last push was on 2026-10-06, and the repository is not archived. The 2.7.3 release and its two betas all landed on 2026-08-18, with the release titles focused on Linux wheels rather than on solver changes. That pattern, packaging work rather than feature work, tells you what the 2.x line has been spending effort on.

The metadata also describes the project as Development Status 5, Production/Stable, and classifies it for console use, developers, science and research, and education. The classifier list pins Python 3.11 to 3.14, which is a hard floor for adoption: an organisation still on 3.9 or 3.10 cannot install the current release.

The upstream project deserves credit for one thing that is easy to overlook. Rather than leaving the old package quietly rotting, it kept the original name working, published a clearly renamed successor, and told users which one to install. That is more than most long running Python packages manage when they have to change a name.

## Conclusion

python-constraint2 suits a Python developer who needs to search finite domains under arithmetic or set constraints, for scheduling, resource assignment or puzzles, and who wants a solver that stays in one file with no external service. It does not suit a problem with infinite or continuous domains, a case where you need proved optimality rather than a set of solutions, and it does not suit an existing deployment that installs python-constraint from PyPI, since that release no longer receives updates and the import name, the package name and the repository name now differ. Verify first by installing python-constraint2 and calling getSolutions on a small problem with a domain you already know the answer for, then check the solver you select against your real domain sizes before trusting the result.

## FAQ

### Which package name should I install for python-constraint?

Install python-constraint2, either with pip install python-constraint2 or with conda install python-constraint2 from the conda-forge channel. The README states that the original pip release will not be updated, while the import name stays constraint.

### How do I write a constraint in python-constraint?

You can pass a function with a tuple of variable names, for example problem.addConstraint(lambda a, b: a*2 == b, ("a", "b")), or the recommended string form, problem.addConstraint("a*2 == b"). String constraints are parsed into faster built-in constraint types.

### Which solvers does python-constraint provide?

The available solvers are OptimizedBacktrackingSolver, which is the default, BacktrackingSolver, RecursiveBacktrackingSolver, MinConflictsSolver and ParallelSolver. The project also ships about twenty predefined constraint types covering sums, products and set membership.

### Is the string constraint format stable?

The README describes it as beta and subject to possible change, and invites feedback. A string such as "50 <= x * y < 100" is parsed into MinProdConstraint and MaxProdConstraint objects, and "x / y == z" into an ExactProdConstraint.

### How do I run the python-constraint tests?

Run nox to test all supported Python versions in their own virtual environments. To test against your local Python version, install the development dependencies and run pytest, optionally adding --no-cov when the C extensions are enabled.

### Which Python versions does python-constraint2 support?

The project requires Python 3.11 or newer, with classifiers for 3.11, 3.12, 3.13 and 3.14. The build uses poetry-core, setuptools and Cython, and version 2.7.3 was released with expanded Linux wheels.

## Sources

- [License: BSD-2-Clause](https://github.com/python-constraint/python-constraint/blob/main/LICENSE)
- [Project website](https://python-constraint.github.io/python-constraint/)
- [python-constraint/python-constraint on GitHub](https://github.com/python-constraint/python-constraint)
- [README](https://github.com/python-constraint/python-constraint/blob/main/README.md)
- [Releases](https://github.com/python-constraint/python-constraint/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/python-constraint-python-constraint
