nab-python 0.0.6__tar.gz → 0.0.7__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.6 → nab_python-0.0.7}/PKG-INFO +3 -3
- {nab_python-0.0.6 → nab_python-0.0.7}/pyproject.toml +3 -3
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_build/runner.py +14 -10
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_lockfile/builder.py +62 -4
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_lockfile/pylock.py +29 -6
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_lockfile/requirements.py +16 -5
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_provider/extras.py +34 -20
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_provider/listing.py +16 -4
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_provider/metadata_resolver.py +46 -35
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_provider/priority.py +4 -2
- nab_python-0.0.7/src/nab_python/_provider/sources.py +396 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vcs_admission.py +38 -1
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/PROVENANCE.md +2 -2
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/markers.py +3 -5
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/build_backend.py +47 -27
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/config.py +92 -5
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/config_sources.py +34 -2
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/download.py +48 -9
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/lockfile.py +32 -1
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/metadata.py +14 -2
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/provider.py +104 -41
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/requirements_file.py +272 -20
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/resolve.py +24 -12
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/universal/provider.py +13 -7
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/universal/reresolve.py +13 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/universal/resolve.py +26 -5
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/universal/validate.py +19 -8
- nab_python-0.0.7/tests/property_python/test_cached_client_equiv.py +320 -0
- nab_python-0.0.7/tests/property_python/test_conflict_fork_markers.py +249 -0
- nab_python-0.0.7/tests/property_python/test_disjointness_differential.py +293 -0
- nab_python-0.0.7/tests/property_python/test_filename_differential.py +183 -0
- nab_python-0.0.7/tests/property_python/test_multi_index_contract.py +180 -0
- nab_python-0.0.7/tests/property_python/test_sdist_archive_safety.py +210 -0
- nab_python-0.0.7/tests/property_python/test_universal_faithfulness.py +259 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/property_python/test_vcs_admission.py +24 -2
- nab_python-0.0.7/tests/test_archive.py +442 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_async_transports.py +63 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_build_backend.py +62 -44
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_build_runner.py +4 -6
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_cache.py +30 -5
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_cached_client.py +95 -10
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_config.py +133 -2
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_config_sources.py +9 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_local_index.py +145 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_lockfile.py +214 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_marker_evaluation.py +38 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_metadata.py +24 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_provider.py +254 -61
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_requirements_file.py +395 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_resolve.py +107 -5
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_resolver_packaging.py +86 -67
- nab_python-0.0.7/tests/test_simple_client_malformed.py +92 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_vcs_admission.py +121 -3
- nab_python-0.0.7/tests/universal/property_universal/test_matrix_labels.py +158 -0
- nab_python-0.0.7/tests/universal/property_universal/test_reresolve_fixed_point.py +168 -0
- nab_python-0.0.7/tests/universal/property_universal/test_validate_mutations.py +184 -0
- nab_python-0.0.7/tests/universal/property_universal/test_wheel_selection_differential.py +316 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/universal/test_resolve.py +84 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/universal/test_universal_provider.py +71 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/universal/test_validate.py +128 -15
- nab_python-0.0.6/src/nab_python/_provider/sources.py +0 -223
- {nab_python-0.0.6 → nab_python-0.0.7}/.gitignore +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/LICENSE +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/README.md +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/README.md +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/_profile_runner.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/cache/.gitignore +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/canary.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/canary_results/.gitignore +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/compare.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/results/.gitignore +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/ai-stack-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/ai-stack-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/ai-stack.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/airflow-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/airflow-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/airflow.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/big-packages-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/big-packages-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/big-packages.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/cross-tracker-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/cross-tracker-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/cross-tracker.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/ecosystem-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/ecosystem-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/ecosystem.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/forums-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/forums-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/forums.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/pdm-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/pdm-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/pdm.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/pip-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/pip-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/pip.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/poetry-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/poetry-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/poetry.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/pytorch-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/pytorch-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/pytorch.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/quick-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/quick-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/quick.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/rip-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/rip-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/rip.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/universal.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/unsupported-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/unsupported-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/unsupported.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/uv-lowest-direct.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/uv-lowest.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios/uv.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/scenarios.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/strategy_sweep.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/strategy_sweep_results/summary.json +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/strategy_sweep_summary.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/universal_scenarios.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/benchmarks/universal_summary.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/__init__.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_build/__init__.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_build/env.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_build/errors.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_conflict_kind.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_lockfile/__init__.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_lockfile/disjointness.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_packaging_provider.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_provider/__init__.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_provider/build_remote.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_provider/lookahead.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_testing/__init__.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_testing/coordinator_fake.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_testing/overrides.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_toml.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/__init__.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/LICENSE +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/LICENSE.APACHE +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/LICENSE.BSD +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/__init__.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/_elffile.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/_manylinux.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/_musllinux.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/_parser.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/_ranges.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/_structures.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/_tokenizer.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/dependency_groups.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/direct_url.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/errors.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/licenses/__init__.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/licenses/_spdx.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/metadata.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/py.typed +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/pylock.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/ranges.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/requirements.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/specifiers.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/tags.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/utils.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/_vendor/packaging/version.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/fetch.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/py.typed +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/universal/__init__.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/universal/matrix.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/universal/wheel_selection.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/src/nab_python/workspace.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/__init__.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/property_python/__init__.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/property_python/strategies.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/property_python/test_build_policy_never.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/property_python/test_download_hashes.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/property_python/test_extras_pep685.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/property_python/test_fetch_coordinator.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/property_python/test_lockfile_pep751.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/property_python/test_marker_overlay.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/property_python/test_multi_index_pep503.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/property_python/test_override_overlap.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/property_python/test_prerelease_admission.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/property_python/test_resolver_pep440.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/ruff.toml +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_benchmark_parse_requirements.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_download.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_fetch.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_multi_index.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_simple_client_filenames.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_simple_client_hashes.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_simple_client_upload_time.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_vcs.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/test_workspace.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/universal/__init__.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/universal/property_universal/__init__.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/universal/property_universal/strategies.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/universal/property_universal/test_alignment.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/universal/property_universal/test_matrix.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/universal/property_universal/test_provider_pep425.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/universal/property_universal/test_validate.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/universal/test_matrix.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/tests/universal/test_reresolve.py +0 -0
- {nab_python-0.0.6 → nab_python-0.0.7}/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.7
|
|
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.7
|
|
24
|
+
Requires-Dist: nab-resolver==0.0.7
|
|
25
25
|
Requires-Dist: pyproject-hooks>=1.2
|
|
26
26
|
Requires-Dist: tomli-w>=1.2
|
|
27
27
|
Requires-Dist: tomli>=2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nab-python"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.7"
|
|
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.7",
|
|
23
|
+
"nab-index==0.0.7",
|
|
24
24
|
"tomli>=2.0",
|
|
25
25
|
"tomli_w>=1.2",
|
|
26
26
|
"build>=1.2",
|
|
@@ -33,12 +33,12 @@ import build
|
|
|
33
33
|
import pyproject_hooks
|
|
34
34
|
import tomli
|
|
35
35
|
|
|
36
|
-
from
|
|
36
|
+
from nab_resolver.resolver import ResolutionError
|
|
37
|
+
|
|
38
|
+
from .._vendor.packaging.requirements import InvalidRequirement, Requirement
|
|
37
39
|
from .._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
|
|
38
40
|
from .._vendor.packaging.utils import canonicalize_name
|
|
39
41
|
from .._vendor.packaging.version import InvalidVersion, Version
|
|
40
|
-
from nab_resolver.resolver import ResolutionError
|
|
41
|
-
|
|
42
42
|
from ..metadata import WheelMetadata
|
|
43
43
|
from .env import BuildEnvError, NabBuildEnv
|
|
44
44
|
from .errors import BuildBackendError
|
|
@@ -238,15 +238,19 @@ def _parse_metadata(metadata_path: Path) -> WheelMetadata:
|
|
|
238
238
|
SpecifierSet(requires_python_raw) if requires_python_raw else None
|
|
239
239
|
)
|
|
240
240
|
except InvalidSpecifier as exc:
|
|
241
|
-
msg =
|
|
241
|
+
msg = (
|
|
242
|
+
f"backend METADATA has invalid Requires-Python "
|
|
243
|
+
f"{requires_python_raw!r}: {exc}"
|
|
244
|
+
)
|
|
242
245
|
raise BuildBackendError(msg) from exc
|
|
243
246
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
try:
|
|
248
|
+
requires_dist: list[Requirement] = [
|
|
249
|
+
Requirement(raw) for raw in msg_obj.get_all("Requires-Dist") or ()
|
|
250
|
+
]
|
|
251
|
+
except InvalidRequirement as exc:
|
|
252
|
+
msg = f"backend METADATA has an invalid Requires-Dist: {exc}"
|
|
253
|
+
raise BuildBackendError(msg) from exc
|
|
250
254
|
|
|
251
255
|
provides_extra: list[str] = sorted(
|
|
252
256
|
{
|
|
@@ -21,6 +21,7 @@ from nab_index.client import SdistFile, WheelFile
|
|
|
21
21
|
|
|
22
22
|
from .._toml import tool_nab_section
|
|
23
23
|
from .._vendor.packaging.pylock import Pylock, PylockValidationError
|
|
24
|
+
from .._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
|
|
24
25
|
from .._vendor.packaging.utils import canonicalize_name
|
|
25
26
|
|
|
26
27
|
if TYPE_CHECKING:
|
|
@@ -30,6 +31,7 @@ if TYPE_CHECKING:
|
|
|
30
31
|
|
|
31
32
|
from .._vendor.packaging.version import Version
|
|
32
33
|
from ..lockfile import (
|
|
34
|
+
ArchivePin,
|
|
33
35
|
IndexPin,
|
|
34
36
|
LockInput,
|
|
35
37
|
PinShape,
|
|
@@ -37,7 +39,7 @@ if TYPE_CHECKING:
|
|
|
37
39
|
VcsPin,
|
|
38
40
|
WheelArtifact,
|
|
39
41
|
)
|
|
40
|
-
from ..provider import DistPolicy, LocalSource, VcsSource
|
|
42
|
+
from ..provider import ArchiveSource, DistPolicy, LocalSource, VcsSource
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
__all__ = [
|
|
@@ -95,6 +97,10 @@ class LockInputProvider(Protocol):
|
|
|
95
97
|
"""Return the configured VcsSource for ``canonical_name`` or None."""
|
|
96
98
|
...
|
|
97
99
|
|
|
100
|
+
def archive_source_for(self, canonical_name: str, /) -> ArchiveSource | None:
|
|
101
|
+
"""Return the configured ArchiveSource for ``canonical_name`` or None."""
|
|
102
|
+
...
|
|
103
|
+
|
|
98
104
|
def vcs_pin_for(self, canonical_name: str, /) -> str | None:
|
|
99
105
|
"""Return the resolved 40-char SHA captured during materialisation."""
|
|
100
106
|
...
|
|
@@ -287,6 +293,12 @@ def build_lock_input_from_provider( # noqa: PLR0913 - each flag maps to a disti
|
|
|
287
293
|
resolved_sha=provider.vcs_pin_for(canonical),
|
|
288
294
|
)
|
|
289
295
|
continue
|
|
296
|
+
archive_source = provider.archive_source_for(canonical)
|
|
297
|
+
if archive_source is not None:
|
|
298
|
+
lock_pins[canonical] = _archive_pin_from_source(
|
|
299
|
+
canonical, version, archive_source
|
|
300
|
+
)
|
|
301
|
+
continue
|
|
290
302
|
lock_pins[canonical] = _index_pin_from_listing(
|
|
291
303
|
provider, canonical, version, indexes
|
|
292
304
|
)
|
|
@@ -339,6 +351,9 @@ def _forward_dependency_graph(
|
|
|
339
351
|
for dep in extra_map.get(extra, {})
|
|
340
352
|
)
|
|
341
353
|
dep_names &= pinned
|
|
354
|
+
# An umbrella extra (pkg[all] pulling pkg[graphviz]) can name its own
|
|
355
|
+
# package; drop it so pkg is never an edge to itself.
|
|
356
|
+
dep_names.discard(canonical)
|
|
342
357
|
if dep_names:
|
|
343
358
|
graph[canonical] = tuple(sorted(dep_names))
|
|
344
359
|
return graph
|
|
@@ -519,11 +534,27 @@ def require_artifact_hashes(lock_input: LockInput) -> None:
|
|
|
519
534
|
|
|
520
535
|
|
|
521
536
|
def _common_requires_python(files: Iterable[WheelFile | SdistFile]) -> str | None:
|
|
522
|
-
"""Return
|
|
537
|
+
"""Return the package-level Requires-Python value, or ``None``.
|
|
538
|
+
|
|
539
|
+
An artefact with no Requires-Python is unconstrained, so a single
|
|
540
|
+
such artefact leaves the whole package unconstrained. Otherwise the
|
|
541
|
+
value survives only when every artefact agrees.
|
|
542
|
+
|
|
543
|
+
A malformed value counts as unconstrained too, matching
|
|
544
|
+
``excluded_by_python``, which admits a dist whose Requires-Python is
|
|
545
|
+
an unparseable specifier on any Python. So the lock writer always
|
|
546
|
+
parses a valid specifier or ``None``, and the pin is never
|
|
547
|
+
over-constrained by an artefact whose floor nab could not read.
|
|
548
|
+
"""
|
|
523
549
|
seen: set[str] = set()
|
|
524
550
|
for f in files:
|
|
525
|
-
if f.requires_python is
|
|
526
|
-
|
|
551
|
+
if f.requires_python is None:
|
|
552
|
+
return None
|
|
553
|
+
try:
|
|
554
|
+
SpecifierSet(f.requires_python)
|
|
555
|
+
except InvalidSpecifier:
|
|
556
|
+
return None
|
|
557
|
+
seen.add(f.requires_python)
|
|
527
558
|
if len(seen) == 1:
|
|
528
559
|
return next(iter(seen))
|
|
529
560
|
return None
|
|
@@ -593,3 +624,30 @@ def _vcs_pin_from_source(
|
|
|
593
624
|
requested_revision=requested_revision,
|
|
594
625
|
vcs_type=parsed.scheme,
|
|
595
626
|
)
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def _archive_pin_from_source(
|
|
630
|
+
canonical: str,
|
|
631
|
+
version: Version,
|
|
632
|
+
source: ArchiveSource,
|
|
633
|
+
) -> ArchivePin:
|
|
634
|
+
"""Build an :class:`ArchivePin` from an :class:`ArchiveSource`.
|
|
635
|
+
|
|
636
|
+
The URL, hashes, and subdirectory come from the source declaration,
|
|
637
|
+
which config parse validated (a hash is required), so the pin records
|
|
638
|
+
the exact archive the resolve used. The URL is stripped of any
|
|
639
|
+
credential userinfo, like every other pin, so a committed lockfile
|
|
640
|
+
never carries a token.
|
|
641
|
+
"""
|
|
642
|
+
from nab_index.archive import ArchiveRequest
|
|
643
|
+
|
|
644
|
+
from ..lockfile import ArchivePin
|
|
645
|
+
|
|
646
|
+
request = ArchiveRequest.parse(source.url)
|
|
647
|
+
return ArchivePin(
|
|
648
|
+
name=canonical,
|
|
649
|
+
version=str(version),
|
|
650
|
+
url=_strip_userinfo(request.url),
|
|
651
|
+
hashes=request.hashes,
|
|
652
|
+
subdirectory=request.subdirectory or None,
|
|
653
|
+
)
|
|
@@ -21,6 +21,7 @@ import tomli_w
|
|
|
21
21
|
from .._vendor.packaging.markers import Marker
|
|
22
22
|
from .._vendor.packaging.pylock import (
|
|
23
23
|
Package,
|
|
24
|
+
PackageArchive,
|
|
24
25
|
PackageDirectory,
|
|
25
26
|
PackageSdist,
|
|
26
27
|
PackageVcs,
|
|
@@ -214,7 +215,7 @@ def _pin_to_package(
|
|
|
214
215
|
lock_dir: Path,
|
|
215
216
|
dependencies: list[dict[str, str]] | None = None,
|
|
216
217
|
) -> Package:
|
|
217
|
-
from ..lockfile import IndexPin, LocalPin, VcsPin
|
|
218
|
+
from ..lockfile import ArchivePin, IndexPin, LocalPin, VcsPin
|
|
218
219
|
|
|
219
220
|
if isinstance(pin, IndexPin):
|
|
220
221
|
return Package(
|
|
@@ -267,6 +268,20 @@ def _pin_to_package(
|
|
|
267
268
|
requested_revision=pin.requested_revision,
|
|
268
269
|
),
|
|
269
270
|
)
|
|
271
|
+
if isinstance(pin, ArchivePin):
|
|
272
|
+
# An archive is content-pinned by its hash, so the version is
|
|
273
|
+
# stable and recorded (unlike the directory/VCS cases above).
|
|
274
|
+
return Package(
|
|
275
|
+
name=canonicalize_name(pin.name),
|
|
276
|
+
version=Version(pin.version),
|
|
277
|
+
marker=marker,
|
|
278
|
+
dependencies=dependencies,
|
|
279
|
+
archive=PackageArchive(
|
|
280
|
+
url=pin.url,
|
|
281
|
+
hashes=dict(pin.hashes),
|
|
282
|
+
subdirectory=pin.subdirectory,
|
|
283
|
+
),
|
|
284
|
+
)
|
|
270
285
|
msg = f"unknown pin shape: {pin!r}"
|
|
271
286
|
raise TypeError(msg)
|
|
272
287
|
|
|
@@ -423,7 +438,7 @@ def _group_pins_by_pin(
|
|
|
423
438
|
|
|
424
439
|
def _pin_discriminator(pin: PinShape) -> tuple:
|
|
425
440
|
"""Return a hashable key that identifies the source + version of ``pin``."""
|
|
426
|
-
from ..lockfile import IndexPin, LocalPin, VcsPin
|
|
441
|
+
from ..lockfile import ArchivePin, IndexPin, LocalPin, VcsPin
|
|
427
442
|
|
|
428
443
|
if isinstance(pin, IndexPin):
|
|
429
444
|
return ("index", pin.version, pin.index)
|
|
@@ -447,6 +462,8 @@ def _pin_discriminator(pin: PinShape) -> tuple:
|
|
|
447
462
|
pin.repo_url,
|
|
448
463
|
pin.subdirectory or "",
|
|
449
464
|
)
|
|
465
|
+
if isinstance(pin, ArchivePin):
|
|
466
|
+
return ("archive", pin.version, pin.url, pin.hashes, pin.subdirectory or "")
|
|
450
467
|
msg = f"unknown pin shape: {pin!r}"
|
|
451
468
|
raise TypeError(msg)
|
|
452
469
|
|
|
@@ -456,8 +473,9 @@ def _merge_pins_in_group(pins: list[PinShape]) -> PinShape:
|
|
|
456
473
|
|
|
457
474
|
For :class:`IndexPin`, accumulates every distinct wheel filename
|
|
458
475
|
across the contributing tuples and keeps the first non-``None``
|
|
459
|
-
sdist. ``requires_python`` survives only when every tuple
|
|
460
|
-
|
|
476
|
+
sdist. ``requires_python`` survives only when every tuple carried
|
|
477
|
+
the same value and none was unconstrained, matching
|
|
478
|
+
:func:`_common_requires_python`'s rule.
|
|
461
479
|
Non-IndexPin shapes are already fully discriminated, so the first
|
|
462
480
|
pin is returned unchanged.
|
|
463
481
|
"""
|
|
@@ -469,16 +487,21 @@ def _merge_pins_in_group(pins: list[PinShape]) -> PinShape:
|
|
|
469
487
|
seen_wheels: dict[str, WheelArtifact] = {}
|
|
470
488
|
sdist = head.sdist
|
|
471
489
|
requires_python_set: set[str] = set()
|
|
490
|
+
any_unconstrained = False
|
|
472
491
|
for pin in pins:
|
|
473
492
|
assert isinstance(pin, IndexPin)
|
|
474
493
|
for wheel in pin.wheels:
|
|
475
494
|
seen_wheels.setdefault(wheel.filename, wheel)
|
|
476
495
|
if sdist is None and pin.sdist is not None:
|
|
477
496
|
sdist = pin.sdist
|
|
478
|
-
if pin.requires_python is
|
|
497
|
+
if pin.requires_python is None:
|
|
498
|
+
any_unconstrained = True
|
|
499
|
+
else:
|
|
479
500
|
requires_python_set.add(pin.requires_python)
|
|
480
501
|
requires_python = (
|
|
481
|
-
next(iter(requires_python_set))
|
|
502
|
+
next(iter(requires_python_set))
|
|
503
|
+
if not any_unconstrained and len(requires_python_set) == 1
|
|
504
|
+
else None
|
|
482
505
|
)
|
|
483
506
|
return IndexPin(
|
|
484
507
|
name=head.name,
|
|
@@ -38,8 +38,11 @@ def write_requirements_with_hashes(
|
|
|
38
38
|
accepts. Local and VCS pins are emitted as ``name @ <url>`` lines
|
|
39
39
|
without hashes (pip does not hash-check those forms); an editable
|
|
40
40
|
local pin renders as ``-e <url>`` and a ``subdirectory`` as a
|
|
41
|
-
``#subdirectory=`` fragment.
|
|
42
|
-
|
|
41
|
+
``#subdirectory=`` fragment. An archive pin is a third form, ``name @
|
|
42
|
+
<url>#sha256=...``, carrying its hash in the fragment;
|
|
43
|
+
:func:`require_artifact_hashes` skips it because that hash is guaranteed
|
|
44
|
+
at config parse. Returns the text and, when ``output_path`` is provided,
|
|
45
|
+
atomically writes it.
|
|
43
46
|
"""
|
|
44
47
|
require_artifact_hashes(lock_input)
|
|
45
48
|
return _render_requirements(lock_input, with_hashes=True, output_path=output_path)
|
|
@@ -51,8 +54,8 @@ def write_requirements_without_hashes(
|
|
|
51
54
|
"""Render ``lock_input`` as a plain ``name==version`` list.
|
|
52
55
|
|
|
53
56
|
Same shape as :func:`write_requirements_with_hashes` but without
|
|
54
|
-
the ``--hash=sha256:...`` lines. Local and
|
|
55
|
-
same in both variants. Returns the text and, when ``output_path``
|
|
57
|
+
the ``--hash=sha256:...`` lines. Local, VCS, and archive pins render
|
|
58
|
+
the same in both variants. Returns the text and, when ``output_path``
|
|
56
59
|
is provided, atomically writes it.
|
|
57
60
|
"""
|
|
58
61
|
return _render_requirements(lock_input, with_hashes=False, output_path=output_path)
|
|
@@ -94,7 +97,7 @@ def _render_per_tuple_requirements(lock_input: LockInput, *, with_hashes: bool)
|
|
|
94
97
|
|
|
95
98
|
def _render_pins(pins: Mapping[str, PinShape], *, with_hashes: bool) -> list[str]:
|
|
96
99
|
"""Render a flat ``{name: pin}`` mapping in alphabetical order."""
|
|
97
|
-
from ..lockfile import IndexPin, LocalPin, VcsPin
|
|
100
|
+
from ..lockfile import ArchivePin, IndexPin, LocalPin, VcsPin
|
|
98
101
|
|
|
99
102
|
lines: list[str] = []
|
|
100
103
|
for canonical in sorted(pins):
|
|
@@ -112,6 +115,14 @@ def _render_pins(pins: Mapping[str, PinShape], *, with_hashes: bool) -> list[str
|
|
|
112
115
|
lines.append(f"{pin.name} @ {url}")
|
|
113
116
|
elif isinstance(pin, VcsPin):
|
|
114
117
|
lines.append(f"{pin.name} @ {pin.repo_url}")
|
|
118
|
+
elif isinstance(pin, ArchivePin):
|
|
119
|
+
# The hash is the archive's identity, so carry it (and any
|
|
120
|
+
# subdirectory) in the fragment for a reproducible, hash-checkable
|
|
121
|
+
# install line, mirroring how VcsPin pins its commit in the URL.
|
|
122
|
+
fragment = "&".join(f"{algo}={digest}" for algo, digest in pin.hashes)
|
|
123
|
+
if pin.subdirectory is not None:
|
|
124
|
+
fragment += f"&subdirectory={quote(pin.subdirectory, safe='/')}"
|
|
125
|
+
lines.append(f"{pin.name} @ {pin.url}#{fragment}")
|
|
115
126
|
else: # pragma: no cover - exhaustive
|
|
116
127
|
msg = f"unknown pin shape: {pin!r}"
|
|
117
128
|
raise TypeError(msg)
|
|
@@ -77,23 +77,15 @@ def _pick_in_mode(
|
|
|
77
77
|
) -> Version | None:
|
|
78
78
|
"""Pick a candidate honoring ``ExtrasMode``.
|
|
79
79
|
|
|
80
|
-
Fetches base metadata so an extraction failure (unparseable
|
|
81
|
-
|
|
82
|
-
candidate
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Missing-metadata cases (no PEP 658, no sdist) skip transitive
|
|
87
|
-
extras but fall through for user-requested ones; mock test
|
|
88
|
-
coordinators rely on this.
|
|
80
|
+
Fetches base metadata so an extraction failure (unparseable PKG-INFO,
|
|
81
|
+
a disallowed sdist build, or no metadata source at all) skips the
|
|
82
|
+
candidate rather than raising later, when the proxy refetches the base
|
|
83
|
+
to expand the extra. This applies to user-requested extras too, since
|
|
84
|
+
the proxy always needs the base metadata. BACKTRACK mode additionally
|
|
85
|
+
checks ``Provides-Extra`` for transitive extras.
|
|
89
86
|
"""
|
|
90
87
|
# Late import: ``pypi`` imports this module at module load.
|
|
91
|
-
from ..provider import
|
|
92
|
-
ExtrasMode,
|
|
93
|
-
MetadataError,
|
|
94
|
-
UnsupportedSdistError,
|
|
95
|
-
_normalize_extra,
|
|
96
|
-
)
|
|
88
|
+
from ..provider import ExtrasMode, MetadataError, _normalize_extra
|
|
97
89
|
|
|
98
90
|
_, _, normalized = provider.split_and_normalize(base)
|
|
99
91
|
is_user = (normalized, extra) in provider.root_extras
|
|
@@ -103,12 +95,8 @@ def _pick_in_mode(
|
|
|
103
95
|
continue
|
|
104
96
|
try:
|
|
105
97
|
provider.get_dependencies(base, version)
|
|
106
|
-
except UnsupportedSdistError:
|
|
107
|
-
continue
|
|
108
98
|
except MetadataError:
|
|
109
|
-
|
|
110
|
-
continue
|
|
111
|
-
return version
|
|
99
|
+
continue
|
|
112
100
|
if is_user or not backtrack:
|
|
113
101
|
return version
|
|
114
102
|
metadata = provider.metadata_cache.get((normalized, version))
|
|
@@ -122,6 +110,32 @@ def _pick_in_mode(
|
|
|
122
110
|
return None
|
|
123
111
|
|
|
124
112
|
|
|
113
|
+
def version_provides_extra(
|
|
114
|
+
provider: Provider,
|
|
115
|
+
base: str,
|
|
116
|
+
extra: str,
|
|
117
|
+
version: Version,
|
|
118
|
+
) -> bool:
|
|
119
|
+
"""Whether ``version`` of ``base`` declares ``extra`` and yields metadata here.
|
|
120
|
+
|
|
121
|
+
Honoring a cross-tuple preference for a ``base[extra]`` proxy is only
|
|
122
|
+
safe when the preferred version both provides the extra and has
|
|
123
|
+
extractable metadata in this tuple.
|
|
124
|
+
"""
|
|
125
|
+
# Late import: provider imports this module at module load.
|
|
126
|
+
from ..provider import MetadataError, _normalize_extra
|
|
127
|
+
|
|
128
|
+
_, _, normalized = provider.split_and_normalize(base)
|
|
129
|
+
try:
|
|
130
|
+
provider.get_dependencies(base, version)
|
|
131
|
+
except MetadataError:
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
metadata = provider.metadata_cache[(normalized, version)]
|
|
135
|
+
provided = {_normalize_extra(e) for e in metadata.provides_extra}
|
|
136
|
+
return extra in provided
|
|
137
|
+
|
|
138
|
+
|
|
125
139
|
def _record_base_range_blocks(
|
|
126
140
|
provider: Provider,
|
|
127
141
|
proxy_pkg: str,
|
|
@@ -50,6 +50,14 @@ def fetch_versions(provider: Provider, package: str) -> list[tuple[Version, Dist
|
|
|
50
50
|
provider.versions_cache[normalized] = result
|
|
51
51
|
return result
|
|
52
52
|
|
|
53
|
+
archive = provider.archive_sources.get(normalized)
|
|
54
|
+
if archive is not None:
|
|
55
|
+
# The download-and-verify guards are gated everywhere; only the
|
|
56
|
+
# post-extraction success tail needs the tar data filter (see sources.py).
|
|
57
|
+
result = provider.materialize_archive_source(normalized, archive)
|
|
58
|
+
provider.versions_cache[normalized] = result # pragma: no cover
|
|
59
|
+
return result # pragma: no cover
|
|
60
|
+
|
|
53
61
|
files = provider.coordinator.index.get_listing(normalized)
|
|
54
62
|
if files is None:
|
|
55
63
|
event = provider.coordinator.request_listing(normalized)
|
|
@@ -572,13 +580,17 @@ def prefetch_new_deps(provider: Provider, deps: Mapping[str, VersionRange]) -> N
|
|
|
572
580
|
candidate. This deepens the prefetch cascade so metadata is
|
|
573
581
|
ready before the resolver asks for it.
|
|
574
582
|
|
|
575
|
-
Local and
|
|
576
|
-
and the materialise path in ``fetch_versions`` will
|
|
577
|
-
them when the resolver asks.
|
|
583
|
+
Local, VCS, and archive sources are skipped; they have no PyPI
|
|
584
|
+
listing and the materialise path in ``fetch_versions`` will
|
|
585
|
+
surface them when the resolver asks.
|
|
578
586
|
"""
|
|
579
587
|
for dep in deps:
|
|
580
588
|
_, _, normalized = provider.split_and_normalize(dep)
|
|
581
|
-
if
|
|
589
|
+
if (
|
|
590
|
+
normalized in provider.local_sources
|
|
591
|
+
or normalized in provider.vcs_sources
|
|
592
|
+
or normalized in provider.archive_sources
|
|
593
|
+
):
|
|
582
594
|
continue
|
|
583
595
|
if normalized not in provider.versions_cache:
|
|
584
596
|
# Listing not cached: request it. When it arrives,
|
|
@@ -11,11 +11,11 @@ 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
15
|
|
|
15
16
|
from .._conflict_kind import EMPTY_MEMBERSHIP_SETS
|
|
16
17
|
from .._vcs_admission import admit_vcs_url
|
|
17
18
|
from .._vendor.packaging.ranges import VersionRange
|
|
18
|
-
from .._vendor.packaging.requirements import InvalidRequirement, Requirement
|
|
19
19
|
from .._vendor.packaging.specifiers import SpecifierSet
|
|
20
20
|
from .._vendor.packaging.utils import canonicalize_name
|
|
21
21
|
from ..metadata import (
|
|
@@ -24,11 +24,18 @@ from ..metadata import (
|
|
|
24
24
|
metadata_deps_are_static,
|
|
25
25
|
parse_metadata,
|
|
26
26
|
)
|
|
27
|
+
from ..requirements_file import (
|
|
28
|
+
InvalidProjectRequirementError,
|
|
29
|
+
_parse_project_requirement,
|
|
30
|
+
_parse_requirements,
|
|
31
|
+
_require_string_list,
|
|
32
|
+
)
|
|
27
33
|
|
|
28
34
|
if TYPE_CHECKING:
|
|
29
35
|
from collections.abc import Sequence
|
|
30
36
|
|
|
31
37
|
from .._vendor.packaging.markers import Marker
|
|
38
|
+
from .._vendor.packaging.requirements import Requirement
|
|
32
39
|
from .._vendor.packaging.version import Version
|
|
33
40
|
from ..provider import DistFile, Provider
|
|
34
41
|
|
|
@@ -47,7 +54,7 @@ def resolve_metadata(
|
|
|
47
54
|
only sdist values are subject to the :pep:`643` Dynamic
|
|
48
55
|
guarantees and may need a ``pyproject.toml`` fallback.
|
|
49
56
|
"""
|
|
50
|
-
# Late import: ``
|
|
57
|
+
# Late import: ``provider`` imports this module at module load.
|
|
51
58
|
from ..provider import MetadataError
|
|
52
59
|
|
|
53
60
|
_, _, normalized = provider.split_and_normalize(package)
|
|
@@ -78,6 +85,8 @@ def resolve_metadata(
|
|
|
78
85
|
if integrity_error is not None:
|
|
79
86
|
raise integrity_error
|
|
80
87
|
metadata_text = provider.coordinator.index.get_metadata(normalized, ver_str)
|
|
88
|
+
elif isinstance(dist, WheelFile) and dist.local_path is not None:
|
|
89
|
+
metadata_text = read_wheel_metadata(dist.local_path)
|
|
81
90
|
else:
|
|
82
91
|
metadata_text = None
|
|
83
92
|
|
|
@@ -210,8 +219,15 @@ def augment_from_pyproject(
|
|
|
210
219
|
"""Replace dynamic deps with statically-declared pyproject deps.
|
|
211
220
|
|
|
212
221
|
Returns the augmented metadata, or ``None`` if pyproject.toml
|
|
213
|
-
is missing,
|
|
222
|
+
is missing, unparseable, or itself marks deps dynamic via
|
|
214
223
|
``[project].dynamic``.
|
|
224
|
+
|
|
225
|
+
Raises :class:`InvalidProjectRequirementError` when ``dependencies``
|
|
226
|
+
or ``optional-dependencies`` is present but structurally wrong (not
|
|
227
|
+
an array of strings / not a table), rather than silently dropping the
|
|
228
|
+
declared dependencies. ``get_dependencies`` catches it and rejects the
|
|
229
|
+
candidate version. A well-typed entry that is not valid PEP 508 is
|
|
230
|
+
dropped with a warning.
|
|
215
231
|
"""
|
|
216
232
|
# Late import keeps the resolver-time path off ``WheelMetadata``
|
|
217
233
|
# construction unless the dynamic-deps pyproject fallback fires.
|
|
@@ -223,15 +239,16 @@ def augment_from_pyproject(
|
|
|
223
239
|
if project is None:
|
|
224
240
|
return None
|
|
225
241
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
optional = project.get("optional-dependencies")
|
|
230
|
-
if
|
|
231
|
-
|
|
242
|
+
deps = _require_string_list(
|
|
243
|
+
project.get("dependencies", []), "[project].dependencies"
|
|
244
|
+
)
|
|
245
|
+
optional = project.get("optional-dependencies", {})
|
|
246
|
+
if not isinstance(optional, dict):
|
|
247
|
+
msg = "[project].optional-dependencies must be a table"
|
|
248
|
+
raise InvalidProjectRequirementError(msg)
|
|
232
249
|
|
|
233
|
-
requires_dist = list(parse_pyproject_deps(
|
|
234
|
-
provides_extra = extend_with_extras(requires_dist, optional
|
|
250
|
+
requires_dist = list(parse_pyproject_deps(deps))
|
|
251
|
+
provides_extra = extend_with_extras(requires_dist, optional)
|
|
235
252
|
|
|
236
253
|
provider.stats.sdist_pyproject_fallbacks += 1
|
|
237
254
|
return _WheelMetadata(
|
|
@@ -246,38 +263,32 @@ def augment_from_pyproject(
|
|
|
246
263
|
|
|
247
264
|
|
|
248
265
|
def extend_with_extras(requires_dist: list[Requirement], optional: dict) -> list[str]:
|
|
249
|
-
"""Append extras-gated requirements and return Provides-Extra names.
|
|
250
|
-
# Late import: ``pypi`` imports this module at module load.
|
|
251
|
-
from ..provider import _add_extra_marker
|
|
266
|
+
"""Append extras-gated requirements and return Provides-Extra names.
|
|
252
267
|
|
|
268
|
+
A per-extra value that is not an array of strings, or a per-extra entry
|
|
269
|
+
that is not valid PEP 508, raises :class:`InvalidProjectRequirementError`,
|
|
270
|
+
so the version is rejected rather than resolved with the entry dropped.
|
|
271
|
+
"""
|
|
253
272
|
provides_extra: list[str] = []
|
|
254
273
|
for extra_name, extra_deps in optional.items():
|
|
255
|
-
|
|
256
|
-
continue
|
|
274
|
+
source = f"[project].optional-dependencies extra {extra_name!r}"
|
|
257
275
|
provides_extra.append(extra_name)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
requires_dist.append(
|
|
263
|
-
Requirement(_add_extra_marker(dep_str, extra_name))
|
|
264
|
-
)
|
|
265
|
-
except InvalidRequirement:
|
|
266
|
-
continue
|
|
276
|
+
requires_dist.extend(
|
|
277
|
+
_parse_project_requirement(dep_str, source, extra=extra_name)
|
|
278
|
+
for dep_str in _require_string_list(extra_deps, source)
|
|
279
|
+
)
|
|
267
280
|
return provides_extra
|
|
268
281
|
|
|
269
282
|
|
|
270
283
|
def parse_pyproject_deps(deps: list) -> list[Requirement]:
|
|
271
|
-
"""Parse a ``project.dependencies`` list,
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
continue
|
|
280
|
-
return out
|
|
284
|
+
"""Parse a ``project.dependencies`` list, raising on a malformed entry.
|
|
285
|
+
|
|
286
|
+
Entries are already validated as strings by :func:`_require_string_list`;
|
|
287
|
+
a string that is not valid PEP 508 raises
|
|
288
|
+
:class:`InvalidProjectRequirementError`, so the whole version is rejected
|
|
289
|
+
rather than resolved with the dependency dropped.
|
|
290
|
+
"""
|
|
291
|
+
return _parse_requirements(deps, "[project].dependencies")
|
|
281
292
|
|
|
282
293
|
|
|
283
294
|
def find_sdist(
|
|
@@ -74,10 +74,12 @@ def compute_matching(
|
|
|
74
74
|
if cached is not None:
|
|
75
75
|
return cached
|
|
76
76
|
|
|
77
|
-
# Local/VCS sources short-circuit the listing path; their synthetic
|
|
77
|
+
# Local/VCS/archive sources short-circuit the listing path; their synthetic
|
|
78
78
|
# listing is materialised lazily by fetch_versions.
|
|
79
79
|
has_local_source = (
|
|
80
|
-
normalized in provider.local_sources
|
|
80
|
+
normalized in provider.local_sources
|
|
81
|
+
or normalized in provider.vcs_sources
|
|
82
|
+
or normalized in provider.archive_sources
|
|
81
83
|
)
|
|
82
84
|
if normalized not in provider.versions_cache and not has_local_source:
|
|
83
85
|
files = provider.coordinator.index.get_listing(normalized)
|