@suss/adapter-python 0.29.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;
@@ -1933,6 +2006,7 @@ function emitClassFacts(emitter, cls) {
1933
2006
  left.text,
1934
2007
  valueKey(emitter, right)
1935
2008
  );
2009
+ emitFieldCall(emitter, classKey, left.text, member, right);
1936
2010
  }
1937
2011
  continue;
1938
2012
  }
@@ -5363,7 +5437,12 @@ function matchedChains(calls, options) {
5363
5437
  }
5364
5438
  const pattern = resolvedMethodPattern(options, callee);
5365
5439
  if (pattern !== void 0) {
5366
- 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
+ });
5367
5446
  }
5368
5447
  }
5369
5448
  return matched;
@@ -7675,6 +7754,12 @@ function emitModelQueryFacts(db, packs) {
7675
7754
  String(entry.argument)
7676
7755
  ]);
7677
7756
  }
7757
+ for (const relationship of model.relationships ?? []) {
7758
+ db.add("associationConstructor", [
7759
+ relationship.module,
7760
+ relationship.name
7761
+ ]);
7762
+ }
7678
7763
  }
7679
7764
  }
7680
7765
  }
package/dist/index.d.cts CHANGED
@@ -170,6 +170,20 @@ interface PyModelQueries {
170
170
  entryMethods: PyModelEntryMethod[];
171
171
  /** Functions the library exports that do the same, called on their own rather than read off a session. */
172
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;
173
187
  }
174
188
  /** `session.get(User, id)`: the method, and where the class is written. */
175
189
  interface PyModelEntryMethod {
package/dist/index.d.ts CHANGED
@@ -170,6 +170,20 @@ interface PyModelQueries {
170
170
  entryMethods: PyModelEntryMethod[];
171
171
  /** Functions the library exports that do the same, called on their own rather than read off a session. */
172
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;
173
187
  }
174
188
  /** `session.get(User, id)`: the method, and where the class is written. */
175
189
  interface PyModelEntryMethod {
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;
@@ -1885,6 +1958,7 @@ function emitClassFacts(emitter, cls) {
1885
1958
  left.text,
1886
1959
  valueKey(emitter, right)
1887
1960
  );
1961
+ emitFieldCall(emitter, classKey, left.text, member, right);
1888
1962
  }
1889
1963
  continue;
1890
1964
  }
@@ -5336,7 +5410,12 @@ function matchedChains(calls, options) {
5336
5410
  }
5337
5411
  const pattern = resolvedMethodPattern(options, callee);
5338
5412
  if (pattern !== void 0) {
5339
- 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
+ });
5340
5419
  }
5341
5420
  }
5342
5421
  return matched;
@@ -7687,6 +7766,12 @@ function emitModelQueryFacts(db, packs) {
7687
7766
  String(entry.argument)
7688
7767
  ]);
7689
7768
  }
7769
+ for (const relationship of model.relationships ?? []) {
7770
+ db.add("associationConstructor", [
7771
+ relationship.module,
7772
+ relationship.name
7773
+ ]);
7774
+ }
7690
7775
  }
7691
7776
  }
7692
7777
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/adapter-python",
3
- "version": "0.29.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.29.0",
53
- "@suss/datalog": "0.29.0",
54
- "@suss/extractor": "0.29.0",
55
- "@suss/ir-core": "0.29.0",
56
- "@suss/resolution": "0.29.0",
57
- "@suss/sql": "0.29.0",
58
- "@suss/values": "0.29.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": {