grimp 3.7__tar.gz → 3.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.
Files changed (48) hide show
  1. {grimp-3.7 → grimp-3.8}/PKG-INFO +1 -1
  2. {grimp-3.7 → grimp-3.8}/pyproject.toml +1 -1
  3. {grimp-3.7 → grimp-3.8}/rust/Cargo.lock +12 -12
  4. {grimp-3.7 → grimp-3.8}/rust/Cargo.toml +1 -1
  5. {grimp-3.7 → grimp-3.8}/src/grimp/__init__.py +1 -1
  6. {grimp-3.7 → grimp-3.8}/src/grimp/application/usecases.py +132 -20
  7. {grimp-3.7 → grimp-3.8}/src/grimp/exceptions.py +5 -0
  8. {grimp-3.7 → grimp-3.8}/README.rst +0 -0
  9. {grimp-3.7 → grimp-3.8}/rust/.cargo/config.toml +0 -0
  10. {grimp-3.7 → grimp-3.8}/rust/src/errors.rs +0 -0
  11. {grimp-3.7 → grimp-3.8}/rust/src/exceptions.rs +0 -0
  12. {grimp-3.7 → grimp-3.8}/rust/src/graph/direct_import_queries.rs +0 -0
  13. {grimp-3.7 → grimp-3.8}/rust/src/graph/graph_manipulation.rs +5 -5
  14. {grimp-3.7 → grimp-3.8}/rust/src/graph/hierarchy_queries.rs +0 -0
  15. {grimp-3.7 → grimp-3.8}/rust/src/graph/higher_order_queries.rs +0 -0
  16. {grimp-3.7 → grimp-3.8}/rust/src/graph/import_chain_queries.rs +0 -0
  17. {grimp-3.7 → grimp-3.8}/rust/src/graph/mod.rs +0 -0
  18. {grimp-3.7 → grimp-3.8}/rust/src/graph/pathfinding.rs +0 -0
  19. {grimp-3.7 → grimp-3.8}/rust/src/lib.rs +0 -0
  20. {grimp-3.7 → grimp-3.8}/rust/src/module_expressions.rs +0 -0
  21. {grimp-3.7 → grimp-3.8}/rust/tests/large.rs +0 -0
  22. {grimp-3.7 → grimp-3.8}/rust/tests/large_graph.json +0 -0
  23. {grimp-3.7 → grimp-3.8}/src/grimp/adaptors/__init__.py +0 -0
  24. {grimp-3.7 → grimp-3.8}/src/grimp/adaptors/caching.py +0 -0
  25. {grimp-3.7 → grimp-3.8}/src/grimp/adaptors/filesystem.py +0 -0
  26. {grimp-3.7 → grimp-3.8}/src/grimp/adaptors/graph.py +0 -0
  27. {grimp-3.7 → grimp-3.8}/src/grimp/adaptors/importscanner.py +0 -0
  28. {grimp-3.7 → grimp-3.8}/src/grimp/adaptors/modulefinder.py +0 -0
  29. {grimp-3.7 → grimp-3.8}/src/grimp/adaptors/packagefinder.py +0 -0
  30. {grimp-3.7 → grimp-3.8}/src/grimp/adaptors/timing.py +0 -0
  31. {grimp-3.7 → grimp-3.8}/src/grimp/algorithms/__init__.py +0 -0
  32. {grimp-3.7 → grimp-3.8}/src/grimp/algorithms/shortest_path.py +0 -0
  33. {grimp-3.7 → grimp-3.8}/src/grimp/application/__init__.py +0 -0
  34. {grimp-3.7 → grimp-3.8}/src/grimp/application/config.py +0 -0
  35. {grimp-3.7 → grimp-3.8}/src/grimp/application/ports/__init__.py +0 -0
  36. {grimp-3.7 → grimp-3.8}/src/grimp/application/ports/caching.py +0 -0
  37. {grimp-3.7 → grimp-3.8}/src/grimp/application/ports/filesystem.py +0 -0
  38. {grimp-3.7 → grimp-3.8}/src/grimp/application/ports/graph.py +0 -0
  39. {grimp-3.7 → grimp-3.8}/src/grimp/application/ports/importscanner.py +0 -0
  40. {grimp-3.7 → grimp-3.8}/src/grimp/application/ports/modulefinder.py +0 -0
  41. {grimp-3.7 → grimp-3.8}/src/grimp/application/ports/packagefinder.py +0 -0
  42. {grimp-3.7 → grimp-3.8}/src/grimp/application/ports/timing.py +0 -0
  43. {grimp-3.7 → grimp-3.8}/src/grimp/domain/__init__.py +0 -0
  44. {grimp-3.7 → grimp-3.8}/src/grimp/domain/analysis.py +0 -0
  45. {grimp-3.7 → grimp-3.8}/src/grimp/domain/valueobjects.py +0 -0
  46. {grimp-3.7 → grimp-3.8}/src/grimp/helpers.py +0 -0
  47. {grimp-3.7 → grimp-3.8}/src/grimp/main.py +0 -0
  48. {grimp-3.7 → grimp-3.8}/src/grimp/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grimp
3
- Version: 3.7
3
+ Version: 3.8
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.7"
11
+ version = "3.8"
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 = [
@@ -293,9 +293,9 @@ dependencies = [
293
293
 
294
294
  [[package]]
295
295
  name = "pyo3"
296
- version = "0.23.4"
296
+ version = "0.24.1"
297
297
  source = "registry+https://github.com/rust-lang/crates.io-index"
298
- checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc"
298
+ checksum = "17da310086b068fbdcefbba30aeb3721d5bb9af8db4987d6735b2183ca567229"
299
299
  dependencies = [
300
300
  "cfg-if",
301
301
  "indoc",
@@ -311,9 +311,9 @@ dependencies = [
311
311
 
312
312
  [[package]]
313
313
  name = "pyo3-build-config"
314
- version = "0.23.4"
314
+ version = "0.24.1"
315
315
  source = "registry+https://github.com/rust-lang/crates.io-index"
316
- checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7"
316
+ checksum = "e27165889bd793000a098bb966adc4300c312497ea25cf7a690a9f0ac5aa5fc1"
317
317
  dependencies = [
318
318
  "once_cell",
319
319
  "target-lexicon",
@@ -321,9 +321,9 @@ dependencies = [
321
321
 
322
322
  [[package]]
323
323
  name = "pyo3-ffi"
324
- version = "0.23.4"
324
+ version = "0.24.1"
325
325
  source = "registry+https://github.com/rust-lang/crates.io-index"
326
- checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d"
326
+ checksum = "05280526e1dbf6b420062f3ef228b78c0c54ba94e157f5cb724a609d0f2faabc"
327
327
  dependencies = [
328
328
  "libc",
329
329
  "pyo3-build-config",
@@ -331,9 +331,9 @@ dependencies = [
331
331
 
332
332
  [[package]]
333
333
  name = "pyo3-macros"
334
- version = "0.23.4"
334
+ version = "0.24.1"
335
335
  source = "registry+https://github.com/rust-lang/crates.io-index"
336
- checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7"
336
+ checksum = "5c3ce5686aa4d3f63359a5100c62a127c9f15e8398e5fdeb5deef1fed5cd5f44"
337
337
  dependencies = [
338
338
  "proc-macro2",
339
339
  "pyo3-macros-backend",
@@ -343,9 +343,9 @@ dependencies = [
343
343
 
344
344
  [[package]]
345
345
  name = "pyo3-macros-backend"
346
- version = "0.23.4"
346
+ version = "0.24.1"
347
347
  source = "registry+https://github.com/rust-lang/crates.io-index"
348
- checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4"
348
+ checksum = "f4cf6faa0cbfb0ed08e89beb8103ae9724eb4750e3a78084ba4017cbe94f3855"
349
349
  dependencies = [
350
350
  "heck",
351
351
  "proc-macro2",
@@ -494,9 +494,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
494
494
 
495
495
  [[package]]
496
496
  name = "target-lexicon"
497
- version = "0.12.16"
497
+ version = "0.13.2"
498
498
  source = "registry+https://github.com/rust-lang/crates.io-index"
499
- checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
499
+ checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
500
500
 
501
501
  [[package]]
502
502
  name = "thiserror"
@@ -24,7 +24,7 @@ regex = "1.11.1"
24
24
  const_format = "0.2.34"
25
25
 
26
26
  [dependencies.pyo3]
27
- version = "0.23.4"
27
+ version = "0.24.1"
28
28
 
29
29
  [features]
30
30
  extension-module = ["pyo3/extension-module"]
@@ -1,4 +1,4 @@
1
- __version__ = "3.7"
1
+ __version__ = "3.8"
2
2
 
3
3
  from .application.ports.graph import DetailedImport, ImportGraph, Import
4
4
  from .domain.analysis import PackageDependency, Route
@@ -1,13 +1,16 @@
1
1
  """
2
2
  Use cases handle application logic.
3
3
  """
4
- from typing import Dict, Sequence, Set, Type, Union, cast
4
+
5
+ from typing import Dict, Sequence, Set, Type, Union, cast, Iterable, Collection
6
+ import multiprocessing
7
+ import math
5
8
 
6
9
  from ..application.ports import caching
7
10
  from ..application.ports.filesystem import AbstractFileSystem
8
11
  from ..application.ports.graph import ImportGraph
9
12
  from ..application.ports.importscanner import AbstractImportScanner
10
- from ..application.ports.modulefinder import AbstractModuleFinder, FoundPackage
13
+ from ..application.ports.modulefinder import AbstractModuleFinder, FoundPackage, ModuleFile
11
14
  from ..application.ports.packagefinder import AbstractPackageFinder
12
15
  from ..domain.valueobjects import DirectImport, Module
13
16
  from .config import settings
@@ -17,6 +20,10 @@ class NotSupplied:
17
20
  pass
18
21
 
19
22
 
23
+ # This is an arbitrary number, but setting it too low slows down our functional tests considerably.
24
+ MIN_NUMBER_OF_MODULES_TO_SCAN_USING_MULTIPROCESSING = 50
25
+
26
+
20
27
  def build_graph(
21
28
  package_name,
22
29
  *additional_package_names,
@@ -106,7 +113,6 @@ def _scan_packages(
106
113
  exclude_type_checking_imports: bool,
107
114
  cache_dir: Union[str, Type[NotSupplied], None],
108
115
  ) -> Dict[Module, Set[DirectImport]]:
109
- imports_by_module: Dict[Module, Set[DirectImport]] = {}
110
116
  if cache_dir is not None:
111
117
  cache_dir_if_supplied = cache_dir if cache_dir != NotSupplied else None
112
118
  cache: caching.Cache = settings.CACHE_CLASS.setup(
@@ -116,24 +122,33 @@ def _scan_packages(
116
122
  exclude_type_checking_imports=exclude_type_checking_imports,
117
123
  cache_dir=cache_dir_if_supplied,
118
124
  )
119
- import_scanner: AbstractImportScanner = settings.IMPORT_SCANNER_CLASS(
120
- file_system=file_system,
121
- found_packages=found_packages,
122
- include_external_packages=include_external_packages,
123
- )
124
125
 
125
- for found_package in found_packages:
126
- for module_file in found_package.module_files:
127
- module = module_file.module
128
- try:
129
- if cache_dir is None:
130
- raise caching.CacheMiss
131
- direct_imports = cache.read_imports(module_file)
132
- except caching.CacheMiss:
133
- direct_imports = import_scanner.scan_for_imports(
134
- module, exclude_type_checking_imports=exclude_type_checking_imports
135
- )
136
- imports_by_module[module] = direct_imports
126
+ module_files_to_scan = {
127
+ module_file
128
+ for found_package in found_packages
129
+ for module_file in found_package.module_files
130
+ }
131
+
132
+ imports_by_module_file: Dict[ModuleFile, Set[DirectImport]] = {}
133
+
134
+ if cache_dir is not None:
135
+ imports_by_module_file.update(_read_imports_from_cache(module_files_to_scan, cache=cache))
136
+
137
+ remaining_module_files_to_scan = module_files_to_scan.difference(imports_by_module_file)
138
+ if remaining_module_files_to_scan:
139
+ imports_by_module_file.update(
140
+ _scan_imports(
141
+ remaining_module_files_to_scan,
142
+ file_system=file_system,
143
+ found_packages=found_packages,
144
+ include_external_packages=include_external_packages,
145
+ exclude_type_checking_imports=exclude_type_checking_imports,
146
+ )
147
+ )
148
+
149
+ imports_by_module: Dict[Module, Set[DirectImport]] = {
150
+ k.module: v for k, v in imports_by_module_file.items()
151
+ }
137
152
 
138
153
  if cache_dir is not None:
139
154
  cache.write(imports_by_module)
@@ -172,3 +187,100 @@ def _is_external(module: Module, found_packages: Set[FoundPackage]) -> bool:
172
187
  module.is_descendant_of(package_module) or module == package_module
173
188
  for package_module in package_modules
174
189
  )
190
+
191
+
192
+ def _read_imports_from_cache(
193
+ module_files: Iterable[ModuleFile], *, cache: caching.Cache
194
+ ) -> Dict[ModuleFile, Set[DirectImport]]:
195
+ imports_by_module_file: Dict[ModuleFile, Set[DirectImport]] = {}
196
+ for module_file in module_files:
197
+ try:
198
+ direct_imports = cache.read_imports(module_file)
199
+ except caching.CacheMiss:
200
+ continue
201
+ else:
202
+ imports_by_module_file[module_file] = direct_imports
203
+ return imports_by_module_file
204
+
205
+
206
+ def _scan_imports(
207
+ module_files: Collection[ModuleFile],
208
+ *,
209
+ file_system: AbstractFileSystem,
210
+ found_packages: Set[FoundPackage],
211
+ include_external_packages: bool,
212
+ exclude_type_checking_imports: bool,
213
+ ) -> Dict[ModuleFile, Set[DirectImport]]:
214
+ chunks = _create_chunks(module_files)
215
+ return _scan_chunks(
216
+ chunks,
217
+ file_system,
218
+ found_packages,
219
+ include_external_packages,
220
+ exclude_type_checking_imports,
221
+ )
222
+
223
+
224
+ def _create_chunks(module_files: Collection[ModuleFile]) -> tuple[tuple[ModuleFile, ...], ...]:
225
+ """
226
+ Split the module files into chunks, each to be worked on by a separate OS process.
227
+ """
228
+ module_files_tuple = tuple(module_files)
229
+
230
+ number_of_module_files = len(module_files_tuple)
231
+ n_chunks = _decide_number_of_of_processes(number_of_module_files)
232
+ chunk_size = math.ceil(number_of_module_files / n_chunks)
233
+
234
+ return tuple(
235
+ module_files_tuple[i * chunk_size : (i + 1) * chunk_size] for i in range(n_chunks)
236
+ )
237
+
238
+
239
+ def _decide_number_of_of_processes(number_of_module_files: int) -> int:
240
+ if number_of_module_files < MIN_NUMBER_OF_MODULES_TO_SCAN_USING_MULTIPROCESSING:
241
+ # Don't incur the overhead of multiprocessing.
242
+ return 1
243
+ return min(multiprocessing.cpu_count(), number_of_module_files)
244
+
245
+
246
+ def _scan_chunks(
247
+ chunks: Collection[Collection[ModuleFile]],
248
+ file_system: AbstractFileSystem,
249
+ found_packages: Set[FoundPackage],
250
+ include_external_packages: bool,
251
+ exclude_type_checking_imports: bool,
252
+ ) -> Dict[ModuleFile, Set[DirectImport]]:
253
+ import_scanner: AbstractImportScanner = settings.IMPORT_SCANNER_CLASS(
254
+ file_system=file_system,
255
+ found_packages=found_packages,
256
+ include_external_packages=include_external_packages,
257
+ )
258
+
259
+ number_of_processes = len(chunks)
260
+ if number_of_processes == 1:
261
+ # No need to spawn a process if there's only one chunk.
262
+ [chunk] = chunks
263
+ return _scan_chunk(import_scanner, exclude_type_checking_imports, chunk)
264
+ else:
265
+ with multiprocessing.Pool(number_of_processes) as pool:
266
+ imports_by_module_file: Dict[ModuleFile, Set[DirectImport]] = {}
267
+ import_scanning_jobs = pool.starmap(
268
+ _scan_chunk,
269
+ [(import_scanner, exclude_type_checking_imports, chunk) for chunk in chunks],
270
+ )
271
+ for chunk_imports_by_module_file in import_scanning_jobs:
272
+ imports_by_module_file.update(chunk_imports_by_module_file)
273
+ return imports_by_module_file
274
+
275
+
276
+ def _scan_chunk(
277
+ import_scanner: AbstractImportScanner,
278
+ exclude_type_checking_imports: bool,
279
+ chunk: Iterable[ModuleFile],
280
+ ) -> Dict[ModuleFile, Set[DirectImport]]:
281
+ return {
282
+ module_file: import_scanner.scan_for_imports(
283
+ module_file.module, exclude_type_checking_imports=exclude_type_checking_imports
284
+ )
285
+ for module_file in chunk
286
+ }
@@ -62,6 +62,11 @@ class SourceSyntaxError(GrimpException):
62
62
  other.text,
63
63
  )
64
64
 
65
+ def __reduce__(self):
66
+ # Implement __reduce__ to make this exception pickleable,
67
+ # allowing it to be sent between processes.
68
+ return SourceSyntaxError, (self.filename, self.lineno, self.text)
69
+
65
70
 
66
71
  class InvalidModuleExpression(GrimpException):
67
72
  pass
File without changes
File without changes
File without changes
File without changes
@@ -178,11 +178,6 @@ impl Graph {
178
178
  })
179
179
  .collect();
180
180
 
181
- // Remove any descendants.
182
- for descendant in descendants {
183
- self.remove_module(descendant);
184
- }
185
-
186
181
  // Add descendants and imports to parent module.
187
182
  for imported in modules_imported_by_descendants {
188
183
  self.add_import(module, imported);
@@ -192,6 +187,11 @@ impl Graph {
192
187
  self.add_import(importer, module);
193
188
  }
194
189
 
190
+ // Remove any descendants.
191
+ for descendant in descendants {
192
+ self.remove_module(descendant);
193
+ }
194
+
195
195
  self.mark_module_squashed(module);
196
196
  }
197
197
  }
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