PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
Functions | Variables
audit_inline_choices Namespace Reference

Functions

 _resolve_modules ()
 Expand the module roots into (path, dotted name) pairs.
 
str literal_key (str module, list values)
 Stable identity for one inline choice set.
 
dict find_inline_choices ()
 Every inline set of string choices in the CLI package.
 
list string_members (collection)
 The string members of a literal collection, or None if it is not all strings.
 
int main ()
 Fail when an inline choice set is neither named nor classified.
 

Variables

 REPO_ROOT = Path(__file__).resolve().parents[2]
 
str WAIVERS = REPO_ROOT / "tests" / "tooling" / "inline_choice_waivers.json"
 
tuple MODULE_ROOTS
 
 MODULES = tuple(item[0] for item in _resolve_modules())
 
 MODULE_DOTTED = dict(_resolve_modules())
 
tuple VALID_CLASSIFICATIONS
 
int MIN_REASON_CHARS = 40
 
int MINIMUM_CHOICE_SIZE = 2
 

Detailed Description

Reject public closed choices written as inline literals.

The family census discovers `normalize_*` functions and named module-level constants.
That is a complete search of what it can see - and an inline `if value not in {"a","b"}`
is invisible to it. The census could therefore call itself exhaustive while public
choices sat in literals nobody enumerated.

This closes the loop from the other side: every inline set of string choices in the CLI
package must either become a named constant, which the census then discovers and
classifies, or be listed here with the same typed classification and a reason. A
literal that is neither is a violation.

Function Documentation

◆ _resolve_modules()

audit_inline_choices._resolve_modules ( )
protected

Expand the module roots into (path, dotted name) pairs.

Returns
List of scanned module pairs.

Definition at line 31 of file audit_inline_choices.py.

31def _resolve_modules():
32 """!
33 @brief Expand the module roots into `(path, dotted name)` pairs.
34 @return List of scanned module pairs.
35 """
36 resolved = []
37 for root in MODULE_ROOTS:
38 path = REPO_ROOT / root
39 if path.is_dir():
40 dotted = root.replace("/", ".")
41 for child in sorted(path.glob("*.py")):
42 if child.name != "__init__.py":
43 resolved.append((str(child.relative_to(REPO_ROOT)), dotted))
44 else:
45 resolved.append((root, root.replace("/", ".")[: -len(".py")]))
46 return resolved
47
48

◆ literal_key()

str audit_inline_choices.literal_key ( str  module,
list  values 
)

Stable identity for one inline choice set.

Keyed by module and sorted values rather than by line number, so the waiver survives edits above it and two sites sharing a set collapse to one entry - which is itself a signal that the set wants a name.

Parameters
[in]moduleDotted module name.
[in]valuesThe string members.
Returns
Waiver key.

Definition at line 63 of file audit_inline_choices.py.

63def literal_key(module: str, values: list) -> str:
64 """!
65 @brief Stable identity for one inline choice set.
66
67 @details Keyed by module and sorted values rather than by line number, so the
68 waiver survives edits above it and two sites sharing a set collapse to one
69 entry - which is itself a signal that the set wants a name.
70 @param[in] module Dotted module name.
71 @param[in] values The string members.
72 @return Waiver key.
73 """
74 return f"{module}::{{{','.join(sorted(values))}}}"
75
76
Here is the caller graph for this function:

◆ find_inline_choices()

dict audit_inline_choices.find_inline_choices ( )

Every inline set of string choices in the CLI package.

Two shapes are recognised: a membership test against a literal collection, and an argparse choices= literal. Both are how a closed public set gets written without a name.

Returns
Mapping of waiver key to the list of sites that produced it.

Definition at line 77 of file audit_inline_choices.py.

77def find_inline_choices() -> dict:
78 """!
79 @brief Every inline set of string choices in the CLI package.
80
81 @details Two shapes are recognised: a membership test against a literal collection,
82 and an argparse `choices=` literal. Both are how a closed public set gets
83 written without a name.
84 @return Mapping of waiver key to the list of sites that produced it.
85 """
86 found: dict = {}
87 for module in MODULES:
88 path = REPO_ROOT / module
89 if not path.is_file():
90 continue
91 dotted = MODULE_DOTTED.get(module, module.replace("/", ".")[: -len(".py")])
92 tree = ast.parse(path.read_text(encoding="utf-8"))
93 for node in ast.walk(tree):
94 values = None
95 shape = None
96 if isinstance(node, ast.Compare) and node.ops and \
97 isinstance(node.ops[0], (ast.In, ast.NotIn)):
98 collection = node.comparators[0]
99 if isinstance(collection, (ast.Set, ast.List, ast.Tuple)):
100 values, shape = string_members(collection), "membership test"
101 elif isinstance(node, ast.Call):
102 for keyword in node.keywords:
103 if keyword.arg == "choices" and \
104 isinstance(keyword.value, (ast.Set, ast.List, ast.Tuple)):
105 values, shape = string_members(keyword.value), "argparse choices"
106 if values and len(values) >= MINIMUM_CHOICE_SIZE:
107 key = literal_key(dotted, values)
108 found.setdefault(key, []).append(f"{module}:{node.lineno} ({shape})")
109 return found
110
111
Here is the call graph for this function:
Here is the caller graph for this function:

◆ string_members()

list audit_inline_choices.string_members (   collection)

The string members of a literal collection, or None if it is not all strings.

Parameters
[in]collectionAn ast Set, List, or Tuple node.
Returns
List of string values, or None.

Definition at line 112 of file audit_inline_choices.py.

112def string_members(collection) -> list:
113 """!
114 @brief The string members of a literal collection, or None if it is not all strings.
115 @param[in] collection An ast Set, List, or Tuple node.
116 @return List of string values, or None.
117 """
118 members = [element.value for element in collection.elts
119 if isinstance(element, ast.Constant) and isinstance(element.value, str)]
120 return members if len(members) == len(collection.elts) and members else None
121
122
Here is the caller graph for this function:

◆ main()

int audit_inline_choices.main ( )

Fail when an inline choice set is neither named nor classified.

Returns
Process status code.

Definition at line 123 of file audit_inline_choices.py.

123def main() -> int:
124 """!
125 @brief Fail when an inline choice set is neither named nor classified.
126 @return Process status code.
127 """
128 document = json.loads(WAIVERS.read_text(encoding="utf-8"))
129 waivers = document["inline_choices"]
130 found = find_inline_choices()
131
132 problems: list = []
133 for key in sorted(found):
134 entry = waivers.get(key)
135 if entry is None:
136 problems.append(
137 f"{key}\n at {', '.join(found[key])}\n"
138 f" Give this set a name so the family census can see it, or "
139 f"classify it in inline_choice_waivers.json."
140 )
141 continue
142 classification = entry.get("classification")
143 if classification not in VALID_CLASSIFICATIONS:
144 problems.append(f"{key}: classification {classification!r} is not one of "
145 f"{list(VALID_CLASSIFICATIONS)}")
146 elif len(str(entry.get("reason", "")).strip()) < MIN_REASON_CHARS:
147 problems.append(f"{key}: classified {classification} without a stated reason")
148 for stale in sorted(set(waivers) - set(found)):
149 problems.append(f"{stale}: waived, but no such inline literal exists any more")
150
151 if problems:
152 print("Inline choice-set violations:", file=sys.stderr)
153 for problem in problems:
154 print(f" {problem}", file=sys.stderr)
155 return 1
156
157 from collections import Counter
158 spread = Counter(waivers[key]["classification"] for key in found)
159 summary = ", ".join(f"{count} {name}" for name, count in sorted(spread.items()))
160 print(f"Inline choice audit passed: {len(found)} inline set(s), every one classified "
161 f"({summary}).")
162 return 0
163
164
int main(int argc, char **argv)
Entry point for the postprocessor executable.
Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ REPO_ROOT

audit_inline_choices.REPO_ROOT = Path(__file__).resolve().parents[2]

Definition at line 23 of file audit_inline_choices.py.

◆ WAIVERS

str audit_inline_choices.WAIVERS = REPO_ROOT / "tests" / "tooling" / "inline_choice_waivers.json"

Definition at line 24 of file audit_inline_choices.py.

◆ MODULE_ROOTS

tuple audit_inline_choices.MODULE_ROOTS
Initial value:
1= ("picurv_cli/core.py", "picurv_cli/cli.py", "picurv_cli/storage",
2 "picurv_cli/main.py")

Definition at line 27 of file audit_inline_choices.py.

◆ MODULES

audit_inline_choices.MODULES = tuple(item[0] for item in _resolve_modules())

Definition at line 49 of file audit_inline_choices.py.

◆ MODULE_DOTTED

audit_inline_choices.MODULE_DOTTED = dict(_resolve_modules())

Definition at line 50 of file audit_inline_choices.py.

◆ VALID_CLASSIFICATIONS

tuple audit_inline_choices.VALID_CLASSIFICATIONS
Initial value:
1= (
2 "parameter_of_entry", "spelling_alias", "structural",
3 "cli_inventory_sufficient", "not_public",
4)

Definition at line 54 of file audit_inline_choices.py.

◆ MIN_REASON_CHARS

int audit_inline_choices.MIN_REASON_CHARS = 40

Definition at line 58 of file audit_inline_choices.py.

◆ MINIMUM_CHOICE_SIZE

int audit_inline_choices.MINIMUM_CHOICE_SIZE = 2

Definition at line 60 of file audit_inline_choices.py.