@descryy/adapter-python 0.1.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orm.js","sourceRoot":"","sources":["../src/orm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * The Pydantic extractor for Python — a `DTO` and the shape it declares.
3
+ *
4
+ * **A `DTO` is a transport-boundary claim, and this file is where it is earned.**
5
+ * DEC-043 declined to emit one from the language adapter and was right to: the
6
+ * conformance corpus holds three structurally identical classes, one expected to
7
+ * be a `MODEL` and two `DTO`s, and nothing in Python separates them but their
8
+ * names. A name rule there is the thing golden pattern 12 exists to punish.
9
+ *
10
+ * Pydantic is the evidence that was missing. A class whose base chain reaches
11
+ * `BaseModel` **imported from `pydantic`** has declared itself a validated
12
+ * boundary object; the framework enforces the shape at runtime. That is a
13
+ * declaration, not an inference, and it is the same standard `orm.ts` holds a
14
+ * `MODEL` to — a declarative base and a mapped column.
15
+ *
16
+ * Until now only `adapter-openapi` declared `DTO`, and it needs a published
17
+ * contract. This is the first *language* adapter to earn one.
18
+ *
19
+ * ## What the shape is, measured rather than assumed
20
+ *
21
+ * §8 asked for "the validation rules a route enforces", which invites reading
22
+ * `ge`, `le`, `min_length` and friends. Counted across 515 models on two real
23
+ * repositories **before this file was written**, those total **18** — two of
24
+ * them on dispatch's 449 models. Ranked by that number they are not the payload.
25
+ *
26
+ * What Pydantic writes down at scale is **presence and type**: 1,409 annotated
27
+ * fields on dispatch, 874 of them optional. "This field is required" is a
28
+ * validation rule the route enforces on every request — a POST without it is a
29
+ * 422 — and it is stated in every model rather than in eighteen of them.
30
+ *
31
+ * So constraints are carried **where they are written** and never invented, and
32
+ * the capability is described by what it actually reads. `attrs.fields` is the
33
+ * same key `orm.ts` uses and the same key golden pattern 05 compares.
34
+ *
35
+ * ## What is deliberately not read
36
+ *
37
+ * A `@field_validator` body is arbitrary Python. 76 of them across the two
38
+ * repositories say something true about what the route accepts, and none of it
39
+ * is readable without executing the code. They are disclosed as an
40
+ * unreadable-constraint count on the node rather than silently treated as
41
+ * absent — the same distinction `orm.ts` draws with `fieldsUndecided`, and the
42
+ * one `nullable=FLAG` exists to protect.
43
+ */
44
+ import type { PyAttribute } from "./orm.ts";
45
+ /** Modules whose serializer roots are DRF's own. */
46
+ export declare const DRF_SERIALIZER_MODULES: ReadonlySet<string>;
47
+ export declare const DRF_SERIALIZER_ROOTS: ReadonlySet<string>;
48
+ /** Is this base name, with this origin module, one of DRF's own serializer roots? */
49
+ export declare function isDrfSerializerRoot(baseName: string, originModule: string | undefined): boolean;
50
+ /**
51
+ * The shape a DRF serializer declares.
52
+ *
53
+ * Returns `null` where the class declares no field at all — the same guard
54
+ * `ormShapeOf` uses to keep a project's own serializer base out of the graph
55
+ * as a `DTO` of its own.
56
+ */
57
+ export declare function drfShapeOf(input: {
58
+ readonly attributes: readonly PyAttribute[];
59
+ }): DtoShape | null;
60
+ /** Modules whose `BaseModel` is Pydantic's. A local class of that name is not. */
61
+ export declare const PYDANTIC_MODULES: ReadonlySet<string>;
62
+ /**
63
+ * Bases that are Pydantic's own roots.
64
+ *
65
+ * `BaseSettings` is included and moved package in Pydantic v2 — it lives in
66
+ * `pydantic_settings` now and in `pydantic` before that, which is why the module
67
+ * set carries both. Restricting a rule to one spelling of a library's own move
68
+ * is the mistake DEC-069 recorded when `collections.abc.Callable` was missed.
69
+ */
70
+ export declare const PYDANTIC_ROOTS: ReadonlySet<string>;
71
+ /** Constraint keywords Pydantic enforces, as opposed to documents. */
72
+ export declare const CONSTRAINT_KEYWORDS: ReadonlySet<string>;
73
+ /**
74
+ * Keywords that describe rather than constrain.
75
+ *
76
+ * `description` and `title` are 189 of dispatch's 214 `Field(...)` keywords.
77
+ * Counting them as validation would make the capability look an order of
78
+ * magnitude larger than it is, which is the reporting failure this project
79
+ * exists to avoid.
80
+ */
81
+ export declare const DOCUMENTATION_KEYWORDS: ReadonlySet<string>;
82
+ export interface DtoField {
83
+ readonly name: string;
84
+ /** The annotation as written — never evaluated. */
85
+ readonly type: string | null;
86
+ /**
87
+ * Must a request supply this field at all?
88
+ *
89
+ * **Distinct from `nullable`, and Pydantic is the reason.** `x: Optional[int]`
90
+ * with no default is *required and nullable*: the caller must pass it, and may
91
+ * pass null. `x: int = 5` is *optional and not nullable*. Collapsing the two
92
+ * into one flag loses exactly the rule a request generator needs — it would
93
+ * omit a required field, get a 422, and report it as a finding.
94
+ *
95
+ * This is the validation rule that is written on every one of 1,694 fields
96
+ * across two repositories, as against the 18 that carry a range constraint.
97
+ */
98
+ readonly required: boolean;
99
+ /** May the value be null once supplied? `Optional[X]`, `X | None`. */
100
+ readonly nullable: boolean;
101
+ /** Constraints the source states, where it states any. Never invented. */
102
+ readonly constraints?: Readonly<Record<string, unknown>>;
103
+ }
104
+ export interface DtoShape {
105
+ readonly fields: readonly DtoField[];
106
+ /**
107
+ * Fields whose constraint is written in a form this reader cannot evaluate —
108
+ * `min_length=LIMIT`. Listed rather than dropped, for the same reason
109
+ * `orm.ts` lists an undecided nullability: the source answered and this
110
+ * reader failed to read it, which is not the same as the source being silent.
111
+ */
112
+ readonly undecided: readonly string[];
113
+ /** `@field_validator` and friends: real rules, none of them statically readable. */
114
+ readonly unreadableValidators: number;
115
+ }
116
+ /** Is this base name, with this origin module, one of Pydantic's own roots? */
117
+ export declare function isPydanticRoot(baseName: string, originModule: string | undefined): boolean;
118
+ /**
119
+ * Does this field have a default, so a request may omit it?
120
+ *
121
+ * `x: int = 5` does. `x: int` does not. The case in between is Pydantic's own
122
+ * spelling and it is the one that was read backwards first:
123
+ *
124
+ * hostname: str = Field(..., min_length=3)
125
+ *
126
+ * The `...` is an **explicit "no default"** — Pydantic's documented way of
127
+ * writing a required field that also carries constraints. Syntactically it is an
128
+ * assignment, so the naive reading is "has a default", and `hostname: str`
129
+ * carrying a `min_length` came out **nullable** — a required field reported as
130
+ * optional. That is the wrong direction to be wrong in: a request generator
131
+ * would omit it and call the resulting 422 a finding.
132
+ *
133
+ * Caught by the field's own type contradicting its verdict — `str`, not
134
+ * `str | None`, marked nullable — rather than by any check.
135
+ */
136
+ export declare function hasDefault(annotated: boolean, attribute: PyAttribute | undefined): boolean;
137
+ /** Does an annotation admit absence — `Optional[X]`, `X | None`, `Any`? */
138
+ export declare function annotationIsNullable(annotation: string | null | undefined): boolean;
139
+ /**
140
+ * The shape a Pydantic class declares.
141
+ *
142
+ * `attributes` is every assignment in the class body, as `extract.py` recorded
143
+ * it — the same input `orm.ts` reads, which is why this extractor cost almost
144
+ * nothing to add.
145
+ */
146
+ export declare function dtoShapeOf(input: {
147
+ readonly attributes: readonly PyAttribute[];
148
+ readonly annotatedFields: readonly {
149
+ name: string;
150
+ annotation: string | null;
151
+ hasDefault: boolean;
152
+ }[];
153
+ readonly validatorCount: number;
154
+ }): DtoShape;
155
+ /** Decorators that declare a validation rule no static reader can evaluate. */
156
+ export declare function isValidatorDecorator(text: string | null): boolean;
157
+ //# sourceMappingURL=pydantic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pydantic.d.ts","sourceRoot":"","sources":["../src/pydantic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAe5C,oDAAoD;AACpD,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAGrD,CAAC;AAEH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAKnD,CAAC;AAEH,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAQ/F;AAkBD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAAE,QAAQ,CAAC,UAAU,EAAE,SAAS,WAAW,EAAE,CAAA;CAAE,GAAG,QAAQ,GAAG,IAAI,CA6ClG;AAED,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAO/C,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,MAAM,CAK7C,CAAC;AAEH,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAclD,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAOrD,CAAC;AAEH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,oFAAoF;IACpF,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACvC;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAU1F;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAc1F;AAED,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAGnF;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAChC,QAAQ,CAAC,UAAU,EAAE,SAAS,WAAW,EAAE,CAAC;IAC5C,QAAQ,CAAC,eAAe,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IACtG,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC,GAAG,QAAQ,CAgDX;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAGjE"}
@@ -0,0 +1,318 @@
1
+ /**
2
+ * The Pydantic extractor for Python — a `DTO` and the shape it declares.
3
+ *
4
+ * **A `DTO` is a transport-boundary claim, and this file is where it is earned.**
5
+ * DEC-043 declined to emit one from the language adapter and was right to: the
6
+ * conformance corpus holds three structurally identical classes, one expected to
7
+ * be a `MODEL` and two `DTO`s, and nothing in Python separates them but their
8
+ * names. A name rule there is the thing golden pattern 12 exists to punish.
9
+ *
10
+ * Pydantic is the evidence that was missing. A class whose base chain reaches
11
+ * `BaseModel` **imported from `pydantic`** has declared itself a validated
12
+ * boundary object; the framework enforces the shape at runtime. That is a
13
+ * declaration, not an inference, and it is the same standard `orm.ts` holds a
14
+ * `MODEL` to — a declarative base and a mapped column.
15
+ *
16
+ * Until now only `adapter-openapi` declared `DTO`, and it needs a published
17
+ * contract. This is the first *language* adapter to earn one.
18
+ *
19
+ * ## What the shape is, measured rather than assumed
20
+ *
21
+ * §8 asked for "the validation rules a route enforces", which invites reading
22
+ * `ge`, `le`, `min_length` and friends. Counted across 515 models on two real
23
+ * repositories **before this file was written**, those total **18** — two of
24
+ * them on dispatch's 449 models. Ranked by that number they are not the payload.
25
+ *
26
+ * What Pydantic writes down at scale is **presence and type**: 1,409 annotated
27
+ * fields on dispatch, 874 of them optional. "This field is required" is a
28
+ * validation rule the route enforces on every request — a POST without it is a
29
+ * 422 — and it is stated in every model rather than in eighteen of them.
30
+ *
31
+ * So constraints are carried **where they are written** and never invented, and
32
+ * the capability is described by what it actually reads. `attrs.fields` is the
33
+ * same key `orm.ts` uses and the same key golden pattern 05 compares.
34
+ *
35
+ * ## What is deliberately not read
36
+ *
37
+ * A `@field_validator` body is arbitrary Python. 76 of them across the two
38
+ * repositories say something true about what the route accepts, and none of it
39
+ * is readable without executing the code. They are disclosed as an
40
+ * unreadable-constraint count on the node rather than silently treated as
41
+ * absent — the same distinction `orm.ts` draws with `fieldsUndecided`, and the
42
+ * one `nullable=FLAG` exists to protect.
43
+ */
44
+ // ---------------------------------------------------------------------------
45
+ // DRF's `serializers.Serializer`/`ModelSerializer` — a second, structurally
46
+ // different producer of the same `DtoShape` this file already earns for
47
+ // Pydantic.
48
+ //
49
+ // **Not an annotation shape.** Pydantic fields are `AnnAssign` —
50
+ // `name: str` — which is why `dtoShapeOf` reads `annotatedFields`. DRF
51
+ // fields are field-CONSTRUCTOR assignments — `email =
52
+ // serializers.EmailField()` — the same shape `orm.ts` already reads for
53
+ // Django's own ORM columns, and for the same reason: neither framework
54
+ // spells a field as a type annotation.
55
+ // ---------------------------------------------------------------------------
56
+ /** Modules whose serializer roots are DRF's own. */
57
+ export const DRF_SERIALIZER_MODULES = new Set([
58
+ "rest_framework.serializers",
59
+ "rest_framework.fields",
60
+ ]);
61
+ export const DRF_SERIALIZER_ROOTS = new Set([
62
+ "Serializer",
63
+ "ModelSerializer",
64
+ "HyperlinkedModelSerializer",
65
+ "ListSerializer",
66
+ ]);
67
+ /** Is this base name, with this origin module, one of DRF's own serializer roots? */
68
+ export function isDrfSerializerRoot(baseName, originModule) {
69
+ const leaf = baseName.includes(".") ? baseName.slice(baseName.lastIndexOf(".") + 1) : baseName;
70
+ const bare = leaf.split("[")[0];
71
+ if (!DRF_SERIALIZER_ROOTS.has(bare))
72
+ return false;
73
+ if (originModule === undefined)
74
+ return false;
75
+ return [...DRF_SERIALIZER_MODULES].some((module) => originModule === module || originModule.startsWith(`${module}.`));
76
+ }
77
+ /**
78
+ * Is this attribute a DRF field, shape-based like Django's own ORM fields —
79
+ * every field class DRF ships ends in `Field`, and third-party field classes
80
+ * follow the same convention. A closed list would miss every custom field a
81
+ * project writes, the same gap `isColumnCall` avoids for Django's ORM.
82
+ */
83
+ function isDrfFieldCall(attribute) {
84
+ if (attribute.callee === null)
85
+ return false;
86
+ const leaf = attribute.callee.includes(".")
87
+ ? attribute.callee.slice(attribute.callee.lastIndexOf(".") + 1)
88
+ : attribute.callee;
89
+ return leaf.endsWith("Field") && leaf.length > "Field".length;
90
+ }
91
+ const DRF_NOT_A_FIELD = new Set(["Meta"]);
92
+ /**
93
+ * The shape a DRF serializer declares.
94
+ *
95
+ * Returns `null` where the class declares no field at all — the same guard
96
+ * `ormShapeOf` uses to keep a project's own serializer base out of the graph
97
+ * as a `DTO` of its own.
98
+ */
99
+ export function drfShapeOf(input) {
100
+ const fields = [];
101
+ const undecided = [];
102
+ for (const attribute of input.attributes) {
103
+ if (DRF_NOT_A_FIELD.has(attribute.name) || attribute.name.startsWith("__"))
104
+ continue;
105
+ if (!isDrfFieldCall(attribute))
106
+ continue;
107
+ const constraints = {};
108
+ let sawUnreadable = false;
109
+ for (const [key, value] of Object.entries(attribute.keywords)) {
110
+ if (CONSTRAINT_KEYWORDS.has(key))
111
+ constraints[key] = value;
112
+ }
113
+ for (const key of attribute.opaqueKeywords) {
114
+ if (CONSTRAINT_KEYWORDS.has(key))
115
+ sawUnreadable = true;
116
+ }
117
+ if (sawUnreadable)
118
+ undecided.push(attribute.name);
119
+ // DRF's own default: a field is required unless the source says
120
+ // otherwise, either with `required=False` directly or by supplying a
121
+ // `default=` — the mirror image of Pydantic's "has a default" trigger,
122
+ // read the way DRF's own field constructor documents it.
123
+ let required = true;
124
+ if (!attribute.opaqueKeywords.includes("required") && typeof attribute.keywords["required"] === "boolean") {
125
+ required = attribute.keywords["required"];
126
+ }
127
+ else if (attribute.keywords["default"] !== undefined || attribute.opaqueKeywords.includes("default")) {
128
+ required = false;
129
+ }
130
+ fields.push({
131
+ name: attribute.name,
132
+ type: attribute.callee,
133
+ required,
134
+ nullable: attribute.keywords["allow_null"] === true,
135
+ ...(Object.keys(constraints).length > 0 ? { constraints } : {}),
136
+ });
137
+ }
138
+ if (fields.length === 0 && undecided.length === 0)
139
+ return null;
140
+ return {
141
+ fields: fields.sort((a, b) => a.name.localeCompare(b.name)),
142
+ undecided: undecided.sort(),
143
+ unreadableValidators: 0,
144
+ };
145
+ }
146
+ /** Modules whose `BaseModel` is Pydantic's. A local class of that name is not. */
147
+ export const PYDANTIC_MODULES = new Set([
148
+ "pydantic",
149
+ "pydantic.main",
150
+ "pydantic.v1",
151
+ "pydantic.v1.main",
152
+ "pydantic.generics",
153
+ "pydantic_settings",
154
+ ]);
155
+ /**
156
+ * Bases that are Pydantic's own roots.
157
+ *
158
+ * `BaseSettings` is included and moved package in Pydantic v2 — it lives in
159
+ * `pydantic_settings` now and in `pydantic` before that, which is why the module
160
+ * set carries both. Restricting a rule to one spelling of a library's own move
161
+ * is the mistake DEC-069 recorded when `collections.abc.Callable` was missed.
162
+ */
163
+ export const PYDANTIC_ROOTS = new Set([
164
+ "BaseModel",
165
+ "BaseSettings",
166
+ "RootModel",
167
+ "GenericModel",
168
+ ]);
169
+ /** Constraint keywords Pydantic enforces, as opposed to documents. */
170
+ export const CONSTRAINT_KEYWORDS = new Set([
171
+ "gt",
172
+ "ge",
173
+ "lt",
174
+ "le",
175
+ "min_length",
176
+ "max_length",
177
+ "min_items",
178
+ "max_items",
179
+ "multiple_of",
180
+ "pattern",
181
+ "regex",
182
+ "max_digits",
183
+ "decimal_places",
184
+ ]);
185
+ /**
186
+ * Keywords that describe rather than constrain.
187
+ *
188
+ * `description` and `title` are 189 of dispatch's 214 `Field(...)` keywords.
189
+ * Counting them as validation would make the capability look an order of
190
+ * magnitude larger than it is, which is the reporting failure this project
191
+ * exists to avoid.
192
+ */
193
+ export const DOCUMENTATION_KEYWORDS = new Set([
194
+ "description",
195
+ "title",
196
+ "examples",
197
+ "example",
198
+ "deprecated",
199
+ "json_schema_extra",
200
+ ]);
201
+ /** Is this base name, with this origin module, one of Pydantic's own roots? */
202
+ export function isPydanticRoot(baseName, originModule) {
203
+ const leaf = baseName.includes(".") ? baseName.slice(baseName.lastIndexOf(".") + 1) : baseName;
204
+ const bare = leaf.split("[")[0];
205
+ if (!PYDANTIC_ROOTS.has(bare))
206
+ return false;
207
+ // The module check is the whole precision control. A repository's own
208
+ // `BaseModel` is not Pydantic's, and matching the bare name would admit it.
209
+ if (originModule === undefined)
210
+ return false;
211
+ return [...PYDANTIC_MODULES].some((module) => originModule === module || originModule.startsWith(`${module}.`));
212
+ }
213
+ /**
214
+ * Does this field have a default, so a request may omit it?
215
+ *
216
+ * `x: int = 5` does. `x: int` does not. The case in between is Pydantic's own
217
+ * spelling and it is the one that was read backwards first:
218
+ *
219
+ * hostname: str = Field(..., min_length=3)
220
+ *
221
+ * The `...` is an **explicit "no default"** — Pydantic's documented way of
222
+ * writing a required field that also carries constraints. Syntactically it is an
223
+ * assignment, so the naive reading is "has a default", and `hostname: str`
224
+ * carrying a `min_length` came out **nullable** — a required field reported as
225
+ * optional. That is the wrong direction to be wrong in: a request generator
226
+ * would omit it and call the resulting 422 a finding.
227
+ *
228
+ * Caught by the field's own type contradicting its verdict — `str`, not
229
+ * `str | None`, marked nullable — rather than by any check.
230
+ */
231
+ export function hasDefault(annotated, attribute) {
232
+ if (!annotated)
233
+ return false;
234
+ if (attribute === undefined || attribute.callee === null)
235
+ return true;
236
+ const callee = attribute.callee.slice(attribute.callee.lastIndexOf(".") + 1);
237
+ if (callee !== "Field")
238
+ return true; // some other call — its result is the default
239
+ if (attribute.keywords["default"] !== undefined)
240
+ return true;
241
+ if (attribute.opaqueKeywords.includes("default"))
242
+ return true;
243
+ if (attribute.keywords["default_factory"] !== undefined)
244
+ return true;
245
+ if (attribute.opaqueKeywords.includes("default_factory"))
246
+ return true;
247
+ const positional = attribute.args[0];
248
+ if (positional === undefined)
249
+ return false; // `Field(min_length=3)` — required
250
+ return positional.trim() !== "..."; // `Field(...)` — explicitly required
251
+ }
252
+ /** Does an annotation admit absence — `Optional[X]`, `X | None`, `Any`? */
253
+ export function annotationIsNullable(annotation) {
254
+ if (annotation === null || annotation === undefined)
255
+ return false;
256
+ return /\bOptional\s*\[/.test(annotation) || /\bNone\b/.test(annotation);
257
+ }
258
+ /**
259
+ * The shape a Pydantic class declares.
260
+ *
261
+ * `attributes` is every assignment in the class body, as `extract.py` recorded
262
+ * it — the same input `orm.ts` reads, which is why this extractor cost almost
263
+ * nothing to add.
264
+ */
265
+ export function dtoShapeOf(input) {
266
+ const byName = new Map();
267
+ for (const attribute of input.attributes)
268
+ byName.set(attribute.name, attribute);
269
+ const fields = [];
270
+ const undecided = [];
271
+ for (const declared of input.annotatedFields) {
272
+ // `model_config = ConfigDict(...)` is configuration, not a field, and it is
273
+ // the one class-body assignment Pydantic treats specially.
274
+ if (declared.name === "model_config" || declared.name.startsWith("__"))
275
+ continue;
276
+ const attribute = byName.get(declared.name);
277
+ const constraints = {};
278
+ let sawUnreadable = false;
279
+ if (attribute !== undefined && attribute.callee !== null) {
280
+ const callee = attribute.callee.slice(attribute.callee.lastIndexOf(".") + 1);
281
+ if (callee === "Field") {
282
+ for (const [key, value] of Object.entries(attribute.keywords)) {
283
+ if (CONSTRAINT_KEYWORDS.has(key))
284
+ constraints[key] = value;
285
+ }
286
+ for (const key of attribute.opaqueKeywords) {
287
+ // A constraint written in a form this reader cannot evaluate is the
288
+ // `nullable=FLAG` case: the source stated a rule and letting silence
289
+ // stand in for it would report the opposite of the source as fact.
290
+ if (CONSTRAINT_KEYWORDS.has(key))
291
+ sawUnreadable = true;
292
+ }
293
+ }
294
+ }
295
+ if (sawUnreadable)
296
+ undecided.push(declared.name);
297
+ const defaulted = hasDefault(declared.hasDefault, attribute);
298
+ fields.push({
299
+ name: declared.name,
300
+ type: declared.annotation,
301
+ required: !defaulted,
302
+ nullable: annotationIsNullable(declared.annotation),
303
+ ...(Object.keys(constraints).length > 0 ? { constraints } : {}),
304
+ });
305
+ }
306
+ return {
307
+ fields: fields.sort((a, b) => a.name.localeCompare(b.name)),
308
+ undecided: undecided.sort(),
309
+ unreadableValidators: input.validatorCount,
310
+ };
311
+ }
312
+ /** Decorators that declare a validation rule no static reader can evaluate. */
313
+ export function isValidatorDecorator(text) {
314
+ if (text === null)
315
+ return false;
316
+ return /\b(field_validator|model_validator|root_validator|validator)\b/.test(text);
317
+ }
318
+ //# sourceMappingURL=pydantic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pydantic.js","sourceRoot":"","sources":["../src/pydantic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The project's own test-collection rules, read rather than assumed.
3
+ *
4
+ * ## Why this exists
5
+ *
6
+ * `test_*.py`, `Test*`, `test_*` are pytest's **defaults**, and a project can
7
+ * change all three. Hard-coding them is the same mistake as matching a class
8
+ * name to decide it is a test suite: approximating a framework's rule when the
9
+ * framework will state it. The standing rule is *if the framework will tell you,
10
+ * never infer it* — and here it will, in a file already sitting in the
11
+ * repository.
12
+ *
13
+ * Not hypothetical: `threeclouds-ai` declares `[tool.pytest.ini_options]` with
14
+ * `testpaths`, and nothing read it.
15
+ *
16
+ * ## What is read, and what is deliberately not
17
+ *
18
+ * Only the three keys that decide **what is a test**: `python_files`,
19
+ * `python_classes`, `python_functions`. `testpaths` is *not* read: it narrows
20
+ * where pytest looks, and narrowing what Descry analyses on the strength of a
21
+ * test-runner setting would silently shrink the graph for a reason that has
22
+ * nothing to do with the graph.
23
+ *
24
+ * ## Precedence
25
+ *
26
+ * pytest's own order for choosing its config file, and only the first one that
27
+ * carries a section counts — a `pytest.ini` wins outright even when empty of
28
+ * these keys, exactly as it does for pytest.
29
+ *
30
+ * ## The parsing, and its honest limit
31
+ *
32
+ * Zero runtime dependencies, so there is no TOML parser here. This reads one
33
+ * known section and three known keys whose values are a string or an array of
34
+ * strings, which is the whole of what these keys can be. A key it cannot parse
35
+ * leaves the default in place — the failure direction is "behaves as before",
36
+ * never "collects something strange".
37
+ */
38
+ export interface PytestConfig {
39
+ /** Globs a test file must match. pytest's default is `test_*.py`, `*_test.py`. */
40
+ readonly filePatterns: readonly string[];
41
+ /** Prefixes a collected class must carry. Default `Test`. */
42
+ readonly classPrefixes: readonly string[];
43
+ /** Prefixes a collected function must carry. Default `test`. */
44
+ readonly functionPrefixes: readonly string[];
45
+ /** Where the values came from, for the capability report. */
46
+ readonly source: string;
47
+ }
48
+ export declare const PYTEST_DEFAULTS: PytestConfig;
49
+ /** `test_*.py` -> a regex anchored at both ends. Only `*` and `?` are glob here. */
50
+ export declare function globToRegExp(glob: string): RegExp;
51
+ /**
52
+ * Read the collection rules a repository declares, or pytest's defaults.
53
+ *
54
+ * Order is pytest's: `pytest.ini`, then `pyproject.toml`, then `tox.ini`, then
55
+ * `setup.cfg`. The first file carrying the section wins outright, whether or not
56
+ * it sets any of the three keys — narrowing to "the first file that happens to
57
+ * set the key I want" would silently blend two projects' configurations.
58
+ */
59
+ export declare function readPytestConfig(absoluteRoot: string): PytestConfig;
60
+ //# sourceMappingURL=pytest-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pytest-config.d.ts","sourceRoot":"","sources":["../src/pytest-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAKH,MAAM,WAAW,YAAY;IAC3B,kFAAkF;IAClF,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,6DAA6D;IAC7D,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,gEAAgE;IAChE,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,eAAe,EAAE,YAK7B,CAAC;AAEF,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGjD;AAiDD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY,CA0CnE"}