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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: symbex
3
- Version: 1.2
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
- `--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.
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
- `--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.
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
@@ -1,7 +1,7 @@
1
1
  from setuptools import setup
2
2
  import os
3
3
 
4
- VERSION = "1.2"
4
+ VERSION = "1.3.1"
5
5
 
6
6
 
7
7
  def get_long_description():
@@ -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.2
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
- `--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.
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()\n"
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)\n"
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