@suss/adapter-python 0.28.0 → 0.30.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.
- package/dist/index.cjs +121 -2
- package/dist/index.d.cts +49 -1
- package/dist/index.d.ts +49 -1
- package/dist/index.js +121 -2
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -1887,6 +1887,79 @@ function assignedValue(right) {
|
|
|
1887
1887
|
const first = fields(right, "subscript")[0];
|
|
1888
1888
|
return outer?.text === "Annotated" && first !== void 0 ? first : right;
|
|
1889
1889
|
}
|
|
1890
|
+
function sideBesidesNone(node) {
|
|
1891
|
+
const left = field(node, "left");
|
|
1892
|
+
const right = field(node, "right");
|
|
1893
|
+
if (right?.type === "none") {
|
|
1894
|
+
return left;
|
|
1895
|
+
}
|
|
1896
|
+
if (left?.type === "none") {
|
|
1897
|
+
return right;
|
|
1898
|
+
}
|
|
1899
|
+
return null;
|
|
1900
|
+
}
|
|
1901
|
+
function annotatedClass(node) {
|
|
1902
|
+
if (node === void 0) {
|
|
1903
|
+
return null;
|
|
1904
|
+
}
|
|
1905
|
+
if (node.type === "identifier" || node.type === "string") {
|
|
1906
|
+
return node;
|
|
1907
|
+
}
|
|
1908
|
+
if (node.type === "type") {
|
|
1909
|
+
return annotatedClass(children(node)[0]);
|
|
1910
|
+
}
|
|
1911
|
+
if (node.type === "generic_type") {
|
|
1912
|
+
const parameter = children(node).find(
|
|
1913
|
+
(child) => child.type === "type_parameter"
|
|
1914
|
+
);
|
|
1915
|
+
return annotatedClass(
|
|
1916
|
+
parameter === void 0 ? void 0 : children(parameter)[0]
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
if (node.type === "binary_operator") {
|
|
1920
|
+
return annotatedClass(sideBesidesNone(node) ?? void 0);
|
|
1921
|
+
}
|
|
1922
|
+
return null;
|
|
1923
|
+
}
|
|
1924
|
+
function classReferenceKey(emitter, node) {
|
|
1925
|
+
if (node.type === "identifier") {
|
|
1926
|
+
return valueKey(emitter, node);
|
|
1927
|
+
}
|
|
1928
|
+
const written = stringLiteralValue(node);
|
|
1929
|
+
if (written === null) {
|
|
1930
|
+
return null;
|
|
1931
|
+
}
|
|
1932
|
+
return nameKey(emitter.filePath, emitter.enclosing, written);
|
|
1933
|
+
}
|
|
1934
|
+
function firstPositional(call) {
|
|
1935
|
+
const args = field(call, "arguments");
|
|
1936
|
+
for (const argument of args === null ? [] : children(args)) {
|
|
1937
|
+
if (argument.type !== "keyword_argument") {
|
|
1938
|
+
return argument;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
return null;
|
|
1942
|
+
}
|
|
1943
|
+
function fieldClassKey(emitter, assignment2, call) {
|
|
1944
|
+
const annotation = field(assignment2, "type");
|
|
1945
|
+
const annotated = annotation === null ? null : annotatedClass(annotation);
|
|
1946
|
+
if (annotated !== null) {
|
|
1947
|
+
return classReferenceKey(emitter, annotated);
|
|
1948
|
+
}
|
|
1949
|
+
const first = firstPositional(call);
|
|
1950
|
+
return first === null ? null : classReferenceKey(emitter, first);
|
|
1951
|
+
}
|
|
1952
|
+
function emitFieldCall(emitter, classKey, name, assignment2, value) {
|
|
1953
|
+
if (value.type !== "call") {
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
const callee = field(value, "function");
|
|
1957
|
+
const target = callee === null ? null : fieldClassKey(emitter, assignment2, value);
|
|
1958
|
+
if (callee === null || target === null) {
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
add(emitter, "fieldCall", classKey, name, valueKey(emitter, callee), target);
|
|
1962
|
+
}
|
|
1890
1963
|
function declaredBy(statement) {
|
|
1891
1964
|
if (statement.type === "decorated_definition") {
|
|
1892
1965
|
return field(statement, "definition") ?? statement;
|
|
@@ -1905,12 +1978,19 @@ function declaredMemberKey(emitter, member, classKey) {
|
|
|
1905
1978
|
}
|
|
1906
1979
|
return null;
|
|
1907
1980
|
}
|
|
1981
|
+
function writtenBaseName(base) {
|
|
1982
|
+
return base.type === "identifier" || base.type === "attribute" ? base.text : null;
|
|
1983
|
+
}
|
|
1908
1984
|
function emitClassFacts(emitter, cls) {
|
|
1909
1985
|
const classKey = nodeId(emitter.filePath, cls);
|
|
1910
1986
|
add(emitter, "objectValue", classKey);
|
|
1911
1987
|
const bases = field(cls, "superclasses");
|
|
1912
1988
|
for (const base of bases === null ? [] : children(bases)) {
|
|
1913
1989
|
add(emitter, "extends", classKey, valueKey(emitter, base));
|
|
1990
|
+
const written = writtenBaseName(base);
|
|
1991
|
+
if (written !== null) {
|
|
1992
|
+
add(emitter, "extendsNamed", classKey, written);
|
|
1993
|
+
}
|
|
1914
1994
|
}
|
|
1915
1995
|
const body = field(cls, "body");
|
|
1916
1996
|
for (const statement of body === null ? [] : children(body)) {
|
|
@@ -1926,6 +2006,7 @@ function emitClassFacts(emitter, cls) {
|
|
|
1926
2006
|
left.text,
|
|
1927
2007
|
valueKey(emitter, right)
|
|
1928
2008
|
);
|
|
2009
|
+
emitFieldCall(emitter, classKey, left.text, member, right);
|
|
1929
2010
|
}
|
|
1930
2011
|
continue;
|
|
1931
2012
|
}
|
|
@@ -5356,7 +5437,12 @@ function matchedChains(calls, options) {
|
|
|
5356
5437
|
}
|
|
5357
5438
|
const pattern = resolvedMethodPattern(options, callee);
|
|
5358
5439
|
if (pattern !== void 0) {
|
|
5359
|
-
|
|
5440
|
+
const returnedOnly = chain.last === chain.root;
|
|
5441
|
+
matched.push({
|
|
5442
|
+
chain,
|
|
5443
|
+
pattern,
|
|
5444
|
+
operation: returnedOnly ? null : chain.operation
|
|
5445
|
+
});
|
|
5360
5446
|
}
|
|
5361
5447
|
}
|
|
5362
5448
|
return matched;
|
|
@@ -7646,6 +7732,37 @@ function reportUnresolvedProjectModules(options, db) {
|
|
|
7646
7732
|
}
|
|
7647
7733
|
}
|
|
7648
7734
|
}
|
|
7735
|
+
function emitModelQueryFacts(db, packs) {
|
|
7736
|
+
for (const pack of packs) {
|
|
7737
|
+
for (const model of pack.models ?? []) {
|
|
7738
|
+
for (const base of model.baseNames) {
|
|
7739
|
+
for (const method of model.givesBack) {
|
|
7740
|
+
db.add("givesBackOne", [base, method]);
|
|
7741
|
+
}
|
|
7742
|
+
for (const entry of model.entryMethods) {
|
|
7743
|
+
db.add("givesBackOneOfArgument", [
|
|
7744
|
+
base,
|
|
7745
|
+
entry.method,
|
|
7746
|
+
String(entry.argument)
|
|
7747
|
+
]);
|
|
7748
|
+
}
|
|
7749
|
+
}
|
|
7750
|
+
for (const entry of model.entryFunctions) {
|
|
7751
|
+
db.add("givesBackOneOfImport", [
|
|
7752
|
+
entry.module,
|
|
7753
|
+
entry.name,
|
|
7754
|
+
String(entry.argument)
|
|
7755
|
+
]);
|
|
7756
|
+
}
|
|
7757
|
+
for (const relationship of model.relationships ?? []) {
|
|
7758
|
+
db.add("associationConstructor", [
|
|
7759
|
+
relationship.module,
|
|
7760
|
+
relationship.name
|
|
7761
|
+
]);
|
|
7762
|
+
}
|
|
7763
|
+
}
|
|
7764
|
+
}
|
|
7765
|
+
}
|
|
7649
7766
|
async function extractPythonProject(options) {
|
|
7650
7767
|
const timer = options.onTiming !== void 0 ? (0, import_extractor10.createTimer)() : (0, import_extractor10.noopTimer)();
|
|
7651
7768
|
const cacheDir = adapterStamp.declineWhenRunFromSource(
|
|
@@ -7693,8 +7810,9 @@ async function extractPythonProject(options) {
|
|
|
7693
7810
|
);
|
|
7694
7811
|
const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
|
|
7695
7812
|
const rawSqlPatterns = options.packs.flatMap((pack) => pack.rawSql ?? []);
|
|
7813
|
+
const modelQueries = options.packs.flatMap((pack) => pack.models ?? []);
|
|
7696
7814
|
const discovers = options.packs.some((pack) => pack.discovery.length > 0);
|
|
7697
|
-
const needsValues = discovers || mountsRouters || storagePatterns.length > 0;
|
|
7815
|
+
const needsValues = discovers || mountsRouters || storagePatterns.length > 0 || modelQueries.length > 0;
|
|
7698
7816
|
const definitions = /* @__PURE__ */ new Map();
|
|
7699
7817
|
timer.time("discover", () => {
|
|
7700
7818
|
for (const { file, root, module: moduleBinding } of bound) {
|
|
@@ -7707,6 +7825,7 @@ async function extractPythonProject(options) {
|
|
|
7707
7825
|
if (needsValues) {
|
|
7708
7826
|
bindEvaluator(db, { files: bound, definitions });
|
|
7709
7827
|
}
|
|
7828
|
+
emitModelQueryFacts(db, options.packs);
|
|
7710
7829
|
});
|
|
7711
7830
|
reportUnresolvedProjectModules(options, db);
|
|
7712
7831
|
const couldMatch = timer.time(
|
package/dist/index.d.cts
CHANGED
|
@@ -109,6 +109,8 @@ interface PythonPack {
|
|
|
109
109
|
clients?: PyClientCall[];
|
|
110
110
|
/** What the library's own database queries look like. The README says how one is matched. */
|
|
111
111
|
storage?: StoragePattern[];
|
|
112
|
+
/** Which of the library's calls give back one of a model class. The README says what a chain of them composes into. */
|
|
113
|
+
models?: PyModelQueries[];
|
|
112
114
|
/** How the library lets a project hand the database SQL it wrote itself. */
|
|
113
115
|
rawSql?: RawSqlPattern[];
|
|
114
116
|
}
|
|
@@ -148,6 +150,52 @@ interface StoragePattern {
|
|
|
148
150
|
/** Which database the library is talking to, for the boundary binding. */
|
|
149
151
|
storageSystem: "postgresql" | "mysql" | "sqlite";
|
|
150
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* What a library gives back when a call is passed one of a project's
|
|
155
|
+
* model classes, so a method read off the result runs the one that
|
|
156
|
+
* class declares. SQLAlchemy and SQLModel take the class as an argument,
|
|
157
|
+
* `session.get(User, id)` and `select(User)`, rather than as the
|
|
158
|
+
* receiver a Rails finder is called on.
|
|
159
|
+
*/
|
|
160
|
+
interface PyModelQueries {
|
|
161
|
+
/**
|
|
162
|
+
* The names a model's ancestry arrives at: a base class the library
|
|
163
|
+
* exports, `DeclarativeBase`, or the function that builds one,
|
|
164
|
+
* `declarative_base`.
|
|
165
|
+
*/
|
|
166
|
+
baseNames: string[];
|
|
167
|
+
/** Methods whose result is the model again: one row, or a query a later read narrows to one. */
|
|
168
|
+
givesBack: string[];
|
|
169
|
+
/** Methods that take the model class and give back one of it, with the position it is written at. */
|
|
170
|
+
entryMethods: PyModelEntryMethod[];
|
|
171
|
+
/** Functions the library exports that do the same, called on their own rather than read off a session. */
|
|
172
|
+
entryFunctions: PyModelEntryFunction[];
|
|
173
|
+
/** The library's own constructors for a field that reaches another model. */
|
|
174
|
+
relationships?: PyRelationshipConstructor[];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* `items: list[Item] = Relationship(...)`: the callable a model's field
|
|
178
|
+
* is given to say it reaches another model, and where it comes from. A
|
|
179
|
+
* project function spelled the same way is somebody else's, so the
|
|
180
|
+
* import is what settles a match.
|
|
181
|
+
*/
|
|
182
|
+
interface PyRelationshipConstructor {
|
|
183
|
+
/** The module it is imported from, `sqlmodel` or `sqlalchemy.orm`. */
|
|
184
|
+
module: string;
|
|
185
|
+
/** Its name, `Relationship` or `relationship`. */
|
|
186
|
+
name: string;
|
|
187
|
+
}
|
|
188
|
+
/** `session.get(User, id)`: the method, and where the class is written. */
|
|
189
|
+
interface PyModelEntryMethod {
|
|
190
|
+
method: string;
|
|
191
|
+
argument: number;
|
|
192
|
+
}
|
|
193
|
+
/** `select(User)`: the function, the module it comes from, and where the class is written. */
|
|
194
|
+
interface PyModelEntryFunction {
|
|
195
|
+
module: string;
|
|
196
|
+
name: string;
|
|
197
|
+
argument: number;
|
|
198
|
+
}
|
|
151
199
|
/**
|
|
152
200
|
* The function a library gives a project for handing the database a
|
|
153
201
|
* statement written as SQL, and where it comes from. SQLAlchemy exports
|
|
@@ -1042,4 +1090,4 @@ declare function pythonImportEvidence(options: PythonImportEvidenceOptions): Pro
|
|
|
1042
1090
|
*/
|
|
1043
1091
|
declare const ADAPTER_VERSION = "0.1.0";
|
|
1044
1092
|
|
|
1045
|
-
export { ADAPTER_VERSION, type AnnotationContext, type Binding, type BoundPythonFile, type DecoratedClassRoute, type DecoratedFunctionRoute, type DecoratorArg, type DecoratorClassification, type DiscoveryOptions, type ExtractPythonOptions, type ExtractPythonResult, type ModuleBinding, type ModuleResolution, type ModuleResolverOptions, type MountObjectCarrier, type MountObjectPrefix, type MountPrefixEffect, type NoValuePrefix, type PathRepeatedSlashes, type PrefixTrailingSlash, type PyStatusCall, type PythonDiscoveryPattern, type PythonImportEvidence, type PythonImportEvidenceOptions, type PythonImportSite, type PythonPack, type PythonValueHandle, PythonWhySession, type PythonWhySessionOptions, type RawSqlPattern, type RelativeModuleSpec, type RouteConventions, type RoutePrefixResolution, type RouterComposition, type RouterIndex, type Scope, type ScopeKind, type StoragePattern, annotationToShape, bindModule, buildRouterIndex, classifyDecorator, containedValues, discoverUnits, emitEntryFact, emitModuleImportFacts, emitValueFacts, extractPythonProject, findPythonFiles, nameKeyIn, nodeId, objectReturnedBy, parsePython, parsePythonSync, preloadPythonGrammar, pythonImportEvidence, readKey, resolveAbsoluteModule, resolveCalls, resolveModule, resolveName, resolveRelativeModule, shapeFromName, unitKey };
|
|
1093
|
+
export { ADAPTER_VERSION, type AnnotationContext, type Binding, type BoundPythonFile, type DecoratedClassRoute, type DecoratedFunctionRoute, type DecoratorArg, type DecoratorClassification, type DiscoveryOptions, type ExtractPythonOptions, type ExtractPythonResult, type ModuleBinding, type ModuleResolution, type ModuleResolverOptions, type MountObjectCarrier, type MountObjectPrefix, type MountPrefixEffect, type NoValuePrefix, type PathRepeatedSlashes, type PrefixTrailingSlash, type PyModelEntryFunction, type PyModelEntryMethod, type PyModelQueries, type PyStatusCall, type PythonDiscoveryPattern, type PythonImportEvidence, type PythonImportEvidenceOptions, type PythonImportSite, type PythonPack, type PythonValueHandle, PythonWhySession, type PythonWhySessionOptions, type RawSqlPattern, type RelativeModuleSpec, type RouteConventions, type RoutePrefixResolution, type RouterComposition, type RouterIndex, type Scope, type ScopeKind, type StoragePattern, annotationToShape, bindModule, buildRouterIndex, classifyDecorator, containedValues, discoverUnits, emitEntryFact, emitModuleImportFacts, emitValueFacts, extractPythonProject, findPythonFiles, nameKeyIn, nodeId, objectReturnedBy, parsePython, parsePythonSync, preloadPythonGrammar, pythonImportEvidence, readKey, resolveAbsoluteModule, resolveCalls, resolveModule, resolveName, resolveRelativeModule, shapeFromName, unitKey };
|
package/dist/index.d.ts
CHANGED
|
@@ -109,6 +109,8 @@ interface PythonPack {
|
|
|
109
109
|
clients?: PyClientCall[];
|
|
110
110
|
/** What the library's own database queries look like. The README says how one is matched. */
|
|
111
111
|
storage?: StoragePattern[];
|
|
112
|
+
/** Which of the library's calls give back one of a model class. The README says what a chain of them composes into. */
|
|
113
|
+
models?: PyModelQueries[];
|
|
112
114
|
/** How the library lets a project hand the database SQL it wrote itself. */
|
|
113
115
|
rawSql?: RawSqlPattern[];
|
|
114
116
|
}
|
|
@@ -148,6 +150,52 @@ interface StoragePattern {
|
|
|
148
150
|
/** Which database the library is talking to, for the boundary binding. */
|
|
149
151
|
storageSystem: "postgresql" | "mysql" | "sqlite";
|
|
150
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* What a library gives back when a call is passed one of a project's
|
|
155
|
+
* model classes, so a method read off the result runs the one that
|
|
156
|
+
* class declares. SQLAlchemy and SQLModel take the class as an argument,
|
|
157
|
+
* `session.get(User, id)` and `select(User)`, rather than as the
|
|
158
|
+
* receiver a Rails finder is called on.
|
|
159
|
+
*/
|
|
160
|
+
interface PyModelQueries {
|
|
161
|
+
/**
|
|
162
|
+
* The names a model's ancestry arrives at: a base class the library
|
|
163
|
+
* exports, `DeclarativeBase`, or the function that builds one,
|
|
164
|
+
* `declarative_base`.
|
|
165
|
+
*/
|
|
166
|
+
baseNames: string[];
|
|
167
|
+
/** Methods whose result is the model again: one row, or a query a later read narrows to one. */
|
|
168
|
+
givesBack: string[];
|
|
169
|
+
/** Methods that take the model class and give back one of it, with the position it is written at. */
|
|
170
|
+
entryMethods: PyModelEntryMethod[];
|
|
171
|
+
/** Functions the library exports that do the same, called on their own rather than read off a session. */
|
|
172
|
+
entryFunctions: PyModelEntryFunction[];
|
|
173
|
+
/** The library's own constructors for a field that reaches another model. */
|
|
174
|
+
relationships?: PyRelationshipConstructor[];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* `items: list[Item] = Relationship(...)`: the callable a model's field
|
|
178
|
+
* is given to say it reaches another model, and where it comes from. A
|
|
179
|
+
* project function spelled the same way is somebody else's, so the
|
|
180
|
+
* import is what settles a match.
|
|
181
|
+
*/
|
|
182
|
+
interface PyRelationshipConstructor {
|
|
183
|
+
/** The module it is imported from, `sqlmodel` or `sqlalchemy.orm`. */
|
|
184
|
+
module: string;
|
|
185
|
+
/** Its name, `Relationship` or `relationship`. */
|
|
186
|
+
name: string;
|
|
187
|
+
}
|
|
188
|
+
/** `session.get(User, id)`: the method, and where the class is written. */
|
|
189
|
+
interface PyModelEntryMethod {
|
|
190
|
+
method: string;
|
|
191
|
+
argument: number;
|
|
192
|
+
}
|
|
193
|
+
/** `select(User)`: the function, the module it comes from, and where the class is written. */
|
|
194
|
+
interface PyModelEntryFunction {
|
|
195
|
+
module: string;
|
|
196
|
+
name: string;
|
|
197
|
+
argument: number;
|
|
198
|
+
}
|
|
151
199
|
/**
|
|
152
200
|
* The function a library gives a project for handing the database a
|
|
153
201
|
* statement written as SQL, and where it comes from. SQLAlchemy exports
|
|
@@ -1042,4 +1090,4 @@ declare function pythonImportEvidence(options: PythonImportEvidenceOptions): Pro
|
|
|
1042
1090
|
*/
|
|
1043
1091
|
declare const ADAPTER_VERSION = "0.1.0";
|
|
1044
1092
|
|
|
1045
|
-
export { ADAPTER_VERSION, type AnnotationContext, type Binding, type BoundPythonFile, type DecoratedClassRoute, type DecoratedFunctionRoute, type DecoratorArg, type DecoratorClassification, type DiscoveryOptions, type ExtractPythonOptions, type ExtractPythonResult, type ModuleBinding, type ModuleResolution, type ModuleResolverOptions, type MountObjectCarrier, type MountObjectPrefix, type MountPrefixEffect, type NoValuePrefix, type PathRepeatedSlashes, type PrefixTrailingSlash, type PyStatusCall, type PythonDiscoveryPattern, type PythonImportEvidence, type PythonImportEvidenceOptions, type PythonImportSite, type PythonPack, type PythonValueHandle, PythonWhySession, type PythonWhySessionOptions, type RawSqlPattern, type RelativeModuleSpec, type RouteConventions, type RoutePrefixResolution, type RouterComposition, type RouterIndex, type Scope, type ScopeKind, type StoragePattern, annotationToShape, bindModule, buildRouterIndex, classifyDecorator, containedValues, discoverUnits, emitEntryFact, emitModuleImportFacts, emitValueFacts, extractPythonProject, findPythonFiles, nameKeyIn, nodeId, objectReturnedBy, parsePython, parsePythonSync, preloadPythonGrammar, pythonImportEvidence, readKey, resolveAbsoluteModule, resolveCalls, resolveModule, resolveName, resolveRelativeModule, shapeFromName, unitKey };
|
|
1093
|
+
export { ADAPTER_VERSION, type AnnotationContext, type Binding, type BoundPythonFile, type DecoratedClassRoute, type DecoratedFunctionRoute, type DecoratorArg, type DecoratorClassification, type DiscoveryOptions, type ExtractPythonOptions, type ExtractPythonResult, type ModuleBinding, type ModuleResolution, type ModuleResolverOptions, type MountObjectCarrier, type MountObjectPrefix, type MountPrefixEffect, type NoValuePrefix, type PathRepeatedSlashes, type PrefixTrailingSlash, type PyModelEntryFunction, type PyModelEntryMethod, type PyModelQueries, type PyStatusCall, type PythonDiscoveryPattern, type PythonImportEvidence, type PythonImportEvidenceOptions, type PythonImportSite, type PythonPack, type PythonValueHandle, PythonWhySession, type PythonWhySessionOptions, type RawSqlPattern, type RelativeModuleSpec, type RouteConventions, type RoutePrefixResolution, type RouterComposition, type RouterIndex, type Scope, type ScopeKind, type StoragePattern, annotationToShape, bindModule, buildRouterIndex, classifyDecorator, containedValues, discoverUnits, emitEntryFact, emitModuleImportFacts, emitValueFacts, extractPythonProject, findPythonFiles, nameKeyIn, nodeId, objectReturnedBy, parsePython, parsePythonSync, preloadPythonGrammar, pythonImportEvidence, readKey, resolveAbsoluteModule, resolveCalls, resolveModule, resolveName, resolveRelativeModule, shapeFromName, unitKey };
|
package/dist/index.js
CHANGED
|
@@ -1839,6 +1839,79 @@ function assignedValue(right) {
|
|
|
1839
1839
|
const first = fields(right, "subscript")[0];
|
|
1840
1840
|
return outer?.text === "Annotated" && first !== void 0 ? first : right;
|
|
1841
1841
|
}
|
|
1842
|
+
function sideBesidesNone(node) {
|
|
1843
|
+
const left = field(node, "left");
|
|
1844
|
+
const right = field(node, "right");
|
|
1845
|
+
if (right?.type === "none") {
|
|
1846
|
+
return left;
|
|
1847
|
+
}
|
|
1848
|
+
if (left?.type === "none") {
|
|
1849
|
+
return right;
|
|
1850
|
+
}
|
|
1851
|
+
return null;
|
|
1852
|
+
}
|
|
1853
|
+
function annotatedClass(node) {
|
|
1854
|
+
if (node === void 0) {
|
|
1855
|
+
return null;
|
|
1856
|
+
}
|
|
1857
|
+
if (node.type === "identifier" || node.type === "string") {
|
|
1858
|
+
return node;
|
|
1859
|
+
}
|
|
1860
|
+
if (node.type === "type") {
|
|
1861
|
+
return annotatedClass(children(node)[0]);
|
|
1862
|
+
}
|
|
1863
|
+
if (node.type === "generic_type") {
|
|
1864
|
+
const parameter = children(node).find(
|
|
1865
|
+
(child) => child.type === "type_parameter"
|
|
1866
|
+
);
|
|
1867
|
+
return annotatedClass(
|
|
1868
|
+
parameter === void 0 ? void 0 : children(parameter)[0]
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
if (node.type === "binary_operator") {
|
|
1872
|
+
return annotatedClass(sideBesidesNone(node) ?? void 0);
|
|
1873
|
+
}
|
|
1874
|
+
return null;
|
|
1875
|
+
}
|
|
1876
|
+
function classReferenceKey(emitter, node) {
|
|
1877
|
+
if (node.type === "identifier") {
|
|
1878
|
+
return valueKey(emitter, node);
|
|
1879
|
+
}
|
|
1880
|
+
const written = stringLiteralValue(node);
|
|
1881
|
+
if (written === null) {
|
|
1882
|
+
return null;
|
|
1883
|
+
}
|
|
1884
|
+
return nameKey(emitter.filePath, emitter.enclosing, written);
|
|
1885
|
+
}
|
|
1886
|
+
function firstPositional(call) {
|
|
1887
|
+
const args = field(call, "arguments");
|
|
1888
|
+
for (const argument of args === null ? [] : children(args)) {
|
|
1889
|
+
if (argument.type !== "keyword_argument") {
|
|
1890
|
+
return argument;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
return null;
|
|
1894
|
+
}
|
|
1895
|
+
function fieldClassKey(emitter, assignment2, call) {
|
|
1896
|
+
const annotation = field(assignment2, "type");
|
|
1897
|
+
const annotated = annotation === null ? null : annotatedClass(annotation);
|
|
1898
|
+
if (annotated !== null) {
|
|
1899
|
+
return classReferenceKey(emitter, annotated);
|
|
1900
|
+
}
|
|
1901
|
+
const first = firstPositional(call);
|
|
1902
|
+
return first === null ? null : classReferenceKey(emitter, first);
|
|
1903
|
+
}
|
|
1904
|
+
function emitFieldCall(emitter, classKey, name, assignment2, value) {
|
|
1905
|
+
if (value.type !== "call") {
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
const callee = field(value, "function");
|
|
1909
|
+
const target = callee === null ? null : fieldClassKey(emitter, assignment2, value);
|
|
1910
|
+
if (callee === null || target === null) {
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
add(emitter, "fieldCall", classKey, name, valueKey(emitter, callee), target);
|
|
1914
|
+
}
|
|
1842
1915
|
function declaredBy(statement) {
|
|
1843
1916
|
if (statement.type === "decorated_definition") {
|
|
1844
1917
|
return field(statement, "definition") ?? statement;
|
|
@@ -1857,12 +1930,19 @@ function declaredMemberKey(emitter, member, classKey) {
|
|
|
1857
1930
|
}
|
|
1858
1931
|
return null;
|
|
1859
1932
|
}
|
|
1933
|
+
function writtenBaseName(base) {
|
|
1934
|
+
return base.type === "identifier" || base.type === "attribute" ? base.text : null;
|
|
1935
|
+
}
|
|
1860
1936
|
function emitClassFacts(emitter, cls) {
|
|
1861
1937
|
const classKey = nodeId(emitter.filePath, cls);
|
|
1862
1938
|
add(emitter, "objectValue", classKey);
|
|
1863
1939
|
const bases = field(cls, "superclasses");
|
|
1864
1940
|
for (const base of bases === null ? [] : children(bases)) {
|
|
1865
1941
|
add(emitter, "extends", classKey, valueKey(emitter, base));
|
|
1942
|
+
const written = writtenBaseName(base);
|
|
1943
|
+
if (written !== null) {
|
|
1944
|
+
add(emitter, "extendsNamed", classKey, written);
|
|
1945
|
+
}
|
|
1866
1946
|
}
|
|
1867
1947
|
const body = field(cls, "body");
|
|
1868
1948
|
for (const statement of body === null ? [] : children(body)) {
|
|
@@ -1878,6 +1958,7 @@ function emitClassFacts(emitter, cls) {
|
|
|
1878
1958
|
left.text,
|
|
1879
1959
|
valueKey(emitter, right)
|
|
1880
1960
|
);
|
|
1961
|
+
emitFieldCall(emitter, classKey, left.text, member, right);
|
|
1881
1962
|
}
|
|
1882
1963
|
continue;
|
|
1883
1964
|
}
|
|
@@ -5329,7 +5410,12 @@ function matchedChains(calls, options) {
|
|
|
5329
5410
|
}
|
|
5330
5411
|
const pattern = resolvedMethodPattern(options, callee);
|
|
5331
5412
|
if (pattern !== void 0) {
|
|
5332
|
-
|
|
5413
|
+
const returnedOnly = chain.last === chain.root;
|
|
5414
|
+
matched.push({
|
|
5415
|
+
chain,
|
|
5416
|
+
pattern,
|
|
5417
|
+
operation: returnedOnly ? null : chain.operation
|
|
5418
|
+
});
|
|
5333
5419
|
}
|
|
5334
5420
|
}
|
|
5335
5421
|
return matched;
|
|
@@ -7658,6 +7744,37 @@ function reportUnresolvedProjectModules(options, db) {
|
|
|
7658
7744
|
}
|
|
7659
7745
|
}
|
|
7660
7746
|
}
|
|
7747
|
+
function emitModelQueryFacts(db, packs) {
|
|
7748
|
+
for (const pack of packs) {
|
|
7749
|
+
for (const model of pack.models ?? []) {
|
|
7750
|
+
for (const base of model.baseNames) {
|
|
7751
|
+
for (const method of model.givesBack) {
|
|
7752
|
+
db.add("givesBackOne", [base, method]);
|
|
7753
|
+
}
|
|
7754
|
+
for (const entry of model.entryMethods) {
|
|
7755
|
+
db.add("givesBackOneOfArgument", [
|
|
7756
|
+
base,
|
|
7757
|
+
entry.method,
|
|
7758
|
+
String(entry.argument)
|
|
7759
|
+
]);
|
|
7760
|
+
}
|
|
7761
|
+
}
|
|
7762
|
+
for (const entry of model.entryFunctions) {
|
|
7763
|
+
db.add("givesBackOneOfImport", [
|
|
7764
|
+
entry.module,
|
|
7765
|
+
entry.name,
|
|
7766
|
+
String(entry.argument)
|
|
7767
|
+
]);
|
|
7768
|
+
}
|
|
7769
|
+
for (const relationship of model.relationships ?? []) {
|
|
7770
|
+
db.add("associationConstructor", [
|
|
7771
|
+
relationship.module,
|
|
7772
|
+
relationship.name
|
|
7773
|
+
]);
|
|
7774
|
+
}
|
|
7775
|
+
}
|
|
7776
|
+
}
|
|
7777
|
+
}
|
|
7661
7778
|
async function extractPythonProject(options) {
|
|
7662
7779
|
const timer = options.onTiming !== void 0 ? createTimer() : noopTimer();
|
|
7663
7780
|
const cacheDir = adapterStamp.declineWhenRunFromSource(
|
|
@@ -7705,8 +7822,9 @@ async function extractPythonProject(options) {
|
|
|
7705
7822
|
);
|
|
7706
7823
|
const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
|
|
7707
7824
|
const rawSqlPatterns = options.packs.flatMap((pack) => pack.rawSql ?? []);
|
|
7825
|
+
const modelQueries = options.packs.flatMap((pack) => pack.models ?? []);
|
|
7708
7826
|
const discovers = options.packs.some((pack) => pack.discovery.length > 0);
|
|
7709
|
-
const needsValues = discovers || mountsRouters || storagePatterns.length > 0;
|
|
7827
|
+
const needsValues = discovers || mountsRouters || storagePatterns.length > 0 || modelQueries.length > 0;
|
|
7710
7828
|
const definitions = /* @__PURE__ */ new Map();
|
|
7711
7829
|
timer.time("discover", () => {
|
|
7712
7830
|
for (const { file, root, module: moduleBinding } of bound) {
|
|
@@ -7719,6 +7837,7 @@ async function extractPythonProject(options) {
|
|
|
7719
7837
|
if (needsValues) {
|
|
7720
7838
|
bindEvaluator(db, { files: bound, definitions });
|
|
7721
7839
|
}
|
|
7840
|
+
emitModelQueryFacts(db, options.packs);
|
|
7722
7841
|
});
|
|
7723
7842
|
reportUnresolvedProjectModules(options, db);
|
|
7724
7843
|
const couldMatch = timer.time(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suss/adapter-python",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Read a Python codebase with suss and check what each FastAPI or Flask route returns against the clients, specs and infrastructure that depend on it.",
|
|
6
6
|
"keywords": [
|
|
@@ -49,13 +49,13 @@
|
|
|
49
49
|
"typecheck": "tsc --noEmit"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@suss/behavioral-ir": "0.
|
|
53
|
-
"@suss/datalog": "0.
|
|
54
|
-
"@suss/extractor": "0.
|
|
55
|
-
"@suss/ir-core": "0.
|
|
56
|
-
"@suss/resolution": "0.
|
|
57
|
-
"@suss/sql": "0.
|
|
58
|
-
"@suss/values": "0.
|
|
52
|
+
"@suss/behavioral-ir": "0.30.0",
|
|
53
|
+
"@suss/datalog": "0.30.0",
|
|
54
|
+
"@suss/extractor": "0.30.0",
|
|
55
|
+
"@suss/ir-core": "0.30.0",
|
|
56
|
+
"@suss/resolution": "0.30.0",
|
|
57
|
+
"@suss/sql": "0.30.0",
|
|
58
|
+
"@suss/values": "0.30.0",
|
|
59
59
|
"web-tree-sitter": "^0.26.11"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|