nab-python 0.0.7__tar.gz → 0.0.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nab_python-0.0.7 → nab_python-0.0.8}/PKG-INFO +3 -3
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/canary.py +7 -2
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios.py +7 -2
- {nab_python-0.0.7 → nab_python-0.0.8}/pyproject.toml +3 -3
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_packaging_provider.py +24 -2
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_provider/extras.py +30 -13
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_provider/metadata_resolver.py +15 -5
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vcs_admission.py +9 -0
- nab_python-0.0.8/src/nab_python/_vendor/packaging/PROVENANCE.md +61 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/_manylinux.py +0 -2
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/_musllinux.py +1 -1
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/_ranges.py +17 -17
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/dependency_groups.py +5 -4
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/direct_url.py +17 -8
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/markers.py +43 -4
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/metadata.py +8 -2
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/pylock.py +4 -1
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/ranges.py +251 -57
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/requirements.py +13 -2
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/specifiers.py +6 -6
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/tags.py +14 -5
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/version.py +21 -5
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/build_backend.py +39 -15
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/config.py +7 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/fetch.py +50 -7
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/provider.py +127 -7
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/requirements_file.py +1 -4
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/resolve.py +23 -4
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/universal/provider.py +12 -1
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/universal/resolve.py +7 -2
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/workspace.py +18 -5
- nab_python-0.0.8/tests/property_python/test_from_bounds_snap_bounds.py +119 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_prerelease_admission.py +3 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_vcs_admission.py +7 -1
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_archive.py +30 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_async_transports.py +16 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_benchmark_parse_requirements.py +3 -1
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_build_backend.py +26 -6
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_cached_client.py +159 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_config.py +18 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_fetch.py +140 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_local_index.py +57 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_multi_index.py +75 -1
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_provider.py +389 -27
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_resolver_packaging.py +19 -13
- nab_python-0.0.8/tests/test_simple_client_size.py +46 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_vcs.py +24 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_vcs_admission.py +26 -0
- nab_python-0.0.8/tests/test_vendor_from_bounds_snap_bounds.py +214 -0
- nab_python-0.0.8/tests/test_widen_decision.py +269 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_workspace.py +28 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/test_resolve.py +8 -8
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/test_universal_provider.py +86 -0
- nab_python-0.0.7/src/nab_python/_vendor/packaging/PROVENANCE.md +0 -92
- {nab_python-0.0.7 → nab_python-0.0.8}/.gitignore +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/LICENSE +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/README.md +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/README.md +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/_profile_runner.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/cache/.gitignore +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/canary_results/.gitignore +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/compare.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/results/.gitignore +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/ai-stack-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/ai-stack-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/ai-stack.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/airflow-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/airflow-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/airflow.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/big-packages-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/big-packages-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/big-packages.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/cross-tracker-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/cross-tracker-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/cross-tracker.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/ecosystem-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/ecosystem-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/ecosystem.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/forums-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/forums-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/forums.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/pdm-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/pdm-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/pdm.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/pip-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/pip-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/pip.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/poetry-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/poetry-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/poetry.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/pytorch-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/pytorch-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/pytorch.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/quick-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/quick-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/quick.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/rip-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/rip-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/rip.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/universal.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/unsupported-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/unsupported-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/unsupported.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/uv-lowest-direct.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/uv-lowest.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/scenarios/uv.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/strategy_sweep.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/strategy_sweep_results/summary.json +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/strategy_sweep_summary.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/universal_scenarios.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/benchmarks/universal_summary.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/__init__.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_build/__init__.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_build/env.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_build/errors.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_build/runner.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_conflict_kind.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_lockfile/__init__.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_lockfile/builder.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_lockfile/disjointness.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_lockfile/pylock.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_lockfile/requirements.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_provider/__init__.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_provider/build_remote.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_provider/listing.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_provider/lookahead.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_provider/priority.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_provider/sources.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_testing/__init__.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_testing/coordinator_fake.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_testing/overrides.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_toml.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/__init__.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/LICENSE +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/LICENSE.APACHE +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/LICENSE.BSD +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/__init__.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/_elffile.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/_parser.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/_structures.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/_tokenizer.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/errors.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/licenses/__init__.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/licenses/_spdx.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/py.typed +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/_vendor/packaging/utils.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/config_sources.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/download.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/lockfile.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/metadata.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/py.typed +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/universal/__init__.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/universal/matrix.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/universal/reresolve.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/universal/validate.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/src/nab_python/universal/wheel_selection.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/__init__.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/__init__.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/strategies.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_build_policy_never.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_cached_client_equiv.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_conflict_fork_markers.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_disjointness_differential.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_download_hashes.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_extras_pep685.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_fetch_coordinator.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_filename_differential.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_lockfile_pep751.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_marker_overlay.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_multi_index_contract.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_multi_index_pep503.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_override_overlap.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_resolver_pep440.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_sdist_archive_safety.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/property_python/test_universal_faithfulness.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/ruff.toml +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_build_runner.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_cache.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_config_sources.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_download.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_lockfile.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_marker_evaluation.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_metadata.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_requirements_file.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_resolve.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_simple_client_filenames.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_simple_client_hashes.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_simple_client_malformed.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/test_simple_client_upload_time.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/__init__.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/property_universal/__init__.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/property_universal/strategies.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/property_universal/test_alignment.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/property_universal/test_matrix.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/property_universal/test_matrix_labels.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/property_universal/test_provider_pep425.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/property_universal/test_reresolve_fixed_point.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/property_universal/test_validate.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/property_universal/test_validate_mutations.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/property_universal/test_wheel_selection_differential.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/test_matrix.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/test_reresolve.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/test_validate.py +0 -0
- {nab_python-0.0.7 → nab_python-0.0.8}/tests/universal/test_wheel_selection.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nab-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: Index-backed provider, lockfile emitter, and downloader for nab
|
|
5
5
|
Project-URL: Homepage, https://github.com/notatallshaw/nab
|
|
6
6
|
Project-URL: Documentation, https://nab.readthedocs.io/
|
|
@@ -20,8 +20,8 @@ Classifier: Typing :: Typed
|
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
21
|
Requires-Dist: build>=1.2
|
|
22
22
|
Requires-Dist: installer>=0.7
|
|
23
|
-
Requires-Dist: nab-index==0.0.
|
|
24
|
-
Requires-Dist: nab-resolver==0.0.
|
|
23
|
+
Requires-Dist: nab-index==0.0.8
|
|
24
|
+
Requires-Dist: nab-resolver==0.0.8
|
|
25
25
|
Requires-Dist: pyproject-hooks>=1.2
|
|
26
26
|
Requires-Dist: tomli-w>=1.2
|
|
27
27
|
Requires-Dist: tomli>=2.0
|
|
@@ -155,9 +155,14 @@ def parse_requirements(
|
|
|
155
155
|
)
|
|
156
156
|
raise NotImplementedError(msg)
|
|
157
157
|
name = canonicalize_name(req.name)
|
|
158
|
-
|
|
158
|
+
term = (
|
|
159
|
+
req.specifier.to_range()
|
|
160
|
+
if req.specifier
|
|
161
|
+
else VersionRange.full(admit_arbitrary=False)
|
|
162
|
+
)
|
|
163
|
+
reqs[name] = reqs.get(name, VersionRange.full()) & term
|
|
159
164
|
for extra in req.extras:
|
|
160
|
-
reqs[f"{name}[{extra}]"] = VersionRange.full()
|
|
165
|
+
reqs[f"{name}[{extra}]"] = VersionRange.full(admit_arbitrary=False)
|
|
161
166
|
return reqs
|
|
162
167
|
|
|
163
168
|
|
|
@@ -138,9 +138,14 @@ def parse_requirements(
|
|
|
138
138
|
)
|
|
139
139
|
raise NotImplementedError(msg)
|
|
140
140
|
name = canonicalize_name(req.name)
|
|
141
|
-
|
|
141
|
+
term = (
|
|
142
|
+
req.specifier.to_range()
|
|
143
|
+
if req.specifier
|
|
144
|
+
else VersionRange.full(admit_arbitrary=False)
|
|
145
|
+
)
|
|
146
|
+
reqs[name] = reqs.get(name, VersionRange.full()) & term
|
|
142
147
|
for extra in req.extras:
|
|
143
|
-
reqs[f"{name}[{extra}]"] = VersionRange.full()
|
|
148
|
+
reqs[f"{name}[{extra}]"] = VersionRange.full(admit_arbitrary=False)
|
|
144
149
|
return reqs
|
|
145
150
|
|
|
146
151
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nab-python"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.8"
|
|
4
4
|
description = "Index-backed provider, lockfile emitter, and downloader for nab"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -19,8 +19,8 @@ classifiers = [
|
|
|
19
19
|
"Typing :: Typed",
|
|
20
20
|
]
|
|
21
21
|
dependencies = [
|
|
22
|
-
"nab-resolver==0.0.
|
|
23
|
-
"nab-index==0.0.
|
|
22
|
+
"nab-resolver==0.0.8",
|
|
23
|
+
"nab-index==0.0.8",
|
|
24
24
|
"tomli>=2.0",
|
|
25
25
|
"tomli_w>=1.2",
|
|
26
26
|
"build>=1.2",
|
|
@@ -4,12 +4,13 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
+
from ._vendor.packaging.ranges import VersionRange
|
|
8
|
+
|
|
7
9
|
if TYPE_CHECKING:
|
|
8
10
|
from collections.abc import Mapping
|
|
9
11
|
|
|
10
12
|
from nab_resolver.types import Incompatibility, RangeProtocol
|
|
11
13
|
|
|
12
|
-
from ._vendor.packaging.ranges import VersionRange
|
|
13
14
|
from ._vendor.packaging.specifiers import SpecifierSet
|
|
14
15
|
from ._vendor.packaging.version import Version
|
|
15
16
|
|
|
@@ -47,12 +48,21 @@ class PackagingProvider:
|
|
|
47
48
|
return version
|
|
48
49
|
return None
|
|
49
50
|
|
|
51
|
+
def has_satisfying_version(
|
|
52
|
+
self, package: str, version_range: RangeProtocol[Version]
|
|
53
|
+
) -> bool:
|
|
54
|
+
"""Report whether any available version falls in the range."""
|
|
55
|
+
return any(v in version_range for v in self._get_versions(package))
|
|
56
|
+
|
|
50
57
|
def get_dependencies(
|
|
51
58
|
self, package: str, version: Version
|
|
52
59
|
) -> dict[str, VersionRange]:
|
|
53
60
|
"""Convert SpecifierSet deps to VersionRange deps."""
|
|
54
61
|
raw = self._packages.get(package, {}).get(version, {})
|
|
55
|
-
return {
|
|
62
|
+
return {
|
|
63
|
+
dep: (spec.to_range() if spec else VersionRange.full(admit_arbitrary=False))
|
|
64
|
+
for dep, spec in raw.items()
|
|
65
|
+
}
|
|
56
66
|
|
|
57
67
|
_CONFLICT_THRESHOLD = 5
|
|
58
68
|
|
|
@@ -96,3 +106,15 @@ class PackagingProvider:
|
|
|
96
106
|
def consume_force_backtrack_targets(self) -> list[str]:
|
|
97
107
|
"""No force-backtrack signal from this in-memory provider."""
|
|
98
108
|
return []
|
|
109
|
+
|
|
110
|
+
def widen_decision(self, package: str, version: Version) -> VersionRange | None:
|
|
111
|
+
"""No widening: dependency clauses keep the exact decided version."""
|
|
112
|
+
del package, version
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
def narrow_for_display(
|
|
116
|
+
self, package: str, constraint: RangeProtocol[Version]
|
|
117
|
+
) -> RangeProtocol[Version]:
|
|
118
|
+
"""Identity: constraints render as stored."""
|
|
119
|
+
del package
|
|
120
|
+
return constraint
|
|
@@ -43,26 +43,43 @@ def choose_extra_version(
|
|
|
43
43
|
_, _, normalized = provider.split_and_normalize(base)
|
|
44
44
|
version_list = provider.fetch_versions(base)
|
|
45
45
|
all_versions = provider.versions_only(normalized, version_list)
|
|
46
|
-
candidates = list(version_range.filter(all_versions))
|
|
47
46
|
|
|
48
|
-
# Filter by base's positive range so we don't pick a proxy version
|
|
49
|
-
# that would force base==V into a known-conflicting state.
|
|
47
|
+
# Filter by the base's positive range so we don't pick a proxy version
|
|
48
|
+
# that would force base==V into a known-conflicting state. Intersect
|
|
49
|
+
# rather than test membership: the base's range carries the pre-release
|
|
50
|
+
# admission granted by the requirement that named the extra, while the
|
|
51
|
+
# proxy's own range is built full.
|
|
50
52
|
base_range = provider.solution_ranges.get(normalized)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
candidates =
|
|
53
|
+
if base_range is None:
|
|
54
|
+
logger.debug(
|
|
55
|
+
"no base range for %s; base admission cannot be applied to %s",
|
|
56
|
+
normalized,
|
|
57
|
+
package,
|
|
58
|
+
)
|
|
59
|
+
candidates = list(version_range.filter(all_versions))
|
|
60
|
+
else:
|
|
61
|
+
candidates = list((version_range & base_range).filter(all_versions))
|
|
60
62
|
|
|
61
63
|
if provider.wants_lowest(normalized):
|
|
62
64
|
candidates = list(reversed(candidates))
|
|
63
65
|
|
|
64
66
|
chosen = _pick_in_mode(provider, base, extra, candidates)
|
|
65
|
-
|
|
67
|
+
# Enumerate pre-releases too: default filtering buffers a pre-release
|
|
68
|
+
# behind any matching final and would drop one that the base's bounds
|
|
69
|
+
# exclude, so it would never be recorded and the proxy would keep a
|
|
70
|
+
# permanent NO_VERSIONS clause past the backjump lifting the base
|
|
71
|
+
# decision. Membership below is bounds-only, so the blocks stay sound.
|
|
72
|
+
if (
|
|
73
|
+
chosen is None
|
|
74
|
+
and base_range is not None
|
|
75
|
+
and (
|
|
76
|
+
excluded_by_base := [
|
|
77
|
+
v
|
|
78
|
+
for v in version_range.filter(all_versions, prereleases=True)
|
|
79
|
+
if v not in base_range
|
|
80
|
+
]
|
|
81
|
+
)
|
|
82
|
+
):
|
|
66
83
|
_record_base_range_blocks(
|
|
67
84
|
provider, package, normalized, base_range, excluded_by_base
|
|
68
85
|
)
|
|
@@ -11,7 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
from typing import TYPE_CHECKING
|
|
12
12
|
|
|
13
13
|
from nab_index.client import SdistFile, WheelFile
|
|
14
|
-
from nab_index.local_index import read_wheel_metadata
|
|
14
|
+
from nab_index.local_index import UnsupportedWheelError, read_wheel_metadata
|
|
15
15
|
|
|
16
16
|
from .._conflict_kind import EMPTY_MEMBERSHIP_SETS
|
|
17
17
|
from .._vcs_admission import admit_vcs_url
|
|
@@ -86,7 +86,11 @@ def resolve_metadata(
|
|
|
86
86
|
raise integrity_error
|
|
87
87
|
metadata_text = provider.coordinator.index.get_metadata(normalized, ver_str)
|
|
88
88
|
elif isinstance(dist, WheelFile) and dist.local_path is not None:
|
|
89
|
-
|
|
89
|
+
try:
|
|
90
|
+
metadata_text = read_wheel_metadata(dist.local_path)
|
|
91
|
+
except UnsupportedWheelError:
|
|
92
|
+
# A contradictory .dist-info is unusable, like an unreadable wheel.
|
|
93
|
+
metadata_text = None
|
|
90
94
|
else:
|
|
91
95
|
metadata_text = None
|
|
92
96
|
|
|
@@ -554,16 +558,22 @@ def add_classified_dep(
|
|
|
554
558
|
from ..provider import join_extra
|
|
555
559
|
|
|
556
560
|
name = canonicalize_name(req.name)
|
|
557
|
-
|
|
561
|
+
# A bare dependency enters the solver without arbitrary-string admission;
|
|
562
|
+
# the accumulator identities stay arbitrary-admitting for === literals.
|
|
563
|
+
vi = (
|
|
564
|
+
req.specifier.to_range()
|
|
565
|
+
if req.specifier
|
|
566
|
+
else VersionRange.full(admit_arbitrary=False)
|
|
567
|
+
)
|
|
558
568
|
dep_extras: set[str] = req.extras
|
|
559
569
|
|
|
560
570
|
if not req_extras:
|
|
561
571
|
base_deps[name] = base_deps.get(name, VersionRange.full()) & vi
|
|
562
572
|
for re in dep_extras:
|
|
563
|
-
base_deps[join_extra(name, re)] = VersionRange.full()
|
|
573
|
+
base_deps[join_extra(name, re)] = VersionRange.full(admit_arbitrary=False)
|
|
564
574
|
else:
|
|
565
575
|
for extra_name in req_extras:
|
|
566
576
|
edeps = extra_deps_map[extra_name]
|
|
567
577
|
edeps[name] = edeps.get(name, VersionRange.full()) & vi
|
|
568
578
|
for re in dep_extras:
|
|
569
|
-
edeps[join_extra(name, re)] = VersionRange.full()
|
|
579
|
+
edeps[join_extra(name, re)] = VersionRange.full(admit_arbitrary=False)
|
|
@@ -15,6 +15,7 @@ after admission lets the URL through.
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import enum
|
|
18
|
+
import posixpath
|
|
18
19
|
from dataclasses import dataclass
|
|
19
20
|
from urllib.parse import urlsplit, urlunsplit
|
|
20
21
|
|
|
@@ -142,7 +143,15 @@ def _repo_prefix_matches(inner_url: str, prefix: str) -> bool:
|
|
|
142
143
|
prefix and skipped once on the candidate before the boundary check.
|
|
143
144
|
Both URLs have their authority ``user[:pass]@`` / ``git@`` stripped
|
|
144
145
|
by the caller.
|
|
146
|
+
|
|
147
|
+
A path git would rewrite is refused first: git applies RFC 3986
|
|
148
|
+
dot-segment removal at fetch time, so a raw ``..`` path could pass the
|
|
149
|
+
string match while git fetches a repo outside the prefix.
|
|
145
150
|
"""
|
|
151
|
+
path = urlsplit(inner_url).path
|
|
152
|
+
if path and posixpath.normpath(path) != path:
|
|
153
|
+
return False
|
|
154
|
+
|
|
146
155
|
prefix = prefix.removesuffix(".git")
|
|
147
156
|
if not inner_url.startswith(prefix):
|
|
148
157
|
return False
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Vendored `packaging` snapshot
|
|
2
|
+
|
|
3
|
+
This directory is a copy of the `packaging` library, vendored so nab can use
|
|
4
|
+
the public `VersionRange` API before a `packaging` release containing it is
|
|
5
|
+
published to PyPI. The tree is pristine `pypa/packaging` at a pinned commit
|
|
6
|
+
plus at most one checked-in patch, and nothing else.
|
|
7
|
+
|
|
8
|
+
## Model
|
|
9
|
+
|
|
10
|
+
- `tasks/vendoring/packaging.pin` holds the upstream commit the tree is pinned
|
|
11
|
+
to.
|
|
12
|
+
- `tasks/vendoring/packaging.patch`, when present, is the only divergence from
|
|
13
|
+
pristine; the rebuild reapplies it after refreshing. With no patch file the
|
|
14
|
+
tree is byte-identical to upstream at the pin. The current patch adds
|
|
15
|
+
`VersionRange.from_bounds` and `VersionRange.snap_bounds` to `ranges.py`,
|
|
16
|
+
generated from the `range-from-bounds` branch of the packaging fork
|
|
17
|
+
(`git diff upstream/main..range-from-bounds -- src/packaging/ranges.py`,
|
|
18
|
+
paths rewritten to this directory). An upstream PR is planned; until it
|
|
19
|
+
lands the patch carries the two methods.
|
|
20
|
+
- `tasks/vendor-packaging.sh` fetches `pypa/packaging` at the pin, replaces this
|
|
21
|
+
package with the pristine `src/packaging/` tree plus the repo-root license
|
|
22
|
+
texts, and reapplies the patch. `--check` rebuilds into a temp location and
|
|
23
|
+
fails if the committed tree has drifted. This file is nab's own and stays out
|
|
24
|
+
of both the rebuild and the comparison.
|
|
25
|
+
- CI runs `tasks/vendor-packaging.sh --check`, so the committed tree is proven
|
|
26
|
+
to equal pristine-at-pin plus the patch on every push.
|
|
27
|
+
|
|
28
|
+
## Refreshing
|
|
29
|
+
|
|
30
|
+
1. Bump `tasks/vendoring/packaging.pin` to the new upstream commit.
|
|
31
|
+
2. If the patch no longer applies, rebase its source branch onto the new pin
|
|
32
|
+
and regenerate `packaging.patch` from it.
|
|
33
|
+
3. Run `tasks/vendor-packaging.sh`, then the test suite.
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
`packaging` is dual-licensed under the Apache License 2.0 and the 2-Clause BSD
|
|
38
|
+
License. The LICENSE files here are the upstream texts; nothing is relicensed.
|
|
39
|
+
|
|
40
|
+
## Why vendor instead of depending on it
|
|
41
|
+
|
|
42
|
+
`packaging` now ships a public `VersionRange` class with set algebra
|
|
43
|
+
(intersection, union, complement, difference), the `is_subset` / `is_superset`
|
|
44
|
+
/ `is_disjoint` predicates, `is_empty`, `filter`, and a `SpecifierSet.to_range()`
|
|
45
|
+
factory that nab's PubGrub solver depends on. The class landed in `main` via
|
|
46
|
+
https://github.com/pypa/packaging/pull/1267 and the difference operator plus the
|
|
47
|
+
relation predicates via https://github.com/pypa/packaging/pull/1298. None of
|
|
48
|
+
this has appeared in a PyPI release yet, so there is no version to pin in
|
|
49
|
+
`pyproject.toml`. Vendoring is a temporary measure.
|
|
50
|
+
|
|
51
|
+
## Removal plan
|
|
52
|
+
|
|
53
|
+
Delete this directory and reinstate `packaging` as a normal dependency once a
|
|
54
|
+
release containing the merged `VersionRange` class reaches PyPI:
|
|
55
|
+
|
|
56
|
+
- Add `packaging>=<release>` back to `nab-python/pyproject.toml`
|
|
57
|
+
`[project].dependencies`.
|
|
58
|
+
- Search-replace `from nab_python._vendor.packaging` -> `from packaging` and
|
|
59
|
+
`import nab_python._vendor.packaging` -> `import packaging` across the
|
|
60
|
+
workspace.
|
|
61
|
+
- Remove this `_vendor/` tree and the `tasks/vendoring/` files.
|
|
@@ -84,5 +84,5 @@ if __name__ == "__main__": # pragma: no cover
|
|
|
84
84
|
print("plat:", plat)
|
|
85
85
|
print("musl:", _get_musl_version(sys.executable))
|
|
86
86
|
print("tags:", end=" ")
|
|
87
|
-
for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])):
|
|
87
|
+
for t in platform_tags([re.sub(r"[.-]", "_", plat.split("-", 1)[-1])]):
|
|
88
88
|
print(t, end="\n ")
|
|
@@ -292,13 +292,13 @@ class UpperBound:
|
|
|
292
292
|
|
|
293
293
|
|
|
294
294
|
if TYPE_CHECKING:
|
|
295
|
-
#: A single contiguous
|
|
296
|
-
|
|
295
|
+
#: A single contiguous interval as a (lower, upper) bound pair.
|
|
296
|
+
Interval = tuple[LowerBound, UpperBound]
|
|
297
297
|
|
|
298
298
|
|
|
299
299
|
NEG_INF: Final[LowerBound] = LowerBound(None, False)
|
|
300
300
|
POS_INF: Final[UpperBound] = UpperBound(None, False)
|
|
301
|
-
FULL_RANGE: Final[tuple[
|
|
301
|
+
FULL_RANGE: Final[tuple[Interval]] = ((NEG_INF, POS_INF),)
|
|
302
302
|
|
|
303
303
|
|
|
304
304
|
def trim_release(release: tuple[int, ...]) -> tuple[int, ...]:
|
|
@@ -501,11 +501,11 @@ def range_is_empty(lower: LowerBound, upper: UpperBound) -> bool:
|
|
|
501
501
|
|
|
502
502
|
|
|
503
503
|
def intersect_ranges(
|
|
504
|
-
left: Sequence[
|
|
505
|
-
right: Sequence[
|
|
506
|
-
) -> list[
|
|
504
|
+
left: Sequence[Interval],
|
|
505
|
+
right: Sequence[Interval],
|
|
506
|
+
) -> list[Interval]:
|
|
507
507
|
"""Intersect two sorted, non-overlapping range lists (two-pointer merge)."""
|
|
508
|
-
result: list[
|
|
508
|
+
result: list[Interval] = []
|
|
509
509
|
left_index = right_index = 0
|
|
510
510
|
while left_index < len(left) and right_index < len(right):
|
|
511
511
|
left_lower, left_upper = left[left_index]
|
|
@@ -527,11 +527,11 @@ def intersect_ranges(
|
|
|
527
527
|
|
|
528
528
|
|
|
529
529
|
def filter_by_ranges(
|
|
530
|
-
ranges: Sequence[
|
|
530
|
+
ranges: Sequence[Interval],
|
|
531
531
|
iterable: Iterable[Any],
|
|
532
532
|
key: Callable[[Any], Version | str] | None,
|
|
533
533
|
prereleases: bool | None,
|
|
534
|
-
region: Sequence[
|
|
534
|
+
region: Sequence[Interval] = (),
|
|
535
535
|
) -> Iterator[Any]:
|
|
536
536
|
"""Filter *iterable* against precomputed version *ranges*.
|
|
537
537
|
|
|
@@ -663,7 +663,7 @@ def _lowest_release_at_or_above(value: Version | BoundaryVersion | None) -> Vers
|
|
|
663
663
|
return _nearest_release_above_prerelease(value)
|
|
664
664
|
|
|
665
665
|
|
|
666
|
-
def ranges_are_prerelease_only(ranges: Sequence[
|
|
666
|
+
def ranges_are_prerelease_only(ranges: Sequence[Interval]) -> bool:
|
|
667
667
|
"""True when every range in *ranges* contains only pre-releases.
|
|
668
668
|
|
|
669
669
|
Used to detect unsatisfiable specifier sets when ``prereleases=False``:
|
|
@@ -678,7 +678,7 @@ def ranges_are_prerelease_only(ranges: Sequence[VersionRange]) -> bool:
|
|
|
678
678
|
return True
|
|
679
679
|
|
|
680
680
|
|
|
681
|
-
def wildcard_ranges(op: str, base: Version) -> list[
|
|
681
|
+
def wildcard_ranges(op: str, base: Version) -> list[Interval]:
|
|
682
682
|
"""Ranges for ==V.* and !=V.*.
|
|
683
683
|
|
|
684
684
|
==1.2.* -> [1.2.dev0, 1.3.dev0); !=1.2.* -> complement.
|
|
@@ -694,7 +694,7 @@ def wildcard_ranges(op: str, base: Version) -> list[VersionRange]:
|
|
|
694
694
|
]
|
|
695
695
|
|
|
696
696
|
|
|
697
|
-
def standard_ranges(op: str, version: Version, has_local: bool) -> list[
|
|
697
|
+
def standard_ranges(op: str, version: Version, has_local: bool) -> list[Interval]:
|
|
698
698
|
"""Ranges for the standard PEP 440 operators (no wildcard, no ===).
|
|
699
699
|
|
|
700
700
|
*has_local* indicates whether the spec string included a ``+local``
|
|
@@ -763,7 +763,7 @@ def standard_ranges(op: str, version: Version, has_local: bool) -> list[VersionR
|
|
|
763
763
|
raise ValueError(f"Unknown operator: {op!r}") # pragma: no cover
|
|
764
764
|
|
|
765
765
|
|
|
766
|
-
def bounds_for_spec(op: str, version_str: str, version: Version) -> list[
|
|
766
|
+
def bounds_for_spec(op: str, version_str: str, version: Version) -> list[Interval]:
|
|
767
767
|
"""Ranges for one specifier's ``(op, version_str)``.
|
|
768
768
|
|
|
769
769
|
Dispatches between the wildcard and standard builders. ``version`` is the
|
|
@@ -778,14 +778,14 @@ def bounds_for_spec(op: str, version_str: str, version: Version) -> list[Version
|
|
|
778
778
|
|
|
779
779
|
|
|
780
780
|
def intersect_specifier_bounds(
|
|
781
|
-
per_specifier_ranges: Iterable[Sequence[
|
|
782
|
-
) -> Sequence[
|
|
781
|
+
per_specifier_ranges: Iterable[Sequence[Interval]],
|
|
782
|
+
) -> Sequence[Interval]:
|
|
783
783
|
"""Intersect each specifier's ranges into a single sequence.
|
|
784
784
|
|
|
785
785
|
Short-circuits once the running intersection is empty, since no later
|
|
786
786
|
specifier can revive it. Callers must pass at least one specifier.
|
|
787
787
|
"""
|
|
788
|
-
result: Sequence[
|
|
788
|
+
result: Sequence[Interval] | None = None
|
|
789
789
|
for sub in per_specifier_ranges:
|
|
790
790
|
if result is None:
|
|
791
791
|
result = sub
|
|
@@ -800,7 +800,7 @@ def intersect_specifier_bounds(
|
|
|
800
800
|
return result
|
|
801
801
|
|
|
802
802
|
|
|
803
|
-
def matches_bounds_only(ranges: Sequence[
|
|
803
|
+
def matches_bounds_only(ranges: Sequence[Interval], version: Version) -> bool:
|
|
804
804
|
"""Whether ``version`` falls within any of ``ranges``.
|
|
805
805
|
|
|
806
806
|
The pure bounds membership test, for a single already-parsed version with
|
|
@@ -4,7 +4,7 @@ import re
|
|
|
4
4
|
from collections.abc import Mapping, Sequence
|
|
5
5
|
|
|
6
6
|
from .errors import _ErrorCollector
|
|
7
|
-
from .requirements import Requirement
|
|
7
|
+
from .requirements import InvalidRequirement, Requirement
|
|
8
8
|
|
|
9
9
|
__all__ = [
|
|
10
10
|
"CyclicDependencyGroup",
|
|
@@ -241,9 +241,10 @@ class DependencyGroupResolver:
|
|
|
241
241
|
for item in raw_group:
|
|
242
242
|
if isinstance(item, str):
|
|
243
243
|
# packaging.requirements.Requirement parsing ensures that this is a
|
|
244
|
-
# valid PEP 508 Dependency Specifier
|
|
245
|
-
#
|
|
246
|
-
|
|
244
|
+
# valid PEP 508 Dependency Specifier. Collect InvalidRequirement
|
|
245
|
+
# if it throws that.
|
|
246
|
+
with errors.collect(InvalidRequirement):
|
|
247
|
+
elements.append(Requirement(item))
|
|
247
248
|
elif isinstance(item, Mapping):
|
|
248
249
|
if tuple(item.keys()) != ("include-group",):
|
|
249
250
|
errors.error(
|
|
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Any, Protocol, TypeVar
|
|
|
9
9
|
if TYPE_CHECKING: # pragma: no cover
|
|
10
10
|
import sys
|
|
11
11
|
from collections.abc import Collection
|
|
12
|
+
from urllib.parse import SplitResult
|
|
12
13
|
|
|
13
14
|
if sys.version_info >= (3, 11):
|
|
14
15
|
from typing import Self
|
|
@@ -109,6 +110,10 @@ def _strip_url(url: str, safe_user_passwords: Collection[str]) -> str:
|
|
|
109
110
|
)
|
|
110
111
|
|
|
111
112
|
|
|
113
|
+
def _file_url_has_absolute_path(parsed_url: SplitResult) -> bool:
|
|
114
|
+
return parsed_url.path.startswith("/")
|
|
115
|
+
|
|
116
|
+
|
|
112
117
|
class DirectUrlValidationError(Exception):
|
|
113
118
|
"""Raised when when input data is not spec-compliant.
|
|
114
119
|
|
|
@@ -289,14 +294,18 @@ class DirectUrl:
|
|
|
289
294
|
raise DirectUrlValidationError(
|
|
290
295
|
"Exactly one of vcs_info, archive_info, dir_info must be present"
|
|
291
296
|
)
|
|
292
|
-
if
|
|
293
|
-
direct_url.
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
)
|
|
297
|
+
if direct_url.dir_info is not None:
|
|
298
|
+
parsed_url = urllib.parse.urlsplit(direct_url.url)
|
|
299
|
+
if parsed_url.scheme != "file":
|
|
300
|
+
raise DirectUrlValidationError(
|
|
301
|
+
"URL scheme must be file:// when dir_info is present",
|
|
302
|
+
context="url",
|
|
303
|
+
)
|
|
304
|
+
if not _file_url_has_absolute_path(parsed_url):
|
|
305
|
+
raise DirectUrlValidationError(
|
|
306
|
+
"File URL must be absolute when dir_info is present",
|
|
307
|
+
context="url",
|
|
308
|
+
)
|
|
300
309
|
# XXX subdirectory must be relative, can we, should we validate that here?
|
|
301
310
|
return direct_url
|
|
302
311
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import functools
|
|
7
8
|
import operator
|
|
8
9
|
import os
|
|
9
10
|
import platform
|
|
@@ -172,6 +173,18 @@ def _normalize_extras(
|
|
|
172
173
|
elif isinstance(rhs, Variable) and rhs.value == "extra" and isinstance(lhs, Value):
|
|
173
174
|
normalized_extra = canonicalize_name(lhs.value)
|
|
174
175
|
lhs = Value(normalized_extra)
|
|
176
|
+
elif (
|
|
177
|
+
isinstance(rhs, Variable)
|
|
178
|
+
and rhs.value in MARKERS_ALLOWING_SET
|
|
179
|
+
and isinstance(lhs, Value)
|
|
180
|
+
):
|
|
181
|
+
# PEP 685 (extras) / PEP 735 (dependency_groups): the set-valued membership
|
|
182
|
+
# literal must also be normalized. evaluate() already canonicalizes both
|
|
183
|
+
# operands for these keys (see _normalize), so normalizing the literal at
|
|
184
|
+
# parse time keeps __str__/__eq__/__hash__ consistent with evaluate() -- e.g.
|
|
185
|
+
# Marker('"Foo" in extras') and Marker('"foo" in extras') must compare and
|
|
186
|
+
# hash equal (the membership variable is always the right-hand operand).
|
|
187
|
+
lhs = Value(canonicalize_name(lhs.value))
|
|
175
188
|
return lhs, op, rhs
|
|
176
189
|
|
|
177
190
|
|
|
@@ -288,7 +301,13 @@ def _evaluate_markers(
|
|
|
288
301
|
environment_key = rhs.value
|
|
289
302
|
rhs_value = _lookup_environment(environment, environment_key)
|
|
290
303
|
|
|
291
|
-
|
|
304
|
+
if not isinstance(lhs_value, str):
|
|
305
|
+
raise UndefinedComparison(
|
|
306
|
+
f"Set-valued marker {environment_key!r} can only be used "
|
|
307
|
+
f'with the membership form (e.g. "<name>" in '
|
|
308
|
+
f"{environment_key}); it cannot appear on the left-hand "
|
|
309
|
+
f"side of {op.serialize()!r}."
|
|
310
|
+
)
|
|
292
311
|
lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
|
|
293
312
|
groups[-1].append(_eval_op(lhs_value, op, rhs_value, key=environment_key))
|
|
294
313
|
elif marker == "or":
|
|
@@ -309,10 +328,14 @@ def _format_full_version(info: sys._version_info) -> str:
|
|
|
309
328
|
return version
|
|
310
329
|
|
|
311
330
|
|
|
312
|
-
|
|
313
|
-
|
|
331
|
+
@functools.cache
|
|
332
|
+
def _cached_default_environment() -> Environment:
|
|
333
|
+
"""Build the default marker environment for the current Python process.
|
|
314
334
|
|
|
315
|
-
|
|
335
|
+
The values are derived from process-constant data (the running interpreter
|
|
336
|
+
and the host platform), so this is cached and built only once. The result is
|
|
337
|
+
shared between callers and must never be mutated; :func:`default_environment`
|
|
338
|
+
returns a fresh copy.
|
|
316
339
|
"""
|
|
317
340
|
iver = _format_full_version(sys.implementation.version)
|
|
318
341
|
implementation_name = sys.implementation.name
|
|
@@ -331,6 +354,22 @@ def default_environment() -> Environment:
|
|
|
331
354
|
}
|
|
332
355
|
|
|
333
356
|
|
|
357
|
+
def default_environment() -> Environment:
|
|
358
|
+
"""Return the default marker environment for the current Python process.
|
|
359
|
+
|
|
360
|
+
This is the base environment used by :meth:`Marker.evaluate`. A fresh copy
|
|
361
|
+
is returned on every call so callers may freely mutate the result; a shallow
|
|
362
|
+
copy suffices because all values are immutable strings.
|
|
363
|
+
|
|
364
|
+
.. versionchanged:: 26.3
|
|
365
|
+
The environment is computed once per process and cached, since it is
|
|
366
|
+
derived from process-constant data. Patching ``platform``/``sys``/``os``
|
|
367
|
+
after the first call has no effect; pass an explicit ``environment`` to
|
|
368
|
+
:meth:`Marker.evaluate` to evaluate against different values.
|
|
369
|
+
"""
|
|
370
|
+
return cast("Environment", dict(_cached_default_environment()))
|
|
371
|
+
|
|
372
|
+
|
|
334
373
|
class Marker:
|
|
335
374
|
"""Represents a parsed dependency marker expression.
|
|
336
375
|
|
|
@@ -228,13 +228,19 @@ def _get_payload(msg: email.message.Message, source: bytes | str) -> str:
|
|
|
228
228
|
# and we don't need to deal with it.
|
|
229
229
|
if isinstance(source, str):
|
|
230
230
|
payload = msg.get_payload()
|
|
231
|
-
|
|
231
|
+
# A multipart payload makes get_payload() return a list of messages
|
|
232
|
+
# rather than a str; route it to ``unparsed``.
|
|
233
|
+
if not isinstance(payload, str):
|
|
234
|
+
raise ValueError("payload is not a string") # noqa: TRY004
|
|
232
235
|
return payload
|
|
233
236
|
# If our source is a bytes, then we're managing the encoding and we need
|
|
234
237
|
# to deal with it.
|
|
235
238
|
else:
|
|
236
239
|
bpayload = msg.get_payload(decode=True)
|
|
237
|
-
|
|
240
|
+
# A multipart payload makes get_payload(decode=True) return None;
|
|
241
|
+
# route it to ``unparsed``.
|
|
242
|
+
if not isinstance(bpayload, bytes):
|
|
243
|
+
raise ValueError("payload in an invalid encoding") # noqa: TRY004
|
|
238
244
|
try:
|
|
239
245
|
return bpayload.decode("utf8", "strict")
|
|
240
246
|
except UnicodeDecodeError as exc:
|
|
@@ -105,7 +105,10 @@ def _get(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T | None:
|
|
|
105
105
|
"""Get a value from the dictionary and verify it's the expected type."""
|
|
106
106
|
if (value := d.get(key)) is None:
|
|
107
107
|
return None
|
|
108
|
-
if not isinstance(value, expected_type)
|
|
108
|
+
if not isinstance(value, expected_type) or (
|
|
109
|
+
# Special case: bool is a subclass of int, but TOML distinguishes the two
|
|
110
|
+
expected_type is int and isinstance(value, bool)
|
|
111
|
+
):
|
|
109
112
|
raise PylockValidationError(
|
|
110
113
|
f"Unexpected type {type(value).__name__} "
|
|
111
114
|
f"(expected {expected_type.__name__})",
|