Markerpry
Table of Contents
Installation
pip install markerpry
Usage
Markerpry provides a powerful way to parse, manipulate and evaluate Python package environment markers.
Parsing Markers
Use the parse() method to create a tree structure from a marker string:
from markerpry import parse
# Parse a marker expression into a tree
tree = parse('python_version >= "3.7" and (os_name == "posix" or platform_system == "Linux")')
The parse method returns a tree where each node is one of:
BooleanNode: Represents True/False valuesCompareNode: Represents comparisons likepython_version >= "3.7"(==,===,!=,<,<=,>,>=,~=)ContainsNode: Represents membership tests like"3.7" in python_versionorpython_version in "3.7"OperatorNode: Represents logical operations (and/or) between nodes
Tree Navigation
The tree can be navigated using the left and right properties of nodes. These properties
return None for leaf nodes (BooleanNode, CompareNode, and ContainsNode):
# For operator nodes (and/or), access child nodes
left_expr = tree.left # python_version >= "3.7"
right_expr = tree.right # (os_name == "posix" or platform_system == "Linux")
# For nested expressions, continue traversing
nested_left = right_expr.left # os_name == "posix"
nested_right = right_expr.right # platform_system == "Linux"
# Leaf nodes have no children
assert nested_left.left is None
assert nested_left.right is None
Checking for Keys
You can check if a marker expression contains a specific environment key using the in operator:
# Check if a marker depends on specific environment keys
tree = parse('python_version >= "3.7" and os_name == "posix"')
assert "python_version" in tree
assert "os_name" in tree
assert "platform_machine" not in tree
String Representation
The tree can be converted back to a string using str(), which produces a format compatible with packaging.markers.Marker:
# Convert tree back to string
marker_string = str(tree)
# 'python_version >= "3.7" and (os_name == "posix" or platform_system == "Linux")'
# Use with packaging.markers
from packaging.markers import Marker
marker = Marker(str(tree))
Evaluation
The evaluate() function partially evaluates a tree based on the provided environment:
from markerpry import evaluate
from packaging.version import Version
import re
# Define an environment with known values
env = {
"python_version": [Version("3.8")],
"os_name": ["posix"],
"platform_system": ["Linux"],
"implementation_name": [re.compile("py.*")], # Matches python, pypy, etc.
}
# Evaluate the tree
result = evaluate(tree, env)
# The result will be a simplified tree or a BooleanNode
# In this case, it would evaluate to BooleanNode(True)
The evaluation process:
Environment Values
Each environment key can contain a list of different types of values:
Versionobjects: Used for version comparisons (python_version, etc.)- Work with all comparators (
==,===,!=,<,<=,>,>=,~=) - Version strings are parsed using
packaging.specifiers.SpecifierSet
- Work with all comparators (
strvalues: Used for exact string matching and substring tests==/===/!=do exact string equality;in/not indo substring tests- Other comparators (
<,<=,>,>=,~=) will leave the expression unevaluated
re.Patternobjects: Used for pattern matching- Only work with equality comparators (
==,===,!=) ==/===checks if the pattern matches!=checks if the pattern doesn't match
- Only work with equality comparators (
boolvalues: Unconditionally decide every comparatorTruemeans "yes";Falsemeans "no"
RangeConstraintobjects: Represent an open or closed interval of versionsRangeConstraint(min, max, include_min=True, include_max=False)— either bound may beNonefor "unbounded on this side"- Decidable for
<,<=,>,>=only when the entire interval agrees on the answer (checked by sampling both boundaries); for==/===/!=only when the compared point falls outside the interval, or the interval is a single point equal to it in/not in/~=are always undecidable for an interval- See Range Constraints below
Multiple Values
When multiple values are provided for an environment key:
env = {"python_version": [Version("3.8"), Version("3.9")], "os_name": ["posix", "nt"]}
- The expression is evaluated against each value
- Results are combined with OR logic (any match makes it true)
- If no values match, the expression remains unevaluated
Tree Simplification
The evaluation simplifies boolean operations where possible:
# For OR operations:
True or X => True # Short circuits to True
False or X => X # Continues evaluation with X
# For AND operations:
False and X => False # Short circuits to False
True and X => X # Continues evaluation with X
For example:
# Original: python_version >= "3.7" and (os_name == "posix" or platform_system == "Linux")
env = {"python_version": [Version("3.6")]}
# Evaluates to: False and (os_name == "posix" or platform_system == "Linux")
# Simplifies to: False
env = {"python_version": [Version("3.8")], "os_name": ["posix"]}
# Evaluates to: True and (True or platform_system == "Linux")
# Simplifies to: True
If any parts of the expression can't be evaluated (due to missing environment values or incompatible comparators), they remain as expressions in the resulting tree.
Range Constraints
RangeConstraint represents an open or closed interval of versions, and only resolves an expression when the entire interval agrees on the answer — unlike a list of Version values, which is evaluated existentially and can spuriously resolve interval questions:
from markerpry import RangeConstraint, evaluate, parse
from packaging.version import Version
# An abi3 wheel's floor: python_version >= 3.9, no ceiling
tree = parse('python_version < "3.11"')
env = {"python_version": [RangeConstraint(min=Version("3.9"), max=None)]}
result = evaluate(tree, env)
# result is the SAME unresolved node: "< 3.11" holds for some but not all
# versions >= 3.9, so it stays conditional.
env = {"python_version": [RangeConstraint(min=Version("3.12"), max=None)]}
result = evaluate(tree, env)
# result is BooleanNode(False): every version >= 3.12 fails "< 3.11".
Modifying Trees
modify() is the general-purpose tree-rewrite primitive evaluate() is itself built on. It walks the tree bottom-up and hands every non-operator node to a leaf callback you provide, returning the (possibly different) node it gives back:
from markerpry import BooleanNode, CompareNode, parse
tree = parse('extra == "docs" or python_version >= "3.8"')
def drop_extras(node):
if isinstance(node, CompareNode) and node.key == "extra":
return BooleanNode(False)
return node
result = tree.modify(leaf=drop_extras)
# result is CompareNode(key="python_version", comparator=">=", literal="3.8")
# - the "extra" branch folded to False, and the OR drops it automatically
License
markerpry is distributed under the terms of the MIT license.
Development
This project uses uv for dependency management and packaging.
# Install dependencies (including dev tools) into a local virtualenv
uv sync
# Run the test suite
uv run pytest
# Run linters
uv run isort --check --diff .
uv run black --check --diff .
uv run mypy --check-untyped-defs markerpry tests
# Build the package
uv build
Metadata
Release files for markerpry 0.5.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| markerpry-0.5.1.tar.gz | 10.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| markerpry-0.5.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 23.8 kB
Release files / markerpry-0.5.1.tar.gz
| Download URL | markerpry-0.5.1.tar.gz |
|---|---|
| Size | 10.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ff2ccc7bb4062ae8fce861542ca5305afa830e77efde96075e34d0991c9c87ac
|
|
BLAKE2b-256 checksum How to use checksums |
a3f95f3339edbda46aab5bf649180bb48068f2968783c4bb0e7bbfabd61d7683
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 13, 2026.
Transparency logRelease files / markerpry-0.5.1-py3-none-any.whl
| Download URL | markerpry-0.5.1-py3-none-any.whl |
|---|---|
| Size | 12.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f0a9b1be4c35fdb4570b9df6b64e9b2be884140a3c606be59808f3a1dea231c3
|
|
BLAKE2b-256 checksum How to use checksums |
cf66e23613568fc01ca3e44149b3440c92254dd04568d3c835360a96d4f58151
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 13, 2026.
Transparency logSecurity for markerpry 0.5.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for markerpry-0.5.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 13, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.