symbex 1.2__tar.gz → 1.3.1__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {symbex-1.2 → symbex-1.3.1}/PKG-INFO +45 -19
- {symbex-1.2 → symbex-1.3.1}/README.md +44 -18
- {symbex-1.2 → symbex-1.3.1}/setup.py +1 -1
- {symbex-1.2 → symbex-1.3.1}/symbex/cli.py +42 -0
- {symbex-1.2 → symbex-1.3.1}/symbex/lib.py +2 -2
- {symbex-1.2 → symbex-1.3.1}/symbex.egg-info/PKG-INFO +45 -19
- {symbex-1.2 → symbex-1.3.1}/tests/test_filters.py +72 -2
- {symbex-1.2 → symbex-1.3.1}/tests/test_symbex.py +5 -5
- {symbex-1.2 → symbex-1.3.1}/tests/test_symbols.py +8 -8
- {symbex-1.2 → symbex-1.3.1}/LICENSE +0 -0
- {symbex-1.2 → symbex-1.3.1}/setup.cfg +0 -0
- {symbex-1.2 → symbex-1.3.1}/symbex/__init__.py +0 -0
- {symbex-1.2 → symbex-1.3.1}/symbex/__main__.py +0 -0
- {symbex-1.2 → symbex-1.3.1}/symbex.egg-info/SOURCES.txt +0 -0
- {symbex-1.2 → symbex-1.3.1}/symbex.egg-info/dependency_links.txt +0 -0
- {symbex-1.2 → symbex-1.3.1}/symbex.egg-info/entry_points.txt +0 -0
- {symbex-1.2 → symbex-1.3.1}/symbex.egg-info/requires.txt +0 -0
- {symbex-1.2 → symbex-1.3.1}/symbex.egg-info/top_level.txt +0 -0
- {symbex-1.2 → symbex-1.3.1}/tests/test_imports.py +0 -0
- {symbex-1.2 → symbex-1.3.1}/tests/test_replace.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: symbex
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.3.1
|
4
4
|
Summary: Find the Python code for specified symbols
|
5
5
|
Home-page: https://github.com/simonw/symbex
|
6
6
|
Author: Simon Willison
|
@@ -115,6 +115,9 @@ The following filters are available:
|
|
115
115
|
- `--unasync` - only non-async functions
|
116
116
|
- `--documented` - functions/classes that have a docstring
|
117
117
|
- `--undocumented` - functions/classes that do not have a docstring
|
118
|
+
- `--public` - functions/classes that are public - don't have a `_name` prefix (or are `__*__` methods)
|
119
|
+
- `--private` - functions/classes that are private - have a `_name` prefix and are not `__*__`
|
120
|
+
- `--dunder` - functions matching `__*__` - this should usually be used with `*.*` to find all dunder methods
|
118
121
|
- `--typed` - functions that have at least one type annotation
|
119
122
|
- `--untyped` - functions that have no type annotations
|
120
123
|
- `--partially-typed` - functions that have some type annotations but not all
|
@@ -135,6 +138,12 @@ This example shows the full source code of every class method in the Python stan
|
|
135
138
|
symbex --fully-typed --no-init '*.*' --stdlib
|
136
139
|
```
|
137
140
|
|
141
|
+
To find all public functions and methods that lack documentation, just showing the signature of each one:
|
142
|
+
|
143
|
+
```bash
|
144
|
+
symbex '*' '*.*' --public --undocumented --signatures
|
145
|
+
```
|
146
|
+
|
138
147
|
### Example output
|
139
148
|
|
140
149
|
In a fresh checkout of [Datasette](https://github.com/simonw/datasette) I ran this command:
|
@@ -190,40 +199,40 @@ cog.out(
|
|
190
199
|
]]] -->
|
191
200
|
```python
|
192
201
|
# File: symbex/lib.py Line: 107
|
193
|
-
def function_definition(function_node: AST)
|
202
|
+
def function_definition(function_node: AST):
|
194
203
|
|
195
204
|
# File: symbex/lib.py Line: 13
|
196
|
-
def find_symbol_nodes(code: str, filename: str, symbols: Iterable[str]) -> List[Tuple[(AST, Optional[str])]]
|
205
|
+
def find_symbol_nodes(code: str, filename: str, symbols: Iterable[str]) -> List[Tuple[(AST, Optional[str])]]:
|
197
206
|
|
198
207
|
# File: symbex/lib.py Line: 175
|
199
|
-
def class_definition(class_def)
|
208
|
+
def class_definition(class_def):
|
200
209
|
|
201
210
|
# File: symbex/lib.py Line: 209
|
202
|
-
def annotation_definition(annotation: AST) -> str
|
211
|
+
def annotation_definition(annotation: AST) -> str:
|
203
212
|
|
204
213
|
# File: symbex/lib.py Line: 227
|
205
|
-
def read_file(path)
|
214
|
+
def read_file(path):
|
206
215
|
|
207
216
|
# File: symbex/lib.py Line: 253
|
208
|
-
class TypeSummary
|
217
|
+
class TypeSummary:
|
209
218
|
|
210
219
|
# File: symbex/lib.py Line: 258
|
211
|
-
def type_summary(node: AST) -> Optional[TypeSummary]
|
220
|
+
def type_summary(node: AST) -> Optional[TypeSummary]:
|
212
221
|
|
213
222
|
# File: symbex/lib.py Line: 304
|
214
|
-
def quoted_string(s)
|
223
|
+
def quoted_string(s):
|
215
224
|
|
216
225
|
# File: symbex/lib.py Line: 315
|
217
|
-
def import_line_for_function(function_name: str, filepath: str, possible_root_dirs: List[str]) -> str
|
226
|
+
def import_line_for_function(function_name: str, filepath: str, possible_root_dirs: List[str]) -> str:
|
218
227
|
|
219
228
|
# File: symbex/lib.py Line: 37
|
220
|
-
def code_for_node(code: str, node: AST, class_name: str, signatures: bool, docstrings: bool) -> Tuple[(str, int)]
|
229
|
+
def code_for_node(code: str, node: AST, class_name: str, signatures: bool, docstrings: bool) -> Tuple[(str, int)]:
|
221
230
|
|
222
231
|
# File: symbex/lib.py Line: 71
|
223
|
-
def add_docstring(definition: str, node: AST, docstrings: bool, is_method: bool) -> str
|
232
|
+
def add_docstring(definition: str, node: AST, docstrings: bool, is_method: bool) -> str:
|
224
233
|
|
225
234
|
# File: symbex/lib.py Line: 82
|
226
|
-
def match(name: str, symbols: Iterable[str]) -> bool
|
235
|
+
def match(name: str, symbols: Iterable[str]) -> bool:
|
227
236
|
```
|
228
237
|
<!-- [[[end]]] -->
|
229
238
|
This can be combined with other options, or you can run `symbex -s` to see every symbol in the current directory and its subdirectories.
|
@@ -245,7 +254,7 @@ cog.out(
|
|
245
254
|
```python
|
246
255
|
# File: symbex/lib.py Line: 82
|
247
256
|
# from symbex.lib import match
|
248
|
-
def match(name: str, symbols: Iterable[str]) -> bool
|
257
|
+
def match(name: str, symbols: Iterable[str]) -> bool:
|
249
258
|
```
|
250
259
|
<!-- [[[end]]] -->
|
251
260
|
To suppress the `# File: ...` comments, use `--no-file` or `-n`.
|
@@ -265,7 +274,7 @@ cog.out(
|
|
265
274
|
]]] -->
|
266
275
|
```python
|
267
276
|
# from symbex.lib import match
|
268
|
-
def match(name: str, symbols: Iterable[str]) -> bool
|
277
|
+
def match(name: str, symbols: Iterable[str]) -> bool:
|
269
278
|
```
|
270
279
|
<!-- [[[end]]] -->
|
271
280
|
|
@@ -282,7 +291,7 @@ cog.out(
|
|
282
291
|
]]] -->
|
283
292
|
```python
|
284
293
|
# File: symbex/lib.py Line: 82
|
285
|
-
def match(name: str, symbols: Iterable[str]) -> bool
|
294
|
+
def match(name: str, symbols: Iterable[str]) -> bool:
|
286
295
|
"Returns True if name matches any of the symbols, resolving wildcards"
|
287
296
|
```
|
288
297
|
<!-- [[[end]]] -->
|
@@ -399,14 +408,24 @@ def second_function(a: int, b: int) -> int:
|
|
399
408
|
|
400
409
|
The `--check` option causes `symbex` to return a non-zero exit code if any matches are found for your query.
|
401
410
|
|
402
|
-
You can use this in CI to guard against things like functions being added without documentation:
|
411
|
+
You can use this in CI to guard against things like public functions being added without documentation:
|
403
412
|
|
404
413
|
```bash
|
405
|
-
symbex --function --undocumented --check
|
414
|
+
symbex --function --public --undocumented --check
|
406
415
|
```
|
407
416
|
This will fail silently but set a `1` exit code if there are any undocumented functions.
|
408
417
|
|
409
|
-
|
418
|
+
Using this as a step in a CI tool such as GitHub Actions should result in a test failure.
|
419
|
+
|
420
|
+
Run this to see the exit code from the last command:
|
421
|
+
```bash
|
422
|
+
echo $?
|
423
|
+
```
|
424
|
+
|
425
|
+
`--check` will not output anything by default. Add `--count` to output a count of matching symbols, or `-s/--signatures` to output the signatures of the matching symbols, for example:
|
426
|
+
```bash
|
427
|
+
symbex --function --public --undocumented --check --count
|
428
|
+
```
|
410
429
|
|
411
430
|
## Similar tools
|
412
431
|
|
@@ -465,6 +484,10 @@ Usage: symbex [OPTIONS] [SYMBOLS]...
|
|
465
484
|
return a + b + 3
|
466
485
|
" | symbex my_function --replace
|
467
486
|
|
487
|
+
# Replace my_function with the output of a command:
|
488
|
+
symbex first_function --rexec "sed 's/^/# /'"
|
489
|
+
# This uses sed to comment out the function body
|
490
|
+
|
468
491
|
Options:
|
469
492
|
--version Show the version and exit.
|
470
493
|
-f, --file FILE Files to search
|
@@ -485,6 +508,9 @@ Options:
|
|
485
508
|
--class Filter classes
|
486
509
|
--documented Filter functions with docstrings
|
487
510
|
--undocumented Filter functions without docstrings
|
511
|
+
--public Filter for symbols without a _ prefix
|
512
|
+
--private Filter for symbols with a _ prefix
|
513
|
+
--dunder Filter for symbols matching __*__
|
488
514
|
--typed Filter functions with type annotations
|
489
515
|
--untyped Filter functions without type annotations
|
490
516
|
--partially-typed Filter functions with partial type annotations
|
@@ -100,6 +100,9 @@ The following filters are available:
|
|
100
100
|
- `--unasync` - only non-async functions
|
101
101
|
- `--documented` - functions/classes that have a docstring
|
102
102
|
- `--undocumented` - functions/classes that do not have a docstring
|
103
|
+
- `--public` - functions/classes that are public - don't have a `_name` prefix (or are `__*__` methods)
|
104
|
+
- `--private` - functions/classes that are private - have a `_name` prefix and are not `__*__`
|
105
|
+
- `--dunder` - functions matching `__*__` - this should usually be used with `*.*` to find all dunder methods
|
103
106
|
- `--typed` - functions that have at least one type annotation
|
104
107
|
- `--untyped` - functions that have no type annotations
|
105
108
|
- `--partially-typed` - functions that have some type annotations but not all
|
@@ -120,6 +123,12 @@ This example shows the full source code of every class method in the Python stan
|
|
120
123
|
symbex --fully-typed --no-init '*.*' --stdlib
|
121
124
|
```
|
122
125
|
|
126
|
+
To find all public functions and methods that lack documentation, just showing the signature of each one:
|
127
|
+
|
128
|
+
```bash
|
129
|
+
symbex '*' '*.*' --public --undocumented --signatures
|
130
|
+
```
|
131
|
+
|
123
132
|
### Example output
|
124
133
|
|
125
134
|
In a fresh checkout of [Datasette](https://github.com/simonw/datasette) I ran this command:
|
@@ -175,40 +184,40 @@ cog.out(
|
|
175
184
|
]]] -->
|
176
185
|
```python
|
177
186
|
# File: symbex/lib.py Line: 107
|
178
|
-
def function_definition(function_node: AST)
|
187
|
+
def function_definition(function_node: AST):
|
179
188
|
|
180
189
|
# File: symbex/lib.py Line: 13
|
181
|
-
def find_symbol_nodes(code: str, filename: str, symbols: Iterable[str]) -> List[Tuple[(AST, Optional[str])]]
|
190
|
+
def find_symbol_nodes(code: str, filename: str, symbols: Iterable[str]) -> List[Tuple[(AST, Optional[str])]]:
|
182
191
|
|
183
192
|
# File: symbex/lib.py Line: 175
|
184
|
-
def class_definition(class_def)
|
193
|
+
def class_definition(class_def):
|
185
194
|
|
186
195
|
# File: symbex/lib.py Line: 209
|
187
|
-
def annotation_definition(annotation: AST) -> str
|
196
|
+
def annotation_definition(annotation: AST) -> str:
|
188
197
|
|
189
198
|
# File: symbex/lib.py Line: 227
|
190
|
-
def read_file(path)
|
199
|
+
def read_file(path):
|
191
200
|
|
192
201
|
# File: symbex/lib.py Line: 253
|
193
|
-
class TypeSummary
|
202
|
+
class TypeSummary:
|
194
203
|
|
195
204
|
# File: symbex/lib.py Line: 258
|
196
|
-
def type_summary(node: AST) -> Optional[TypeSummary]
|
205
|
+
def type_summary(node: AST) -> Optional[TypeSummary]:
|
197
206
|
|
198
207
|
# File: symbex/lib.py Line: 304
|
199
|
-
def quoted_string(s)
|
208
|
+
def quoted_string(s):
|
200
209
|
|
201
210
|
# File: symbex/lib.py Line: 315
|
202
|
-
def import_line_for_function(function_name: str, filepath: str, possible_root_dirs: List[str]) -> str
|
211
|
+
def import_line_for_function(function_name: str, filepath: str, possible_root_dirs: List[str]) -> str:
|
203
212
|
|
204
213
|
# File: symbex/lib.py Line: 37
|
205
|
-
def code_for_node(code: str, node: AST, class_name: str, signatures: bool, docstrings: bool) -> Tuple[(str, int)]
|
214
|
+
def code_for_node(code: str, node: AST, class_name: str, signatures: bool, docstrings: bool) -> Tuple[(str, int)]:
|
206
215
|
|
207
216
|
# File: symbex/lib.py Line: 71
|
208
|
-
def add_docstring(definition: str, node: AST, docstrings: bool, is_method: bool) -> str
|
217
|
+
def add_docstring(definition: str, node: AST, docstrings: bool, is_method: bool) -> str:
|
209
218
|
|
210
219
|
# File: symbex/lib.py Line: 82
|
211
|
-
def match(name: str, symbols: Iterable[str]) -> bool
|
220
|
+
def match(name: str, symbols: Iterable[str]) -> bool:
|
212
221
|
```
|
213
222
|
<!-- [[[end]]] -->
|
214
223
|
This can be combined with other options, or you can run `symbex -s` to see every symbol in the current directory and its subdirectories.
|
@@ -230,7 +239,7 @@ cog.out(
|
|
230
239
|
```python
|
231
240
|
# File: symbex/lib.py Line: 82
|
232
241
|
# from symbex.lib import match
|
233
|
-
def match(name: str, symbols: Iterable[str]) -> bool
|
242
|
+
def match(name: str, symbols: Iterable[str]) -> bool:
|
234
243
|
```
|
235
244
|
<!-- [[[end]]] -->
|
236
245
|
To suppress the `# File: ...` comments, use `--no-file` or `-n`.
|
@@ -250,7 +259,7 @@ cog.out(
|
|
250
259
|
]]] -->
|
251
260
|
```python
|
252
261
|
# from symbex.lib import match
|
253
|
-
def match(name: str, symbols: Iterable[str]) -> bool
|
262
|
+
def match(name: str, symbols: Iterable[str]) -> bool:
|
254
263
|
```
|
255
264
|
<!-- [[[end]]] -->
|
256
265
|
|
@@ -267,7 +276,7 @@ cog.out(
|
|
267
276
|
]]] -->
|
268
277
|
```python
|
269
278
|
# File: symbex/lib.py Line: 82
|
270
|
-
def match(name: str, symbols: Iterable[str]) -> bool
|
279
|
+
def match(name: str, symbols: Iterable[str]) -> bool:
|
271
280
|
"Returns True if name matches any of the symbols, resolving wildcards"
|
272
281
|
```
|
273
282
|
<!-- [[[end]]] -->
|
@@ -384,14 +393,24 @@ def second_function(a: int, b: int) -> int:
|
|
384
393
|
|
385
394
|
The `--check` option causes `symbex` to return a non-zero exit code if any matches are found for your query.
|
386
395
|
|
387
|
-
You can use this in CI to guard against things like functions being added without documentation:
|
396
|
+
You can use this in CI to guard against things like public functions being added without documentation:
|
388
397
|
|
389
398
|
```bash
|
390
|
-
symbex --function --undocumented --check
|
399
|
+
symbex --function --public --undocumented --check
|
391
400
|
```
|
392
401
|
This will fail silently but set a `1` exit code if there are any undocumented functions.
|
393
402
|
|
394
|
-
|
403
|
+
Using this as a step in a CI tool such as GitHub Actions should result in a test failure.
|
404
|
+
|
405
|
+
Run this to see the exit code from the last command:
|
406
|
+
```bash
|
407
|
+
echo $?
|
408
|
+
```
|
409
|
+
|
410
|
+
`--check` will not output anything by default. Add `--count` to output a count of matching symbols, or `-s/--signatures` to output the signatures of the matching symbols, for example:
|
411
|
+
```bash
|
412
|
+
symbex --function --public --undocumented --check --count
|
413
|
+
```
|
395
414
|
|
396
415
|
## Similar tools
|
397
416
|
|
@@ -450,6 +469,10 @@ Usage: symbex [OPTIONS] [SYMBOLS]...
|
|
450
469
|
return a + b + 3
|
451
470
|
" | symbex my_function --replace
|
452
471
|
|
472
|
+
# Replace my_function with the output of a command:
|
473
|
+
symbex first_function --rexec "sed 's/^/# /'"
|
474
|
+
# This uses sed to comment out the function body
|
475
|
+
|
453
476
|
Options:
|
454
477
|
--version Show the version and exit.
|
455
478
|
-f, --file FILE Files to search
|
@@ -470,6 +493,9 @@ Options:
|
|
470
493
|
--class Filter classes
|
471
494
|
--documented Filter functions with docstrings
|
472
495
|
--undocumented Filter functions without docstrings
|
496
|
+
--public Filter for symbols without a _ prefix
|
497
|
+
--private Filter for symbols with a _ prefix
|
498
|
+
--dunder Filter for symbols matching __*__
|
473
499
|
--typed Filter functions with type annotations
|
474
500
|
--untyped Filter functions without type annotations
|
475
501
|
--partially-typed Filter functions with partial type annotations
|
@@ -120,6 +120,21 @@ from .lib import (
|
|
120
120
|
is_flag=True,
|
121
121
|
help="Filter functions without docstrings",
|
122
122
|
)
|
123
|
+
@click.option(
|
124
|
+
"--public",
|
125
|
+
is_flag=True,
|
126
|
+
help="Filter for symbols without a _ prefix",
|
127
|
+
)
|
128
|
+
@click.option(
|
129
|
+
"--private",
|
130
|
+
is_flag=True,
|
131
|
+
help="Filter for symbols with a _ prefix",
|
132
|
+
)
|
133
|
+
@click.option(
|
134
|
+
"--dunder",
|
135
|
+
is_flag=True,
|
136
|
+
help="Filter for symbols matching __*__",
|
137
|
+
)
|
123
138
|
@click.option(
|
124
139
|
"--typed",
|
125
140
|
is_flag=True,
|
@@ -174,6 +189,9 @@ def cli(
|
|
174
189
|
class_,
|
175
190
|
documented,
|
176
191
|
undocumented,
|
192
|
+
public,
|
193
|
+
private,
|
194
|
+
dunder,
|
177
195
|
typed,
|
178
196
|
untyped,
|
179
197
|
partially_typed,
|
@@ -234,6 +252,11 @@ def cli(
|
|
234
252
|
# This is a replacement implementation
|
235
253
|
return a + b + 3
|
236
254
|
" | symbex my_function --replace
|
255
|
+
|
256
|
+
\b
|
257
|
+
# Replace my_function with the output of a command:
|
258
|
+
symbex first_function --rexec "sed 's/^/# /'"
|
259
|
+
# This uses sed to comment out the function body
|
237
260
|
"""
|
238
261
|
if modules:
|
239
262
|
module_dirs = []
|
@@ -281,6 +304,9 @@ def cli(
|
|
281
304
|
class_,
|
282
305
|
documented,
|
283
306
|
undocumented,
|
307
|
+
public,
|
308
|
+
private,
|
309
|
+
dunder,
|
284
310
|
typed,
|
285
311
|
untyped,
|
286
312
|
partially_typed,
|
@@ -312,6 +338,9 @@ def cli(
|
|
312
338
|
class_,
|
313
339
|
documented,
|
314
340
|
undocumented,
|
341
|
+
public,
|
342
|
+
private,
|
343
|
+
dunder,
|
315
344
|
typed,
|
316
345
|
untyped,
|
317
346
|
partially_typed,
|
@@ -347,6 +376,9 @@ def cli(
|
|
347
376
|
class_,
|
348
377
|
documented,
|
349
378
|
undocumented,
|
379
|
+
public,
|
380
|
+
private,
|
381
|
+
dunder,
|
350
382
|
typed,
|
351
383
|
untyped,
|
352
384
|
partially_typed,
|
@@ -371,6 +403,12 @@ def cli(
|
|
371
403
|
return False
|
372
404
|
if undocumented and ast.get_docstring(node):
|
373
405
|
return False
|
406
|
+
if public and node.name.startswith("_") and not is_dunder(node.name):
|
407
|
+
return False
|
408
|
+
if private and (is_dunder(node.name) or not node.name.startswith("_")):
|
409
|
+
return False
|
410
|
+
if dunder and not is_dunder(node.name):
|
411
|
+
return False
|
374
412
|
summary = type_summary(node)
|
375
413
|
# if no summary, type filters all fail
|
376
414
|
if not summary and (
|
@@ -499,3 +537,7 @@ def is_subpath(path: pathlib.Path, parent: pathlib.Path) -> bool:
|
|
499
537
|
return True
|
500
538
|
except ValueError:
|
501
539
|
return False
|
540
|
+
|
541
|
+
|
542
|
+
def is_dunder(name):
|
543
|
+
return name.startswith("__") and name.endswith("__")
|
@@ -169,7 +169,7 @@ def function_definition(function_node: AST):
|
|
169
169
|
if isinstance(function_node, AsyncFunctionDef):
|
170
170
|
def_ = "async def "
|
171
171
|
|
172
|
-
return f"{def_}{function_name}({arguments_str}){return_annotation}"
|
172
|
+
return f"{def_}{function_name}({arguments_str}){return_annotation}:"
|
173
173
|
|
174
174
|
|
175
175
|
def class_definition(class_def):
|
@@ -201,7 +201,7 @@ def class_definition(class_def):
|
|
201
201
|
if signature:
|
202
202
|
signature = f"({signature})"
|
203
203
|
|
204
|
-
class_definition = f"class {class_def.name}{signature}"
|
204
|
+
class_definition = f"class {class_def.name}{signature}:"
|
205
205
|
|
206
206
|
return class_definition
|
207
207
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: symbex
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.3.1
|
4
4
|
Summary: Find the Python code for specified symbols
|
5
5
|
Home-page: https://github.com/simonw/symbex
|
6
6
|
Author: Simon Willison
|
@@ -115,6 +115,9 @@ The following filters are available:
|
|
115
115
|
- `--unasync` - only non-async functions
|
116
116
|
- `--documented` - functions/classes that have a docstring
|
117
117
|
- `--undocumented` - functions/classes that do not have a docstring
|
118
|
+
- `--public` - functions/classes that are public - don't have a `_name` prefix (or are `__*__` methods)
|
119
|
+
- `--private` - functions/classes that are private - have a `_name` prefix and are not `__*__`
|
120
|
+
- `--dunder` - functions matching `__*__` - this should usually be used with `*.*` to find all dunder methods
|
118
121
|
- `--typed` - functions that have at least one type annotation
|
119
122
|
- `--untyped` - functions that have no type annotations
|
120
123
|
- `--partially-typed` - functions that have some type annotations but not all
|
@@ -135,6 +138,12 @@ This example shows the full source code of every class method in the Python stan
|
|
135
138
|
symbex --fully-typed --no-init '*.*' --stdlib
|
136
139
|
```
|
137
140
|
|
141
|
+
To find all public functions and methods that lack documentation, just showing the signature of each one:
|
142
|
+
|
143
|
+
```bash
|
144
|
+
symbex '*' '*.*' --public --undocumented --signatures
|
145
|
+
```
|
146
|
+
|
138
147
|
### Example output
|
139
148
|
|
140
149
|
In a fresh checkout of [Datasette](https://github.com/simonw/datasette) I ran this command:
|
@@ -190,40 +199,40 @@ cog.out(
|
|
190
199
|
]]] -->
|
191
200
|
```python
|
192
201
|
# File: symbex/lib.py Line: 107
|
193
|
-
def function_definition(function_node: AST)
|
202
|
+
def function_definition(function_node: AST):
|
194
203
|
|
195
204
|
# File: symbex/lib.py Line: 13
|
196
|
-
def find_symbol_nodes(code: str, filename: str, symbols: Iterable[str]) -> List[Tuple[(AST, Optional[str])]]
|
205
|
+
def find_symbol_nodes(code: str, filename: str, symbols: Iterable[str]) -> List[Tuple[(AST, Optional[str])]]:
|
197
206
|
|
198
207
|
# File: symbex/lib.py Line: 175
|
199
|
-
def class_definition(class_def)
|
208
|
+
def class_definition(class_def):
|
200
209
|
|
201
210
|
# File: symbex/lib.py Line: 209
|
202
|
-
def annotation_definition(annotation: AST) -> str
|
211
|
+
def annotation_definition(annotation: AST) -> str:
|
203
212
|
|
204
213
|
# File: symbex/lib.py Line: 227
|
205
|
-
def read_file(path)
|
214
|
+
def read_file(path):
|
206
215
|
|
207
216
|
# File: symbex/lib.py Line: 253
|
208
|
-
class TypeSummary
|
217
|
+
class TypeSummary:
|
209
218
|
|
210
219
|
# File: symbex/lib.py Line: 258
|
211
|
-
def type_summary(node: AST) -> Optional[TypeSummary]
|
220
|
+
def type_summary(node: AST) -> Optional[TypeSummary]:
|
212
221
|
|
213
222
|
# File: symbex/lib.py Line: 304
|
214
|
-
def quoted_string(s)
|
223
|
+
def quoted_string(s):
|
215
224
|
|
216
225
|
# File: symbex/lib.py Line: 315
|
217
|
-
def import_line_for_function(function_name: str, filepath: str, possible_root_dirs: List[str]) -> str
|
226
|
+
def import_line_for_function(function_name: str, filepath: str, possible_root_dirs: List[str]) -> str:
|
218
227
|
|
219
228
|
# File: symbex/lib.py Line: 37
|
220
|
-
def code_for_node(code: str, node: AST, class_name: str, signatures: bool, docstrings: bool) -> Tuple[(str, int)]
|
229
|
+
def code_for_node(code: str, node: AST, class_name: str, signatures: bool, docstrings: bool) -> Tuple[(str, int)]:
|
221
230
|
|
222
231
|
# File: symbex/lib.py Line: 71
|
223
|
-
def add_docstring(definition: str, node: AST, docstrings: bool, is_method: bool) -> str
|
232
|
+
def add_docstring(definition: str, node: AST, docstrings: bool, is_method: bool) -> str:
|
224
233
|
|
225
234
|
# File: symbex/lib.py Line: 82
|
226
|
-
def match(name: str, symbols: Iterable[str]) -> bool
|
235
|
+
def match(name: str, symbols: Iterable[str]) -> bool:
|
227
236
|
```
|
228
237
|
<!-- [[[end]]] -->
|
229
238
|
This can be combined with other options, or you can run `symbex -s` to see every symbol in the current directory and its subdirectories.
|
@@ -245,7 +254,7 @@ cog.out(
|
|
245
254
|
```python
|
246
255
|
# File: symbex/lib.py Line: 82
|
247
256
|
# from symbex.lib import match
|
248
|
-
def match(name: str, symbols: Iterable[str]) -> bool
|
257
|
+
def match(name: str, symbols: Iterable[str]) -> bool:
|
249
258
|
```
|
250
259
|
<!-- [[[end]]] -->
|
251
260
|
To suppress the `# File: ...` comments, use `--no-file` or `-n`.
|
@@ -265,7 +274,7 @@ cog.out(
|
|
265
274
|
]]] -->
|
266
275
|
```python
|
267
276
|
# from symbex.lib import match
|
268
|
-
def match(name: str, symbols: Iterable[str]) -> bool
|
277
|
+
def match(name: str, symbols: Iterable[str]) -> bool:
|
269
278
|
```
|
270
279
|
<!-- [[[end]]] -->
|
271
280
|
|
@@ -282,7 +291,7 @@ cog.out(
|
|
282
291
|
]]] -->
|
283
292
|
```python
|
284
293
|
# File: symbex/lib.py Line: 82
|
285
|
-
def match(name: str, symbols: Iterable[str]) -> bool
|
294
|
+
def match(name: str, symbols: Iterable[str]) -> bool:
|
286
295
|
"Returns True if name matches any of the symbols, resolving wildcards"
|
287
296
|
```
|
288
297
|
<!-- [[[end]]] -->
|
@@ -399,14 +408,24 @@ def second_function(a: int, b: int) -> int:
|
|
399
408
|
|
400
409
|
The `--check` option causes `symbex` to return a non-zero exit code if any matches are found for your query.
|
401
410
|
|
402
|
-
You can use this in CI to guard against things like functions being added without documentation:
|
411
|
+
You can use this in CI to guard against things like public functions being added without documentation:
|
403
412
|
|
404
413
|
```bash
|
405
|
-
symbex --function --undocumented --check
|
414
|
+
symbex --function --public --undocumented --check
|
406
415
|
```
|
407
416
|
This will fail silently but set a `1` exit code if there are any undocumented functions.
|
408
417
|
|
409
|
-
|
418
|
+
Using this as a step in a CI tool such as GitHub Actions should result in a test failure.
|
419
|
+
|
420
|
+
Run this to see the exit code from the last command:
|
421
|
+
```bash
|
422
|
+
echo $?
|
423
|
+
```
|
424
|
+
|
425
|
+
`--check` will not output anything by default. Add `--count` to output a count of matching symbols, or `-s/--signatures` to output the signatures of the matching symbols, for example:
|
426
|
+
```bash
|
427
|
+
symbex --function --public --undocumented --check --count
|
428
|
+
```
|
410
429
|
|
411
430
|
## Similar tools
|
412
431
|
|
@@ -465,6 +484,10 @@ Usage: symbex [OPTIONS] [SYMBOLS]...
|
|
465
484
|
return a + b + 3
|
466
485
|
" | symbex my_function --replace
|
467
486
|
|
487
|
+
# Replace my_function with the output of a command:
|
488
|
+
symbex first_function --rexec "sed 's/^/# /'"
|
489
|
+
# This uses sed to comment out the function body
|
490
|
+
|
468
491
|
Options:
|
469
492
|
--version Show the version and exit.
|
470
493
|
-f, --file FILE Files to search
|
@@ -485,6 +508,9 @@ Options:
|
|
485
508
|
--class Filter classes
|
486
509
|
--documented Filter functions with docstrings
|
487
510
|
--undocumented Filter functions without docstrings
|
511
|
+
--public Filter for symbols without a _ prefix
|
512
|
+
--private Filter for symbols with a _ prefix
|
513
|
+
--dunder Filter for symbols matching __*__
|
488
514
|
--typed Filter functions with type annotations
|
489
515
|
--untyped Filter functions without type annotations
|
490
516
|
--partially-typed Filter functions with partial type annotations
|
@@ -30,6 +30,7 @@ from symbex.cli import cli
|
|
30
30
|
"def func_partially_typed_no_typed_return",
|
31
31
|
"def func_partially_typed_only_typed_return",
|
32
32
|
"def func_typed_no_params",
|
33
|
+
"def _private",
|
33
34
|
],
|
34
35
|
),
|
35
36
|
(
|
@@ -41,6 +42,7 @@ from symbex.cli import cli
|
|
41
42
|
"class ClassWithMeta",
|
42
43
|
"class ClassWithMethods",
|
43
44
|
"class ClassForTypedTests",
|
45
|
+
"class _PrivateClass",
|
44
46
|
],
|
45
47
|
),
|
46
48
|
(
|
@@ -69,6 +71,7 @@ from symbex.cli import cli
|
|
69
71
|
"def func_partially_typed_no_typed_return",
|
70
72
|
"def func_partially_typed_only_typed_return",
|
71
73
|
"def func_typed_no_params",
|
74
|
+
"def _private",
|
72
75
|
],
|
73
76
|
),
|
74
77
|
# This doesn't make sense, so should return []
|
@@ -89,6 +92,7 @@ from symbex.cli import cli
|
|
89
92
|
"def func_partially_typed_no_typed_return",
|
90
93
|
"def func_partially_typed_only_typed_return",
|
91
94
|
"def func_typed_no_params",
|
95
|
+
"def _private",
|
92
96
|
],
|
93
97
|
),
|
94
98
|
(
|
@@ -128,6 +132,7 @@ from symbex.cli import cli
|
|
128
132
|
"def func_fully_typed",
|
129
133
|
"async def async_func_fully_typed",
|
130
134
|
"def func_typed_no_params",
|
135
|
+
"def _private",
|
131
136
|
],
|
132
137
|
),
|
133
138
|
# Test against methods
|
@@ -148,6 +153,7 @@ from symbex.cli import cli
|
|
148
153
|
"def method_keyword_only_args",
|
149
154
|
"async def async_method",
|
150
155
|
"def method_untyped",
|
156
|
+
"def _private_method",
|
151
157
|
],
|
152
158
|
),
|
153
159
|
(
|
@@ -197,6 +203,70 @@ from symbex.cli import cli
|
|
197
203
|
"def method_untyped",
|
198
204
|
],
|
199
205
|
),
|
206
|
+
# Private and public and dunder
|
207
|
+
(
|
208
|
+
["--public"],
|
209
|
+
[
|
210
|
+
"def func_no_args",
|
211
|
+
"def func_positional_args",
|
212
|
+
"async def async_func",
|
213
|
+
"def func_default_args",
|
214
|
+
"def func_arbitrary_positional_args",
|
215
|
+
"def func_arbitrary_keyword_args",
|
216
|
+
"def func_arbitrary_args",
|
217
|
+
"def func_positional_only_args",
|
218
|
+
"def func_keyword_only_args",
|
219
|
+
"def func_type_annotations",
|
220
|
+
"class ClassNoBase",
|
221
|
+
"class ClassSingleBase",
|
222
|
+
"class ClassMultipleBase",
|
223
|
+
"class ClassWithMeta",
|
224
|
+
"class ClassWithMethods",
|
225
|
+
"def function_with_non_pep_0484_annotation",
|
226
|
+
"def complex_annotations",
|
227
|
+
"def func_fully_typed",
|
228
|
+
"async def async_func_fully_typed",
|
229
|
+
"def func_partially_typed",
|
230
|
+
"def func_partially_typed_no_typed_return",
|
231
|
+
"def func_partially_typed_only_typed_return",
|
232
|
+
"def func_typed_no_params",
|
233
|
+
"class ClassForTypedTests",
|
234
|
+
],
|
235
|
+
),
|
236
|
+
(
|
237
|
+
["--public", "*.*"],
|
238
|
+
[
|
239
|
+
"def __init__",
|
240
|
+
"def method_types",
|
241
|
+
"def method_positional_only_args",
|
242
|
+
"def method_keyword_only_args",
|
243
|
+
"async def async_method",
|
244
|
+
"def __init__",
|
245
|
+
"def method_fully_typed",
|
246
|
+
"def method_partially_typed",
|
247
|
+
"def method_untyped",
|
248
|
+
],
|
249
|
+
),
|
250
|
+
(["--private", "*.*"], ["def _private_method"]),
|
251
|
+
(
|
252
|
+
["--public", "--class"],
|
253
|
+
[
|
254
|
+
"class ClassNoBase",
|
255
|
+
"class ClassSingleBase",
|
256
|
+
"class ClassMultipleBase",
|
257
|
+
"class ClassWithMeta",
|
258
|
+
"class ClassWithMethods",
|
259
|
+
"class ClassForTypedTests",
|
260
|
+
],
|
261
|
+
),
|
262
|
+
(
|
263
|
+
["--private", "--class"],
|
264
|
+
[
|
265
|
+
"class _PrivateClass",
|
266
|
+
],
|
267
|
+
),
|
268
|
+
(["--private"], ["def _private", "class _PrivateClass"]),
|
269
|
+
(["--dunder", "*.*"], ["def __init__", "def __init__"]),
|
200
270
|
),
|
201
271
|
)
|
202
272
|
def test_filters(args, expected):
|
@@ -218,8 +288,8 @@ def test_filters(args, expected):
|
|
218
288
|
for line in result.stdout.splitlines()
|
219
289
|
if line.strip() and not line.startswith("# File:")
|
220
290
|
]
|
221
|
-
# We only match up to the opening "("
|
222
|
-
defs = [line.split("(")[0] for line in lines]
|
291
|
+
# We only match up to the opening "(" or ":"
|
292
|
+
defs = [line.split("(")[0].split(":")[0] for line in lines]
|
223
293
|
assert defs == expected
|
224
294
|
|
225
295
|
# Test the --count option too
|
@@ -151,23 +151,23 @@ def test_fixture(directory_full_of_code, monkeypatch, args, expected):
|
|
151
151
|
(
|
152
152
|
["foo*", "--silent"],
|
153
153
|
"# File: foo.py Line: 1\n"
|
154
|
-
"def foo1()
|
154
|
+
"def foo1():\n"
|
155
155
|
"\n"
|
156
156
|
"# File: foo.py Line: 5\n"
|
157
|
-
"def foo2()",
|
157
|
+
"def foo2():",
|
158
158
|
),
|
159
|
-
(["BarClass", "--silent"], "# File: bar.py Line: 1\n" "class BarClass"),
|
159
|
+
(["BarClass", "--silent"], "# File: bar.py Line: 1\n" "class BarClass:"),
|
160
160
|
(
|
161
161
|
["baz", "--silent"],
|
162
162
|
(
|
163
163
|
"# File: nested.py/x/baz.py Line: 1\n"
|
164
|
-
'def baz(delimiter=", ", type=str)'
|
164
|
+
'def baz(delimiter=", ", type=str):'
|
165
165
|
),
|
166
166
|
),
|
167
167
|
# Test for the --module option
|
168
168
|
(
|
169
169
|
["-m", "pathlib", "Path", "--silent", "-in"],
|
170
|
-
("# from pathlib import Path\nclass Path(PurePath)"),
|
170
|
+
("# from pathlib import Path\nclass Path(PurePath):"),
|
171
171
|
),
|
172
172
|
),
|
173
173
|
)
|
@@ -69,7 +69,7 @@ def test_method_symbols():
|
|
69
69
|
assert result.exit_code == 0
|
70
70
|
assert result.stdout == (
|
71
71
|
"# File: tests/example_symbols.py Class: ClassWithMethods Line: 91\n"
|
72
|
-
" async def async_method(a, b, c)
|
72
|
+
" async def async_method(a, b, c):\n"
|
73
73
|
"\n"
|
74
74
|
)
|
75
75
|
|
@@ -88,20 +88,20 @@ def test_docstrings():
|
|
88
88
|
assert result.exit_code == 0
|
89
89
|
expected = """
|
90
90
|
# File: tests/example_symbols.py Line: X
|
91
|
-
def func_no_args()
|
91
|
+
def func_no_args():
|
92
92
|
"This has a single line docstring"
|
93
93
|
|
94
94
|
# File: tests/example_symbols.py Line: X
|
95
|
-
def func_positional_args(a, b, c)
|
95
|
+
def func_positional_args(a, b, c):
|
96
96
|
\"\"\"This has a
|
97
97
|
multi-line docstring\"\"\"
|
98
98
|
|
99
99
|
# File: tests/example_symbols.py Class: ClassForTypedTests Line: X
|
100
|
-
def method_fully_typed(self, a: int, b: str) -> bool
|
100
|
+
def method_fully_typed(self, a: int, b: str) -> bool:
|
101
101
|
"Single line"
|
102
102
|
|
103
103
|
# File: tests/example_symbols.py Class: ClassForTypedTests Line: X
|
104
|
-
def method_partially_typed(self, a: int, b) -> bool
|
104
|
+
def method_partially_typed(self, a: int, b) -> bool:
|
105
105
|
\"\"\"Multiple
|
106
106
|
lines\"\"\"
|
107
107
|
""".strip()
|
@@ -126,15 +126,15 @@ def test_imports(no_file):
|
|
126
126
|
expected = """
|
127
127
|
# File: tests/example_symbols.py Line: 28
|
128
128
|
# from example_symbols import func_arbitrary_positional_args
|
129
|
-
def func_arbitrary_positional_args(*args)
|
129
|
+
def func_arbitrary_positional_args(*args):
|
130
130
|
|
131
131
|
# File: tests/example_symbols.py Line: 33
|
132
132
|
# from example_symbols import func_arbitrary_keyword_args
|
133
|
-
def func_arbitrary_keyword_args(**kwargs)
|
133
|
+
def func_arbitrary_keyword_args(**kwargs):
|
134
134
|
|
135
135
|
# File: tests/example_symbols.py Line: 38
|
136
136
|
# from example_symbols import func_arbitrary_args
|
137
|
-
def func_arbitrary_args(*args, **kwargs)
|
137
|
+
def func_arbitrary_args(*args, **kwargs):
|
138
138
|
""".strip()
|
139
139
|
if no_file:
|
140
140
|
lines = expected.split("\n")
|
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
|