pretty-mod 0.2.0__tar.gz → 0.2.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.
Files changed (46) hide show
  1. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/CLAUDE.md +9 -6
  2. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/Cargo.lock +125 -6
  3. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/Cargo.toml +3 -1
  4. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/PKG-INFO +8 -6
  5. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/README.md +7 -5
  6. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/RELEASE_NOTES.md +86 -0
  7. pretty_mod-0.2.2/python/pretty_mod/__main__.py +6 -0
  8. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/python/pretty_mod/_pretty_mod.pyi +7 -2
  9. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/python/pretty_mod/cli.py +21 -2
  10. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/src/explorer.rs +2 -1
  11. pretty_mod-0.2.2/src/import_resolver.rs +147 -0
  12. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/src/lib.rs +30 -10
  13. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/src/module_info.rs +10 -1
  14. pretty_mod-0.2.2/src/output_format.rs +121 -0
  15. pretty_mod-0.2.2/src/semantic.rs +139 -0
  16. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/src/signature.rs +36 -10
  17. pretty_mod-0.2.2/tests/test_import_chain.py +66 -0
  18. pretty_mod-0.2.2/tests/test_json_output.py +103 -0
  19. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/.github/workflows/CI.yml +0 -0
  20. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/.github/workflows/tests.yml +0 -0
  21. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/.gitignore +0 -0
  22. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/.pre-commit-config.yaml +0 -0
  23. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/LICENSE +0 -0
  24. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/examples/0_hello.py +0 -0
  25. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/examples/1_tree.py +0 -0
  26. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/examples/2_sig.py +0 -0
  27. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/justfile +0 -0
  28. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/pyproject.toml +0 -0
  29. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/python/pretty_mod/__init__.py +0 -0
  30. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/python/pretty_mod/explorer.py +0 -0
  31. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/python/pretty_mod/py.typed +0 -0
  32. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/scripts/compare_local.py +0 -0
  33. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/scripts/compare_versions.py +0 -0
  34. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/scripts/perf_test.py +0 -0
  35. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/scripts/profile.py +0 -0
  36. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/src/config.rs +0 -0
  37. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/src/package_downloader.rs +0 -0
  38. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/src/stdlib.rs +0 -0
  39. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/src/tree_formatter.rs +0 -0
  40. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/src/utils.rs +0 -0
  41. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/tests/__init__.py +0 -0
  42. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/tests/conftest.py +0 -0
  43. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/tests/test_cli.py +0 -0
  44. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/tests/test_double_colon.py +0 -0
  45. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/tests/test_explorer.py +0 -0
  46. {pretty_mod-0.2.0 → pretty_mod-0.2.2}/uv.lock +0 -0
@@ -2,30 +2,33 @@
2
2
 
3
3
  `pretty-mod` is a python package built on pyo3 to explore python packages for LLMs
4
4
 
5
- # Getting oriented
5
+ ## getting oriented
6
6
 
7
7
  - read @RELEASE_NOTES.md, @README.md, @pyproject.toml, and @justfile
8
8
 
9
- # run the tests
9
+ ## run the tests
10
10
 
11
11
  ```
12
12
  just test
13
13
  ```
14
14
 
15
- if for some reason you need to only build (just test does this automatically)
15
+ if you only need to build (`just test` runs `just build` automatically)
16
16
 
17
17
  ```
18
18
  just build
19
19
  ```
20
20
 
21
- # run the local python package
21
+ ## run the local python package
22
22
 
23
23
  ```
24
24
  uv run pretty-mod tree fastapi.routing
25
25
  ```
26
26
 
27
- # run the remote python package
27
+ ## run the remote python package
28
28
 
29
29
  ```
30
30
  uvx pretty-mod tree fastapi.routing
31
- ```
31
+ ```
32
+
33
+ # IMPORTANT
34
+ - avoid breaking changes to the public api defined by type stubs in @python/pretty_mod/_pretty_mod.pyi
@@ -478,6 +478,25 @@ version = "0.31.1"
478
478
  source = "registry+https://github.com/rust-lang/crates.io-index"
479
479
  checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
480
480
 
481
+ [[package]]
482
+ name = "glob"
483
+ version = "0.3.2"
484
+ source = "registry+https://github.com/rust-lang/crates.io-index"
485
+ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
486
+
487
+ [[package]]
488
+ name = "globset"
489
+ version = "0.4.16"
490
+ source = "registry+https://github.com/rust-lang/crates.io-index"
491
+ checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
492
+ dependencies = [
493
+ "aho-corasick",
494
+ "bstr",
495
+ "log",
496
+ "regex-automata",
497
+ "regex-syntax",
498
+ ]
499
+
481
500
  [[package]]
482
501
  name = "hashbrown"
483
502
  version = "0.15.4"
@@ -1017,13 +1036,15 @@ dependencies = [
1017
1036
 
1018
1037
  [[package]]
1019
1038
  name = "pretty-mod"
1020
- version = "0.2.0"
1039
+ version = "0.2.2"
1021
1040
  dependencies = [
1022
1041
  "flate2",
1023
1042
  "pyo3",
1024
1043
  "reqwest",
1025
1044
  "ruff_python_ast",
1026
1045
  "ruff_python_parser",
1046
+ "ruff_python_resolver",
1047
+ "ruff_python_semantic",
1027
1048
  "serde",
1028
1049
  "serde_json",
1029
1050
  "tar",
@@ -1240,11 +1261,34 @@ dependencies = [
1240
1261
  "bitflags",
1241
1262
  ]
1242
1263
 
1264
+ [[package]]
1265
+ name = "regex"
1266
+ version = "1.11.1"
1267
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1268
+ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
1269
+ dependencies = [
1270
+ "aho-corasick",
1271
+ "memchr",
1272
+ "regex-automata",
1273
+ "regex-syntax",
1274
+ ]
1275
+
1243
1276
  [[package]]
1244
1277
  name = "regex-automata"
1245
1278
  version = "0.4.9"
1246
1279
  source = "registry+https://github.com/rust-lang/crates.io-index"
1247
1280
  checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
1281
+ dependencies = [
1282
+ "aho-corasick",
1283
+ "memchr",
1284
+ "regex-syntax",
1285
+ ]
1286
+
1287
+ [[package]]
1288
+ name = "regex-syntax"
1289
+ version = "0.8.5"
1290
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1291
+ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
1248
1292
 
1249
1293
  [[package]]
1250
1294
  name = "reqwest"
@@ -1301,10 +1345,44 @@ dependencies = [
1301
1345
  "windows-sys 0.52.0",
1302
1346
  ]
1303
1347
 
1348
+ [[package]]
1349
+ name = "ruff_cache"
1350
+ version = "0.0.0"
1351
+ source = "git+https://github.com/astral-sh/ruff#0724bee59c12da9cedc84f4238b08aabe9ecaaa1"
1352
+ dependencies = [
1353
+ "filetime",
1354
+ "glob",
1355
+ "globset",
1356
+ "itertools",
1357
+ "regex",
1358
+ "seahash",
1359
+ ]
1360
+
1361
+ [[package]]
1362
+ name = "ruff_index"
1363
+ version = "0.0.0"
1364
+ source = "git+https://github.com/astral-sh/ruff#0724bee59c12da9cedc84f4238b08aabe9ecaaa1"
1365
+ dependencies = [
1366
+ "ruff_macros",
1367
+ ]
1368
+
1369
+ [[package]]
1370
+ name = "ruff_macros"
1371
+ version = "0.0.0"
1372
+ source = "git+https://github.com/astral-sh/ruff#0724bee59c12da9cedc84f4238b08aabe9ecaaa1"
1373
+ dependencies = [
1374
+ "heck",
1375
+ "itertools",
1376
+ "proc-macro2",
1377
+ "quote",
1378
+ "ruff_python_trivia",
1379
+ "syn",
1380
+ ]
1381
+
1304
1382
  [[package]]
1305
1383
  name = "ruff_python_ast"
1306
1384
  version = "0.0.0"
1307
- source = "git+https://github.com/astral-sh/ruff#475a02b725d43d7e38bb243b3ed40afbfaf85da6"
1385
+ source = "git+https://github.com/astral-sh/ruff#0724bee59c12da9cedc84f4238b08aabe9ecaaa1"
1308
1386
  dependencies = [
1309
1387
  "aho-corasick",
1310
1388
  "bitflags",
@@ -1321,7 +1399,7 @@ dependencies = [
1321
1399
  [[package]]
1322
1400
  name = "ruff_python_parser"
1323
1401
  version = "0.0.0"
1324
- source = "git+https://github.com/astral-sh/ruff#475a02b725d43d7e38bb243b3ed40afbfaf85da6"
1402
+ source = "git+https://github.com/astral-sh/ruff#0724bee59c12da9cedc84f4238b08aabe9ecaaa1"
1325
1403
  dependencies = [
1326
1404
  "bitflags",
1327
1405
  "bstr",
@@ -1337,10 +1415,45 @@ dependencies = [
1337
1415
  "unicode_names2",
1338
1416
  ]
1339
1417
 
1418
+ [[package]]
1419
+ name = "ruff_python_resolver"
1420
+ version = "0.0.0"
1421
+ source = "git+https://github.com/astral-sh/ruff#0724bee59c12da9cedc84f4238b08aabe9ecaaa1"
1422
+ dependencies = [
1423
+ "log",
1424
+ ]
1425
+
1426
+ [[package]]
1427
+ name = "ruff_python_semantic"
1428
+ version = "0.0.0"
1429
+ source = "git+https://github.com/astral-sh/ruff#0724bee59c12da9cedc84f4238b08aabe9ecaaa1"
1430
+ dependencies = [
1431
+ "bitflags",
1432
+ "is-macro",
1433
+ "ruff_cache",
1434
+ "ruff_index",
1435
+ "ruff_macros",
1436
+ "ruff_python_ast",
1437
+ "ruff_python_parser",
1438
+ "ruff_python_stdlib",
1439
+ "ruff_text_size",
1440
+ "rustc-hash",
1441
+ "smallvec",
1442
+ ]
1443
+
1444
+ [[package]]
1445
+ name = "ruff_python_stdlib"
1446
+ version = "0.0.0"
1447
+ source = "git+https://github.com/astral-sh/ruff#0724bee59c12da9cedc84f4238b08aabe9ecaaa1"
1448
+ dependencies = [
1449
+ "bitflags",
1450
+ "unicode-ident",
1451
+ ]
1452
+
1340
1453
  [[package]]
1341
1454
  name = "ruff_python_trivia"
1342
1455
  version = "0.0.0"
1343
- source = "git+https://github.com/astral-sh/ruff#475a02b725d43d7e38bb243b3ed40afbfaf85da6"
1456
+ source = "git+https://github.com/astral-sh/ruff#0724bee59c12da9cedc84f4238b08aabe9ecaaa1"
1344
1457
  dependencies = [
1345
1458
  "itertools",
1346
1459
  "ruff_source_file",
@@ -1351,7 +1464,7 @@ dependencies = [
1351
1464
  [[package]]
1352
1465
  name = "ruff_source_file"
1353
1466
  version = "0.0.0"
1354
- source = "git+https://github.com/astral-sh/ruff#475a02b725d43d7e38bb243b3ed40afbfaf85da6"
1467
+ source = "git+https://github.com/astral-sh/ruff#0724bee59c12da9cedc84f4238b08aabe9ecaaa1"
1355
1468
  dependencies = [
1356
1469
  "memchr",
1357
1470
  "ruff_text_size",
@@ -1360,7 +1473,7 @@ dependencies = [
1360
1473
  [[package]]
1361
1474
  name = "ruff_text_size"
1362
1475
  version = "0.0.0"
1363
- source = "git+https://github.com/astral-sh/ruff#475a02b725d43d7e38bb243b3ed40afbfaf85da6"
1476
+ source = "git+https://github.com/astral-sh/ruff#0724bee59c12da9cedc84f4238b08aabe9ecaaa1"
1364
1477
 
1365
1478
  [[package]]
1366
1479
  name = "rustc-demangle"
@@ -1455,6 +1568,12 @@ dependencies = [
1455
1568
  "windows-sys 0.59.0",
1456
1569
  ]
1457
1570
 
1571
+ [[package]]
1572
+ name = "seahash"
1573
+ version = "4.1.0"
1574
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1575
+ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
1576
+
1458
1577
  [[package]]
1459
1578
  name = "security-framework"
1460
1579
  version = "3.2.0"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "pretty-mod"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
@@ -13,6 +13,8 @@ serde = { version = "1.0", features = ["derive"] }
13
13
  serde_json = "1.0"
14
14
  ruff_python_parser = { git = "https://github.com/astral-sh/ruff" }
15
15
  ruff_python_ast = { git = "https://github.com/astral-sh/ruff" }
16
+ ruff_python_semantic = { git = "https://github.com/astral-sh/ruff" }
17
+ ruff_python_resolver = { git = "https://github.com/astral-sh/ruff" }
16
18
  reqwest = { version = "0.12", default-features = false, features = [
17
19
  "blocking",
18
20
  "json",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pretty-mod
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  License-File: LICENSE
5
5
  Summary: A python module tree explorer for LLMs (and humans)
6
6
  Author-email: zzstoatzz <thrast36@gmail.com>
@@ -12,11 +12,9 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
12
12
 
13
13
  a python module tree explorer for LLMs (and humans)
14
14
 
15
- > [!IMPORTANT]
16
- > for all versions `>=0.1.0`, wheels for different operating systems are built via `maturin` and published to pypi, install `<0.1.0` for a pure python version
17
-
18
15
  > [!NOTE]
19
- > Starting from v0.2.0, output includes colors by default. Use `PRETTY_MOD_NO_COLOR=1` to disable.
16
+ > - For all versions `>=0.1.0`, wheels for different operating systems are built via `maturin` and published to PyPI. Install `<0.1.0` for a pure Python version.
17
+ > - Starting from v0.2.0, output includes colors by default. Use `PRETTY_MOD_NO_COLOR=1` to disable.
20
18
 
21
19
  ```bash
22
20
  # Explore module structure
@@ -36,7 +34,7 @@ a python module tree explorer for LLMs (and humans)
36
34
  └── ⚡ functions: main
37
35
 
38
36
  # Inspect function signatures (even if the package is not installed)
39
- » uv run pretty-mod sig fastmcp:FastMCP --quiet
37
+ » uvx pretty-mod sig fastmcp:FastMCP --quiet
40
38
  📎 FastMCP
41
39
  ├── Parameters:
42
40
  ├── self
@@ -89,6 +87,10 @@ pretty-mod tree requests --depth 3
89
87
 
90
88
  # Display function signatures
91
89
  pretty-mod sig json:loads
90
+
91
+ # Get JSON output for programmatic use
92
+ pretty-mod tree json -o json | jq '.tree.submodules | keys'
93
+ pretty-mod sig json:dumps -o json | jq '.parameters'
92
94
  pretty-mod sig os.path:join
93
95
 
94
96
  # Explore packages even without having them installed
@@ -2,11 +2,9 @@
2
2
 
3
3
  a python module tree explorer for LLMs (and humans)
4
4
 
5
- > [!IMPORTANT]
6
- > for all versions `>=0.1.0`, wheels for different operating systems are built via `maturin` and published to pypi, install `<0.1.0` for a pure python version
7
-
8
5
  > [!NOTE]
9
- > Starting from v0.2.0, output includes colors by default. Use `PRETTY_MOD_NO_COLOR=1` to disable.
6
+ > - For all versions `>=0.1.0`, wheels for different operating systems are built via `maturin` and published to PyPI. Install `<0.1.0` for a pure Python version.
7
+ > - Starting from v0.2.0, output includes colors by default. Use `PRETTY_MOD_NO_COLOR=1` to disable.
10
8
 
11
9
  ```bash
12
10
  # Explore module structure
@@ -26,7 +24,7 @@ a python module tree explorer for LLMs (and humans)
26
24
  └── ⚡ functions: main
27
25
 
28
26
  # Inspect function signatures (even if the package is not installed)
29
- » uv run pretty-mod sig fastmcp:FastMCP --quiet
27
+ » uvx pretty-mod sig fastmcp:FastMCP --quiet
30
28
  📎 FastMCP
31
29
  ├── Parameters:
32
30
  ├── self
@@ -79,6 +77,10 @@ pretty-mod tree requests --depth 3
79
77
 
80
78
  # Display function signatures
81
79
  pretty-mod sig json:loads
80
+
81
+ # Get JSON output for programmatic use
82
+ pretty-mod tree json -o json | jq '.tree.submodules | keys'
83
+ pretty-mod sig json:dumps -o json | jq '.parameters'
82
84
  pretty-mod sig os.path:join
83
85
 
84
86
  # Explore packages even without having them installed
@@ -1,3 +1,85 @@
1
+ # Release Notes - 0.2.2
2
+
3
+ ## 🚀 comprehensive import chain resolution system
4
+
5
+ this release delivers the most requested feature: intuitive import chain resolution. no more guessing where symbols live in complex packages.
6
+
7
+ ### ✨ major features
8
+
9
+ - **import chain resolution**: `prefect:flow`, `fastapi:FastAPI`, and `fastmcp:FastMCP` now just work
10
+ - automatically resolves re-exported symbols to their implementation
11
+ - pattern-based resolution for known library structures
12
+ - smart fallback for unknown patterns
13
+
14
+ - **enhanced method signatures**: complete support for extracting signatures from classes
15
+ - `__init__` methods for class constructors
16
+ - `__call__` methods for callable instances
17
+ - proper scope tracking distinguishes methods from functions
18
+
19
+ - **smart download management**: no more duplicate "downloading package" messages
20
+ - intelligent caching during resolution attempts
21
+ - efficient fallback strategies
22
+
23
+ ### 🔧 technical implementation
24
+
25
+ - **ImportChainResolver**: pattern-based resolution engine
26
+ - handles namespace packages transparently
27
+ - integrated download logic for missing packages
28
+ - extensible pattern matching for new libraries
29
+
30
+ - **SemanticAnalyzer**: scope-aware ast visitor
31
+ - tracks module → class → function context
32
+ - distinguishes methods from functions accurately
33
+ - stores signatures under multiple keys for flexible lookup
34
+
35
+ ### 📊 examples that now work
36
+
37
+ ```bash
38
+ # these all resolve automatically
39
+ uv run pretty-mod sig prefect:flow # → FlowDecorator.__call__
40
+ uv run pretty-mod sig fastapi:FastAPI # → FastAPI.__init__
41
+ uv run pretty-mod sig pathlib:Path # → Path.__init__
42
+
43
+ # direct access still works
44
+ uv run pretty-mod sig prefect.flows:FlowDecorator
45
+ uv run pretty-mod sig pathlib.Path:exists
46
+ ```
47
+
48
+ ### 🐛 fixes
49
+
50
+ - duplicate download messages eliminated
51
+ - method signatures now correctly show self parameter
52
+ - namespace package exploration improved
53
+
54
+ this release closes issues #13, #14, and #15 in a single comprehensive implementation.
55
+
56
+ ---
57
+
58
+ # Release Notes - v0.2.1
59
+
60
+ ## 📊 JSON Output Support & Better Type Annotation Handling
61
+
62
+ This release adds machine-readable JSON output and fixes a critical bug with complex type annotations.
63
+
64
+ ### ✨ New Features
65
+
66
+ - **📊 JSON Output Support**: Export tree and signature data as JSON for programmatic use
67
+ - `pretty-mod tree json -o json` - Get module structure as JSON
68
+ - `pretty-mod sig json:dumps -o json` - Get function signature as JSON
69
+ - Perfect for piping to `jq` or other JSON processors
70
+ - Follows the Kubernetes pattern of `-o <format>` for output selection
71
+ - Example: `pretty-mod tree json -o json | jq '.tree.submodules | keys'`
72
+
73
+ ### 🏗️ Technical Improvements
74
+
75
+ - **Visitor Pattern**: Implemented output formatters using the Visitor pattern for extensibility
76
+ - Clean separation between data structure and formatting
77
+ - Easy to add new output formats in the future
78
+ - Type-safe implementation using Rust traits
79
+
80
+
81
+ ---
82
+
1
83
  # Release Notes - v0.2.0
2
84
 
3
85
  ## 🎨 Customizable Display & Colors + Enhanced Signature Support
@@ -64,6 +146,10 @@ This release introduces customizable display characters, color output, full type
64
146
 
65
147
  ### 🐛 Bug Fixes
66
148
 
149
+ - **Complex type annotations**: Fixed parameter splitting for nested generics
150
+ - Previously: `Callable[[Any], str]` would split incorrectly on the comma
151
+ - Now: Properly handles all nested brackets and quotes in type annotations
152
+ - Affects all complex types like `Dict[str, List[int]]`, `Literal['a', 'b']`, etc.
67
153
  - **Stdlib module handling**: Built-in modules no longer trigger PyPI download attempts
68
154
  - **Signature discovery**: Improved recursive search for symbols exported in `__all__`
69
155
  - **Download messages**: Colored warning messages for better visibility
@@ -0,0 +1,6 @@
1
+ """Enable running pretty-mod as a module: python -m pretty_mod"""
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -14,7 +14,12 @@ class ModuleTreeExplorer:
14
14
  def get_tree_string(self) -> str: ...
15
15
 
16
16
  def display_tree(
17
- root_module_path: str, max_depth: int = 2, quiet: bool = False
17
+ root_module_path: str,
18
+ max_depth: int = 2,
19
+ quiet: bool = False,
20
+ format: str = "pretty",
18
21
  ) -> None: ...
19
- def display_signature(import_path: str, quiet: bool = False) -> str: ...
22
+ def display_signature(
23
+ import_path: str, quiet: bool = False, format: str = "pretty"
24
+ ) -> str: ...
20
25
  def import_object(import_path: str) -> Any: ...
@@ -26,6 +26,14 @@ def main():
26
26
  action="store_true",
27
27
  help="Suppress warnings and informational messages",
28
28
  )
29
+ tree_parser.add_argument(
30
+ "-o",
31
+ "--output",
32
+ type=str,
33
+ choices=["pretty", "json"],
34
+ default="pretty",
35
+ help="Output format (default: pretty)",
36
+ )
29
37
 
30
38
  sig_parser = subparsers.add_parser("sig", help="Display function signature")
31
39
  sig_parser.add_argument(
@@ -36,14 +44,25 @@ def main():
36
44
  action="store_true",
37
45
  help="Suppress download messages",
38
46
  )
47
+ sig_parser.add_argument(
48
+ "-o",
49
+ "--output",
50
+ type=str,
51
+ choices=["pretty", "json"],
52
+ default="pretty",
53
+ help="Output format (default: pretty)",
54
+ )
39
55
 
40
56
  args = parser.parse_args()
41
57
 
42
58
  try:
43
59
  if args.command == "tree":
44
- display_tree(args.module, args.depth, args.quiet)
60
+ # Call display_tree with format parameter
61
+ display_tree(args.module, args.depth, args.quiet, args.output)
45
62
  elif args.command == "sig":
46
- print(display_signature(args.import_path, args.quiet))
63
+ # Call display_signature with format parameter
64
+ result = display_signature(args.import_path, args.quiet, args.output)
65
+ print(result)
47
66
  else:
48
67
  parser.print_help()
49
68
  sys.exit(1)
@@ -1,4 +1,5 @@
1
1
  use crate::module_info::ModuleInfo;
2
+ use crate::tree_formatter::format_tree_display;
2
3
  use pyo3::prelude::*;
3
4
  use std::fs;
4
5
  use std::path::{Path, PathBuf};
@@ -116,7 +117,7 @@ impl ModuleTreeExplorer {
116
117
  };
117
118
 
118
119
  // Use the display_tree formatting logic, which expects the wrapped format
119
- crate::format_tree_display(py, &tree_obj, &self.root_module_path)
120
+ format_tree_display(py, &tree_obj, &self.root_module_path)
120
121
  }
121
122
  }
122
123
 
@@ -0,0 +1,147 @@
1
+ use crate::module_info::FunctionSignature;
2
+ use pyo3::prelude::*;
3
+
4
+ /// Resolves symbols through import chains using existing infrastructure
5
+ pub struct ImportChainResolver;
6
+
7
+ impl ImportChainResolver {
8
+ pub fn new() -> Self {
9
+ Self
10
+ }
11
+
12
+ /// Try to resolve a symbol by following import chains
13
+ /// Focus on specific patterns like prefect:flow -> prefect.flows:FlowDecorator
14
+ pub fn resolve_symbol_signature(
15
+ &self,
16
+ py: Python,
17
+ module_path: &str,
18
+ symbol_name: &str
19
+ ) -> Option<FunctionSignature> {
20
+ // For known patterns, skip direct exploration and go straight to pattern matching
21
+ // This handles cases where the base module is a namespace package
22
+ if let Some(sig) = self.resolve_known_patterns(py, module_path, symbol_name) {
23
+ return Some(sig);
24
+ }
25
+
26
+ // Fallback: try to get module info using our existing system for direct symbols
27
+ let explorer = crate::explorer::ModuleTreeExplorer::new(module_path.to_string(), 2);
28
+ if let Ok(module_info) = explorer.explore_module_pure_filesystem(py, module_path) {
29
+ // Check if symbol is directly available
30
+ if let Some(sig) = module_info.signatures.get(symbol_name) {
31
+ return Some(sig.clone());
32
+ }
33
+ }
34
+
35
+ None
36
+ }
37
+
38
+ /// Handle known import patterns for specific libraries
39
+ fn resolve_known_patterns(
40
+ &self,
41
+ py: Python,
42
+ module_path: &str,
43
+ symbol_name: &str
44
+ ) -> Option<FunctionSignature> {
45
+ match (module_path, symbol_name) {
46
+ // Prefect pattern: prefect:flow -> prefect.flows:FlowDecorator.__call__
47
+ ("prefect", "flow") => {
48
+ self.try_resolve_from_submodule(py, "prefect.flows", "FlowDecorator")
49
+ }
50
+
51
+ // FastMCP pattern: fastmcp:FastMCP -> fastmcp.server:FastMCP.__init__
52
+ ("fastmcp", "FastMCP") => {
53
+ // Try fastmcp.server first (allows download), then fastmcp (no download to avoid duplicates)
54
+ self.try_resolve_from_submodule(py, "fastmcp.server", "FastMCP")
55
+ .or_else(|| self.try_resolve_from_submodule_internal(py, "fastmcp", "FastMCP", false))
56
+ }
57
+
58
+ // FastAPI pattern: fastapi:FastAPI -> fastapi.applications:FastAPI.__init__
59
+ ("fastapi", "FastAPI") => {
60
+ self.try_resolve_from_submodule(py, "fastapi.applications", "FastAPI")
61
+ }
62
+
63
+ // Add more patterns as needed
64
+ _ => None
65
+ }
66
+ }
67
+
68
+ /// Try to resolve a symbol from a specific submodule
69
+ fn try_resolve_from_submodule(
70
+ &self,
71
+ py: Python,
72
+ submodule_path: &str,
73
+ class_name: &str
74
+ ) -> Option<FunctionSignature> {
75
+ self.try_resolve_from_submodule_internal(py, submodule_path, class_name, true)
76
+ }
77
+
78
+ /// Internal helper that can optionally skip download to avoid duplicates
79
+ fn try_resolve_from_submodule_internal(
80
+ &self,
81
+ py: Python,
82
+ submodule_path: &str,
83
+ class_name: &str,
84
+ allow_download: bool
85
+ ) -> Option<FunctionSignature> {
86
+ // Helper function to try exploration
87
+ let try_get_signature = |py: Python| -> Option<FunctionSignature> {
88
+ let explorer = crate::explorer::ModuleTreeExplorer::new(submodule_path.to_string(), 2);
89
+
90
+ if let Ok(module_info) = explorer.explore_module_pure_filesystem(py, submodule_path) {
91
+ // Try to find the class signature (which should be __init__ or __call__)
92
+ if let Some(sig) = module_info.signatures.get(class_name) {
93
+ return Some(sig.clone());
94
+ }
95
+
96
+ // Also try looking for __call__ method signature
97
+ let call_method = format!("{}.{}", class_name, "__call__");
98
+ if let Some(sig) = module_info.signatures.get(&call_method) {
99
+ return Some(sig.clone());
100
+ }
101
+ }
102
+
103
+ None
104
+ };
105
+
106
+ // First try direct filesystem exploration
107
+ if let Some(sig) = try_get_signature(py) {
108
+ return Some(sig);
109
+ }
110
+
111
+ // If download is not allowed, return None
112
+ if !allow_download {
113
+ return None;
114
+ }
115
+
116
+ // If that fails, extract the base package and try downloading
117
+ let base_package = crate::utils::extract_base_package(submodule_path);
118
+
119
+ // Check if this is a stdlib module - if so, don't try to download
120
+ if crate::stdlib::is_stdlib_module(&base_package) {
121
+ return None;
122
+ }
123
+
124
+ // Try downloading the package and re-attempting exploration
125
+ let mut download_result = None;
126
+ if let Ok(()) = crate::utils::try_download_and_import(py, &base_package, false, || {
127
+ download_result = try_get_signature(py);
128
+ Ok(())
129
+ }) {
130
+ return download_result;
131
+ }
132
+
133
+ None
134
+ }
135
+ }
136
+
137
+ #[cfg(test)]
138
+ mod tests {
139
+ use super::*;
140
+
141
+ #[test]
142
+ fn test_import_resolver_creation() {
143
+ let resolver = ImportChainResolver::new();
144
+ // Just test that it can be created
145
+ assert!(std::mem::size_of_val(&resolver) >= 0);
146
+ }
147
+ }