API Reference¶
This covers all of the interfaces in Simplesat. For an overview of how these pieces fit together, take a look at Architecture.
Main Interface¶
For testing of the validity of a set of requirements, typical usage might be the following:
installed_repository = Repository([package1, package2])
remote_repository = Repository([package1, package3, package4])
R = Requirment.from_string
requirements = [R('package1 > 1.2.3'), R('package4 < 2.8')]
repositories = [installed_repository, remote_repository]
if packages_are_consistent(installed_repository):
print("Installed packages are OK!")
if requirements_are_satisfiable(repositories, requirements):
print("The requirements are mutually compatible.")
else:
print("The requirements conflict.")
if requirements_are_complete(repositories, requirements):
print("These requirements include all necessary dependencies.")
else:
print("The requirements are incomplete. Dependencies are missing.")
-
simplesat.dependency_solver.packages_are_consistent(packages, modifiers=None)¶ Return True if all packages can be installed together.
Note
This will return False if more than one version of a package is present because we only permit one at a time.
Parameters: - packages (iterable of PackageMetadata) – The packages to check for consistency.
- modifiers (ConstraintModifiers, optional) – If not None, modify requirements before resolving packages.
Returns: True if every package in packages can be installed simultaneously, otherwise False.
Return type: bool
-
simplesat.dependency_solver.requirements_are_complete(packages, requirements, modifiers=None)¶ Return True if requirements includes all required transitive dependencies. I.e. it will report whether all the packages that are needed are explicitly required.
Parameters: - packages (iterable of PackageMetadata) – The packages available to draw from when satisfying requirements.
- requirements (iterable of Requirement) – The requirements used to identify relevent packages.
- modifiers (ConstraintModifiers, optional) – If not None, modify requirements before resolving packages.
Returns: True if the requirements specify all necessary packages.
Return type: bool
-
simplesat.dependency_solver.requirements_are_satisfiable(packages, requirements, modifiers=None)¶ Determine if the requirements can be satisfied together.
Parameters: - packages (iterable of PackageMetadata) – The packages available to draw from when satisfying requirements.
- requirements (list of Requirement) – The requirements used to identify relevent packages.
- modifiers (ConstraintModifiers, optional) – If not None, modify requirements before resolving packages.
Returns: Return True if the requirements can be satisfied by the packages.
Return type: bool
-
simplesat.dependency_solver.satisfy_requirements(packages, requirements, modifiers=None)¶ Find a collection of packages that satisfy the requirements.
Parameters: - packages (iterable of PackageMetadata) – The packages available to draw from when satisfying requirements.
- requirements (list of Requirement) – The requirements used to identify relevent packages.
- modifiers (ConstraintModifiers, optional) – If not None, modify requirements before resolving packages.
Returns: Return a tuple of packages that together satisfy all of the requirements. The packages are in topological order.
Return type: tuple of PackageMetadata
Raises: SatisfiabilityError– If the requirements cannot be satisfied using the packages.
-
simplesat.dependency_solver.simplify_requirements(packages, requirements)¶ Return a reduced, but equivalent set of requirements.
Parameters: - packages (iterable of PackageMetadata) – The packages available to draw from when satisfying requirements.
- requirements (list of Requirement) – The requirements used to identify relevent packages.
Returns: The reduced requirements.
Return type: tuple of Requirement
-
class
simplesat.constraints.requirement.ConflictRequirement(name, constraints=None)¶ A Requirement that describes packages which must not be installed.
-
class
simplesat.constraints.requirement.InstallRequirement(name, constraints=None)¶ A Requirement that describes packages to be installed.
-
class
simplesat.constraints.requirement.Requirement(name, constraints=None)¶ Requirements instances represent a ‘package requirement’, that is a package + version constraints.
Parameters: - name (str) – PackageInfo name
- specs (seq) – Sequence of constraints
-
classmethod
from_constraints(constraint_tuple)¶ Return a Requirement object from a PackageMetadata constraint tuple.
Parameters: constraints – A 2-tuple of constraints where the first element is the distribution name, and the second is a tuple of tuple of string, representing a disjuntion of conjunctions of version ranges, e.g.
('nose', (('< 1.4', '>= 1.3'),)).Returns: A Requirement that matches the given constraints.
Return type: Raises: InvalidConstraint– If there is more than one conjunction. In less formal terms, we do not currently support the OR operator.InvalidConstraint– If the constraint tuple has the wrong shape.
-
classmethod
from_package_string(package_string, version_factory=<bound method type.from_string of <class 'okonomiyaki.versions.enpkg.EnpkgVersion'>>)¶ Creates a requirement from a package full version.
Parameters: - package_string (str) – The package string, e.g. ‘numpy-1.8.1-1’
- version_factory (callable, optional) – A function from version strings to version objects.
Returns: A requirement matching the exact package and version in package_string.
Return type:
-
has_any_version_constraint¶ True if there is any version constraint.
-
matches(version_candidate)¶ Returns True if the given version matches this set of requirements, False otherwise.
Parameters: version_candidate (obj) – A valid version object (must match the version factory of the requirement instance).
-
to_constraints()¶ Return a constraint tuple as described by
from_constraints().
-
simplesat.constraints.requirement.parse_package_full_name(full_name)¶ Parse a package full name (e.g. ‘numpy-1.6.0-1’) into a (name, version_string) pair.
Functional classes¶
Internally, these make use of the DependencySolver. To use it
yourself, you’ll need to create some Packages,
populate at least one Repository with them, add that to a
Pool and give all of that to the constructor. Then you can make some
Requirements that describe what you’d like to do, add
them to a Request and pass it to
solve.
-
class
simplesat.dependency_solver.DependencySolver(pool, remote_repositories, installed_repository, use_pruning=True, strict=False)¶ Top-level class for resolving a package management scenario.
The solver is configured at construction time with packages repositories and a
Policyand exposes an API for computing aTransactionthat describes what to do.Parameters: - pool (Pool) – Pool against which to resolve Requirements.
- remote_repositories (list of Repository) – Repositories containing package available for installation.
- installed_repository (Repository) – Repository containing the packages which are currently installed.
- use_pruning (bool, optional) –
When True, attempt to prune package operations that are not strictly necessary for meeting the requirements. Without this, packages whose assignments have changed as an artefact of the search process, but which are not needed for the solution will be modified.
A typical example might be the installation of a dependency for a package that was proposed but later backtracked away.
- strict (bool, optional) – When true, behave more harshly when dealing with broken packages. INFO level log messages become WARNINGs and missing dependencies become errors rather than causing the package to be ignored.
>>> from simplesat.constraints.package_parser import \ ... pretty_string_to_package as P >>> numpy1921 = P('numpy 1.9.2-1; depends (MKL 10.2-1)') >>> mkl = P('MKL 10.3-1') >>> installed_repository = Repository([mkl]) >>> remote_repository = Repository([mkl, numpy1921]) >>> request = Request() >>> request.install(Requirement.from_string('numpy >= 1.9')) >>> request.allow_newer('MKL') >>> pool = Pool([installed_repo] + remote_repos) >>> pool.modifiers = request.modifiers >>> solver = DependencySolver(pool, remote_repos, installed_repo) >>> transaction = solver.solve(request)
-
solve(request)¶ Given a request return a Transaction that would satisfy it.
Parameters: request (Request) – The request that should be satisifed. Returns: The operations to apply to resolve the request. Return type: Transaction Raises: SatisfiabilityError– If no resolution is found.
-
solve_with_hint(request)¶ Given a request return a Transaction that would satisfy it.
If the solver cannot find a solution, it will raise a SatisfiabilityErrorWithHint exception, that contains enough information to give a human-readable error message.
Parameters: request (Request) – The request that should be satisifed. Returns: The operations to apply to resolve the request. Return type: Transaction Raises: SatisfiabilityErrorWithHint– If no resolution is found.
-
class
simplesat.request.Request(modifiers=NOTHING, jobs=NOTHING)¶ A proposed change to the state of the installed repository.
The Request is built up from
Requirementobjects and ad-hoc package constraint modifiers.Parameters: modifiers (ConstraintModifiers, optional) – The contraint modifiers are used to relax constraints when deciding on which packages meet a requirement. >>> from simplesat.request import Request >>> from simplesat.constraints import Requirement >>> request = Request() >>> recent_mkl = Requirement.from_string('MKL >= 11.0') >>> request.install(recent_mkl) >>> request.jobs [_Job(requirement=Requirement('MKL >= 11.0-0'), kind=<JobType.install: 1>)] >>> request.modifiers ConstraintModifiers(allow_newer=set(), allow_any=set(), allow_older=set()) >>> request.allow_newer('MKL') >>> request.modifiers.asdict() {'allow_older': [], 'allow_any': ['MKL'], 'allow_newer': []}
Package Hierarchy¶
-
class
simplesat.package.PackageMetadata(name, version, install_requires=None, conflicts=None, provides=None)¶ PackageMetadata represents an immutable, versioned Python distribution and its relationship with other packages.
-
class
simplesat.repository.Repository(packages=None)¶ A Repository is a set of packages that knows about which package it contains.
It also supports the iterator protocol. Iteration is guaranteed to be deterministic and independent of the order in which packages have been added.
Parameters: packages (list of PackageMetadata) – The packages available in this repository. >>> from simplesat.constraints.package_parser import \ ... pretty_string_to_package as P >>> mkl = P('MKL 10.3-1') >>> numpy1921 = P('numpy 1.9.2-1; depends (MKL)') >>> numpy1922 = P('numpy 1.9.2-2; depends (MKL, libgfortran)') >>> repository = Repository([mkl, numpy1922]) >>> repository.add_package(numpy1921) >>> assert list(repository) == some_pkgs + [another_one] >>> numpies = repository.find_packages['numpy'] >>> assert numpies == [numpy1921, numpy1922]
-
add_package(package_metadata)¶ Add the given package to this repository.
Parameters: package (PackageMetadata) – The package metadata to add. May be a subclass of PackageMetadata. Note
If the same package is added multiple times to a repository, every copy will be available when calling find_package or when iterating.
-
find_package(name, version)¶ Search for the first match of a package with the given name and version.
Parameters: - name (str) – The package name to look for.
- version (EnpkgVersion) – The version to look for.
Returns: package – The corresponding metadata.
Return type:
-
find_packages(name)¶ Returns an iterable of package metadata with the given name, sorted from lowest to highest version.
Parameters: name (str) – The package’s name Returns: packages – Iterable of PackageMetadata instances (order is from lower to higher version) Return type: iterable
-
update(iterable)¶ Add the packages from the given iterable into this repository.
-
-
class
simplesat.pool.Pool(repositories=None, modifiers=None)¶ A pool of repositories.
The main feature of a pool is to search for every package matching a given requirement.
Parameters: - repositories (list of Repository, optional) – The repositories to query for packages.
- modifiers (ConstraintModifiers, optional) – If given, modify the requirements prior to querying.
-
add_repository(repository)¶ Add the repository to this pool.
Parameters: repository (Repository) – The repository to add
-
id_to_package(package_id)¶ Returns the package of the given ‘package id’.
-
id_to_string(package_id)¶ Convert a package id to a nice string representation.
-
iter_package_ids()¶ Iterate over all package ids.
-
iter_packages()¶ Iterate over all PackageMetadata objects.
-
modify_requirement(requirement)¶ Return requirement modified by the pool’s ConstraintModifiers.
-
package_id(package)¶ Returns the ‘package id’ of the given package.
-
what_provides(requirement, use_modifiers=True)¶ Computes the list of packages fulfilling the given requirement.
Parameters: - requirement (Requirement) – The requirement to match candidates against.
- use_modifiers (bool) – If True, modify the requirement according to self.modifiers.
Returns: The packages satisfying requirement.
Return type: list of PackageMetadata
Conveniences¶
-
simplesat.constraints.package_parser.constraints_to_pretty_strings(constraint_tuples)¶ Convert a sequence of constraint tuples as used in PackageMetadata to a list of pretty constraint strings.
Parameters: constraint_tuples (tuple of constraint) – Sequence of constraint tuples, e.g. ((“MKL”, ((“< 11”, “>= 10.1”),)),)
-
simplesat.constraints.package_parser.package_to_pretty_string(package)¶ Given a PackageMetadata instance, returns a pretty string.
-
class
simplesat.test_utils.Scenario(packages, remote_repositories, installed_repository, request, decisions, operations, pretty_operations, failure=None)¶ A high level description of a scenario that should be solved.
The Scenario class bundles together several important related pieces of data that together characterize a package management scenario. This includes a
Request, a singularRepositoryrepresenting packages that are currently installed and a list ofRepositoryrepresenting available packages.The key feature is the ability to create one from a human-readable yaml description:
>>> Scenario.from_yaml(io.StringIO(u''' ... packages: ... - MKL 10.2-1 ... - MKL 10.3-1 ... - numpy 1.7.1-1; depends (MKL == 10.3-1) ... - numpy 1.8.1-1; depends (MKL == 10.3-1) ... ... request: ... - operation: "install" ... requirement: "numpy" ... ''')
-
simplesat.test_utils.generate_rules_for_requirement(pool, requirement, installed_map=None)¶ Generate CNF rules for a requirement.
Parameters: - pool (Pool) – A Pool of Repositories to use when fulfilling the requirement.
- requirement (Requirement) – The description of the package to be installed.
Returns: rules – Package rules describing the given scenario.
Return type: list
-
simplesat.test_utils.parse_package_list(packages)¶ Yield PackageMetadata instances given an sequence of pretty package strings.
Parameters: packages (iterator) – An iterator of package strings (e.g. ‘numpy 1.8.1-1; depends (MKL ^= 10.3)’).
-
simplesat.dependency_solver.requirements_from_packages(packages)¶ Return a list of requirements, one to match each package in packages.
Parameters: packages (iterable of PackageMetadata) – The packages for which to generate requirements. Returns: The matching requirements. Return type: tuple of Requirement
-
simplesat.dependency_solver.packages_from_requirements(packages, requirements, modifiers=None)¶ Return a new tuple that only contains packages explicitly mentioned in the requirements.
Parameters: - packages (iterable of PackageMetadata) – The packages available for inclusion in the result.
- requirements (list of Requirement) – The requirements used to identify relevant packages. All packages that satisfy any of the requirements will be included.
- modifiers (ConstraintModifiers, optional) – If not None, modify requirements before resolving packages.
Returns: A tuple containing the relevant packages.
Return type: Tuple of PackageMetadata
Exceptions¶
-
exception
simplesat.errors.SatisfiabilityErrorWithHint(unsat, conflicting_jobs)¶ A satistibiality error class with information about minimally unsatisfiable problem.
This is used when one wants to give more human-readable error messages about conflicts and other satistiability issues.
Lower level utilities¶
These are used internally.
-
simplesat.utils.graph.backtrack(end, start, visited)¶ Return a tuple of nodes from start to end by recursively looking up the current node in visited. visited is a dictionary of one-way edges between nodes.
-
simplesat.utils.graph.breadth_first_search(start, neighbor_func, targets, target_func=None, visited=None)¶ Return an iterable of paths from start to each reachable terminal node end.
Parameters: - start (node) – The starting point of the search
- neighbor_func (callable) – Returns the neighbors of a node
- targets (set) – The nodes we’re searching for. The search terminates when each member of targets has been encountered at least once, but only path is returned per target.
- target_func (callable, optional) – If given, then target_func is applied to node and the result
is used to determine if node is a target via
target_func(node) in targets. - visited (dict, optional) – If given, it will be used to track the current path. You can use it to directly inspect the search path after calling breadth_first_search().
Yields: path (tuple of nodes) – A path from node start to some node end such that terminate_func(end) is in targets, by following neighbors as given by neighborfunc(node):
>>> start = 0 >>> targets = {10, 4} >>> def target_func(node): ... return node*2 >>> def neighbor_func(node): ... return [node + 1] >>> tuple(breadth_first_search(start, neighbor_func, targets, target_func)) ((0, 1, 2), (0, 1, 2, 3, 4, 5))
-
simplesat.utils.graph.connected_nodes(node, neighbor_func, visited=None)¶ Recursively build up a set of nodes connected to node by following neighbors as given by neighbor_func(node), i.e. “flood fill.”
>>> def neighbor_func(node): ... return {-node, min(node+1, 5)} >>> connected_nodes(0, neighbor_func) {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5}
-
simplesat.utils.graph.package_lit_dependency_graph(pool, package_lits, closed=True)¶ Return a dict of nodes to edges, suitable for use with
toposort().Parameters: - pool (Pool) – The pool to use when resolving package literals to packages.
- package_lits (iterable of int) – The package literals to build the dependency graph for. These can be positive or negative. The sign will be maintained.
- closed (bool, optional) – If True, only include edges to packages dependencies that are themselves in package_lits. No package literals that are not in package_lits will appear in the graph.
Returns: nodes_to_edges – A dict of package_literals to sets of package_literals, as described in
toposort().Return type: dict
-
simplesat.utils.graph.toposort(nodes_to_edges)¶ Return an iterator over topologically sorted groups of nodes.
Output is a list of sets in topological order. The first set consists of items with no dependences, each subsequent set consists of items that depend upon items in the preceeding sets.
Parameters: nodes_to_edges (dict from node to set(node)) – A directed graph expressed as a dictionary of edges whose keys are nodes and values are all of the nodes on which the key depends.
For example, if node 1 depends on 2, we have
{1: {2}, 2: set()}.Yields: set of nodes – Each yielded set contains nodes which depend only on nodes that have already been yielded in a previous set. The first set contains the nodes with no outgoing edges. Raises: ValueError– If the graph contains cyclic dependencies.>>> print '\n'.join(repr(sorted(x)) for x in toposort2({ ... 2: set([11]), ... 9: set([11,8]), ... 10: set([11,3]), ... 11: set([7,5]), ... 8: set([7,3]), ... })) {3, 5, 7} {8, 11} {2, 9, 10}
-
simplesat.utils.graph.transitive_neighbors(nodes_to_edges)¶ Return the set of all reachable nodes for each node in the nodes_to_edges adjacency dict.