@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 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
- matched.push({ chain, pattern, operation: chain.operation });
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
- matched.push({ chain, pattern, operation: chain.operation });
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.28.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.28.0",
53
- "@suss/datalog": "0.28.0",
54
- "@suss/extractor": "0.28.0",
55
- "@suss/ir-core": "0.28.0",
56
- "@suss/resolution": "0.28.0",
57
- "@suss/sql": "0.28.0",
58
- "@suss/values": "0.28.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": {