grimp 3.1__tar.gz → 3.2__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.
- {grimp-3.1 → grimp-3.2}/PKG-INFO +2 -1
- {grimp-3.1 → grimp-3.2}/pyproject.toml +2 -1
- {grimp-3.1 → grimp-3.2}/rust/src/layers.rs +115 -7
- {grimp-3.1 → grimp-3.2}/rust/src/lib.rs +58 -10
- {grimp-3.1 → grimp-3.2}/rust/tests/large.rs +5 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/__init__.py +3 -2
- grimp-3.2/src/grimp/adaptors/_layers.py +174 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/adaptors/graph.py +4 -3
- {grimp-3.1 → grimp-3.2}/src/grimp/adaptors/modulefinder.py +4 -1
- {grimp-3.1 → grimp-3.2}/src/grimp/application/ports/graph.py +15 -10
- {grimp-3.1 → grimp-3.2}/src/grimp/domain/valueobjects.py +20 -0
- grimp-3.1/src/grimp/adaptors/_layers.py +0 -353
- {grimp-3.1 → grimp-3.2}/README.rst +0 -0
- {grimp-3.1 → grimp-3.2}/rust/.cargo/config +0 -0
- {grimp-3.1 → grimp-3.2}/rust/Cargo.lock +0 -0
- {grimp-3.1 → grimp-3.2}/rust/Cargo.toml +0 -0
- {grimp-3.1 → grimp-3.2}/rust/src/containers.rs +0 -0
- {grimp-3.1 → grimp-3.2}/rust/src/dependencies.rs +0 -0
- {grimp-3.1 → grimp-3.2}/rust/src/importgraph.rs +0 -0
- {grimp-3.1 → grimp-3.2}/rust/tests/large_graph.json +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/adaptors/__init__.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/adaptors/caching.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/adaptors/filesystem.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/adaptors/importscanner.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/adaptors/packagefinder.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/adaptors/timing.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/algorithms/__init__.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/algorithms/shortest_path.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/application/__init__.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/application/config.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/application/ports/__init__.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/application/ports/caching.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/application/ports/filesystem.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/application/ports/importscanner.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/application/ports/modulefinder.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/application/ports/packagefinder.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/application/ports/timing.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/application/usecases.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/domain/__init__.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/domain/analysis.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/exceptions.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/helpers.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/main.py +0 -0
- {grimp-3.1 → grimp-3.2}/src/grimp/py.typed +0 -0
{grimp-3.1 → grimp-3.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: grimp
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2
|
|
4
4
|
Classifier: Development Status :: 5 - Production/Stable
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: BSD License
|
|
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
16
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
16
17
|
Classifier: Programming Language :: Rust
|
|
17
18
|
Classifier: Topic :: Utilities
|
|
@@ -8,7 +8,7 @@ module-name = "grimp._rustgrimp"
|
|
|
8
8
|
|
|
9
9
|
[project]
|
|
10
10
|
name = "grimp"
|
|
11
|
-
version = "3.
|
|
11
|
+
version = "3.2"
|
|
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 = [
|
|
@@ -30,6 +30,7 @@ classifiers = [
|
|
|
30
30
|
"Programming Language :: Python :: 3.9",
|
|
31
31
|
"Programming Language :: Python :: 3.10",
|
|
32
32
|
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
33
34
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
34
35
|
"Programming Language :: Rust",
|
|
35
36
|
"Topic :: Utilities",
|
|
@@ -6,10 +6,10 @@ use std::collections::HashSet;
|
|
|
6
6
|
use std::time::Instant;
|
|
7
7
|
|
|
8
8
|
/// A group of layers at the same level in the layering.
|
|
9
|
-
/// These layers should be independent.
|
|
10
9
|
#[derive(PartialEq, Eq, Hash, Debug)]
|
|
11
10
|
pub struct Level<'a> {
|
|
12
11
|
pub layers: Vec<&'a str>,
|
|
12
|
+
pub independent: bool,
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
pub fn find_illegal_dependencies<'a>(
|
|
@@ -45,6 +45,8 @@ pub fn find_illegal_dependencies<'a>(
|
|
|
45
45
|
dependencies
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/// Return every permutation of modules that exist in the graph
|
|
49
|
+
/// in which the second should not import the first.
|
|
48
50
|
fn _generate_module_permutations<'a>(
|
|
49
51
|
graph: &'a ImportGraph,
|
|
50
52
|
levels: &'a [Level],
|
|
@@ -69,13 +71,18 @@ fn _generate_module_permutations<'a>(
|
|
|
69
71
|
continue;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
// Build the layers that mustn't import this higher layer.
|
|
73
|
-
// includes
|
|
74
|
+
// Build the layers that mustn't import this higher layer.
|
|
75
|
+
// That includes:
|
|
76
|
+
// * lower layers.
|
|
77
|
+
// * sibling layers, if the layer is independent.
|
|
74
78
|
let mut layers_forbidden_to_import_higher_layer: Vec<&str> = vec![];
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
|
|
80
|
+
if higher_level.independent {
|
|
81
|
+
for potential_sibling_layer in &higher_level.layers {
|
|
82
|
+
if potential_sibling_layer != higher_layer {
|
|
83
|
+
// It's a sibling layer.
|
|
84
|
+
layers_forbidden_to_import_higher_layer.push(potential_sibling_layer);
|
|
85
|
+
}
|
|
79
86
|
}
|
|
80
87
|
}
|
|
81
88
|
|
|
@@ -333,12 +340,15 @@ mod tests {
|
|
|
333
340
|
]));
|
|
334
341
|
let levels = vec![
|
|
335
342
|
Level {
|
|
343
|
+
independent: true,
|
|
336
344
|
layers: vec!["high"],
|
|
337
345
|
},
|
|
338
346
|
Level {
|
|
347
|
+
independent: true,
|
|
339
348
|
layers: vec!["mid_a", "mid_b", "mid_c"],
|
|
340
349
|
},
|
|
341
350
|
Level {
|
|
351
|
+
independent: true,
|
|
342
352
|
layers: vec!["low"],
|
|
343
353
|
},
|
|
344
354
|
];
|
|
@@ -405,9 +415,11 @@ mod tests {
|
|
|
405
415
|
]));
|
|
406
416
|
let levels = vec![
|
|
407
417
|
Level {
|
|
418
|
+
independent: true,
|
|
408
419
|
layers: vec!["high"],
|
|
409
420
|
},
|
|
410
421
|
Level {
|
|
422
|
+
independent: true,
|
|
411
423
|
layers: vec!["low"],
|
|
412
424
|
},
|
|
413
425
|
];
|
|
@@ -460,12 +472,15 @@ mod tests {
|
|
|
460
472
|
]));
|
|
461
473
|
let levels = vec![
|
|
462
474
|
Level {
|
|
475
|
+
independent: true,
|
|
463
476
|
layers: vec!["high"],
|
|
464
477
|
},
|
|
465
478
|
Level {
|
|
479
|
+
independent: true,
|
|
466
480
|
layers: vec!["mid_a", "mid_b", "mid_c"],
|
|
467
481
|
},
|
|
468
482
|
Level {
|
|
483
|
+
independent: true,
|
|
469
484
|
layers: vec!["low"],
|
|
470
485
|
},
|
|
471
486
|
];
|
|
@@ -553,16 +568,109 @@ mod tests {
|
|
|
553
568
|
);
|
|
554
569
|
}
|
|
555
570
|
|
|
571
|
+
#[test]
|
|
572
|
+
fn test_generate_module_permutations_sibling_layer_not_independent() {
|
|
573
|
+
let graph = ImportGraph::new(HashMap::from([
|
|
574
|
+
("mypackage.low", HashSet::new()),
|
|
575
|
+
("mypackage.low.blue", HashSet::from(["mypackage.utils"])),
|
|
576
|
+
("mypackage.low.green", HashSet::new()),
|
|
577
|
+
(
|
|
578
|
+
"mypackage.low.green.alpha",
|
|
579
|
+
HashSet::from(["mypackage.high.yellow"]),
|
|
580
|
+
),
|
|
581
|
+
("mypackage.mid_a", HashSet::new()),
|
|
582
|
+
("mypackage.mid_a.foo", HashSet::new()),
|
|
583
|
+
("mypackage.mid_b", HashSet::new()),
|
|
584
|
+
("mypackage.mid_b.foo", HashSet::new()),
|
|
585
|
+
("mypackage.mid_c", HashSet::new()),
|
|
586
|
+
("mypackage.mid_c.foo", HashSet::new()),
|
|
587
|
+
("mypackage.high", HashSet::from(["mypackage.low.blue"])),
|
|
588
|
+
("mypackage.high.yellow", HashSet::new()),
|
|
589
|
+
("mypackage.high.red", HashSet::new()),
|
|
590
|
+
("mypackage.high.red.beta", HashSet::new()),
|
|
591
|
+
("mypackage.utils", HashSet::from(["mypackage.high.red"])),
|
|
592
|
+
]));
|
|
593
|
+
let levels = vec![
|
|
594
|
+
Level {
|
|
595
|
+
independent: true,
|
|
596
|
+
layers: vec!["high"],
|
|
597
|
+
},
|
|
598
|
+
Level {
|
|
599
|
+
independent: false,
|
|
600
|
+
layers: vec!["mid_a", "mid_b", "mid_c"],
|
|
601
|
+
},
|
|
602
|
+
Level {
|
|
603
|
+
independent: true,
|
|
604
|
+
layers: vec!["low"],
|
|
605
|
+
},
|
|
606
|
+
];
|
|
607
|
+
let containers = HashSet::from(["mypackage"]);
|
|
608
|
+
|
|
609
|
+
let perms = _generate_module_permutations(&graph, &levels, &containers);
|
|
610
|
+
|
|
611
|
+
let result: HashSet<(String, String, Option<String>)> = HashSet::from_iter(perms);
|
|
612
|
+
let (high, mid_a, mid_b, mid_c, low) = (
|
|
613
|
+
"mypackage.high",
|
|
614
|
+
"mypackage.mid_a",
|
|
615
|
+
"mypackage.mid_b",
|
|
616
|
+
"mypackage.mid_c",
|
|
617
|
+
"mypackage.low",
|
|
618
|
+
);
|
|
619
|
+
assert_eq!(
|
|
620
|
+
result,
|
|
621
|
+
HashSet::from_iter([
|
|
622
|
+
(
|
|
623
|
+
high.to_string(),
|
|
624
|
+
mid_a.to_string(),
|
|
625
|
+
Some("mypackage".to_string())
|
|
626
|
+
),
|
|
627
|
+
(
|
|
628
|
+
high.to_string(),
|
|
629
|
+
mid_b.to_string(),
|
|
630
|
+
Some("mypackage".to_string())
|
|
631
|
+
),
|
|
632
|
+
(
|
|
633
|
+
high.to_string(),
|
|
634
|
+
mid_c.to_string(),
|
|
635
|
+
Some("mypackage".to_string())
|
|
636
|
+
),
|
|
637
|
+
(
|
|
638
|
+
high.to_string(),
|
|
639
|
+
low.to_string(),
|
|
640
|
+
Some("mypackage".to_string())
|
|
641
|
+
),
|
|
642
|
+
(
|
|
643
|
+
mid_a.to_string(),
|
|
644
|
+
low.to_string(),
|
|
645
|
+
Some("mypackage".to_string())
|
|
646
|
+
),
|
|
647
|
+
(
|
|
648
|
+
mid_b.to_string(),
|
|
649
|
+
low.to_string(),
|
|
650
|
+
Some("mypackage".to_string())
|
|
651
|
+
),
|
|
652
|
+
(
|
|
653
|
+
mid_c.to_string(),
|
|
654
|
+
low.to_string(),
|
|
655
|
+
Some("mypackage".to_string())
|
|
656
|
+
),
|
|
657
|
+
])
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
556
661
|
#[test]
|
|
557
662
|
fn test_layers_from_levels() {
|
|
558
663
|
let levels = vec![
|
|
559
664
|
Level {
|
|
665
|
+
independent: true,
|
|
560
666
|
layers: vec!["high"],
|
|
561
667
|
},
|
|
562
668
|
Level {
|
|
669
|
+
independent: true,
|
|
563
670
|
layers: vec!["medium_a", "medium_b", "medium_c"],
|
|
564
671
|
},
|
|
565
672
|
Level {
|
|
673
|
+
independent: true,
|
|
566
674
|
layers: vec!["low"],
|
|
567
675
|
},
|
|
568
676
|
];
|
|
@@ -50,8 +50,11 @@ pub fn find_illegal_dependencies<'a>(
|
|
|
50
50
|
fn rustify_levels(levels_python: &PyTuple) -> Vec<Level> {
|
|
51
51
|
let mut rust_levels: Vec<Level> = vec![];
|
|
52
52
|
for level_python in levels_python.into_iter() {
|
|
53
|
-
let
|
|
53
|
+
let level_dict = level_python.downcast::<PyDict>().unwrap();
|
|
54
|
+
let layers: HashSet<&str> = level_dict.get_item("layers").unwrap().extract().unwrap();
|
|
55
|
+
let independent: bool = level_dict.get_item("independent").unwrap().extract().unwrap();
|
|
54
56
|
rust_levels.push(Level {
|
|
57
|
+
independent,
|
|
55
58
|
layers: layers.into_iter().collect(),
|
|
56
59
|
});
|
|
57
60
|
}
|
|
@@ -104,16 +107,38 @@ fn convert_dependencies_to_python<'a>(
|
|
|
104
107
|
#[cfg(test)]
|
|
105
108
|
mod tests {
|
|
106
109
|
use super::*;
|
|
107
|
-
|
|
110
|
+
|
|
111
|
+
// Macro to easily define a python dict.
|
|
112
|
+
// Adapted from the hash_map! macro in https://github.com/jofas/map_macro.
|
|
113
|
+
macro_rules! pydict {
|
|
114
|
+
($py: ident, {$($k: expr => $v: expr),*, $(,)?}) => {
|
|
115
|
+
{
|
|
116
|
+
let dict = PyDict::new($py);
|
|
117
|
+
$(
|
|
118
|
+
dict.set_item($k, $v)?;
|
|
119
|
+
)*
|
|
120
|
+
dict
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
108
124
|
|
|
109
125
|
#[test]
|
|
110
126
|
fn test_rustify_levels_no_sibling_layers() {
|
|
111
127
|
pyo3::prepare_freethreaded_python();
|
|
112
128
|
Python::with_gil(|py| -> PyResult<()> {
|
|
113
|
-
let elements: Vec<&
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
let elements: Vec<&PyDict> = vec![
|
|
130
|
+
pydict! (py, {
|
|
131
|
+
"independent" => true,
|
|
132
|
+
"layers" => HashSet::from(["high"]),
|
|
133
|
+
}),
|
|
134
|
+
pydict! (py, {
|
|
135
|
+
"independent" => true,
|
|
136
|
+
"layers" => HashSet::from(["medium"]),
|
|
137
|
+
}),
|
|
138
|
+
pydict! (py, {
|
|
139
|
+
"independent" => true,
|
|
140
|
+
"layers" => HashSet::from(["low"]),
|
|
141
|
+
})
|
|
117
142
|
];
|
|
118
143
|
let python_levels: &PyTuple = PyTuple::new(py, elements);
|
|
119
144
|
|
|
@@ -123,12 +148,15 @@ mod tests {
|
|
|
123
148
|
result,
|
|
124
149
|
vec![
|
|
125
150
|
Level {
|
|
151
|
+
independent: true,
|
|
126
152
|
layers: vec!["high"]
|
|
127
153
|
},
|
|
128
154
|
Level {
|
|
155
|
+
independent: true,
|
|
129
156
|
layers: vec!["medium"]
|
|
130
157
|
},
|
|
131
158
|
Level {
|
|
159
|
+
independent: true,
|
|
132
160
|
layers: vec!["low"]
|
|
133
161
|
}
|
|
134
162
|
]
|
|
@@ -143,10 +171,23 @@ mod tests {
|
|
|
143
171
|
fn test_rustify_levels_sibling_layers() {
|
|
144
172
|
pyo3::prepare_freethreaded_python();
|
|
145
173
|
Python::with_gil(|py| -> PyResult<()> {
|
|
146
|
-
let elements: Vec<&
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
174
|
+
let elements: Vec<&PyDict> = vec![
|
|
175
|
+
pydict! (py, {
|
|
176
|
+
"independent" => true,
|
|
177
|
+
"layers" => HashSet::from(["high"]),
|
|
178
|
+
}),
|
|
179
|
+
pydict! (py, {
|
|
180
|
+
"independent" => true,
|
|
181
|
+
"layers" => HashSet::from(["blue", "green", "orange"]),
|
|
182
|
+
}),
|
|
183
|
+
pydict! (py, {
|
|
184
|
+
"independent" => false,
|
|
185
|
+
"layers" => HashSet::from(["red", "yellow"]),
|
|
186
|
+
}),
|
|
187
|
+
pydict! (py, {
|
|
188
|
+
"independent" => true,
|
|
189
|
+
"layers" => HashSet::from(["low"]),
|
|
190
|
+
})
|
|
150
191
|
];
|
|
151
192
|
let python_levels: &PyTuple = PyTuple::new(py, elements);
|
|
152
193
|
|
|
@@ -159,12 +200,19 @@ mod tests {
|
|
|
159
200
|
result,
|
|
160
201
|
vec![
|
|
161
202
|
Level {
|
|
203
|
+
independent: true,
|
|
162
204
|
layers: vec!["high"]
|
|
163
205
|
},
|
|
164
206
|
Level {
|
|
207
|
+
independent: true,
|
|
165
208
|
layers: vec!["blue", "green", "orange"]
|
|
166
209
|
},
|
|
167
210
|
Level {
|
|
211
|
+
independent: false,
|
|
212
|
+
layers: vec!["red", "yellow"]
|
|
213
|
+
},
|
|
214
|
+
Level {
|
|
215
|
+
independent: true,
|
|
168
216
|
layers: vec!["low"]
|
|
169
217
|
}
|
|
170
218
|
]
|
|
@@ -21,18 +21,23 @@ fn test_large_graph() {
|
|
|
21
21
|
|
|
22
22
|
let levels = vec![
|
|
23
23
|
Level {
|
|
24
|
+
independent: true,
|
|
24
25
|
layers: vec!["plugins"],
|
|
25
26
|
},
|
|
26
27
|
Level {
|
|
28
|
+
independent: true,
|
|
27
29
|
layers: vec!["interfaces"],
|
|
28
30
|
},
|
|
29
31
|
Level {
|
|
32
|
+
independent: true,
|
|
30
33
|
layers: vec!["application"],
|
|
31
34
|
},
|
|
32
35
|
Level {
|
|
36
|
+
independent: true,
|
|
33
37
|
layers: vec!["domain"],
|
|
34
38
|
},
|
|
35
39
|
Level {
|
|
40
|
+
independent: true,
|
|
36
41
|
layers: vec!["data"],
|
|
37
42
|
},
|
|
38
43
|
];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
__version__ = "3.
|
|
1
|
+
__version__ = "3.2"
|
|
2
2
|
|
|
3
3
|
from .application.ports.graph import DetailedImport, ImportGraph
|
|
4
4
|
from .domain.analysis import PackageDependency, Route
|
|
5
|
-
from .domain.valueobjects import DirectImport, Module
|
|
5
|
+
from .domain.valueobjects import DirectImport, Module, Layer
|
|
6
6
|
from .main import build_graph
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
@@ -13,4 +13,5 @@ __all__ = [
|
|
|
13
13
|
"PackageDependency",
|
|
14
14
|
"Route",
|
|
15
15
|
"build_graph",
|
|
16
|
+
"Layer",
|
|
16
17
|
]
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Iterator, Sequence, TypedDict
|
|
5
|
+
|
|
6
|
+
from grimp import Route
|
|
7
|
+
from grimp import _rustgrimp as rust # type: ignore[attr-defined]
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from grimp.adaptors.graph import ImportGraph
|
|
11
|
+
|
|
12
|
+
from grimp.domain.analysis import PackageDependency
|
|
13
|
+
from grimp.exceptions import NoSuchContainer
|
|
14
|
+
from grimp.domain.valueobjects import Layer
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def parse_layers(layers: Sequence[Layer | str | set[str]]) -> tuple[Layer, ...]:
|
|
18
|
+
"""
|
|
19
|
+
Convert the passed raw `layers` into `Layer`s.
|
|
20
|
+
"""
|
|
21
|
+
out_layers = []
|
|
22
|
+
for layer in layers:
|
|
23
|
+
if isinstance(layer, Layer):
|
|
24
|
+
out_layers.append(layer)
|
|
25
|
+
elif isinstance(layer, str):
|
|
26
|
+
out_layers.append(Layer(layer, independent=True))
|
|
27
|
+
else:
|
|
28
|
+
out_layers.append(Layer(*tuple(layer), independent=True))
|
|
29
|
+
return tuple(out_layers)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def find_illegal_dependencies(
|
|
33
|
+
graph: ImportGraph,
|
|
34
|
+
layers: Sequence[Layer],
|
|
35
|
+
containers: set[str],
|
|
36
|
+
) -> set[PackageDependency]:
|
|
37
|
+
"""
|
|
38
|
+
Find dependencies that don't conform to the supplied layered architecture.
|
|
39
|
+
|
|
40
|
+
See ImportGraph.find_illegal_dependencies_for_layers.
|
|
41
|
+
|
|
42
|
+
The only difference between this and the method is that the containers passed in
|
|
43
|
+
is already a (potentially empty) set.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
rust_package_dependency_tuple = rust.find_illegal_dependencies(
|
|
47
|
+
levels=tuple(
|
|
48
|
+
{"layers": layer.module_tails, "independent": layer.independent}
|
|
49
|
+
for layer in layers
|
|
50
|
+
),
|
|
51
|
+
containers=set(containers),
|
|
52
|
+
importeds_by_importer=graph._importeds_by_importer,
|
|
53
|
+
)
|
|
54
|
+
except rust.NoSuchContainer as e:
|
|
55
|
+
raise NoSuchContainer(str(e))
|
|
56
|
+
|
|
57
|
+
rust_package_dependencies = _dependencies_from_tuple(rust_package_dependency_tuple)
|
|
58
|
+
return rust_package_dependencies
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class _RustRoute(TypedDict):
|
|
62
|
+
heads: frozenset[str]
|
|
63
|
+
middle: tuple[str, ...]
|
|
64
|
+
tails: frozenset[str]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class _RustPackageDependency(TypedDict):
|
|
68
|
+
importer: str
|
|
69
|
+
imported: str
|
|
70
|
+
routes: tuple[_RustRoute, ...]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _dependencies_from_tuple(
|
|
74
|
+
rust_package_dependency_tuple: tuple[_RustPackageDependency, ...]
|
|
75
|
+
) -> set[PackageDependency]:
|
|
76
|
+
return {
|
|
77
|
+
PackageDependency(
|
|
78
|
+
imported=dep_dict["imported"],
|
|
79
|
+
importer=dep_dict["importer"],
|
|
80
|
+
routes=frozenset(
|
|
81
|
+
{
|
|
82
|
+
Route(
|
|
83
|
+
heads=route_dict["heads"],
|
|
84
|
+
middle=route_dict["middle"],
|
|
85
|
+
tails=route_dict["tails"],
|
|
86
|
+
)
|
|
87
|
+
for route_dict in dep_dict["routes"]
|
|
88
|
+
}
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
for dep_dict in rust_package_dependency_tuple
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class _Module:
|
|
96
|
+
"""
|
|
97
|
+
A Python module.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(self, name: str) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Args:
|
|
103
|
+
name: The fully qualified name of a Python module, e.g. 'package.foo.bar'.
|
|
104
|
+
"""
|
|
105
|
+
self.name = name
|
|
106
|
+
|
|
107
|
+
def __str__(self) -> str:
|
|
108
|
+
return self.name
|
|
109
|
+
|
|
110
|
+
def __eq__(self, other: Any) -> bool:
|
|
111
|
+
if isinstance(other, self.__class__):
|
|
112
|
+
return hash(self) == hash(other)
|
|
113
|
+
else:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
def __hash__(self) -> int:
|
|
117
|
+
return hash(str(self))
|
|
118
|
+
|
|
119
|
+
def is_descendant_of(self, module: "_Module") -> bool:
|
|
120
|
+
return self.name.startswith(f"{module.name}.")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@dataclass(frozen=True)
|
|
124
|
+
class _Link:
|
|
125
|
+
importer: str
|
|
126
|
+
imported: str
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# A chain of modules, each of which imports the next.
|
|
130
|
+
if TYPE_CHECKING:
|
|
131
|
+
# TODO: remove TYPE_CHECKING conditional once on Python 3.9.
|
|
132
|
+
_Chain = tuple[str, ...]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _generate_module_permutations(
|
|
136
|
+
graph: ImportGraph,
|
|
137
|
+
layers: Sequence[str],
|
|
138
|
+
containers: set[str],
|
|
139
|
+
) -> Iterator[tuple[_Module, _Module, str | None]]:
|
|
140
|
+
"""
|
|
141
|
+
Return all possible combinations of higher level and lower level modules, in pairs.
|
|
142
|
+
|
|
143
|
+
Each pair of modules consists of immediate children of two different layers. The first
|
|
144
|
+
module is in a layer higher than the layer of the second module. This means the first
|
|
145
|
+
module is allowed to import the second, but not the other way around.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
module_in_higher_layer, module_in_lower_layer, container
|
|
149
|
+
"""
|
|
150
|
+
# If there are no containers, we still want to run the loop once.
|
|
151
|
+
quasi_containers = containers or [None]
|
|
152
|
+
|
|
153
|
+
for container in quasi_containers:
|
|
154
|
+
for index, higher_layer in enumerate(layers):
|
|
155
|
+
higher_layer_module = _module_from_layer(higher_layer, container)
|
|
156
|
+
|
|
157
|
+
if higher_layer_module.name not in graph.modules:
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
for lower_layer in layers[index + 1 :]:
|
|
161
|
+
lower_layer_module = _module_from_layer(lower_layer, container)
|
|
162
|
+
|
|
163
|
+
if lower_layer_module.name not in graph.modules:
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
yield higher_layer_module, lower_layer_module, container
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _module_from_layer(layer: str, container: str | None = None) -> _Module:
|
|
170
|
+
if container:
|
|
171
|
+
name = ".".join([container, layer])
|
|
172
|
+
else:
|
|
173
|
+
name = layer
|
|
174
|
+
return _Module(name)
|
|
@@ -6,7 +6,7 @@ from typing import Dict, List, Optional, Sequence, Set, Tuple, cast
|
|
|
6
6
|
from grimp.algorithms.shortest_path import bidirectional_shortest_path
|
|
7
7
|
from grimp.application.ports import graph
|
|
8
8
|
from grimp.domain.analysis import PackageDependency
|
|
9
|
-
from grimp.domain.valueobjects import Module
|
|
9
|
+
from grimp.domain.valueobjects import Module, Layer
|
|
10
10
|
from grimp.exceptions import ModuleNotPresent
|
|
11
11
|
|
|
12
12
|
from . import _layers
|
|
@@ -365,9 +365,10 @@ class ImportGraph(graph.ImportGraph):
|
|
|
365
365
|
|
|
366
366
|
def find_illegal_dependencies_for_layers(
|
|
367
367
|
self,
|
|
368
|
-
layers: Sequence[str | set[str]],
|
|
369
|
-
containers:
|
|
368
|
+
layers: Sequence[Layer | str | set[str]],
|
|
369
|
+
containers: set[str] | None = None,
|
|
370
370
|
) -> set[PackageDependency]:
|
|
371
|
+
layers = _layers.parse_layers(layers)
|
|
371
372
|
return _layers.find_illegal_dependencies(
|
|
372
373
|
graph=self, layers=layers, containers=containers or set()
|
|
373
374
|
)
|
|
@@ -74,6 +74,9 @@ class ModuleFinder(modulefinder.AbstractModuleFinder):
|
|
|
74
74
|
if filename.startswith("."):
|
|
75
75
|
return False
|
|
76
76
|
|
|
77
|
+
if not filename.endswith(".py"):
|
|
78
|
+
return False
|
|
79
|
+
|
|
77
80
|
# Ignore files like some.module.py.
|
|
78
81
|
if filename.count(".") > 1:
|
|
79
82
|
logger.warning(
|
|
@@ -82,7 +85,7 @@ class ModuleFinder(modulefinder.AbstractModuleFinder):
|
|
|
82
85
|
)
|
|
83
86
|
return False
|
|
84
87
|
|
|
85
|
-
return
|
|
88
|
+
return True
|
|
86
89
|
|
|
87
90
|
def _module_name_from_filename(
|
|
88
91
|
self, package_name: str, filename_and_path: str, package_directory: str
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
|
-
from typing import Iterator, List, Optional, Sequence, Set, Tuple
|
|
4
|
+
from typing import Iterator, List, Optional, Sequence, Set, Tuple
|
|
5
5
|
|
|
6
6
|
from typing_extensions import TypedDict
|
|
7
7
|
|
|
8
8
|
from grimp.domain.analysis import PackageDependency
|
|
9
|
+
from grimp.domain.valueobjects import Layer
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class DetailedImport(TypedDict):
|
|
@@ -271,25 +272,29 @@ class ImportGraph(abc.ABC):
|
|
|
271
272
|
|
|
272
273
|
def find_illegal_dependencies_for_layers(
|
|
273
274
|
self,
|
|
274
|
-
layers: Sequence[
|
|
275
|
-
containers:
|
|
275
|
+
layers: Sequence[Layer | str | set[str]],
|
|
276
|
+
containers: set[str] | None = None,
|
|
276
277
|
) -> set[PackageDependency]:
|
|
277
278
|
"""
|
|
278
279
|
Find dependencies that don't conform to the supplied layered architecture.
|
|
279
280
|
|
|
280
|
-
'Layers' is an architectural pattern in which a list of
|
|
281
|
+
'Layers' is an architectural pattern in which a list of modules/packages
|
|
281
282
|
have a dependency direction from high to low. In other words, a higher layer would
|
|
282
283
|
be allowed to import a lower layer, but not the other way around.
|
|
283
284
|
|
|
284
|
-
Additionally, multiple
|
|
285
|
-
`mypackage.utils` and `mypackage.logging` might sit at the bottom, so they
|
|
286
|
-
import from any other layers.
|
|
287
|
-
|
|
285
|
+
Additionally, multiple modules can be grouped together at the same layer;
|
|
286
|
+
for example `mypackage.utils` and `mypackage.logging` might sit at the bottom, so they
|
|
287
|
+
cannot import from any other layers. To specify that multiple modules should
|
|
288
|
+
be treated as siblings within a single layer, pass a `Layer`. The `Layer.independent`
|
|
289
|
+
field can be used to specify whether the sibling modules should be treated as independent
|
|
290
|
+
- should imports between sibling modules be forbidden (default) or allowed? For backwards
|
|
291
|
+
compatibility it is also possible to pass a simple `set[str]` to describe a layer. In this
|
|
292
|
+
case the sibling modules within the layer will be considered independent.
|
|
288
293
|
|
|
289
294
|
Arguments:
|
|
290
295
|
|
|
291
|
-
- layers: A sequence, each element of which consists either of the name
|
|
292
|
-
module
|
|
296
|
+
- layers: A sequence, each element of which consists either of a `Layer`, the name
|
|
297
|
+
of a layer module or a set of sibling modules. If containers
|
|
293
298
|
are also specified, then these names must be relative to the container.
|
|
294
299
|
The order is from higher to lower level layers. Any layers that don't
|
|
295
300
|
exist in the graph will be ignored.
|
|
@@ -84,3 +84,23 @@ class DirectImport(ValueObject):
|
|
|
84
84
|
|
|
85
85
|
def __hash__(self) -> int:
|
|
86
86
|
return hash((str(self), self.line_contents))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class Layer(ValueObject):
|
|
90
|
+
"""
|
|
91
|
+
A layer within a layered architecture.
|
|
92
|
+
|
|
93
|
+
If layer.independent is True then the modules within the layer are considered
|
|
94
|
+
independent. This is the default.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
*module_tails: str,
|
|
100
|
+
independent: bool = True,
|
|
101
|
+
) -> None:
|
|
102
|
+
self.module_tails = set(module_tails)
|
|
103
|
+
self.independent = independent
|
|
104
|
+
|
|
105
|
+
def __str__(self) -> str:
|
|
106
|
+
return f"{self.module_tails}, independent={self.independent}"
|
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import copy
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Iterator, Sequence, TypedDict, Union
|
|
6
|
-
|
|
7
|
-
from grimp import Route
|
|
8
|
-
from grimp import _rustgrimp as rust # type: ignore[attr-defined]
|
|
9
|
-
|
|
10
|
-
if TYPE_CHECKING:
|
|
11
|
-
from grimp.adaptors.graph import ImportGraph
|
|
12
|
-
|
|
13
|
-
from grimp.domain.analysis import PackageDependency
|
|
14
|
-
from grimp.exceptions import NoSuchContainer
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def find_illegal_dependencies(
|
|
18
|
-
graph: ImportGraph,
|
|
19
|
-
layers: Sequence[Union[str, set[str]]],
|
|
20
|
-
containers: set[str],
|
|
21
|
-
) -> set[PackageDependency]:
|
|
22
|
-
"""
|
|
23
|
-
Find dependencies that don't conform to the supplied layered architecture.
|
|
24
|
-
|
|
25
|
-
See ImportGraph.find_illegal_dependencies_for_layers.
|
|
26
|
-
|
|
27
|
-
The only difference between this and the method is that the containers passed in
|
|
28
|
-
is already a (potentially empty) set.
|
|
29
|
-
"""
|
|
30
|
-
try:
|
|
31
|
-
rust_package_dependency_tuple = rust.find_illegal_dependencies(
|
|
32
|
-
levels=_layers_to_levels(layers),
|
|
33
|
-
containers=set(containers),
|
|
34
|
-
importeds_by_importer=graph._importeds_by_importer,
|
|
35
|
-
)
|
|
36
|
-
except rust.NoSuchContainer as e:
|
|
37
|
-
raise NoSuchContainer(str(e))
|
|
38
|
-
|
|
39
|
-
rust_package_dependencies = _dependencies_from_tuple(rust_package_dependency_tuple)
|
|
40
|
-
return rust_package_dependencies
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class _RustRoute(TypedDict):
|
|
44
|
-
heads: frozenset[str]
|
|
45
|
-
middle: tuple[str, ...]
|
|
46
|
-
tails: frozenset[str]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class _RustPackageDependency(TypedDict):
|
|
50
|
-
importer: str
|
|
51
|
-
imported: str
|
|
52
|
-
routes: tuple[_RustRoute, ...]
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _layers_to_levels(layers: Sequence[Union[str, set[str]]]) -> tuple[set[str], ...]:
|
|
56
|
-
"""
|
|
57
|
-
Convert any standalone layers to a one-element level.
|
|
58
|
-
"""
|
|
59
|
-
return tuple({layer} if isinstance(layer, str) else set(layer) for layer in layers)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def _dependencies_from_tuple(
|
|
63
|
-
rust_package_dependency_tuple: tuple[_RustPackageDependency, ...]
|
|
64
|
-
) -> set[PackageDependency]:
|
|
65
|
-
return {
|
|
66
|
-
PackageDependency(
|
|
67
|
-
imported=dep_dict["imported"],
|
|
68
|
-
importer=dep_dict["importer"],
|
|
69
|
-
routes=frozenset(
|
|
70
|
-
{
|
|
71
|
-
Route(
|
|
72
|
-
heads=route_dict["heads"],
|
|
73
|
-
middle=route_dict["middle"],
|
|
74
|
-
tails=route_dict["tails"],
|
|
75
|
-
)
|
|
76
|
-
for route_dict in dep_dict["routes"]
|
|
77
|
-
}
|
|
78
|
-
),
|
|
79
|
-
)
|
|
80
|
-
for dep_dict in rust_package_dependency_tuple
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class _Module:
|
|
85
|
-
"""
|
|
86
|
-
A Python module.
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
|
-
def __init__(self, name: str) -> None:
|
|
90
|
-
"""
|
|
91
|
-
Args:
|
|
92
|
-
name: The fully qualified name of a Python module, e.g. 'package.foo.bar'.
|
|
93
|
-
"""
|
|
94
|
-
self.name = name
|
|
95
|
-
|
|
96
|
-
def __str__(self) -> str:
|
|
97
|
-
return self.name
|
|
98
|
-
|
|
99
|
-
def __eq__(self, other: Any) -> bool:
|
|
100
|
-
if isinstance(other, self.__class__):
|
|
101
|
-
return hash(self) == hash(other)
|
|
102
|
-
else:
|
|
103
|
-
return False
|
|
104
|
-
|
|
105
|
-
def __hash__(self) -> int:
|
|
106
|
-
return hash(str(self))
|
|
107
|
-
|
|
108
|
-
def is_descendant_of(self, module: "_Module") -> bool:
|
|
109
|
-
return self.name.startswith(f"{module.name}.")
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
@dataclass(frozen=True)
|
|
113
|
-
class _Link:
|
|
114
|
-
importer: str
|
|
115
|
-
imported: str
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
# A chain of modules, each of which imports the next.
|
|
119
|
-
if TYPE_CHECKING:
|
|
120
|
-
# TODO: remove TYPE_CHECKING conditional once on Python 3.9.
|
|
121
|
-
_Chain = tuple[str, ...]
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def _generate_module_permutations(
|
|
125
|
-
graph: ImportGraph,
|
|
126
|
-
layers: Sequence[str],
|
|
127
|
-
containers: set[str],
|
|
128
|
-
) -> Iterator[tuple[_Module, _Module, str | None]]:
|
|
129
|
-
"""
|
|
130
|
-
Return all possible combinations of higher level and lower level modules, in pairs.
|
|
131
|
-
|
|
132
|
-
Each pair of modules consists of immediate children of two different layers. The first
|
|
133
|
-
module is in a layer higher than the layer of the second module. This means the first
|
|
134
|
-
module is allowed to import the second, but not the other way around.
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
module_in_higher_layer, module_in_lower_layer, container
|
|
138
|
-
"""
|
|
139
|
-
# If there are no containers, we still want to run the loop once.
|
|
140
|
-
quasi_containers = containers or [None]
|
|
141
|
-
|
|
142
|
-
for container in quasi_containers:
|
|
143
|
-
for index, higher_layer in enumerate(layers):
|
|
144
|
-
higher_layer_module = _module_from_layer(higher_layer, container)
|
|
145
|
-
|
|
146
|
-
if higher_layer_module.name not in graph.modules:
|
|
147
|
-
continue
|
|
148
|
-
|
|
149
|
-
for lower_layer in layers[index + 1 :]:
|
|
150
|
-
lower_layer_module = _module_from_layer(lower_layer, container)
|
|
151
|
-
|
|
152
|
-
if lower_layer_module.name not in graph.modules:
|
|
153
|
-
continue
|
|
154
|
-
|
|
155
|
-
yield higher_layer_module, lower_layer_module, container
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def _module_from_layer(layer: str, container: str | None = None) -> _Module:
|
|
159
|
-
if container:
|
|
160
|
-
name = ".".join([container, layer])
|
|
161
|
-
else:
|
|
162
|
-
name = layer
|
|
163
|
-
return _Module(name)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def _search_for_package_dependency(
|
|
167
|
-
higher_layer_package: _Module,
|
|
168
|
-
lower_layer_package: _Module,
|
|
169
|
-
layers: Sequence[str],
|
|
170
|
-
container: str | None,
|
|
171
|
-
graph: ImportGraph,
|
|
172
|
-
) -> PackageDependency | None:
|
|
173
|
-
"""
|
|
174
|
-
Return a PackageDependency containing illegal chains between two layers, if they exist.
|
|
175
|
-
"""
|
|
176
|
-
temp_graph = copy.deepcopy(graph)
|
|
177
|
-
_remove_other_layers(
|
|
178
|
-
temp_graph,
|
|
179
|
-
layers=layers,
|
|
180
|
-
container=container,
|
|
181
|
-
layers_to_preserve=(higher_layer_package, lower_layer_package),
|
|
182
|
-
)
|
|
183
|
-
# Assemble direct imports between the layers, then remove them.
|
|
184
|
-
import_details_between_layers = _pop_direct_imports(
|
|
185
|
-
higher_layer_package=higher_layer_package,
|
|
186
|
-
lower_layer_package=lower_layer_package,
|
|
187
|
-
graph=temp_graph,
|
|
188
|
-
)
|
|
189
|
-
routes: set[Route] = set()
|
|
190
|
-
|
|
191
|
-
for import_details_list in import_details_between_layers:
|
|
192
|
-
any_element = tuple(import_details_list)[0]
|
|
193
|
-
routes.add(
|
|
194
|
-
Route(
|
|
195
|
-
heads=frozenset({any_element.importer}),
|
|
196
|
-
middle=(),
|
|
197
|
-
tails=frozenset({any_element.imported}),
|
|
198
|
-
)
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
indirect_routes = _get_indirect_routes(
|
|
202
|
-
temp_graph,
|
|
203
|
-
importer_package=lower_layer_package,
|
|
204
|
-
imported_package=higher_layer_package,
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
routes |= indirect_routes
|
|
208
|
-
|
|
209
|
-
if routes:
|
|
210
|
-
return PackageDependency(
|
|
211
|
-
importer=lower_layer_package.name,
|
|
212
|
-
imported=higher_layer_package.name,
|
|
213
|
-
routes=frozenset(routes),
|
|
214
|
-
)
|
|
215
|
-
else:
|
|
216
|
-
return None
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def _get_indirect_routes(
|
|
220
|
-
graph: ImportGraph, importer_package: _Module, imported_package: _Module
|
|
221
|
-
) -> set[Route]:
|
|
222
|
-
"""
|
|
223
|
-
Squashes the two packages.
|
|
224
|
-
Gets a list of paths between them, called segments.
|
|
225
|
-
Add the heads and tails to the segments.
|
|
226
|
-
"""
|
|
227
|
-
temp_graph = copy.deepcopy(graph)
|
|
228
|
-
|
|
229
|
-
temp_graph.squash_module(importer_package.name)
|
|
230
|
-
temp_graph.squash_module(imported_package.name)
|
|
231
|
-
|
|
232
|
-
middles = _find_middles(
|
|
233
|
-
temp_graph,
|
|
234
|
-
importer=importer_package,
|
|
235
|
-
imported=imported_package,
|
|
236
|
-
)
|
|
237
|
-
return _middles_to_routes(graph, middles, importer=importer_package, imported=imported_package)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def _remove_other_layers(
|
|
241
|
-
graph: ImportGraph,
|
|
242
|
-
layers: Sequence[str],
|
|
243
|
-
container: str | None,
|
|
244
|
-
layers_to_preserve: tuple[_Module, ...],
|
|
245
|
-
) -> None:
|
|
246
|
-
for index, layer in enumerate(layers): # type: ignore
|
|
247
|
-
candidate_layer = _module_from_layer(layer, container)
|
|
248
|
-
if candidate_layer.name in graph.modules and candidate_layer not in layers_to_preserve:
|
|
249
|
-
_remove_layer(graph, layer_package=candidate_layer)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
def _remove_layer(graph: ImportGraph, layer_package: _Module) -> None:
|
|
253
|
-
for module in graph.find_descendants(layer_package.name):
|
|
254
|
-
graph.remove_module(module)
|
|
255
|
-
graph.remove_module(layer_package.name)
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
def _pop_direct_imports(
|
|
259
|
-
higher_layer_package, lower_layer_package, graph: ImportGraph
|
|
260
|
-
) -> set[frozenset[_Link]]:
|
|
261
|
-
import_details_set: set[frozenset[_Link]] = set()
|
|
262
|
-
|
|
263
|
-
lower_layer_modules = {lower_layer_package.name} | graph.find_descendants(
|
|
264
|
-
lower_layer_package.name
|
|
265
|
-
)
|
|
266
|
-
for lower_layer_module in lower_layer_modules:
|
|
267
|
-
imported_modules = graph.find_modules_directly_imported_by(lower_layer_module).copy()
|
|
268
|
-
for imported_module in imported_modules:
|
|
269
|
-
if _Module(imported_module) == higher_layer_package or _Module(
|
|
270
|
-
imported_module
|
|
271
|
-
).is_descendant_of(higher_layer_package):
|
|
272
|
-
import_details = frozenset(
|
|
273
|
-
{
|
|
274
|
-
_Link(
|
|
275
|
-
importer=lower_layer_module,
|
|
276
|
-
imported=imported_module,
|
|
277
|
-
),
|
|
278
|
-
}
|
|
279
|
-
)
|
|
280
|
-
import_details_set.add(import_details)
|
|
281
|
-
graph.remove_import(importer=lower_layer_module, imported=imported_module)
|
|
282
|
-
return import_details_set
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
def _find_middles(graph: ImportGraph, importer: _Module, imported: _Module) -> set[_Chain]:
|
|
286
|
-
"""
|
|
287
|
-
Return set of headless and tailless chains.
|
|
288
|
-
"""
|
|
289
|
-
middles: set[_Chain] = set()
|
|
290
|
-
|
|
291
|
-
for chain in _pop_shortest_chains(graph, importer=importer.name, imported=imported.name):
|
|
292
|
-
if len(chain) == 2:
|
|
293
|
-
raise ValueError("Direct chain found - these should have been removed.")
|
|
294
|
-
middles.add(chain[1:-1])
|
|
295
|
-
|
|
296
|
-
return middles
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
def _middles_to_routes(
|
|
300
|
-
graph: ImportGraph, middles: set[_Chain], importer: _Module, imported: _Module
|
|
301
|
-
) -> set[Route]:
|
|
302
|
-
"""
|
|
303
|
-
Build a set of routes from the chains between one package and another.
|
|
304
|
-
|
|
305
|
-
The middles are the chains that exist from the importer package to
|
|
306
|
-
the importer package. This function works out the head and tail packages of
|
|
307
|
-
those chains by consulting the graph.
|
|
308
|
-
"""
|
|
309
|
-
routes: set[Route] = set()
|
|
310
|
-
|
|
311
|
-
for middle in middles:
|
|
312
|
-
heads: set[str] = set()
|
|
313
|
-
imported_module = middle[0]
|
|
314
|
-
candidate_modules = sorted(graph.find_modules_that_directly_import(imported_module))
|
|
315
|
-
for module in [
|
|
316
|
-
m
|
|
317
|
-
for m in candidate_modules
|
|
318
|
-
if _Module(m) == importer or _Module(m).is_descendant_of(importer)
|
|
319
|
-
]:
|
|
320
|
-
heads.add(module)
|
|
321
|
-
|
|
322
|
-
tails: set[str] = set()
|
|
323
|
-
importer_module = middle[-1]
|
|
324
|
-
candidate_modules = sorted(graph.find_modules_directly_imported_by(importer_module))
|
|
325
|
-
for module in [
|
|
326
|
-
m
|
|
327
|
-
for m in candidate_modules
|
|
328
|
-
if _Module(m) == imported or _Module(m).is_descendant_of(imported)
|
|
329
|
-
]:
|
|
330
|
-
tails.add(module)
|
|
331
|
-
|
|
332
|
-
routes.add(
|
|
333
|
-
Route(
|
|
334
|
-
heads=frozenset(heads),
|
|
335
|
-
middle=middle,
|
|
336
|
-
tails=frozenset(tails),
|
|
337
|
-
)
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
return routes
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
def _pop_shortest_chains(
|
|
344
|
-
graph: ImportGraph, importer: str, imported: str
|
|
345
|
-
) -> Iterator[tuple[str, ...]]:
|
|
346
|
-
chain: tuple[str, ...] | bool | None = True
|
|
347
|
-
while chain:
|
|
348
|
-
chain = graph.find_shortest_chain(importer, imported)
|
|
349
|
-
if chain:
|
|
350
|
-
# Remove chain of imports from graph.
|
|
351
|
-
for index in range(len(chain) - 1):
|
|
352
|
-
graph.remove_import(importer=chain[index], imported=chain[index + 1])
|
|
353
|
-
yield chain
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|