Skip to content

Commit 2820f60

Browse files
committed
fixed CI, fixed formatting, improved doc generation
1 parent 6616fb7 commit 2820f60

File tree

12 files changed

+259
-155
lines changed

12 files changed

+259
-155
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/astral-sh/ruff-pre-commit
3-
rev: v0.8.4
3+
rev: v0.11.13
44
hooks:
55
- id: ruff
66
args: [--fix]

Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ help:
2323
@echo ""
2424
@echo "Documentation:"
2525
@echo " make docs - Generate API documentation"
26+
@echo " make docs-test - Test API documentation generation"
2627
@echo " make docs-serve - Serve documentation locally"
2728
@echo ""
2829
@echo "Build & Package:"
@@ -79,7 +80,12 @@ test-quick:
7980
# Documentation
8081
docs:
8182
@echo "📚 Generating API documentation..."
82-
@python dev/generate_api_docs.py
83+
@echo "ℹ️ This regenerates docs/reference/api-full.md from source code"
84+
@python dev/generate_api_docs.py --write-file
85+
86+
docs-test:
87+
@echo "🧪 Testing API documentation generation..."
88+
@python dev/test_doc_generation.py
8389

8490
docs-serve:
8591
@echo "🌐 Serving documentation locally..."

dev/dev.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,38 @@
66
make setup # Complete dev environment setup
77
make check # Run all quality checks + tests
88
make test # Run tests with coverage
9+
make docs # Generate API documentation
910
make docs-serve # Serve docs locally
1011
```
1112

1213
**For all available commands**: `make help`
1314

15+
## Documentation
16+
17+
### Generating API Documentation
18+
19+
The API documentation is auto-generated from source code docstrings:
20+
21+
```bash
22+
# Generate API documentation
23+
make docs
24+
# or
25+
python dev/generate_api_docs.py
26+
```
27+
28+
**Important**: API documentation is **not** regenerated during pytest runs to avoid constant file changes. The doc generation test is skipped by default. To test doc generation:
29+
30+
```bash
31+
GENERATE_DOCS=true pytest tests/test_api_docs.py::test_api_doc_generation_output
32+
```
33+
34+
### Documentation Types
35+
36+
- `docs/reference/api.md` - Curated, example-driven API guide (manually maintained)
37+
- `docs/reference/api-full.md` - Complete auto-generated reference (regenerated via `make docs`)
38+
- `docs/reference/cli.md` - Command-line interface documentation
39+
- `docs/reference/dsl.md` - YAML DSL syntax reference
40+
1441
## Publishing
1542

1643
**Manual**: `make clean && make build && make publish-test && make publish`

dev/generate_api_docs.py

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
"""
33
Generate API documentation for NetGraph
44
This script should be run from the project root directory.
5+
6+
By default, outputs documentation to stdout.
7+
Use --write-file to write to docs/reference/api-full.md instead.
58
"""
69

10+
import argparse
711
import dataclasses
812
import importlib
913
import inspect
@@ -145,8 +149,16 @@ def document_module(module_name):
145149
return doc
146150

147151

148-
def generate_api_documentation():
149-
"""Generate the complete API documentation."""
152+
def generate_api_documentation(output_to_file=False):
153+
"""Generate the complete API documentation.
154+
155+
Args:
156+
output_to_file (bool): If True, write to docs/reference/api-full.md.
157+
If False, return the documentation string.
158+
159+
Returns:
160+
str: The generated documentation (when output_to_file=False)
161+
"""
150162

151163
# Modules to document (in order)
152164
modules = [
@@ -230,19 +242,45 @@ def generate_api_documentation():
230242

231243
doc += footer
232244

233-
# Ensure output directory exists
234-
output_path = Path("docs/reference/api-full.md")
235-
output_path.parent.mkdir(parents=True, exist_ok=True)
245+
if output_to_file:
246+
# Ensure output directory exists
247+
output_path = Path("docs/reference/api-full.md")
248+
output_path.parent.mkdir(parents=True, exist_ok=True)
236249

237-
# Write to file
238-
with open(output_path, "w", encoding="utf-8") as f:
239-
f.write(doc)
250+
# Write to file
251+
with open(output_path, "w", encoding="utf-8") as f:
252+
f.write(doc)
240253

241-
print("✅ API documentation generated successfully!")
242-
print(f"📄 Written to: {output_path}")
243-
print(f"📊 Size: {len(doc):,} characters")
244-
print(f"📚 Modules documented: {len(modules)}")
254+
print("✅ API documentation generated successfully!")
255+
print(f"📄 Written to: {output_path}")
256+
print(f"📊 Size: {len(doc):,} characters")
257+
print(f"📚 Modules documented: {len(modules)}")
258+
else:
259+
# Return the documentation string
260+
return doc
245261

246262

247263
if __name__ == "__main__":
248-
generate_api_documentation()
264+
parser = argparse.ArgumentParser(
265+
description="Generate API documentation for NetGraph",
266+
formatter_class=argparse.RawDescriptionHelpFormatter,
267+
epilog="""
268+
Examples:
269+
python generate_api_docs.py # Output to stdout
270+
python generate_api_docs.py --write-file # Write to docs/reference/api-full.md
271+
""",
272+
)
273+
parser.add_argument(
274+
"--write-file",
275+
action="store_true",
276+
help="Write documentation to docs/reference/api-full.md instead of stdout",
277+
)
278+
279+
args = parser.parse_args()
280+
281+
if args.write_file:
282+
generate_api_documentation(output_to_file=True)
283+
else:
284+
# Output to stdout
285+
doc = generate_api_documentation(output_to_file=False)
286+
print(doc)

docs/reference/api-full.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ For a curated, example-driven API guide, see **[api.md](api.md)**.
1010
> - **[CLI Reference](cli.md)** - Command-line interface
1111
> - **[DSL Reference](dsl.md)** - YAML syntax guide
1212
13-
**Generated from source code on:** June 08, 2025 at 00:33 UTC
13+
**Generated from source code on:** June 08, 2025 at 01:15 UTC
1414

1515
---
1616

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ dev = [
4444
"mkdocs-material",
4545
"pdoc",
4646
# style + type checking
47-
"ruff>=0.5",
47+
"ruff==0.11.13",
4848
"pyright",
4949
# pre-commit hooks
5050
"pre-commit",

tests/scenarios/test_scenario_1.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,32 @@ def test_scenario_1_build_graph() -> None:
2727

2828
# 4) Retrieve the graph built by BuildGraph
2929
graph = scenario.results.get("build_graph", "graph")
30-
assert isinstance(
31-
graph, StrictMultiDiGraph
32-
), "Expected a StrictMultiDiGraph in scenario.results under key ('build_graph', 'graph')."
30+
assert isinstance(graph, StrictMultiDiGraph), (
31+
"Expected a StrictMultiDiGraph in scenario.results under key ('build_graph', 'graph')."
32+
)
3333

3434
# 5) Check the total number of nodes matches what's listed in scenario_1.yaml
3535
# For a 6-node scenario, we expect 6 nodes in the final Nx graph.
3636
expected_nodes = 6
3737
actual_nodes = len(graph.nodes)
38-
assert (
39-
actual_nodes == expected_nodes
40-
), f"Expected {expected_nodes} nodes, found {actual_nodes}"
38+
assert actual_nodes == expected_nodes, (
39+
f"Expected {expected_nodes} nodes, found {actual_nodes}"
40+
)
4141

4242
# 6) Each physical link from the YAML becomes 2 directed edges in MultiDiGraph.
4343
# If the YAML has 10 link definitions, we expect 2 * 10 = 20 directed edges.
4444
expected_links = 10
4545
expected_nx_edges = expected_links * 2
4646
actual_edges = len(graph.edges)
47-
assert (
48-
actual_edges == expected_nx_edges
49-
), f"Expected {expected_nx_edges} directed edges, found {actual_edges}"
47+
assert actual_edges == expected_nx_edges, (
48+
f"Expected {expected_nx_edges} directed edges, found {actual_edges}"
49+
)
5050

5151
# 7) Verify the traffic demands.
5252
expected_demands = 4
53-
assert (
54-
len(scenario.traffic_demands) == expected_demands
55-
), f"Expected {expected_demands} traffic demands."
53+
assert len(scenario.traffic_demands) == expected_demands, (
54+
f"Expected {expected_demands} traffic demands."
55+
)
5656

5757
# 8) Check the multi-rule failure policy for "any single link".
5858
# This should have exactly 1 rule that picks exactly 1 link from all links.

tests/scenarios/test_scenario_2.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ def test_scenario_2_build_graph() -> None:
2828

2929
# 4) Retrieve the graph built by BuildGraph
3030
graph = scenario.results.get("build_graph", "graph")
31-
assert isinstance(
32-
graph, StrictMultiDiGraph
33-
), "Expected a StrictMultiDiGraph in scenario.results under key ('build_graph', 'graph')."
31+
assert isinstance(graph, StrictMultiDiGraph), (
32+
"Expected a StrictMultiDiGraph in scenario.results under key ('build_graph', 'graph')."
33+
)
3434

3535
# 5) Verify total node count after blueprint expansion
3636
# city_cloud blueprint: (4 leaves + 6 spines + 4 edge_nodes) = 14
@@ -39,9 +39,9 @@ def test_scenario_2_build_graph() -> None:
3939
# => 14 + 1 + 4 = 19 total
4040
expected_nodes = 19
4141
actual_nodes = len(graph.nodes)
42-
assert (
43-
actual_nodes == expected_nodes
44-
), f"Expected {expected_nodes} nodes, found {actual_nodes}"
42+
assert actual_nodes == expected_nodes, (
43+
f"Expected {expected_nodes} nodes, found {actual_nodes}"
44+
)
4545

4646
# 6) Verify total physical links before direction is applied to Nx
4747
# - clos_2tier adjacency: 4 leaf * 6 spine = 24
@@ -59,15 +59,15 @@ def test_scenario_2_build_graph() -> None:
5959
expected_links = 56
6060
expected_nx_edges = expected_links * 2
6161
actual_edges = len(graph.edges)
62-
assert (
63-
actual_edges == expected_nx_edges
64-
), f"Expected {expected_nx_edges} directed edges, found {actual_edges}"
62+
assert actual_edges == expected_nx_edges, (
63+
f"Expected {expected_nx_edges} directed edges, found {actual_edges}"
64+
)
6565

6666
# 7) Verify the traffic demands (should have 4)
6767
expected_demands = 4
68-
assert (
69-
len(scenario.traffic_demands) == expected_demands
70-
), f"Expected {expected_demands} traffic demands."
68+
assert len(scenario.traffic_demands) == expected_demands, (
69+
f"Expected {expected_demands} traffic demands."
70+
)
7171

7272
# 8) Check the single-rule failure policy "anySingleLink"
7373
policy: FailurePolicy = scenario.failure_policy
@@ -87,9 +87,9 @@ def test_scenario_2_build_graph() -> None:
8787
# 9) Check presence of key expanded nodes
8888
# For example: the overridden spine node "myspine-6" under "SEA/clos_instance/spine"
8989
# and the single node blueprint "SFO/single/single-1".
90-
assert (
91-
"SEA/clos_instance/spine/myspine-6" in scenario.network.nodes
92-
), "Missing expected overridden spine node (myspine-6) in expanded blueprint."
93-
assert (
94-
"SFO/single/single-1" in scenario.network.nodes
95-
), "Missing expected single-node blueprint expansion under SFO."
90+
assert "SEA/clos_instance/spine/myspine-6" in scenario.network.nodes, (
91+
"Missing expected overridden spine node (myspine-6) in expanded blueprint."
92+
)
93+
assert "SFO/single/single-1" in scenario.network.nodes, (
94+
"Missing expected single-node blueprint expansion under SFO."
95+
)

tests/scenarios/test_scenario_3.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,17 @@ def test_scenario_3_build_graph_and_capacity_probe() -> None:
3131

3232
# 4) Retrieve the graph from the BuildGraph step
3333
graph = scenario.results.get("build_graph", "graph")
34-
assert isinstance(
35-
graph, StrictMultiDiGraph
36-
), "Expected a StrictMultiDiGraph in scenario.results under key ('build_graph', 'graph')."
34+
assert isinstance(graph, StrictMultiDiGraph), (
35+
"Expected a StrictMultiDiGraph in scenario.results under key ('build_graph', 'graph')."
36+
)
3737

3838
# 5) Verify total node count:
3939
# Each 3-tier CLOS instance has 32 nodes -> 2 instances => 64 total.
4040
expected_nodes = 64
4141
actual_nodes = len(graph.nodes)
42-
assert (
43-
actual_nodes == expected_nodes
44-
), f"Expected {expected_nodes} nodes, found {actual_nodes}"
42+
assert actual_nodes == expected_nodes, (
43+
f"Expected {expected_nodes} nodes, found {actual_nodes}"
44+
)
4545

4646
# 6) Verify total physical links (before direction):
4747
# Each 3-tier CLOS has 64 links internally => 2 instances => 128
@@ -50,9 +50,9 @@ def test_scenario_3_build_graph_and_capacity_probe() -> None:
5050
expected_links = 144
5151
expected_directed_edges = expected_links * 2
5252
actual_edges = len(graph.edges)
53-
assert (
54-
actual_edges == expected_directed_edges
55-
), f"Expected {expected_directed_edges} edges, found {actual_edges}"
53+
assert actual_edges == expected_directed_edges, (
54+
f"Expected {expected_directed_edges} edges, found {actual_edges}"
55+
)
5656

5757
# 7) Verify no traffic demands in this scenario
5858
assert len(scenario.traffic_demands) == 0, "Expected zero traffic demands."
@@ -62,24 +62,24 @@ def test_scenario_3_build_graph_and_capacity_probe() -> None:
6262
assert policy is None, "Expected no failure policy in this scenario."
6363

6464
# 9) Check presence of some expanded nodes
65-
assert (
66-
"my_clos1/b1/t1/t1-1" in scenario.network.nodes
67-
), "Missing expected node 'my_clos1/b1/t1/t1-1' in expanded blueprint."
68-
assert (
69-
"my_clos2/spine/t3-16" in scenario.network.nodes
70-
), "Missing expected node 'my_clos2/spine/t3-16' in expanded blueprint."
65+
assert "my_clos1/b1/t1/t1-1" in scenario.network.nodes, (
66+
"Missing expected node 'my_clos1/b1/t1/t1-1' in expanded blueprint."
67+
)
68+
assert "my_clos2/spine/t3-16" in scenario.network.nodes, (
69+
"Missing expected node 'my_clos2/spine/t3-16' in expanded blueprint."
70+
)
7171

7272
net = scenario.network
7373

7474
# (A) Node attribute checks from node_overrides:
7575
# For "my_clos1/b1/t1/t1-1", we expect hw_component="LeafHW-A" and SRG="clos1-b1t1-SRG"
7676
node_a1 = net.nodes["my_clos1/b1/t1/t1-1"]
77-
assert (
78-
node_a1.attrs.get("hw_component") == "LeafHW-A"
79-
), "Expected hw_component=LeafHW-A for 'my_clos1/b1/t1/t1-1', but not found."
80-
assert node_a1.attrs.get("shared_risk_groups") == [
81-
"clos1-b1t1-SRG"
82-
], "Expected shared_risk_group=clos1-b1t1-SRG for 'my_clos1/b1/t1/t1-1'."
77+
assert node_a1.attrs.get("hw_component") == "LeafHW-A", (
78+
"Expected hw_component=LeafHW-A for 'my_clos1/b1/t1/t1-1', but not found."
79+
)
80+
assert node_a1.attrs.get("shared_risk_groups") == ["clos1-b1t1-SRG"], (
81+
"Expected shared_risk_group=clos1-b1t1-SRG for 'my_clos1/b1/t1/t1-1'."
82+
)
8383

8484
# For "my_clos2/b2/t1/t1-1", check hw_component="LeafHW-B" and SRG="clos2-b2t1-SRG"
8585
node_b2 = net.nodes["my_clos2/b2/t1/t1-1"]
@@ -114,12 +114,12 @@ def test_scenario_3_build_graph_and_capacity_probe() -> None:
114114
)
115115
assert link_id_2, "Spine link (t3-2) not found for override check."
116116
for link_obj in link_id_2:
117-
assert link_obj.attrs.get("shared_risk_groups") == [
118-
"SpineSRG"
119-
], "Expected SRG=SpineSRG on spine<->spine link."
120-
assert (
121-
link_obj.attrs.get("hw_component") == "400G-LR4"
122-
), "Expected hw_component=400G-LR4 on spine<->spine link."
117+
assert link_obj.attrs.get("shared_risk_groups") == ["SpineSRG"], (
118+
"Expected SRG=SpineSRG on spine<->spine link."
119+
)
120+
assert link_obj.attrs.get("hw_component") == "400G-LR4", (
121+
"Expected hw_component=400G-LR4 on spine<->spine link."
122+
)
123123

124124
# 10) The capacity probe step computed forward and reverse flows in 'combine' mode
125125
# with PROPORTIONAL flow placement.

0 commit comments

Comments
 (0)