@gspothq/cli 0.0.0-stage → 0.1.1
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.
- package/README.md +18 -2
- package/configurations/database/postgres/manifest.toml +114 -0
- package/configurations/database/postgres/rules/POSTGRES.md +78 -0
- package/configurations/framework/astro/eslint-presets.json +138 -0
- package/configurations/framework/astro/manifest.toml +95 -0
- package/configurations/framework/astro/rules/ASTRO.md +39 -0
- package/configurations/framework/expo/manifest.toml +43 -0
- package/configurations/framework/express/manifest.toml +17 -0
- package/configurations/framework/express/rules/EXPRESS.md +73 -0
- package/configurations/framework/express/semgrep/express.yml +71 -0
- package/configurations/framework/fastapi/manifest.toml +11 -0
- package/configurations/framework/fastapi/rules/FASTAPI.md +100 -0
- package/configurations/framework/fastapi/semgrep/fastapi.yml +44 -0
- package/configurations/framework/nestjs/eslint-presets.json +62 -0
- package/configurations/framework/nestjs/manifest.toml +52 -0
- package/configurations/framework/nestjs/rules/NESTJS.md +44 -0
- package/configurations/framework/nextjs/eslint-presets.json +73 -0
- package/configurations/framework/nextjs/manifest.toml +119 -0
- package/configurations/framework/nextjs/rules/NEXTJS.md +43 -0
- package/configurations/framework/react/eslint-presets.json +77 -0
- package/configurations/framework/react/manifest.toml +93 -0
- package/configurations/framework/react/rules/REACT.md +108 -0
- package/configurations/framework/react-dom/eslint-presets.json +110 -0
- package/configurations/framework/react-dom/manifest.toml +24 -0
- package/configurations/framework/react-native/manifest.toml +46 -0
- package/configurations/framework/react-native/rules/REACT-NATIVE.md +51 -0
- package/configurations/framework/site/manifest.toml +170 -0
- package/configurations/framework/site/rules/SITE.md +20 -0
- package/configurations/framework/svelte/eslint-presets.json +67 -0
- package/configurations/framework/svelte/manifest.toml +83 -0
- package/configurations/framework/svelte/rules/SVELTE.md +48 -0
- package/configurations/framework/vue/eslint-presets.json +180 -0
- package/configurations/framework/vue/manifest.toml +83 -0
- package/configurations/framework/vue/rules/VUE.md +41 -0
- package/configurations/general/commits/manifest.toml +111 -0
- package/configurations/general/commits/rules/GIT.md +31 -0
- package/configurations/general/coverage/manifest.toml +3 -0
- package/configurations/general/dependencies/manifest.toml +159 -0
- package/configurations/general/dependencies/rules/DEPENDENCIES.md +30 -0
- package/configurations/general/dependencies/syncpack.json +15 -0
- package/configurations/general/docs/manifest.toml +79 -0
- package/configurations/general/docs/rules/DOCS.md +86 -0
- package/configurations/general/duplication/manifest.toml +32 -0
- package/configurations/general/engineering/manifest.toml +8 -0
- package/configurations/general/engineering/rules/agent/PLANNING.md +42 -0
- package/configurations/general/engineering/rules/agent/SUPPRESSIONS.md +16 -0
- package/configurations/general/engineering/rules/agent/TALKING.md +3 -0
- package/configurations/general/engineering/rules/agent/TASKS.md +19 -0
- package/configurations/general/engineering/rules/agent/WORKING.md +83 -0
- package/configurations/general/engineering/rules/code/ACCESSIBILITY.md +32 -0
- package/configurations/general/engineering/rules/code/CLI.md +37 -0
- package/configurations/general/engineering/rules/code/COMMENTS.md +38 -0
- package/configurations/general/engineering/rules/code/CONFIGURATION.md +25 -0
- package/configurations/general/engineering/rules/code/ERRORS.md +49 -0
- package/configurations/general/engineering/rules/code/HTTP.md +46 -0
- package/configurations/general/engineering/rules/code/LOGGING.md +55 -0
- package/configurations/general/engineering/rules/code/TESTING.md +58 -0
- package/configurations/general/files/manifest.toml +141 -0
- package/configurations/general/files/rules/YAML.md +19 -0
- package/configurations/general/format/manifest.toml +152 -0
- package/configurations/general/gspot/manifest.toml +65 -0
- package/configurations/general/gspot/rules/GENERATED.md +10 -0
- package/configurations/general/licenses/manifest.toml +33 -0
- package/configurations/general/naming/defaults.json +423 -0
- package/configurations/general/naming/manifest.toml +35 -0
- package/configurations/general/naming/rules/NAMING.md +179 -0
- package/configurations/general/prose/manifest.toml +86 -0
- package/configurations/general/prose/rules/WRITING.md +31 -0
- package/configurations/general/prose/vale/styles/LICENSES.md +55 -0
- package/configurations/general/prose/vale/styles/gspot/ampm.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/conscious-language.yml +16 -0
- package/configurations/general/prose/vale/styles/gspot/contractions.yml +43 -0
- package/configurations/general/prose/vale/styles/gspot/currency.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/dashes.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/date-ordinals.yml +41 -0
- package/configurations/general/prose/vale/styles/gspot/dates.yml +7 -0
- package/configurations/general/prose/vale/styles/gspot/file-paths.yml +7 -0
- package/configurations/general/prose/vale/styles/gspot/future.yml +17 -0
- package/configurations/general/prose/vale/styles/gspot/hedging.yml +22 -0
- package/configurations/general/prose/vale/styles/gspot/history.yml +16 -0
- package/configurations/general/prose/vale/styles/gspot/idioms.yml +21 -0
- package/configurations/general/prose/vale/styles/gspot/interface-verbs.yml +11 -0
- package/configurations/general/prose/vale/styles/gspot/jargon.yml +11 -0
- package/configurations/general/prose/vale/styles/gspot/leading-conjunctions.yml +12 -0
- package/configurations/general/prose/vale/styles/gspot/link-text.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/locations.yml +14 -0
- package/configurations/general/prose/vale/styles/gspot/marketing.yml +26 -0
- package/configurations/general/prose/vale/styles/gspot/merge-conflict-markers.yml +13 -0
- package/configurations/general/prose/vale/styles/gspot/militaristic-language.yml +22 -0
- package/configurations/general/prose/vale/styles/gspot/modals.yml +9 -0
- package/configurations/general/prose/vale/styles/gspot/numbered-units.yml +18 -0
- package/configurations/general/prose/vale/styles/gspot/obvious-fields.yml +14 -0
- package/configurations/general/prose/vale/styles/gspot/ordinal-adverbs.yml +15 -0
- package/configurations/general/prose/vale/styles/gspot/percentage-numbers.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/placeholders.yml +7 -0
- package/configurations/general/prose/vale/styles/gspot/product-centric-writing.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/punctuated-headings.yml +11 -0
- package/configurations/general/prose/vale/styles/gspot/self-reference.yml +15 -0
- package/configurations/general/prose/vale/styles/gspot/session-identifiers.yml +11 -0
- package/configurations/general/prose/vale/styles/gspot/since.yml +6 -0
- package/configurations/general/prose/vale/styles/gspot/straight-quotes.yml +11 -0
- package/configurations/general/prose/vale/styles/gspot/suspended-hyphens.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/symbols.yml +8 -0
- package/configurations/general/prose/vale/styles/gspot/time-ranges.yml +14 -0
- package/configurations/general/prose/vale/styles/gspot/url-prepositions.yml +10 -0
- package/configurations/general/prose/vale/styles/gspot/version-range.yml +8 -0
- package/configurations/general/repository/manifest.toml +24 -0
- package/configurations/general/secrets/gitleaks.toml +2 -0
- package/configurations/general/secrets/manifest.toml +125 -0
- package/configurations/general/secrets/rules/SECRETS.md +13 -0
- package/configurations/general/security/manifest.toml +77 -0
- package/configurations/general/security/rules/SECURITY.md +55 -0
- package/configurations/general/spelling/manifest.toml +46 -0
- package/configurations/general/structure/manifest.toml +70 -0
- package/configurations/infrastructure/ansible/manifest.toml +33 -0
- package/configurations/infrastructure/docker/manifest.toml +127 -0
- package/configurations/infrastructure/docker/rules/DOCKER.md +135 -0
- package/configurations/infrastructure/docker/trivyignore +2 -0
- package/configurations/infrastructure/github-actions/manifest.toml +76 -0
- package/configurations/infrastructure/github-actions/rules/GITHUB-ACTIONS.md +43 -0
- package/configurations/infrastructure/github-actions/zizmor.yml +3 -0
- package/configurations/infrastructure/nginx/manifest.toml +51 -0
- package/configurations/infrastructure/nginx/rules/NGINX.md +38 -0
- package/configurations/infrastructure/openapi/manifest.toml +50 -0
- package/configurations/infrastructure/openapi/rules/OPENAPI.md +39 -0
- package/configurations/infrastructure/xcode/manifest.toml +131 -0
- package/configurations/infrastructure/xcode/rules/XCODE.md +34 -0
- package/configurations/language/bash/manifest.toml +261 -0
- package/configurations/language/bash/rules/BASH.md +106 -0
- package/configurations/language/bash/rules/LANGUAGE.md +213 -0
- package/configurations/language/bash/rules/NAMING.md +62 -0
- package/configurations/language/bash/rules/OPERATIONS.md +77 -0
- package/configurations/language/bash/rules/SAFETY.md +222 -0
- package/configurations/language/bash/semgrep/bash.yml +30 -0
- package/configurations/language/css/manifest.toml +98 -0
- package/configurations/language/css/rules/CSS.md +28 -0
- package/configurations/language/css/rules/NAMING.md +17 -0
- package/configurations/language/css/rules/TAILWIND.md +26 -0
- package/configurations/language/css/stylelint-rule-names.json +89 -0
- package/configurations/language/html/manifest.toml +59 -0
- package/configurations/language/html/rules/HTML.md +27 -0
- package/configurations/language/html/rules/NAMING.md +16 -0
- package/configurations/language/javascript/eslint-presets.json +997 -0
- package/configurations/language/javascript/eslint-rule-names.json +299 -0
- package/configurations/language/javascript/eslint-runtime-names.json +61 -0
- package/configurations/language/javascript/manifest.toml +393 -0
- package/configurations/language/javascript/rules/BUN.md +13 -0
- package/configurations/language/javascript/rules/COMPONENTS.md +18 -0
- package/configurations/language/javascript/rules/DENO.md +15 -0
- package/configurations/language/javascript/rules/JAVASCRIPT.md +60 -0
- package/configurations/language/javascript/rules/NAMING.md +31 -0
- package/configurations/language/javascript/rules/NODE.md +21 -0
- package/configurations/language/javascript/rules/PLAYWRIGHT.md +34 -0
- package/configurations/language/javascript/semgrep/javascript.yml +84 -0
- package/configurations/language/markdown/manifest.toml +85 -0
- package/configurations/language/markdown/markdownlint-cli2.mjs +19 -0
- package/configurations/language/python/manifest.toml +621 -0
- package/configurations/language/python/rules/DESIGN.md +136 -0
- package/configurations/language/python/rules/FLOW.md +124 -0
- package/configurations/language/python/rules/NAMING.md +53 -0
- package/configurations/language/python/rules/PACKAGING.md +49 -0
- package/configurations/language/python/rules/PYTHON.md +136 -0
- package/configurations/language/python/rules/TYPING.md +132 -0
- package/configurations/language/sql/manifest.toml +68 -0
- package/configurations/language/sql/rules/NAMING.md +90 -0
- package/configurations/language/sql/rules/SQL.md +9 -0
- package/configurations/language/swift/manifest.toml +164 -0
- package/configurations/language/swift/rules/NAMING.md +88 -0
- package/configurations/language/swift/rules/SWIFT.md +134 -0
- package/configurations/language/swift/rules/SWIFTUI.md +61 -0
- package/configurations/language/swift/rules/UIKIT.md +99 -0
- package/configurations/language/swift/semgrep/swift.yml +126 -0
- package/configurations/language/typescript/eslint-presets.json +215 -0
- package/configurations/language/typescript/manifest.toml +72 -0
- package/configurations/language/typescript/rules/NAMING.md +73 -0
- package/configurations/language/typescript/rules/TYPESCRIPT.md +79 -0
- package/configurations/library/drizzle/manifest.toml +68 -0
- package/configurations/library/drizzle/rules/DRIZZLE.md +44 -0
- package/configurations/library/react-hook-form/manifest.toml +15 -0
- package/configurations/library/react-hook-form/rules/REACT-HOOK-FORM.md +48 -0
- package/configurations/library/swift-snapshot-testing/manifest.toml +32 -0
- package/configurations/library/tanstack-query/eslint-presets.json +21 -0
- package/configurations/library/tanstack-query/manifest.toml +26 -0
- package/configurations/library/tanstack-query/rules/TANSTACK-QUERY.md +41 -0
- package/configurations/library/testing-library/eslint-presets.json +191 -0
- package/configurations/library/testing-library/manifest.toml +47 -0
- package/configurations/library/translations/manifest.toml +40 -0
- package/configurations/library/translations/rules/NEXT-INTL.md +33 -0
- package/configurations/library/translations/rules/TRANSLATIONS.md +102 -0
- package/configurations/library/trpc/manifest.toml +14 -0
- package/configurations/library/trpc/rules/TRPC.md +43 -0
- package/configurations/library/zod/manifest.toml +25 -0
- package/configurations/library/zod/rules/ZOD.md +44 -0
- package/configurations/library/zustand/manifest.toml +18 -0
- package/configurations/library/zustand/rules/ZUSTAND.md +106 -0
- package/configurations/platform/cloudflare/manifest.toml +94 -0
- package/configurations/platform/cloudflare/rules/CLOUDFLARE.md +23 -0
- package/configurations/platform/cloudflare/semgrep/cloudflare.yml +12 -0
- package/configurations/platform/supabase/manifest.toml +112 -0
- package/configurations/platform/supabase/rules/SUPABASE.md +129 -0
- package/configurations/platform/supabase/semgrep/supabase.yml +60 -0
- package/configurations/testing/jest/manifest.toml +54 -0
- package/configurations/testing/pytest/manifest.toml +53 -0
- package/configurations/testing/swift-tests/manifest.toml +52 -0
- package/configurations/testing/swift-tests/rules/SWIFT-TESTS.md +79 -0
- package/configurations/testing/vitest/eslint-presets.json +22 -0
- package/configurations/testing/vitest/manifest.toml +69 -0
- package/configurations/testing/vitest/rules/VITEST.md +18 -0
- package/dist/LICENSE.md +201 -0
- package/dist/THIRD-PARTY-NOTICES.md +25 -0
- package/dist/eslint-rule-levels.json +10 -0
- package/dist/gspot.js +30740 -0
- package/dist/linkinator-program.js +65 -0
- package/dist/purgecss-program.js +51 -0
- package/dist/tool-versions.json +3 -0
- package/grammars/bash.wasm +0 -0
- package/grammars/css.wasm +0 -0
- package/grammars/html.wasm +0 -0
- package/grammars/javascript.wasm +0 -0
- package/grammars/licenses/tree-sitter-bash.txt +21 -0
- package/grammars/licenses/tree-sitter-css.txt +21 -0
- package/grammars/licenses/tree-sitter-html.txt +21 -0
- package/grammars/licenses/tree-sitter-javascript.txt +21 -0
- package/grammars/licenses/tree-sitter-python.txt +21 -0
- package/grammars/licenses/tree-sitter-swift.txt +21 -0
- package/grammars/licenses/tree-sitter-typescript.txt +21 -0
- package/grammars/python.wasm +0 -0
- package/grammars/swift.wasm +0 -0
- package/grammars/tsx.wasm +0 -0
- package/grammars/typescript.wasm +0 -0
- package/gspot.schema.json +45991 -0
- package/package.json +93 -5
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Python Design
|
|
2
|
+
|
|
3
|
+
## Functions
|
|
4
|
+
|
|
5
|
+
I/O never hides in a helper that looks like a pure transformation. Declare an ordinary no-result case in the return type and return an explicit `None`; input that
|
|
6
|
+
violates the API contract raises a specific exception. A generator owns its resource lifetime
|
|
7
|
+
explicitly and documents its yielded values and cleanup contract. A partially consumed
|
|
8
|
+
generator never holds a resource implicitly. A generator expression serves a consumer that
|
|
9
|
+
only needs a lazy projection. Comprehensions serve one mapping or one filter; nested
|
|
10
|
+
logic, several conditions, mutation, or a transformation that needs explanation gets an ordinary loop so
|
|
11
|
+
its ordering stays visible.
|
|
12
|
+
|
|
13
|
+
### Function conventions
|
|
14
|
+
|
|
15
|
+
<!-- level: all -->
|
|
16
|
+
|
|
17
|
+
A function owns one responsibility. Name it for its action and domain concept, except required framework callback names. Make side effects clear in its name or docstring. Plain
|
|
18
|
+
functions and explicit data flow come before classes. When a function passes a limit, split
|
|
19
|
+
out a step with a real name. If the limit does not fit the code, ask the user to raise it:
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
"""Select recent message text under a validated nonnegative limit."""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from collections.abc import Sequence
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def trim_history_messages(messages: Sequence[str], max_messages: int) -> list[str]:
|
|
33
|
+
"""Return at most the requested number of recent messages."""
|
|
34
|
+
if max_messages == 0:
|
|
35
|
+
return []
|
|
36
|
+
if len(messages) <= max_messages:
|
|
37
|
+
return list(messages)
|
|
38
|
+
return list(messages[-max_messages:])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
__all__ = ["trim_history_messages"]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
A nested function closes over a local value to make the outer function clearer, keeping its
|
|
45
|
+
captured state and lifetime explicit. It never exists to
|
|
46
|
+
hide a helper from users or to turn a directly usable expression into a local call. A
|
|
47
|
+
module-level private helper serves reusable logic that a nested function hides. A conversion that a named helper only
|
|
48
|
+
forwards has its body placed directly at the call site.
|
|
49
|
+
|
|
50
|
+
Write a lambda as a one-line expression at its consumer, such as
|
|
51
|
+
a sorting key `lambda item: item.name`. Prefer a generator expression or `operator` function over
|
|
52
|
+
`map()` or `filter()` with a lambda. Use a `def` for anything longer. A conditional expression
|
|
53
|
+
holds one binary choice, `"stream" if is_streaming else "batch"`, and a path with several
|
|
54
|
+
operations gets `if` branches.
|
|
55
|
+
|
|
56
|
+
## Classes
|
|
57
|
+
|
|
58
|
+
A class exists for real state, invariants, or behavior. Keep a coordinating function without
|
|
59
|
+
polymorphism as a function. Never group static functions into a class. Related
|
|
60
|
+
classes that form one contract, such as schemas, exceptions, protocols, and their records,
|
|
61
|
+
stay together, and modules split by independent responsibility rather than class count. Plain
|
|
62
|
+
data has public attributes, internal attributes carry one leading
|
|
63
|
+
underscore, and a double underscore only protects a base class from subclass collisions.
|
|
64
|
+
|
|
65
|
+
`__init__` stays small, takes the values the class needs rather than the external objects
|
|
66
|
+
that carry them, and validates invariants. Keep it separate from database rows, object-relational mapping (ORM) objects,
|
|
67
|
+
API payloads, CLI namespaces, or provider responses. An external representation enters
|
|
68
|
+
through a classmethod named constructor such as `from_row` or `from_payload` when the class
|
|
69
|
+
owns that conversion. Neither `ClassName(**external_attributes)` nor a
|
|
70
|
+
constructor that only renames the initializer handles that conversion.
|
|
71
|
+
|
|
72
|
+
Loading, serialization, and validation systems stay outside the
|
|
73
|
+
business object. Keep a derived attribute cheap and deterministic. Build it from initialized
|
|
74
|
+
fields; a named constructor handles derivation through I/O or complex parsing.
|
|
75
|
+
|
|
76
|
+
Use a dataclass as a typed plain data record with methods only from the data contract. Mutable
|
|
77
|
+
defaults use `field(default_factory=...)`, and `__post_init__` holds cheap invariants and
|
|
78
|
+
derived fields. A named constructor serves construction that needs parsing, I/O, or
|
|
79
|
+
alternate sources. Never use it to disguise a mutable global configuration object. A frozen dataclass
|
|
80
|
+
expresses a value fixed after construction. A project on `attrs` applies the same principles
|
|
81
|
+
through its factories, validators, and converters, and do not introduce `attrs` to avoid a
|
|
82
|
+
small dataclass or a function.
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
"""Represent an independently owned batch of prompt text."""
|
|
86
|
+
|
|
87
|
+
from dataclasses import field, dataclass
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class Batch:
|
|
91
|
+
"""Prompt text owned by one processing batch.
|
|
92
|
+
|
|
93
|
+
Attributes:
|
|
94
|
+
prompts: The text items assigned to this batch.
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
prompts: list[str] = field(default_factory=list)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
__all__ = ["Batch"]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Dataclass conventions
|
|
105
|
+
|
|
106
|
+
<!-- level: all -->
|
|
107
|
+
|
|
108
|
+
Document public dataclass fields in the configured docstring convention: Google style
|
|
109
|
+
uses `Attributes:`, NumPy style an underlined `Attributes` heading.
|
|
110
|
+
|
|
111
|
+
A property provides cheap, unsurprising attribute access with no side effects. Never use it as a
|
|
112
|
+
getter and setter over an internal attribute, expensive work, a rename of a stored attribute,
|
|
113
|
+
or a computation subclasses must override. `@property` beats a hand-written descriptor unless
|
|
114
|
+
the public interface requires descriptor behavior.
|
|
115
|
+
|
|
116
|
+
## Inheritance and decorators
|
|
117
|
+
|
|
118
|
+
Design a class for inheritance or forbid inheritance. Composition shares code, and
|
|
119
|
+
protocols or small abstract base classes define interfaces. Reserve specialization for a
|
|
120
|
+
subclass that adds to the base class and works wherever callers expect the base class. A
|
|
121
|
+
hierarchy never mixes code sharing, interface definition, and specialization, stays shallow,
|
|
122
|
+
and lives physically together. Do not default to the template method pattern: a base class
|
|
123
|
+
that defines control flow and calls subclass hooks takes more work to read than a function with
|
|
124
|
+
explicit delegation.
|
|
125
|
+
|
|
126
|
+
Do not put variants in one class with a type field and optional fields that apply to some values.
|
|
127
|
+
Make invalid states unrepresentable. Behavior varying on more than one axis uses composition.
|
|
128
|
+
Cross-cutting behavior such as tracking, caching, timing, or logging uses a wrapper that
|
|
129
|
+
records completion only after the wrapped operation succeeds. An operation that varies by
|
|
130
|
+
type without a clear owner may use `functools.singledispatch`. A class meant for subclassing
|
|
131
|
+
documents its public API and its subclass API separately when the distinction matters.
|
|
132
|
+
|
|
133
|
+
A decorator removes real repetition or expresses a framework contract. It behaves
|
|
134
|
+
unsurprisingly and preserves function metadata. Because it runs at import time, it never
|
|
135
|
+
depends on files, sockets, databases, or network calls. `classmethod` marks named constructors and
|
|
136
|
+
class-specific routines.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Python Flow
|
|
2
|
+
|
|
3
|
+
## Exceptions and error handling
|
|
4
|
+
|
|
5
|
+
Use explicit validation for external inputs and required preconditions. Avoid exceptions for
|
|
6
|
+
ordinary branching when code can check the value directly.
|
|
7
|
+
|
|
8
|
+
The following module accepts only bounded positive decimal integers and preserves the original
|
|
9
|
+
conversion error.
|
|
10
|
+
|
|
11
|
+
Good:
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
"""Validate limits supplied by a command or environment variable."""
|
|
15
|
+
|
|
16
|
+
MAX_BATCH_SIZE = 1000
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def parse_batch_limit(raw: str) -> int:
|
|
20
|
+
"""Return a positive batch limit within the supported range."""
|
|
21
|
+
try:
|
|
22
|
+
value = int(raw)
|
|
23
|
+
except ValueError as error:
|
|
24
|
+
message = "Batch limit must be a decimal integer."
|
|
25
|
+
raise ValueError(message) from error
|
|
26
|
+
if not 1 <= value <= MAX_BATCH_SIZE:
|
|
27
|
+
message = f"Batch limit must be between 1 and {MAX_BATCH_SIZE}."
|
|
28
|
+
raise ValueError(message)
|
|
29
|
+
return value
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Exception message conventions
|
|
33
|
+
|
|
34
|
+
<!-- level: all -->
|
|
35
|
+
|
|
36
|
+
Keep the exception's public contract stable when changing its message.
|
|
37
|
+
|
|
38
|
+
## Exception contracts
|
|
39
|
+
|
|
40
|
+
A custom exception inherits from `Exception`, describes its condition in the docstring, and
|
|
41
|
+
lives beside the behavior that raises it rather than in a one-declaration file.
|
|
42
|
+
|
|
43
|
+
Do not substitute a built-in exception for an external API's declared error type. Follow a
|
|
44
|
+
library's public error contract when callers depend on it.
|
|
45
|
+
|
|
46
|
+
## Assertions
|
|
47
|
+
|
|
48
|
+
Keep assertions in tests. Use explicit validation for required application checks.
|
|
49
|
+
|
|
50
|
+
## Boolean logic and comparisons
|
|
51
|
+
|
|
52
|
+
Compare singleton values with `is None`, `is not None`, `is True`, or `is False` when the check concerns
|
|
53
|
+
identity. Use truth-value testing only when all falsy values mean the same thing.
|
|
54
|
+
Do not treat an empty collection, zero, and a missing value as interchangeable by default.
|
|
55
|
+
|
|
56
|
+
Use `==` for value equality. Use membership tests for supported containers, and avoid chained
|
|
57
|
+
comparisons whose repeated values have side effects. Keep evaluation order and short-circuit
|
|
58
|
+
behavior intact when simplifying a condition.
|
|
59
|
+
|
|
60
|
+
### Control-flow conventions
|
|
61
|
+
|
|
62
|
+
<!-- level: all -->
|
|
63
|
+
|
|
64
|
+
Prefer direct predicates and early exits when they reduce nesting. Keep multi-step branches
|
|
65
|
+
as statements.
|
|
66
|
+
|
|
67
|
+
## Iteration and collections
|
|
68
|
+
|
|
69
|
+
Iterate containers through their supported interfaces. Do not mutate a collection in a way
|
|
70
|
+
that invalidates the current iteration.
|
|
71
|
+
|
|
72
|
+
Read file objects as iterators instead of loading all lines when the operation can stream them.
|
|
73
|
+
Use `yield from` to delegate an iterator, and `any()` or `all()` for short-circuit predicate checks.
|
|
74
|
+
Keep comprehensions limited to transformations whose order and side effects remain clear.
|
|
75
|
+
|
|
76
|
+
Choose concrete return values or lazy iteration according to the caller's resource and reuse
|
|
77
|
+
requirements, not solely to shorten the implementation.
|
|
78
|
+
|
|
79
|
+
## Strings and output
|
|
80
|
+
|
|
81
|
+
Use f-strings for ordinary value interpolation. Preserve formatting required by an external API,
|
|
82
|
+
and keep logging arguments separate from the message when the logging API formats them lazily.
|
|
83
|
+
Use efficient joining for a sequence of strings instead of repeatedly growing a large string.
|
|
84
|
+
|
|
85
|
+
Formatting neither validates nor escapes values. Use parameterized database queries, argument arrays
|
|
86
|
+
for subprocesses, and the appropriate encoder for HTML, JSON, or another output format.
|
|
87
|
+
|
|
88
|
+
## Files and stateful resources
|
|
89
|
+
|
|
90
|
+
Use context managers for resources with an explicit lifetime. Close files and network resources
|
|
91
|
+
on both success and failure. Declare text encoding when opening a text file. Use binary mode
|
|
92
|
+
for bytes instead of relying on an implicit conversion.
|
|
93
|
+
|
|
94
|
+
A generator can keep a resource open while suspended. Make that lifetime clear to callers,
|
|
95
|
+
especially when they can stop consuming it early. This command reads the path supplied as its
|
|
96
|
+
first argument and closes the input before printing the collected records.
|
|
97
|
+
|
|
98
|
+
Good:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
"""Read normalized, nonempty records from a text file."""
|
|
102
|
+
|
|
103
|
+
import sys
|
|
104
|
+
from pathlib import Path
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def main() -> int:
|
|
108
|
+
"""Read the first argument as a path and print its normalized records."""
|
|
109
|
+
path = Path(sys.argv[1])
|
|
110
|
+
with path.open(encoding="utf-8") as source:
|
|
111
|
+
records = [record for line in source if (record := line.strip())]
|
|
112
|
+
_ = sys.stdout.write("\n".join(records))
|
|
113
|
+
return 0
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
raise SystemExit(main())
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For large inputs, provide an explicit streaming interface whose caller owns the open resource
|
|
121
|
+
or uses a context manager. Do not materialize the full input merely to avoid documenting its
|
|
122
|
+
lifetime.
|
|
123
|
+
|
|
124
|
+
Write to a temporary file in the same folder, validate it, then rename it over the target. Clean up only paths and resources owned by the current operation.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Python Naming
|
|
2
|
+
|
|
3
|
+
Python naming follows Python Enhancement Proposal (PEP) 8, with the rules below.
|
|
4
|
+
|
|
5
|
+
## Command inputs and external names
|
|
6
|
+
|
|
7
|
+
<!-- level: all -->
|
|
8
|
+
|
|
9
|
+
- CLI flags use lowercase kebab case, such as `--account-name` and
|
|
10
|
+
`--output-dir`.
|
|
11
|
+
- Avoid single-letter names except loop counters and math notation.
|
|
12
|
+
- Preserve provider capitalization in external names such as `AWS_REGION` and
|
|
13
|
+
provider resource IDs.
|
|
14
|
+
|
|
15
|
+
## Python modules and imports
|
|
16
|
+
|
|
17
|
+
<!-- level: all -->
|
|
18
|
+
|
|
19
|
+
- Do not create package-level re-export layers only to preserve old names.
|
|
20
|
+
- Keep `__all__` names accurate and ordered according to local lint rules.
|
|
21
|
+
|
|
22
|
+
Import `AccountSettings` under its declared name. An alias such as `Thing`
|
|
23
|
+
hides the contract and provides no collision information.
|
|
24
|
+
|
|
25
|
+
## Python types and dataclasses
|
|
26
|
+
|
|
27
|
+
<!-- level: all -->
|
|
28
|
+
|
|
29
|
+
- Dataclass names describe the domain value they represent.
|
|
30
|
+
- Field names describe the value inside the owning type without repeating the
|
|
31
|
+
type name.
|
|
32
|
+
- Use `Path` variables with names that reveal whether they point to a directory,
|
|
33
|
+
file, report, output, or repository root.
|
|
34
|
+
- Use `*_path` for filesystem paths and `*_dir` only for directories.
|
|
35
|
+
- Use `*_id` only for real identifiers, not arbitrary names, or labels.
|
|
36
|
+
- Use `*_name` for display or provider names.
|
|
37
|
+
- Use `*_key` for dictionary keys and supported variant keys.
|
|
38
|
+
|
|
39
|
+
| Avoid | Prefer | Meaning |
|
|
40
|
+
| ------------------------ | ---------------- | ------------------- |
|
|
41
|
+
| `Invoice.invoice_number` | `Invoice.number` | The invoice number. |
|
|
42
|
+
| `Shipment.shipment_date` | `Shipment.date` | The shipment date. |
|
|
43
|
+
|
|
44
|
+
## Python boundary names
|
|
45
|
+
|
|
46
|
+
<!-- level: all -->
|
|
47
|
+
|
|
48
|
+
- Keep raw provider or CLI names at the boundary.
|
|
49
|
+
- Translate external names into domain names before passing values inward when
|
|
50
|
+
the external name differs from the domain concept.
|
|
51
|
+
- Keep path construction with the code that owns the resource instead of repeating it in
|
|
52
|
+
unrelated operations.
|
|
53
|
+
- Name functions that cross boundaries for the operation they perform.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Python Packaging
|
|
2
|
+
|
|
3
|
+
Deployment installs and lockfiles.
|
|
4
|
+
|
|
5
|
+
## Package installation security
|
|
6
|
+
|
|
7
|
+
Install from the uv lockfile:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv sync --frozen
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
At level `all`, justify pip use with `gspot ignore python/pip-installs`. Update lockfiles only
|
|
14
|
+
when the task includes dependency maintenance.
|
|
15
|
+
|
|
16
|
+
## Import correctness
|
|
17
|
+
|
|
18
|
+
- Respect the import-linter contracts the project declares.
|
|
19
|
+
- Run entrypoints through the configured environment, installation, or module entrypoint.
|
|
20
|
+
- Ensure packaged imports work outside the repository root. Do not depend on an accidental
|
|
21
|
+
current-directory import path.
|
|
22
|
+
|
|
23
|
+
## Packages and architecture
|
|
24
|
+
|
|
25
|
+
<!-- level: all -->
|
|
26
|
+
|
|
27
|
+
- Keep packages shallow and purposeful.
|
|
28
|
+
- Flatten packages that contain only `__init__.py` and one other module.
|
|
29
|
+
- Do not use package `__init__.py` files to hide expensive imports.
|
|
30
|
+
- Keep `__init__.py` files small and import-stable.
|
|
31
|
+
- Barrel `__init__.py` files may contain imports and `__all__`.
|
|
32
|
+
- Keep lower-level packages independent of higher-level workflow packages.
|
|
33
|
+
|
|
34
|
+
## Source layout
|
|
35
|
+
|
|
36
|
+
<!-- level: all -->
|
|
37
|
+
|
|
38
|
+
Keep the layout the project has. A new package puts its code under `src/`:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
pyproject.toml
|
|
42
|
+
src/
|
|
43
|
+
order_service/
|
|
44
|
+
__init__.py
|
|
45
|
+
orders.py
|
|
46
|
+
accounts.py
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Keep helper scripts that nobody imports outside the package import path.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
|
|
3
|
+
## Python scope
|
|
4
|
+
|
|
5
|
+
Write readable Python before clever Python: explicit data flow, clear names, small functions,
|
|
6
|
+
and the formatter's layout.
|
|
7
|
+
Importing a module does no I/O, starts no work, and parses no arguments. Prefer the project's rules to generic style guides, and consistency with the
|
|
8
|
+
surrounding module where a guide allows more than one style.
|
|
9
|
+
|
|
10
|
+
## Runtime and files
|
|
11
|
+
|
|
12
|
+
Use the syntax of the declared Python version, stated once in the runtime pin, and name
|
|
13
|
+
version requirements in examples whose syntax depends on them. Use `.py` for source and
|
|
14
|
+
`.pyi` for stubs.
|
|
15
|
+
|
|
16
|
+
### Comment language
|
|
17
|
+
|
|
18
|
+
<!-- level: all -->
|
|
19
|
+
|
|
20
|
+
Comments and docstrings use the project's declared language, and valid user content
|
|
21
|
+
never restricts names to ASCII.
|
|
22
|
+
|
|
23
|
+
## Environment and configuration
|
|
24
|
+
|
|
25
|
+
An importable module never depends on an active shell, a virtual environment, the working directory, or a
|
|
26
|
+
globally installed package. Imports resolve through project configuration, editable
|
|
27
|
+
installs, `python -m`, or the configured environment. Virtual environments and package caches
|
|
28
|
+
stay out of commits.
|
|
29
|
+
|
|
30
|
+
The configuration owner takes the environment as a mapping, so a test passes its own:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
|
|
34
|
+
def get_config(environ: Mapping[str, str]) -> AppConfig:
|
|
35
|
+
"""Parse an item limit, using 100 when the setting is absent.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
environ: Environment values supplied by the application boundary.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Settings containing a positive item limit.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ValueError: The supplied limit is not an integer greater than zero.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
max_items = int(environ.get("MAX_ITEMS", "100"))
|
|
48
|
+
if max_items < 1:
|
|
49
|
+
message = "MAX_ITEMS must be greater than zero"
|
|
50
|
+
raise ValueError(message)
|
|
51
|
+
return AppConfig(max_items=max_items)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Module structure
|
|
55
|
+
|
|
56
|
+
<!-- level: all -->
|
|
57
|
+
|
|
58
|
+
Every runtime module has a docstring describing its present purpose.
|
|
59
|
+
|
|
60
|
+
## Imports
|
|
61
|
+
|
|
62
|
+
Imports resolve in the declared project environment, never through the script directory on
|
|
63
|
+
`sys.path`, and break cycles by moving shared data or interfaces into a lower-level module.
|
|
64
|
+
|
|
65
|
+
### Import conventions
|
|
66
|
+
|
|
67
|
+
<!-- level: all -->
|
|
68
|
+
|
|
69
|
+
Imports sit at the top after the docstring and future imports, one per line, with typing and
|
|
70
|
+
`collections.abc` symbols imported directly. Use absolute cross-package imports; explicit
|
|
71
|
+
relative imports serve siblings inside a package where surrounding code does that. Never an
|
|
72
|
+
implicit relative import, a wildcard import, an import inside a function, method, or class
|
|
73
|
+
body in runtime code, or a dynamic import through `importlib.import_module`, `__import__`, or
|
|
74
|
+
`builtins.__import__`.
|
|
75
|
+
|
|
76
|
+
Direct symbol imports keep call sites readable; use a module import when
|
|
77
|
+
its prefix identifies the module, as `logging.getLogger(__name__)` does. Give every alias a reason. Two modules can share a final name, a module can collide with a local
|
|
78
|
+
name, or a name can have too many words for the call site. A standard abbreviation such as `np` and a
|
|
79
|
+
disambiguated generic name provide the other reasons.
|
|
80
|
+
|
|
81
|
+
## Docstrings
|
|
82
|
+
|
|
83
|
+
Docstrings match their signatures and behavior at both levels, in the project's declared
|
|
84
|
+
convention. Use the explicit pydoclint style when the settings define one. Otherwise, follow the Google
|
|
85
|
+
or NumPy convention that Ruff carries, and failing that the pydoclint default. The examples
|
|
86
|
+
here follow Google style.
|
|
87
|
+
|
|
88
|
+
### Docstring conventions
|
|
89
|
+
|
|
90
|
+
<!-- level: all -->
|
|
91
|
+
|
|
92
|
+
- A class docstring says what an instance represents, never `Class that...`.
|
|
93
|
+
- A function that mutates an argument says so.
|
|
94
|
+
|
|
95
|
+
## Constants, globals, and mutable state
|
|
96
|
+
|
|
97
|
+
Prefer module constants. Do not expose mutable global state, lazy singletons, or mutable globals as
|
|
98
|
+
public API. Required mutable state stays internal with its design reason documented,
|
|
99
|
+
and no ordinary call mutates a module global as a hidden side effect. Validated configuration
|
|
100
|
+
passes into the client or operation that consumes it, without a renaming wrapper.
|
|
101
|
+
|
|
102
|
+
### Constant naming
|
|
103
|
+
|
|
104
|
+
<!-- level: all -->
|
|
105
|
+
|
|
106
|
+
Name constants `UPPER_SNAKE`, private ones with one leading underscore, and retained module
|
|
107
|
+
state takes a name for its concept.
|
|
108
|
+
|
|
109
|
+
## Main programs
|
|
110
|
+
|
|
111
|
+
An executable module puts its behavior in `main()`, runs it under the `__main__` guard, and
|
|
112
|
+
prefers `raise SystemExit(main())` when `main()` returns an exit code.
|
|
113
|
+
Repository entrypoints run as `python -m package.module`, and shell scripts call Python
|
|
114
|
+
modules rather than inline snippets. A shebang appears only on a file meant to execute
|
|
115
|
+
directly.
|
|
116
|
+
|
|
117
|
+
Never execute untrusted input as Python code, and dynamic execution never replaces input
|
|
118
|
+
validation.
|
|
119
|
+
|
|
120
|
+
## Power features
|
|
121
|
+
|
|
122
|
+
<!-- level: all -->
|
|
123
|
+
|
|
124
|
+
Avoid power features unless the project has a clear local pattern and the feature serves
|
|
125
|
+
necessary. That covers custom metaclasses, bytecode manipulation, dynamic inheritance, object
|
|
126
|
+
reparenting, import hooks, runtime monkeypatching, and reflection-heavy designs. It also
|
|
127
|
+
covers interpreter internals, `__del__` cleanup, manual descriptors, and dynamic code
|
|
128
|
+
generation. A power feature never makes code shorter or hides a dependency cycle; ordinary
|
|
129
|
+
functions, dataclasses, explicit imports, and explicit data structures come first.
|
|
130
|
+
|
|
131
|
+
## Threading
|
|
132
|
+
|
|
133
|
+
Built-in types and variable assignment do not synchronize threads: use `queue.Queue` for thread
|
|
134
|
+
communication and locks or conditions for shared state, which stays small and explicit.
|
|
135
|
+
|
|
136
|
+
Name Python test files `test_<behavior>.py`, grouped by behavior.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Python Typing
|
|
2
|
+
|
|
3
|
+
## Annotations
|
|
4
|
+
|
|
5
|
+
<!-- level: all -->
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
"""Group labeled records while preserving their key types."""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Hashable, Iterable
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def group_records[Key: Hashable](records: Iterable[tuple[Key, str]]) -> dict[Key, list[str]]:
|
|
19
|
+
"""Group record values under their original keys."""
|
|
20
|
+
grouped: dict[Key, list[str]] = {}
|
|
21
|
+
for key, value in records:
|
|
22
|
+
grouped.setdefault(key, []).append(value)
|
|
23
|
+
return grouped
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The type parameter syntax needs Python 3.12; `Key` connects input keys to output keys, and
|
|
27
|
+
`Hashable` states the dictionary-key requirement. A variable annotation appears where the
|
|
28
|
+
compiler cannot infer a clear type, such as an empty collection, not on every
|
|
29
|
+
intermediate value.
|
|
30
|
+
|
|
31
|
+
`typing.Annotated` attaches framework or validation metadata to a real type, real type
|
|
32
|
+
first. The default stays an ordinary parameter default and never repeats in the
|
|
33
|
+
metadata object. Arbitrary string metadata never replaces a domain type or validator. A
|
|
34
|
+
FastAPI query field reads `Annotated[str | None, Query(max_length=50)] = None`.
|
|
35
|
+
|
|
36
|
+
## Any, object, and shapes
|
|
37
|
+
|
|
38
|
+
Use `object` for a value of any type when the function only uses operations
|
|
39
|
+
every object has, such as passing it to `str()`. Also use it for an ignored callback
|
|
40
|
+
result. Reserve `Any` for a type the type system cannot express, a correct type that makes the
|
|
41
|
+
API unreasonable, or a value that intentionally escapes checking. Never use it as a shortcut; a
|
|
42
|
+
protocol, type variable, overload, or small value object models the contract first.
|
|
43
|
+
|
|
44
|
+
Arguments take protocols and abstract collections from `collections.abc`, such as `Iterable`,
|
|
45
|
+
`Sequence`, `Mapping`, or `Callable`, and `object` for any value. Concrete implementations return
|
|
46
|
+
concrete types such as `list`, `dict`, or a dataclass. Protocols and abstract base classes choose
|
|
47
|
+
return types by the promised interface. A union return that forces callers into an immediate
|
|
48
|
+
`isinstance()` branch becomes separate functions, a tagged dataclass, a protocol, or a small
|
|
49
|
+
hierarchy with a common contract.
|
|
50
|
+
|
|
51
|
+
Use `float` for numeric APIs that also accept integers. Use `str` for text and `bytes` for binary data.
|
|
52
|
+
`AnyStr` ties several annotations to one text or binary type.
|
|
53
|
+
|
|
54
|
+
A `Path | None = None` input handles its `None` branch
|
|
55
|
+
before calling path methods, and the default path belongs to the configuration owner. Generic
|
|
56
|
+
types name their parameters, such as `Sequence[int]`. A bare `Sequence` or `dict` exists only when
|
|
57
|
+
the element type intentionally accepts any type and says so with `Any`. A `TypeVar` connects
|
|
58
|
+
input and output types when that relationship matters, as `Key` does above.
|
|
59
|
+
|
|
60
|
+
## Aliases, variables, and forward references
|
|
61
|
+
|
|
62
|
+
A type alias names a complex repeated type in CapWords, with a leading underscore when
|
|
63
|
+
internal. It uses a `type` statement when the Python version supports it and the module
|
|
64
|
+
already uses it, or `TypeAlias` where a syntax change falls outside the task. `MetricMap` for
|
|
65
|
+
`Mapping[str, float]` names an alias; `Path = pathlib.Path` names a class alias and `errno.EEXIST` a
|
|
66
|
+
value, and neither defines a typing interface.
|
|
67
|
+
|
|
68
|
+
### Type variable naming
|
|
69
|
+
|
|
70
|
+
<!-- level: all -->
|
|
71
|
+
|
|
72
|
+
A private unconstrained type variable can use `_T` or `_P`; a public or constrained one has a
|
|
73
|
+
descriptive name such as `Key`, with `_co` and `_contra` for variance, and no public
|
|
74
|
+
single-letter `T` or `P`. `ParamSpec` forwards a callable's parameters, and declare a `Callable` with
|
|
75
|
+
bound to the signature its consumer needs rather than left bare.
|
|
76
|
+
|
|
77
|
+
### Forward references
|
|
78
|
+
|
|
79
|
+
`from __future__ import annotations` handles forward references and stays as the project's
|
|
80
|
+
convention even where newer Python defers evaluation. String annotations exist only where
|
|
81
|
+
the runtime does not provide future annotations or a type-checking-only import needs them. A
|
|
82
|
+
`TYPE_CHECKING` import avoids a runtime import only when the runtime does not need the
|
|
83
|
+
symbol. Validators and frameworks that evaluate annotations need the real import. A type-only circular
|
|
84
|
+
import signals a need to move the shared contract.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
@dataclass(frozen=True)
|
|
88
|
+
class Node:
|
|
89
|
+
"""One node whose parent chain is acyclic.
|
|
90
|
+
|
|
91
|
+
Attributes:
|
|
92
|
+
name: Display name of this node.
|
|
93
|
+
parent: Parent node, or None for a root.
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
name: str
|
|
98
|
+
parent: Node | None = None
|
|
99
|
+
|
|
100
|
+
def list_lineage(self) -> list[str]:
|
|
101
|
+
"""Return names from this node toward the root."""
|
|
102
|
+
names: list[str] = []
|
|
103
|
+
current: Node | None = self
|
|
104
|
+
while current is not None:
|
|
105
|
+
names.append(current.name)
|
|
106
|
+
current = current.parent
|
|
107
|
+
return names
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Protocols
|
|
111
|
+
|
|
112
|
+
Define a structural interface with a narrow `typing.Protocol` near the consumer, with
|
|
113
|
+
only what that consumer needs; implementations satisfy it without importing or subclassing
|
|
114
|
+
it. `@runtime_checkable` exists only for a real `isinstance()` need, and an abstract base
|
|
115
|
+
class serves nominal identity, runtime instantiation checks, or a standard-library contract,
|
|
116
|
+
never shared implementation mixed with interface definition.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
class Reader(Protocol):
|
|
120
|
+
"""A source of one text document."""
|
|
121
|
+
|
|
122
|
+
def read(self) -> str:
|
|
123
|
+
"""Return the document text."""
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def read_unique_records(reader: Reader) -> list[str]:
|
|
128
|
+
"""Read sorted unique records, omitting blank lines."""
|
|
129
|
+
records = {line.strip() for line in reader.read().splitlines()}
|
|
130
|
+
records.discard("")
|
|
131
|
+
return sorted(records)
|
|
132
|
+
```
|