grimp 3.19__tar.gz → 3.20__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.
Files changed (53) hide show
  1. {grimp-3.19 → grimp-3.20}/PKG-INFO +1 -1
  2. {grimp-3.19 → grimp-3.20}/pyproject.toml +1 -1
  3. grimp-3.20/rust/src/graph/direct_import_queries.rs +328 -0
  4. {grimp-3.19 → grimp-3.20}/rust/src/graph/graph_manipulation.rs +4 -12
  5. {grimp-3.19 → grimp-3.20}/rust/src/graph/hierarchy_queries.rs +15 -6
  6. {grimp-3.19 → grimp-3.20}/rust/src/graph/mod.rs +14 -4
  7. {grimp-3.19 → grimp-3.20}/src/grimp/__init__.py +1 -1
  8. {grimp-3.19 → grimp-3.20}/src/grimp/application/graph.py +18 -4
  9. grimp-3.19/rust/src/graph/direct_import_queries.rs +0 -158
  10. {grimp-3.19 → grimp-3.20}/AUTHORS.rst +0 -0
  11. {grimp-3.19 → grimp-3.20}/LICENSE +0 -0
  12. {grimp-3.19 → grimp-3.20}/README.rst +0 -0
  13. {grimp-3.19 → grimp-3.20}/rust/Cargo.lock +0 -0
  14. {grimp-3.19 → grimp-3.20}/rust/Cargo.toml +0 -0
  15. {grimp-3.19 → grimp-3.20}/rust/build.rs +0 -0
  16. {grimp-3.19 → grimp-3.20}/rust/rust-toolchain.toml +0 -0
  17. {grimp-3.19 → grimp-3.20}/rust/src/caching.rs +0 -0
  18. {grimp-3.19 → grimp-3.20}/rust/src/errors.rs +0 -0
  19. {grimp-3.19 → grimp-3.20}/rust/src/exceptions.rs +0 -0
  20. {grimp-3.19 → grimp-3.20}/rust/src/filesystem.rs +0 -0
  21. {grimp-3.19 → grimp-3.20}/rust/src/graph/cycle_breakers.rs +0 -0
  22. {grimp-3.19 → grimp-3.20}/rust/src/graph/higher_order_queries.rs +0 -0
  23. {grimp-3.19 → grimp-3.20}/rust/src/graph/import_chain_queries.rs +0 -0
  24. {grimp-3.19 → grimp-3.20}/rust/src/graph/pathfinding.rs +0 -0
  25. {grimp-3.19 → grimp-3.20}/rust/src/import_parsing.rs +0 -0
  26. {grimp-3.19 → grimp-3.20}/rust/src/import_scanning.rs +0 -0
  27. {grimp-3.19 → grimp-3.20}/rust/src/lib.rs +0 -0
  28. {grimp-3.19 → grimp-3.20}/rust/src/module_expressions.rs +0 -0
  29. {grimp-3.19 → grimp-3.20}/rust/src/module_finding.rs +0 -0
  30. {grimp-3.19 → grimp-3.20}/rust/tests/large.rs +0 -0
  31. {grimp-3.19 → grimp-3.20}/rust/tests/large_graph.json +0 -0
  32. {grimp-3.19 → grimp-3.20}/src/grimp/adaptors/__init__.py +0 -0
  33. {grimp-3.19 → grimp-3.20}/src/grimp/adaptors/caching.py +0 -0
  34. {grimp-3.19 → grimp-3.20}/src/grimp/adaptors/filesystem.py +0 -0
  35. {grimp-3.19 → grimp-3.20}/src/grimp/adaptors/modulefinder.py +0 -0
  36. {grimp-3.19 → grimp-3.20}/src/grimp/adaptors/packagefinder.py +0 -0
  37. {grimp-3.19 → grimp-3.20}/src/grimp/adaptors/timing.py +0 -0
  38. {grimp-3.19 → grimp-3.20}/src/grimp/application/__init__.py +0 -0
  39. {grimp-3.19 → grimp-3.20}/src/grimp/application/config.py +0 -0
  40. {grimp-3.19 → grimp-3.20}/src/grimp/application/ports/__init__.py +0 -0
  41. {grimp-3.19 → grimp-3.20}/src/grimp/application/ports/caching.py +0 -0
  42. {grimp-3.19 → grimp-3.20}/src/grimp/application/ports/filesystem.py +0 -0
  43. {grimp-3.19 → grimp-3.20}/src/grimp/application/ports/modulefinder.py +0 -0
  44. {grimp-3.19 → grimp-3.20}/src/grimp/application/ports/packagefinder.py +0 -0
  45. {grimp-3.19 → grimp-3.20}/src/grimp/application/ports/timing.py +0 -0
  46. {grimp-3.19 → grimp-3.20}/src/grimp/application/scanning.py +0 -0
  47. {grimp-3.19 → grimp-3.20}/src/grimp/application/usecases.py +0 -0
  48. {grimp-3.19 → grimp-3.20}/src/grimp/domain/__init__.py +0 -0
  49. {grimp-3.19 → grimp-3.20}/src/grimp/domain/analysis.py +0 -0
  50. {grimp-3.19 → grimp-3.20}/src/grimp/domain/valueobjects.py +0 -0
  51. {grimp-3.19 → grimp-3.20}/src/grimp/exceptions.py +0 -0
  52. {grimp-3.19 → grimp-3.20}/src/grimp/main.py +0 -0
  53. {grimp-3.19 → grimp-3.20}/src/grimp/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grimp
3
- Version: 3.19
3
+ Version: 3.20
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: BSD License
@@ -8,7 +8,7 @@ module-name = "grimp._rustgrimp"
8
8
 
9
9
  [project]
10
10
  name = "grimp"
11
- version = "3.19"
11
+ version = "3.20"
12
12
  license = {text = "BSD 2-Clause License"}
13
13
  description = "Builds a queryable graph of the imports within one or more Python packages."
14
14
  authors = [
@@ -0,0 +1,328 @@
1
+ use crate::errors::{GrimpError, GrimpResult};
2
+ use crate::graph::{
3
+ EMPTY_IMPORT_DETAILS, EMPTY_MODULE_TOKENS, ExtendWithDescendants, Graph, ModuleIterator,
4
+ ModuleToken, PyImportDetails,
5
+ };
6
+ use crate::module_expressions::ModuleExpression;
7
+ use rustc_hash::FxHashSet;
8
+
9
+ impl Graph {
10
+ pub fn count_imports(&self) -> usize {
11
+ self.imports.values().map(|imports| imports.len()).sum()
12
+ }
13
+
14
+ pub fn direct_import_exists(
15
+ &self,
16
+ importer: ModuleToken,
17
+ imported: ModuleToken,
18
+ as_packages: bool,
19
+ ) -> GrimpResult<bool> {
20
+ let mut importers: FxHashSet<_> = importer.into();
21
+ let mut importeds: FxHashSet<_> = imported.into();
22
+ if as_packages {
23
+ importers.extend_with_descendants(self);
24
+ importeds.extend_with_descendants(self);
25
+ if !(&importers & &importeds).is_empty() {
26
+ return Err(GrimpError::SharedDescendants);
27
+ }
28
+ }
29
+
30
+ let direct_imports = importers
31
+ .iter()
32
+ .flat_map(|importer_module| self.imports.get(*importer_module).unwrap().iter().cloned())
33
+ .collect::<FxHashSet<ModuleToken>>();
34
+
35
+ Ok(!(&direct_imports & &importeds).is_empty())
36
+ }
37
+
38
+ pub fn find_direct_imports_between(
39
+ &self,
40
+ importer: ModuleToken,
41
+ imported: ModuleToken,
42
+ as_packages: bool,
43
+ ) -> GrimpResult<FxHashSet<(ModuleToken, ModuleToken)>> {
44
+ let mut all_imports: FxHashSet<(ModuleToken, ModuleToken)> = FxHashSet::default();
45
+
46
+ let mut importers: FxHashSet<_> = importer.into();
47
+ let mut importeds: FxHashSet<_> = imported.into();
48
+ if as_packages {
49
+ importers.extend_with_descendants(self);
50
+ importeds.extend_with_descendants(self);
51
+ if !(&importers & &importeds).is_empty() {
52
+ return Err(GrimpError::SharedDescendants);
53
+ }
54
+ }
55
+
56
+ for importer_module in importers.iter() {
57
+ if let Some(all_imports_imported_by_this_one) = self.imports.get(*importer_module) {
58
+ let imports_of_supplied_package: FxHashSet<_> = all_imports_imported_by_this_one
59
+ .iter()
60
+ .filter(|candidate| importeds.contains(*candidate))
61
+ .map(|imported_module| (*importer_module, *imported_module))
62
+ .collect();
63
+ all_imports.extend(imports_of_supplied_package);
64
+ }
65
+ }
66
+
67
+ Ok(all_imports)
68
+ }
69
+
70
+ /// Find the modules directly imported by the module.
71
+ ///
72
+ /// If `as_package` is true, the module is treated as a package: the result is the modules
73
+ /// directly imported by it or any of its descendants, excluding modules within the package.
74
+ pub fn modules_directly_imported_by(
75
+ &self,
76
+ importer: ModuleToken,
77
+ as_package: bool,
78
+ ) -> FxHashSet<ModuleToken> {
79
+ let mut importers: FxHashSet<_> = importer.into();
80
+ if as_package {
81
+ importers.extend_with_descendants(self);
82
+ }
83
+
84
+ let mut imported_modules: FxHashSet<_> = importers
85
+ .iter()
86
+ .flat_map(|importer| self.imports.get(*importer).unwrap_or(&EMPTY_MODULE_TOKENS))
87
+ .cloned()
88
+ .collect();
89
+ if as_package {
90
+ imported_modules.retain(|imported| !importers.contains(imported));
91
+ }
92
+ imported_modules
93
+ }
94
+
95
+ /// Find the modules that directly import the module.
96
+ ///
97
+ /// If `as_package` is true, the module is treated as a package: the result is the modules
98
+ /// that directly import it or any of its descendants, excluding modules within the package.
99
+ pub fn modules_that_directly_import(
100
+ &self,
101
+ imported: ModuleToken,
102
+ as_package: bool,
103
+ ) -> FxHashSet<ModuleToken> {
104
+ let mut importeds: FxHashSet<_> = imported.into();
105
+ if as_package {
106
+ importeds.extend_with_descendants(self);
107
+ }
108
+
109
+ let mut importers: FxHashSet<_> = importeds
110
+ .iter()
111
+ .flat_map(|imported| {
112
+ self.reverse_imports
113
+ .get(*imported)
114
+ .unwrap_or(&EMPTY_MODULE_TOKENS)
115
+ })
116
+ .cloned()
117
+ .collect();
118
+ if as_package {
119
+ importers.retain(|importer| !importeds.contains(importer));
120
+ }
121
+ importers
122
+ }
123
+
124
+ pub fn get_import_details(
125
+ &self,
126
+ importer: ModuleToken,
127
+ imported: ModuleToken,
128
+ ) -> &FxHashSet<PyImportDetails> {
129
+ match self.import_details.get(&(importer, imported)) {
130
+ Some(import_details) => import_details,
131
+ None => &EMPTY_IMPORT_DETAILS,
132
+ }
133
+ }
134
+
135
+ pub fn find_matching_direct_imports(
136
+ &self,
137
+ importer_expression: &ModuleExpression,
138
+ imported_expression: &ModuleExpression,
139
+ ) -> FxHashSet<(ModuleToken, ModuleToken)> {
140
+ let importers: FxHashSet<ModuleToken> = self
141
+ .find_matching_modules(importer_expression)
142
+ .tokens()
143
+ .collect();
144
+ let importeds: FxHashSet<ModuleToken> = self
145
+ .find_matching_modules(imported_expression)
146
+ .tokens()
147
+ .collect();
148
+
149
+ importers
150
+ .iter()
151
+ .flat_map(|importer| {
152
+ self.modules_directly_imported_by(*importer, false)
153
+ .into_iter()
154
+ .filter(|imported| importeds.contains(imported))
155
+ .map(move |imported| (*importer, imported))
156
+ })
157
+ .collect()
158
+ }
159
+ }
160
+
161
+ #[cfg(test)]
162
+ mod test {
163
+ use super::*;
164
+
165
+ #[test]
166
+ fn test_modules_directly_imported_by() {
167
+ let mut graph = Graph::default();
168
+
169
+ let animals = graph.get_or_add_module("pkg.animals").token;
170
+ let dog = graph.get_or_add_module("pkg.animals.dog").token;
171
+ let cat = graph.get_or_add_module("pkg.animals.cat").token;
172
+ let food = graph.get_or_add_module("pkg.food").token;
173
+ let chicken = graph.get_or_add_module("pkg.food.chicken").token;
174
+ let tesco = graph.get_or_add_module("pkg.shops.tesco").token;
175
+
176
+ graph.add_import(dog, chicken);
177
+ graph.add_import(cat, food);
178
+ // An import within the package.
179
+ graph.add_import(dog, cat);
180
+ // An import of the package, not by it.
181
+ graph.add_import(tesco, animals);
182
+
183
+ // Without as_package, only the module itself is looked at.
184
+ assert_eq!(
185
+ graph.modules_directly_imported_by(animals, false),
186
+ FxHashSet::default()
187
+ );
188
+ assert_eq!(
189
+ graph.modules_directly_imported_by(dog, false),
190
+ FxHashSet::from_iter([chicken, cat])
191
+ );
192
+ // With as_package, descendants are looked at too, but modules within the package
193
+ // are excluded.
194
+ assert_eq!(
195
+ graph.modules_directly_imported_by(animals, true),
196
+ FxHashSet::from_iter([chicken, food])
197
+ );
198
+ }
199
+
200
+ #[test]
201
+ fn test_modules_that_directly_import() {
202
+ let mut graph = Graph::default();
203
+
204
+ let animals = graph.get_or_add_module("pkg.animals").token;
205
+ let dog = graph.get_or_add_module("pkg.animals.dog").token;
206
+ let cat = graph.get_or_add_module("pkg.animals.cat").token;
207
+ let chicken = graph.get_or_add_module("pkg.food.chicken").token;
208
+ let shops = graph.get_or_add_module("pkg.shops").token;
209
+ let tesco = graph.get_or_add_module("pkg.shops.tesco").token;
210
+
211
+ graph.add_import(tesco, animals);
212
+ graph.add_import(shops, cat);
213
+ // An import within the package.
214
+ graph.add_import(dog, cat);
215
+ // An import by the package, not of it.
216
+ graph.add_import(dog, chicken);
217
+
218
+ // Without as_package, only the module itself is looked at.
219
+ assert_eq!(
220
+ graph.modules_that_directly_import(animals, false),
221
+ FxHashSet::from_iter([tesco])
222
+ );
223
+ assert_eq!(
224
+ graph.modules_that_directly_import(cat, false),
225
+ FxHashSet::from_iter([shops, dog])
226
+ );
227
+ // With as_package, descendants are looked at too, but modules within the package
228
+ // are excluded.
229
+ assert_eq!(
230
+ graph.modules_that_directly_import(animals, true),
231
+ FxHashSet::from_iter([tesco, shops])
232
+ );
233
+ }
234
+
235
+ #[test]
236
+ fn test_find_matching_direct_imports() {
237
+ let mut graph = Graph::default();
238
+
239
+ let _ = graph.get_or_add_module("pkg.animals").token;
240
+ let dog = graph.get_or_add_module("pkg.animals.dog").token;
241
+ let cat = graph.get_or_add_module("pkg.animals.cat").token;
242
+ let _ = graph.get_or_add_module("pkg.food").token;
243
+ let chicken = graph.get_or_add_module("pkg.food.chicken").token;
244
+ let fish = graph.get_or_add_module("pkg.food.fish").token;
245
+ let _ = graph.get_or_add_module("pkg.colors").token;
246
+ let golden = graph.get_or_add_module("pkg.colors.golden").token;
247
+ let ginger = graph.get_or_add_module("pkg.colors.ginger").token;
248
+ let _ = graph.get_or_add_module("pkg.shops").token;
249
+ let tesco = graph.get_or_add_module("pkg.shops.tesco").token;
250
+ let coop = graph.get_or_add_module("pkg.shops.coop").token;
251
+
252
+ // Should match
253
+ graph.add_import(dog, chicken);
254
+ graph.add_import(cat, fish);
255
+ // Should not match: Imported does not match
256
+ graph.add_import(dog, golden);
257
+ graph.add_import(cat, ginger);
258
+ // Should not match: Importer does not match
259
+ graph.add_import(tesco, chicken);
260
+ graph.add_import(coop, fish);
261
+
262
+ let importer_expression = "pkg.animals.*".parse().unwrap();
263
+ let imported_expression = "pkg.food.*".parse().unwrap();
264
+ let matching_imports =
265
+ graph.find_matching_direct_imports(&importer_expression, &imported_expression);
266
+
267
+ assert_eq!(
268
+ matching_imports,
269
+ FxHashSet::from_iter([(dog, chicken), (cat, fish)])
270
+ );
271
+ }
272
+
273
+ #[test]
274
+ fn test_find_matching_direct_imports_with_and_without_wildcards() {
275
+ let mut graph = Graph::default();
276
+
277
+ let dog = graph.get_or_add_module("pkg.animals.dog").token;
278
+ let cat = graph.get_or_add_module("pkg.animals.cat").token;
279
+ let chicken = graph.get_or_add_module("pkg.food.chicken").token;
280
+ let fish = graph.get_or_add_module("pkg.food.fish").token;
281
+ let golden = graph.get_or_add_module("pkg.colors.golden").token;
282
+ let tesco = graph.get_or_add_module("pkg.shops.tesco").token;
283
+ graph.get_or_add_module("pkg.shops.coop");
284
+
285
+ graph.add_import(dog, chicken);
286
+ graph.add_import(dog, golden);
287
+ graph.add_import(cat, fish);
288
+ graph.add_import(tesco, chicken);
289
+
290
+ let find = |importer_expression: &str, imported_expression: &str| {
291
+ graph.find_matching_direct_imports(
292
+ &importer_expression.parse().unwrap(),
293
+ &imported_expression.parse().unwrap(),
294
+ )
295
+ };
296
+
297
+ // Neither side has wildcards.
298
+ assert_eq!(
299
+ find("pkg.animals.dog", "pkg.food.chicken"),
300
+ FxHashSet::from_iter([(dog, chicken)])
301
+ );
302
+ // Only the importer is a single module.
303
+ assert_eq!(
304
+ find("pkg.animals.dog", "pkg.*.*"),
305
+ FxHashSet::from_iter([(dog, chicken), (dog, golden)])
306
+ );
307
+ // Only the imported is a single module.
308
+ assert_eq!(
309
+ find("pkg.*.*", "pkg.food.chicken"),
310
+ FxHashSet::from_iter([(dog, chicken), (tesco, chicken)])
311
+ );
312
+ // Both sides have wildcards.
313
+ assert_eq!(
314
+ find("pkg.shops.*", "pkg.food.*"),
315
+ FxHashSet::from_iter([(tesco, chicken)])
316
+ );
317
+ // A module with no imports.
318
+ assert!(find("pkg.shops.coop", "pkg.*.*").is_empty());
319
+ // A module that has no importers.
320
+ assert!(find("pkg.*.*", "pkg.shops.coop").is_empty());
321
+ // Modules that aren't in the graph.
322
+ assert!(find("pkg.animals.missing", "pkg.*.*").is_empty());
323
+ assert!(find("pkg.*.*", "pkg.food.missing").is_empty());
324
+ assert!(find("pkg.animals.missing", "pkg.food.missing").is_empty());
325
+ // Wildcards that match nothing.
326
+ assert!(find("pkg.nothing.*", "pkg.*.*").is_empty());
327
+ }
328
+ }
@@ -91,10 +91,10 @@ impl Graph {
91
91
  self.module_children.remove(module);
92
92
 
93
93
  // Update imports.
94
- for imported in self.modules_directly_imported_by(module).clone() {
94
+ for imported in self.modules_directly_imported_by(module, false) {
95
95
  self.remove_import(module, imported);
96
96
  }
97
- for importer in self.modules_that_directly_import(module).clone() {
97
+ for importer in self.modules_that_directly_import(module, false) {
98
98
  self.remove_import(importer, module);
99
99
  }
100
100
  self.imports.remove(module);
@@ -164,19 +164,11 @@ impl Graph {
164
164
 
165
165
  let modules_imported_by_descendants: FxHashSet<_> = descendants
166
166
  .iter()
167
- .flat_map(|descendant| {
168
- self.modules_directly_imported_by(*descendant)
169
- .iter()
170
- .cloned()
171
- })
167
+ .flat_map(|descendant| self.modules_directly_imported_by(*descendant, false))
172
168
  .collect();
173
169
  let modules_that_import_descendants: FxHashSet<_> = descendants
174
170
  .iter()
175
- .flat_map(|descendant| {
176
- self.modules_that_directly_import(*descendant)
177
- .iter()
178
- .cloned()
179
- })
171
+ .flat_map(|descendant| self.modules_that_directly_import(*descendant, false))
180
172
  .collect();
181
173
 
182
174
  // Add descendants and imports to parent module.
@@ -54,12 +54,19 @@ impl Graph {
54
54
  &self,
55
55
  expression: &ModuleExpression,
56
56
  ) -> impl ModuleIterator<'_> + use<'_> {
57
- let interner = MODULE_NAMES.read().unwrap();
58
- let modules: FxHashSet<_> = self
59
- .modules
60
- .values()
61
- .filter(|m| expression.is_match(interner.resolve(m.interned_name).unwrap()))
62
- .collect();
57
+ let modules: FxHashSet<_> = if expression.as_ref().contains('*') {
58
+ let interner = MODULE_NAMES.read().unwrap();
59
+ self.modules
60
+ .values()
61
+ .filter(|m| expression.is_match(interner.resolve(m.interned_name).unwrap()))
62
+ .collect()
63
+ } else {
64
+ // Without wildcards the expression can only match the module with that name,
65
+ // so there's no need to check every module.
66
+ self.get_module_by_name(expression.as_ref())
67
+ .into_iter()
68
+ .collect()
69
+ };
63
70
  modules.into_iter()
64
71
  }
65
72
  }
@@ -80,6 +87,8 @@ mod tests {
80
87
 
81
88
  #[parameterized(case = {
82
89
  FindMatchingModulesTestCase::new("foo", ["foo"]),
90
+ FindMatchingModulesTestCase::new("foo.bar.baz", ["foo.bar.baz"]),
91
+ FindMatchingModulesTestCase::new("foo.missing", Vec::<&str>::new()),
83
92
  FindMatchingModulesTestCase::new("foo.*", ["foo.bar"]),
84
93
  FindMatchingModulesTestCase::new("foo.**", ["foo.bar", "foo.bar.baz"]),
85
94
  })]
@@ -394,11 +394,16 @@ impl GraphWrapper {
394
394
  .direct_import_exists(importer, imported, as_packages)?)
395
395
  }
396
396
 
397
- pub fn find_modules_directly_imported_by(&self, module: &str) -> PyResult<HashSet<String>> {
397
+ #[pyo3(signature = (module, as_package=false))]
398
+ pub fn find_modules_directly_imported_by(
399
+ &self,
400
+ module: &str,
401
+ as_package: bool,
402
+ ) -> PyResult<HashSet<String>> {
398
403
  let module = self.get_visible_module_by_name(module)?.token();
399
404
  Ok(self
400
405
  ._graph
401
- .modules_directly_imported_by(module)
406
+ .modules_directly_imported_by(module, as_package)
402
407
  .iter()
403
408
  .into_module_iterator(&self._graph)
404
409
  .visible()
@@ -406,11 +411,16 @@ impl GraphWrapper {
406
411
  .collect())
407
412
  }
408
413
 
409
- pub fn find_modules_that_directly_import(&self, module: &str) -> PyResult<HashSet<String>> {
414
+ #[pyo3(signature = (module, as_package=false))]
415
+ pub fn find_modules_that_directly_import(
416
+ &self,
417
+ module: &str,
418
+ as_package: bool,
419
+ ) -> PyResult<HashSet<String>> {
410
420
  let module = self.get_visible_module_by_name(module)?.token();
411
421
  Ok(self
412
422
  ._graph
413
- .modules_that_directly_import(module)
423
+ .modules_that_directly_import(module, as_package)
414
424
  .iter()
415
425
  .into_module_iterator(&self._graph)
416
426
  .visible()
@@ -1,4 +1,4 @@
1
- __version__ = "3.19"
1
+ __version__ = "3.20"
2
2
 
3
3
  from .application.graph import DetailedImport, Import, ImportGraph
4
4
  from .domain.analysis import PackageDependency, Route
@@ -223,27 +223,41 @@ class ImportGraph:
223
223
  importer=importer, imported=imported, as_packages=as_packages
224
224
  )
225
225
 
226
- def find_modules_directly_imported_by(self, module: str) -> set[str]:
226
+ def find_modules_directly_imported_by(self, module: str, as_package: bool = False) -> set[str]:
227
227
  """
228
228
  Find all modules that are directly imported by the supplied module.
229
229
 
230
+ Args:
231
+ module: The name of the importing module.
232
+ as_package: Whether or not to treat the supplied module as an individual module,
233
+ or as a package (i.e. including any descendants, if there are any). If
234
+ treating it as a package, the result will include modules *external* to
235
+ the package, and won't include modules within it.
236
+
230
237
  If the module is not present in the graph, grimp.exceptions.ModuleNotPresent will be raised.
231
238
  """
232
239
  if not self._rustgraph.contains_module(module):
233
240
  msg = f'"{module}" not present in the graph.'
234
241
  raise ModuleNotPresent(msg)
235
- return self._rustgraph.find_modules_directly_imported_by(module)
242
+ return self._rustgraph.find_modules_directly_imported_by(module, as_package)
236
243
 
237
- def find_modules_that_directly_import(self, module: str) -> set[str]:
244
+ def find_modules_that_directly_import(self, module: str, as_package: bool = False) -> set[str]:
238
245
  """
239
246
  Find all modules that directly import the supplied module.
240
247
 
248
+ Args:
249
+ module: The name of the imported module.
250
+ as_package: Whether or not to treat the supplied module as an individual module,
251
+ or as a package (i.e. including any descendants, if there are any). If
252
+ treating it as a package, the result will include modules *external* to
253
+ the package, and won't include modules within it.
254
+
241
255
  If the module is not present in the graph, grimp.exceptions.ModuleNotPresent will be raised.
242
256
  """
243
257
  if not self._rustgraph.contains_module(module):
244
258
  msg = f'"{module}" not present in the graph.'
245
259
  raise ModuleNotPresent(msg)
246
- return self._rustgraph.find_modules_that_directly_import(module)
260
+ return self._rustgraph.find_modules_that_directly_import(module, as_package)
247
261
 
248
262
  def get_import_details(self, *, importer: str, imported: str) -> list[DetailedImport]:
249
263
  """
@@ -1,158 +0,0 @@
1
- use crate::errors::{GrimpError, GrimpResult};
2
- use crate::graph::{
3
- EMPTY_IMPORT_DETAILS, EMPTY_MODULE_TOKENS, ExtendWithDescendants, Graph, MODULE_NAMES,
4
- ModuleToken, PyImportDetails,
5
- };
6
- use crate::module_expressions::ModuleExpression;
7
- use rustc_hash::FxHashSet;
8
-
9
- impl Graph {
10
- pub fn count_imports(&self) -> usize {
11
- self.imports.values().map(|imports| imports.len()).sum()
12
- }
13
-
14
- pub fn direct_import_exists(
15
- &self,
16
- importer: ModuleToken,
17
- imported: ModuleToken,
18
- as_packages: bool,
19
- ) -> GrimpResult<bool> {
20
- let mut importers: FxHashSet<_> = importer.into();
21
- let mut importeds: FxHashSet<_> = imported.into();
22
- if as_packages {
23
- importers.extend_with_descendants(self);
24
- importeds.extend_with_descendants(self);
25
- if !(&importers & &importeds).is_empty() {
26
- return Err(GrimpError::SharedDescendants);
27
- }
28
- }
29
-
30
- let direct_imports = importers
31
- .iter()
32
- .flat_map(|importer_module| self.imports.get(*importer_module).unwrap().iter().cloned())
33
- .collect::<FxHashSet<ModuleToken>>();
34
-
35
- Ok(!(&direct_imports & &importeds).is_empty())
36
- }
37
-
38
- pub fn find_direct_imports_between(
39
- &self,
40
- importer: ModuleToken,
41
- imported: ModuleToken,
42
- as_packages: bool,
43
- ) -> GrimpResult<FxHashSet<(ModuleToken, ModuleToken)>> {
44
- let mut all_imports: FxHashSet<(ModuleToken, ModuleToken)> = FxHashSet::default();
45
-
46
- let mut importers: FxHashSet<_> = importer.into();
47
- let mut importeds: FxHashSet<_> = imported.into();
48
- if as_packages {
49
- importers.extend_with_descendants(self);
50
- importeds.extend_with_descendants(self);
51
- if !(&importers & &importeds).is_empty() {
52
- return Err(GrimpError::SharedDescendants);
53
- }
54
- }
55
-
56
- for importer_module in importers.iter() {
57
- if let Some(all_imports_imported_by_this_one) = self.imports.get(*importer_module) {
58
- let imports_of_supplied_package: FxHashSet<_> = all_imports_imported_by_this_one
59
- .iter()
60
- .filter(|candidate| importeds.contains(*candidate))
61
- .map(|imported_module| (*importer_module, *imported_module))
62
- .collect();
63
- all_imports.extend(imports_of_supplied_package);
64
- }
65
- }
66
-
67
- Ok(all_imports)
68
- }
69
-
70
- pub fn modules_directly_imported_by(&self, importer: ModuleToken) -> &FxHashSet<ModuleToken> {
71
- self.imports.get(importer).unwrap_or(&EMPTY_MODULE_TOKENS)
72
- }
73
-
74
- pub fn modules_that_directly_import(&self, imported: ModuleToken) -> &FxHashSet<ModuleToken> {
75
- self.reverse_imports
76
- .get(imported)
77
- .unwrap_or(&EMPTY_MODULE_TOKENS)
78
- }
79
-
80
- pub fn get_import_details(
81
- &self,
82
- importer: ModuleToken,
83
- imported: ModuleToken,
84
- ) -> &FxHashSet<PyImportDetails> {
85
- match self.import_details.get(&(importer, imported)) {
86
- Some(import_details) => import_details,
87
- None => &EMPTY_IMPORT_DETAILS,
88
- }
89
- }
90
-
91
- pub fn find_matching_direct_imports(
92
- &self,
93
- importer_expression: &ModuleExpression,
94
- imported_expression: &ModuleExpression,
95
- ) -> FxHashSet<(ModuleToken, ModuleToken)> {
96
- let interner = MODULE_NAMES.read().unwrap();
97
- self.imports
98
- .iter()
99
- .flat_map(|(importer, imports)| {
100
- imports
101
- .iter()
102
- .cloned()
103
- .map(move |imported| (importer, imported))
104
- })
105
- .filter(|(importer, imported)| {
106
- let importer = self.get_module(*importer).unwrap();
107
- let importer_name = interner.resolve(importer.interned_name).unwrap();
108
- let imported = self.get_module(*imported).unwrap();
109
- let imported_name = interner.resolve(imported.interned_name).unwrap();
110
- importer_expression.is_match(importer_name)
111
- && imported_expression.is_match(imported_name)
112
- })
113
- .collect()
114
- }
115
- }
116
-
117
- #[cfg(test)]
118
- mod test {
119
- use super::*;
120
-
121
- #[test]
122
- fn test_find_matching_direct_imports() {
123
- let mut graph = Graph::default();
124
-
125
- let _ = graph.get_or_add_module("pkg.animals").token;
126
- let dog = graph.get_or_add_module("pkg.animals.dog").token;
127
- let cat = graph.get_or_add_module("pkg.animals.cat").token;
128
- let _ = graph.get_or_add_module("pkg.food").token;
129
- let chicken = graph.get_or_add_module("pkg.food.chicken").token;
130
- let fish = graph.get_or_add_module("pkg.food.fish").token;
131
- let _ = graph.get_or_add_module("pkg.colors").token;
132
- let golden = graph.get_or_add_module("pkg.colors.golden").token;
133
- let ginger = graph.get_or_add_module("pkg.colors.ginger").token;
134
- let _ = graph.get_or_add_module("pkg.shops").token;
135
- let tesco = graph.get_or_add_module("pkg.shops.tesco").token;
136
- let coop = graph.get_or_add_module("pkg.shops.coop").token;
137
-
138
- // Should match
139
- graph.add_import(dog, chicken);
140
- graph.add_import(cat, fish);
141
- // Should not match: Imported does not match
142
- graph.add_import(dog, golden);
143
- graph.add_import(cat, ginger);
144
- // Should not match: Importer does not match
145
- graph.add_import(tesco, chicken);
146
- graph.add_import(coop, fish);
147
-
148
- let importer_expression = "pkg.animals.*".parse().unwrap();
149
- let imported_expression = "pkg.food.*".parse().unwrap();
150
- let matching_imports =
151
- graph.find_matching_direct_imports(&importer_expression, &imported_expression);
152
-
153
- assert_eq!(
154
- matching_imports,
155
- FxHashSet::from_iter([(dog, chicken), (cat, fish)])
156
- );
157
- }
158
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes