Skip to main content

deps_pypi/
lib.rs

1//! PyPI/Python support for deps-lsp.
2//!
3//! This crate provides parsing, validation, and registry client functionality
4//! for Python dependency management in `pyproject.toml` files (PEP 621 and
5//! Poetry formats) and in `requirements.txt`/`constraints.txt` files (pip's
6//! requirements file format).
7//!
8//! # Features
9//!
10//! - **PEP 621 Support**: Parse `[project.dependencies]` and `[project.optional-dependencies]`
11//! - **Poetry Support**: Parse `[tool.poetry.dependencies]` and `[tool.poetry.group.*.dependencies]`
12//! - **requirements.txt / constraints.txt Support**: Parse pip's line-oriented requirements
13//!   file format, including comments, continuations, per-requirement options, and includes
14//! - **PEP 508 Parsing**: Handle complex dependency specifications with extras and markers
15//! - **PEP 440 Versions**: Validate and compare Python version specifiers
16//! - **PEP 503 Name Normalization**: One canonical normalizer ([`name::normalize`]) shared by
17//!   registry lookups, the formatter, and lock file parsing
18//! - **PyPI API Client**: Fetch package metadata from PyPI JSON API with HTTP caching
19//!
20//! # Architecture
21//!
22//! deps-pypi follows the same architecture as deps-cargo and deps-npm:
23//! - **Types**: `PypiDependency`, `PypiVersion`, `PypiPackage` with LSP range tracking
24//! - **Parser**: Parse both PEP 621 and Poetry formats using `toml-span`
25//! - **Registry**: PyPI JSON API client with HTTP caching
26//! - **Error Handling**: Typed errors with `thiserror`
27//!
28//! # Examples
29//!
30//! ## Parsing pyproject.toml
31//!
32//! ```no_run
33//! use deps_pypi::PypiParser;
34//! use tower_lsp_server::ls_types::Uri;
35//!
36//! let content = r#"
37//! [project]
38//! dependencies = [
39//!     "requests>=2.28.0,<3.0",
40//!     "flask[async]>=3.0",
41//! ]
42//! "#;
43//!
44//! let parser = PypiParser::new();
45//! let uri = Uri::from_file_path("/project/pyproject.toml").unwrap();
46//! let result = parser.parse_content(content, &uri).unwrap();
47//!
48//! assert_eq!(result.dependencies.len(), 2);
49//! assert_eq!(result.dependencies[0].name, "requests");
50//! assert_eq!(result.dependencies[1].extras, vec!["async"]);
51//! ```
52//!
53//! ## Fetching versions from PyPI
54//!
55//! ```no_run
56//! use deps_pypi::PypiRegistry;
57//! use deps_core::HttpCache;
58//! use std::sync::Arc;
59//!
60//! # #[tokio::main]
61//! # async fn main() {
62//! let cache = Arc::new(HttpCache::new());
63//! let registry = PypiRegistry::new(cache);
64//!
65//! let versions = registry.get_versions("requests").await.unwrap();
66//! assert!(!versions.is_empty());
67//!
68//! let latest = registry
69//!     .get_latest_matching("requests", ">=2.28.0,<3.0")
70//!     .await
71//!     .unwrap();
72//! assert!(latest.is_some());
73//! # }
74//! ```
75//!
76//! ## Supported Formats
77//!
78//! ### PEP 621 (Standard)
79//!
80//! ```toml
81//! [project]
82//! dependencies = [
83//!     "requests>=2.28.0,<3.0",
84//!     "flask[async]>=3.0",
85//!     "numpy>=1.24; python_version>='3.9'",
86//! ]
87//!
88//! [project.optional-dependencies]
89//! dev = ["pytest>=7.0", "mypy>=1.0"]
90//! ```
91//!
92//! ### Poetry
93//!
94//! ```toml
95//! [tool.poetry.dependencies]
96//! python = "^3.9"
97//! requests = "^2.28.0"
98//! flask = {version = "^3.0", extras = ["async"]}
99//!
100//! [tool.poetry.group.dev.dependencies]
101//! pytest = "^7.0"
102//! mypy = "^1.0"
103//! ```
104
105pub mod config;
106pub mod ecosystem;
107pub mod error;
108pub mod formatter;
109pub mod lockfile;
110pub mod name;
111pub mod parser;
112pub mod registry;
113mod search;
114pub mod types;
115
116// Re-export commonly used types
117pub use config::{PypiIndexConfig, PypiIndexUrl};
118pub use ecosystem::PypiEcosystem;
119pub use error::{PypiError, Result};
120pub use formatter::PypiFormatter;
121pub use lockfile::PypiLockParser;
122pub use parser::PypiParser;
123pub use registry::PypiRegistry;
124pub use types::{
125    PypiDependency, PypiDependencySection, PypiDependencySource, PypiPackage, PypiVersion,
126};