py_import_helper/registry/
mod.rs

1//! Package registry for categorizing Python imports
2//!
3//! This module provides a configurable registry for determining whether packages
4//! are standard library, third-party, or local imports. Users can update these
5//! registries at runtime to handle custom packages or Python version differences.
6
7pub mod constants;
8
9use constants::{COMMON_THIRD_PARTY_PACKAGES, PYTHON_STDLIB_MODULES};
10use std::collections::HashSet;
11
12/// Registry for package categorization
13///
14/// Maintains lists of known standard library and third-party packages.
15/// These can be updated by users to handle custom packages or different
16/// Python versions.
17#[derive(Debug, Clone)]
18pub struct PackageRegistry {
19    /// Known Python standard library packages
20    stdlib_packages: HashSet<String>,
21    /// Known third-party packages
22    third_party_packages: HashSet<String>,
23}
24
25impl PackageRegistry {
26    /// Create a new registry with default Python 3.13 stdlib and common third-party packages
27    #[must_use]
28    pub fn new() -> Self {
29        Self {
30            stdlib_packages: Self::default_stdlib_packages(),
31            third_party_packages: Self::default_third_party_packages(),
32        }
33    }
34
35    /// Check if a package is in the standard library
36    #[must_use]
37    pub fn is_stdlib(&self, package: &str) -> bool {
38        self.stdlib_packages.contains(package)
39    }
40
41    /// Check if a package is a known third-party package
42    #[must_use]
43    pub fn is_third_party(&self, package: &str) -> bool {
44        self.third_party_packages.contains(package)
45    }
46
47    /// Add a package to the standard library registry
48    ///
49    /// # Examples
50    ///
51    /// ```
52    /// use py_import_helper::ImportHelper;
53    ///
54    /// let mut helper = ImportHelper::new();
55    /// helper.registry_mut().add_stdlib_package("my_custom_stdlib");
56    /// ```
57    pub fn add_stdlib_package(&mut self, package: impl Into<String>) -> &mut Self {
58        self.stdlib_packages.insert(package.into());
59        self
60    }
61
62    /// Add a package to the third-party registry
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use py_import_helper::ImportHelper;
68    ///
69    /// let mut helper = ImportHelper::new();
70    /// helper.registry_mut().add_third_party_package("my_company_lib");
71    /// ```
72    pub fn add_third_party_package(&mut self, package: impl Into<String>) -> &mut Self {
73        self.third_party_packages.insert(package.into());
74        self
75    }
76
77    /// Remove a package from the standard library registry
78    pub fn remove_stdlib_package(&mut self, package: &str) -> &mut Self {
79        self.stdlib_packages.remove(package);
80        self
81    }
82
83    /// Remove a package from the third-party registry
84    pub fn remove_third_party_package(&mut self, package: &str) -> &mut Self {
85        self.third_party_packages.remove(package);
86        self
87    }
88
89    /// Clear all standard library packages
90    pub fn clear_stdlib_packages(&mut self) -> &mut Self {
91        self.stdlib_packages.clear();
92        self
93    }
94
95    /// Clear all third-party packages
96    pub fn clear_third_party_packages(&mut self) -> &mut Self {
97        self.third_party_packages.clear();
98        self
99    }
100
101    /// Add multiple standard library packages at once
102    pub fn add_stdlib_packages(&mut self, packages: &[&str]) -> &mut Self {
103        for package in packages {
104            self.stdlib_packages.insert((*package).to_string());
105        }
106        self
107    }
108
109    /// Add multiple third-party packages at once
110    pub fn add_third_party_packages(&mut self, packages: &[&str]) -> &mut Self {
111        for package in packages {
112            self.third_party_packages.insert((*package).to_string());
113        }
114        self
115    }
116
117    /// Get the default Python 3.13 standard library packages
118    fn default_stdlib_packages() -> HashSet<String> {
119        PYTHON_STDLIB_MODULES
120            .iter()
121            .map(|s| (*s).to_string())
122            .collect()
123    }
124
125    /// Get the default common third-party packages
126    fn default_third_party_packages() -> HashSet<String> {
127        COMMON_THIRD_PARTY_PACKAGES
128            .iter()
129            .map(|s| (*s).to_string())
130            .collect()
131    }
132
133    /// Reset to default stdlib packages (Python 3.13)
134    pub fn reset_stdlib_to_defaults(&mut self) -> &mut Self {
135        self.stdlib_packages = Self::default_stdlib_packages();
136        self
137    }
138
139    /// Reset to default third-party packages
140    pub fn reset_third_party_to_defaults(&mut self) -> &mut Self {
141        self.third_party_packages = Self::default_third_party_packages();
142        self
143    }
144
145    /// Get count of registered stdlib packages
146    #[must_use]
147    pub fn count_stdlib_packages(&self) -> usize {
148        self.stdlib_packages.len()
149    }
150
151    /// Get count of registered third-party packages
152    #[must_use]
153    pub fn count_third_party_packages(&self) -> usize {
154        self.third_party_packages.len()
155    }
156}
157
158impl Default for PackageRegistry {
159    fn default() -> Self {
160        Self::new()
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_default_registry() {
170        let registry = PackageRegistry::new();
171        assert!(registry.is_stdlib("typing"));
172        assert!(registry.is_stdlib("os"));
173        assert!(registry.is_third_party("pydantic"));
174        assert!(registry.is_third_party("pytest"));
175    }
176
177    #[test]
178    fn test_add_stdlib_package() {
179        let mut registry = PackageRegistry::new();
180        registry.add_stdlib_package("my_custom_stdlib");
181        assert!(registry.is_stdlib("my_custom_stdlib"));
182    }
183
184    #[test]
185    fn test_add_third_party_package() {
186        let mut registry = PackageRegistry::new();
187        registry.add_third_party_package("my_company_lib");
188        assert!(registry.is_third_party("my_company_lib"));
189    }
190
191    #[test]
192    fn test_remove_packages() {
193        let mut registry = PackageRegistry::new();
194        registry.remove_stdlib_package("typing");
195        assert!(!registry.is_stdlib("typing"));
196    }
197
198    #[test]
199    fn test_bulk_add() {
200        let mut registry = PackageRegistry::new();
201        registry.add_stdlib_packages(&["pkg1", "pkg2", "pkg3"]);
202        assert!(registry.is_stdlib("pkg1"));
203        assert!(registry.is_stdlib("pkg2"));
204        assert!(registry.is_stdlib("pkg3"));
205    }
206
207    #[test]
208    fn test_reset_to_defaults() {
209        let mut registry = PackageRegistry::new();
210        registry.clear_stdlib_packages();
211        assert_eq!(registry.count_stdlib_packages(), 0);
212
213        registry.reset_stdlib_to_defaults();
214        assert!(registry.count_stdlib_packages() > 0);
215        assert!(registry.is_stdlib("typing"));
216    }
217
218    #[test]
219    fn test_chaining() {
220        let mut registry = PackageRegistry::new();
221        registry
222            .add_stdlib_package("pkg1")
223            .add_stdlib_package("pkg2")
224            .add_third_party_package("lib1");
225
226        assert!(registry.is_stdlib("pkg1"));
227        assert!(registry.is_stdlib("pkg2"));
228        assert!(registry.is_third_party("lib1"));
229    }
230}