https://github.com/sdispater/mixology
A generic dependency-resolution library written in pure Python
https://github.com/sdispater/mixology
dependency-resolution python
Last synced: 9 months ago
JSON representation
A generic dependency-resolution library written in pure Python
- Host: GitHub
- URL: https://github.com/sdispater/mixology
- Owner: sdispater
- License: mit
- Created: 2019-11-29T16:13:43.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2020-08-10T13:15:25.000Z (almost 6 years ago)
- Last Synced: 2025-01-05T21:34:11.044Z (over 1 year ago)
- Topics: dependency-resolution, python
- Language: Python
- Homepage:
- Size: 62.5 KB
- Stars: 78
- Watchers: 6
- Forks: 11
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Mixology
A generic dependency-resolution library written in pure Python.
It is based on the [PubGrub](https://github.com/dart-lang/pub/blob/master/doc/solver.md) algorithm.
## Installation
If you are using [poetry](https://github.com/sdispater/poetry), it's as simple as:
```bash
poetry add mixology
```
If not you can use `pip`:
```bash
pip install mixology
```
## Usage
Mixology is a dependency resolution algorithm.
In order to start using Mixology you need to initialize a [`VersionSolver`](mixology/version_solver.py) instance
with a [`PackageSource`](mixology/package_source.py) which should be adapted to work with your system.
Then, you need to call `VersionSolver.solve()` which will return a [result](mixology/result.py) with the list of decisions
or raise a [`SolveFailure`](mixology/failure.py) which will give a detailed explanation of the reason why the resolution failed.
## Example
This example is extracted from the test suite of Mixology
and uses the [`poetry-semver`](https://github.com/python-poetry/semver) library.
First we need to have our own `PackageSource` class which implements the required methods
and a simple `Dependency` class. Packages will be represented by simple strings.
```python
from semver import Version
from semver import VersionRange
from semver import parse_constraint
from mixology.constraint import Constraint
from mixology.package_source import PackageSource as BasePackageSource
from mixology.range import Range
from mixology.union import Union
class Dependency:
def __init__(self, name, constraint): # type: (str, str) -> None
self.name = name
self.constraint = parse_constraint(constraint)
self.pretty_constraint = constraint
def __str__(self): # type: () -> str
return self.pretty_constraint
class PackageSource(BasePackageSource):
def __init__(self): # type: () -> None
self._root_version = Version.parse("0.0.0")
self._root_dependencies = []
self._packages = {}
super(PackageSource, self).__init__()
@property
def root_version(self):
return self._root_version
def add(
self, name, version, deps=None
): # type: (str, str, Optional[Dict[str, str]]) -> None
if deps is None:
deps = {}
version = Version.parse(version)
if name not in self._packages:
self._packages[name] = {}
if version in self._packages[name]:
raise ValueError("{} ({}) already exists".format(name, version))
dependencies = []
for dep_name, spec in deps.items():
dependencies.append(Dependency(dep_name, spec))
self._packages[name][version] = dependencies
def root_dep(self, name, constraint): # type: (str, str) -> None
self._root_dependencies.append(Dependency(name, constraint))
def _versions_for(
self, package, constraint=None
): # type: (Hashable, Any) -> List[Hashable]
if package not in self._packages:
return []
versions = []
for version in self._packages[package].keys():
if not constraint or constraint.allows_any(
Range(version, version, True, True)
):
versions.append(version)
return sorted(versions, reverse=True)
def dependencies_for(self, package, version): # type: (Hashable, Any) -> List[Any]
if package == self.root:
return self._root_dependencies
return self._packages[package][version]
def convert_dependency(self, dependency): # type: (Dependency) -> Constraint
if isinstance(dependency.constraint, VersionRange):
constraint = Range(
dependency.constraint.min,
dependency.constraint.max,
dependency.constraint.include_min,
dependency.constraint.include_max,
dependency.pretty_constraint,
)
else:
# VersionUnion
ranges = [
Range(
range.min,
range.max,
range.include_min,
range.include_max,
str(range),
)
for range in dependency.constraint.ranges
]
constraint = Union.of(ranges)
return Constraint(dependency.name, constraint)
```
Now, we need to specify our root dependencies and the available packages.
```python
source = PackageSource()
source.root_dep("a", "1.0.0")
source.root_dep("b", "1.0.0")
source.add("a", "1.0.0", deps={"shared": ">=2.0.0 <4.0.0"})
source.add("b", "1.0.0", deps={"shared": ">=3.0.0 <5.0.0"})
source.add("shared", "2.0.0")
source.add("shared", "3.0.0")
source.add("shared", "3.6.9")
source.add("shared", "4.0.0")
source.add("shared", "5.0.0")
```
Now that everything is in place we can create a `VersionSolver` instance
with the newly created `PackageSource` and call `solve()` to retrieve a `SolverResult` instance.
```python
from mixology.version_solver import VersionSolver
solver = VersionSolver(source)
result = solver.solve()
result.decisions
# {Package("_root_"): , 'b': , 'a': , 'shared': }
result.attempted_solutions
# 1
```
## Contributing
To work on the Mixology codebase, you'll want to fork the project, clone the fork locally
and install the required dependencies via `poetry `_.
```bash
git clone git@github.com:sdispater/mixology.git
poetry install
```
Then, create your feature branch:
```bash
git checkout -b my-new-feature
```
Make your modifications, add tests accordingly and execute the test suite:
```bash
poetry run pytest tests/
```
When you are ready, commit your changes:
```bash
git commit -am 'Add new feature'
```
push your branch:
```bash
git push origin my-new-feature
```
and finally create a pull request.