@suss/adapter-python 0.5.11 → 0.7.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
@@ -15747,7 +15747,7 @@ function date4(params) {
15747
15747
  // ../../../node_modules/zod/v4/classic/external.js
15748
15748
  config(en_default());
15749
15749
 
15750
- // ../../ir-core/dist/chunk-WKR5SU4N.js
15750
+ // ../../ir-core/dist/chunk-TEXSN75M.js
15751
15751
  function gqlIdentityKey(typeName, fieldName) {
15752
15752
  return `gql:${typeName}.${fieldName}`;
15753
15753
  }
@@ -16116,27 +16116,41 @@ var runtimeConfigSemantics = defineBoundarySemantics({
16116
16116
  identityKey: () => null
16117
16117
  }
16118
16118
  });
16119
- var StorageRelationalSemanticsSchema = external_exports.object({
16120
- name: external_exports.literal("storage-relational"),
16121
- storageSystem: external_exports.enum(["postgres", "mysql", "sqlite"]),
16119
+ var StorageSemanticsSchema = external_exports.object({
16120
+ name: external_exports.literal("storage"),
16122
16121
  /**
16123
- * The ORM or driver scope. A single-database setup uses `"default"`,
16124
- * and a monorepo with several schemas gives each one its own value.
16122
+ * Which store this is: postgres, mysql, sqlite, dynamodb. Two
16123
+ * products' containers can share a name, so this keeps them apart,
16124
+ * and it is what a finding message calls the store.
16125
+ */
16126
+ storageSystem: external_exports.string(),
16127
+ /**
16128
+ * The ORM, schema, or deployment scope. A single-database setup uses
16129
+ * `"default"`, and a monorepo with several schemas gives each one its
16130
+ * own value.
16125
16131
  */
16126
16132
  scope: external_exports.string(),
16127
16133
  /**
16128
- * The table or model name as the schema declares it, or null when
16129
- * the source states one this reader could not settle. A null table
16130
- * pairs with nothing, rather than with whatever its source text
16131
- * happens to spell.
16134
+ * The table, bucket, collection, or index, as the source declares
16135
+ * it, or null when the source states one this reader could not
16136
+ * settle. A null container pairs with nothing, rather than with
16137
+ * whatever its source text happens to spell.
16138
+ */
16139
+ container: external_exports.string().nullable(),
16140
+ /**
16141
+ * A secondary way into the container, a DynamoDB global secondary
16142
+ * index or an Elasticsearch alias, each with its own key fields.
16143
+ * Null means the container's own primary way in. A query through an
16144
+ * index and a query through the table are different accesses, so
16145
+ * they pair separately.
16132
16146
  */
16133
- table: external_exports.string().nullable()
16147
+ accessPath: external_exports.string().nullable()
16134
16148
  });
16135
- var storageRelationalSemantics = defineBoundarySemantics({
16136
- name: "storage-relational",
16137
- schema: StorageRelationalSemanticsSchema,
16149
+ var storageSemantics = defineBoundarySemantics({
16150
+ name: "storage",
16151
+ schema: StorageSemanticsSchema,
16138
16152
  behavior: {
16139
- /** A query returns rows, not a status and a body. */
16153
+ /** A query returns rows or items, not a status and a body. */
16140
16154
  exchangesHttpResponses: false,
16141
16155
  reportsUnpairedItself: false,
16142
16156
  identityKey: () => null
@@ -16148,7 +16162,7 @@ var SemanticsSchema = external_exports.discriminatedUnion("name", [
16148
16162
  graphqlResolverSemantics.schema,
16149
16163
  graphqlOperationSemantics.schema,
16150
16164
  runtimeConfigSemantics.schema,
16151
- storageRelationalSemantics.schema,
16165
+ storageSemantics.schema,
16152
16166
  messageBusSemantics.schema
16153
16167
  ]);
16154
16168
  var DEFINITIONS = [
@@ -16157,7 +16171,7 @@ var DEFINITIONS = [
16157
16171
  graphqlResolverSemantics,
16158
16172
  graphqlOperationSemantics,
16159
16173
  runtimeConfigSemantics,
16160
- storageRelationalSemantics,
16174
+ storageSemantics,
16161
16175
  messageBusSemantics
16162
16176
  ];
16163
16177
  var BY_NAME = new Map(
@@ -16297,19 +16311,97 @@ function namedOrNull(value, field2) {
16297
16311
  }
16298
16312
  return value;
16299
16313
  }
16300
- function storageRelationalBinding(opts) {
16314
+ function storageBinding(opts) {
16301
16315
  return {
16302
- transport: opts.storageSystem,
16316
+ transport: opts.transport ?? opts.storageSystem,
16303
16317
  semantics: {
16304
- name: "storage-relational",
16318
+ name: "storage",
16305
16319
  storageSystem: opts.storageSystem,
16306
16320
  scope: opts.scope,
16307
- table: namedOrNull(opts.table, "storage table")
16321
+ container: namedOrNull(opts.container, "storage container"),
16322
+ accessPath: opts.accessPath ?? null
16308
16323
  },
16309
16324
  recognition: opts.recognition
16310
16325
  };
16311
16326
  }
16312
16327
 
16328
+ // src/rawSql.ts
16329
+ var import_sql = require("@suss/sql");
16330
+ function rawSqlEffects(calls, options) {
16331
+ if (options.patterns.length === 0) {
16332
+ return [];
16333
+ }
16334
+ const effects = [];
16335
+ for (const call of calls) {
16336
+ const pattern = patternFor(call, options);
16337
+ if (pattern === void 0) {
16338
+ continue;
16339
+ }
16340
+ const statement = statementIn(call);
16341
+ if (statement === null) {
16342
+ continue;
16343
+ }
16344
+ for (const access of (0, import_sql.readSqlAccess)(statement, {
16345
+ dialect: pattern.storageSystem
16346
+ })) {
16347
+ effects.push({
16348
+ type: "interaction",
16349
+ binding: storageBinding({
16350
+ recognition: `python-${pattern.module}`,
16351
+ storageSystem: pattern.storageSystem,
16352
+ scope: pattern.module,
16353
+ container: access.table
16354
+ }),
16355
+ callee: field(call, "function")?.text ?? "",
16356
+ interaction: {
16357
+ class: "storage-access",
16358
+ kind: access.kind,
16359
+ fields: access.fields,
16360
+ ...access.selector.length > 0 ? { selector: access.selector } : {},
16361
+ operation: field(call, "function")?.text ?? ""
16362
+ }
16363
+ });
16364
+ }
16365
+ }
16366
+ return effects;
16367
+ }
16368
+ function patternFor(call, options) {
16369
+ const callee = field(call, "function");
16370
+ if (callee === null || callee.type !== "identifier") {
16371
+ return void 0;
16372
+ }
16373
+ const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
16374
+ if (from === void 0) {
16375
+ return void 0;
16376
+ }
16377
+ const module2 = String(from[1]);
16378
+ return options.patterns.find(
16379
+ (pattern) => pattern.module === module2 && pattern.functions.includes(callee.text)
16380
+ );
16381
+ }
16382
+ function statementIn(call) {
16383
+ const args = field(call, "arguments");
16384
+ const first = args?.namedChildren.find((child) => child !== null) ?? null;
16385
+ if (first === null || first.type !== "string") {
16386
+ return null;
16387
+ }
16388
+ const parts = [""];
16389
+ for (const child of first.namedChildren) {
16390
+ if (child === null) {
16391
+ continue;
16392
+ }
16393
+ if (child.type === "string_content") {
16394
+ parts[parts.length - 1] += child.text;
16395
+ continue;
16396
+ }
16397
+ if (child.type === "interpolation") {
16398
+ parts.push("");
16399
+ }
16400
+ }
16401
+ const statement = (0, import_sql.sqlFromParts)(parts);
16402
+ return statement.trim() === "" ? null : statement;
16403
+ }
16404
+
16313
16405
  // src/facts/resolve.ts
16314
16406
  var import_datalog2 = require("@suss/datalog");
16315
16407
 
@@ -17160,11 +17252,11 @@ function effectFor(pattern, chain, options) {
17160
17252
  ];
17161
17253
  return {
17162
17254
  type: "interaction",
17163
- binding: storageRelationalBinding({
17255
+ binding: storageBinding({
17164
17256
  recognition: "python-storage",
17165
17257
  storageSystem: pattern.storageSystem,
17166
17258
  scope: pattern.module,
17167
- table: chain.subject
17259
+ container: chain.subject
17168
17260
  }),
17169
17261
  callee: chain.last.text,
17170
17262
  interaction: {
@@ -17907,10 +17999,17 @@ function buildRouteUnit(options) {
17907
17999
  () => readReturnAnnotation(definitionNode, enclosingScope, ctx)
17908
18000
  );
17909
18001
  const effects = invocationEffects(definitionNode);
17910
- const storage = storageLookup === void 0 ? [] : storageEffects(bodyCalls(definitionNode), {
17911
- ...storageLookup,
17912
- filePath: storageLookup.factsPath
17913
- });
18002
+ const storage = storageLookup === void 0 ? [] : [
18003
+ ...storageEffects(bodyCalls(definitionNode), {
18004
+ ...storageLookup,
18005
+ filePath: storageLookup.factsPath
18006
+ }),
18007
+ ...rawSqlEffects(bodyCalls(definitionNode), {
18008
+ facts: storageLookup.facts,
18009
+ filePath: storageLookup.factsPath,
18010
+ patterns: storageLookup.rawSql ?? []
18011
+ })
18012
+ ];
17914
18013
  const perReturn = branchesFromReturns(
17915
18014
  readsReturnedStatus,
17916
18015
  definitionNode,
@@ -19266,6 +19365,7 @@ async function extractPythonProject(options) {
19266
19365
  (pack) => pack.discovery.some((pattern) => pattern.routerComposition !== void 0)
19267
19366
  );
19268
19367
  const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
19368
+ const rawSqlPatterns = options.packs.flatMap((pack) => pack.rawSql ?? []);
19269
19369
  const needsValues = mountsRouters || storagePatterns.length > 0;
19270
19370
  const definitions = /* @__PURE__ */ new Map();
19271
19371
  for (const { file: file2, root, module: moduleBinding } of bound) {
@@ -19295,14 +19395,15 @@ async function extractPythonProject(options) {
19295
19395
  absoluteFile: file2,
19296
19396
  routerIndex,
19297
19397
  gapHandling,
19298
- ...storagePatterns.length > 0 ? {
19398
+ ...storagePatterns.length > 0 || rawSqlPatterns.length > 0 ? {
19299
19399
  storage: {
19300
19400
  facts: db,
19301
19401
  factsPath: file2,
19302
19402
  patterns: storagePatterns,
19303
19403
  definitionAt: (key) => definitions.get(key),
19304
19404
  couldMatch,
19305
- leadsToStorage
19405
+ leadsToStorage,
19406
+ rawSql: rawSqlPatterns
19306
19407
  }
19307
19408
  } : {}
19308
19409
  });
@@ -19313,8 +19414,31 @@ async function extractPythonProject(options) {
19313
19414
  emitEntryFact(db, file2, raw.identity.range, raw.identity.name);
19314
19415
  }
19315
19416
  }
19417
+ stampModuleImports(summaries, db, displayPathOf);
19316
19418
  return { summaries, facts: db };
19317
19419
  }
19420
+ function stampModuleImports(summaries, db, displayPathOf) {
19421
+ const importsByFile = /* @__PURE__ */ new Map();
19422
+ for (const [from, , to] of db.facts("pyImportResolved")) {
19423
+ if (typeof from !== "string" || typeof to !== "string") {
19424
+ continue;
19425
+ }
19426
+ const key = displayPathOf(from);
19427
+ const seen = importsByFile.get(key) ?? /* @__PURE__ */ new Set();
19428
+ seen.add(displayPathOf(to));
19429
+ importsByFile.set(key, seen);
19430
+ }
19431
+ for (const summary of summaries) {
19432
+ const imports = importsByFile.get(summary.location.file);
19433
+ if (imports === void 0 || imports.size === 0) {
19434
+ continue;
19435
+ }
19436
+ summary.metadata = {
19437
+ ...summary.metadata ?? {},
19438
+ moduleImports: [...imports].sort()
19439
+ };
19440
+ }
19441
+ }
19318
19442
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
19319
19443
  "__pycache__",
19320
19444
  ".venv",
package/dist/index.d.cts CHANGED
@@ -189,6 +189,8 @@ interface PythonPack {
189
189
  projectModules?: string[];
190
190
  /** What the library's own database queries look like. The README says how one is matched. */
191
191
  storage?: StoragePattern[];
192
+ /** How the library lets a project hand the database SQL it wrote itself. */
193
+ rawSql?: RawSqlPattern[];
192
194
  }
193
195
  /**
194
196
  * A call talks to the database when the method behind it says it returns one
@@ -212,6 +214,20 @@ interface StoragePattern {
212
214
  /** Which database the library is talking to, for the boundary binding. */
213
215
  storageSystem: "postgres" | "mysql" | "sqlite";
214
216
  }
217
+ /**
218
+ * The function a library gives a project for handing the database a
219
+ * statement written as SQL, and where it comes from. SQLAlchemy exports
220
+ * `text` from `sqlalchemy`. A local function of the same name is
221
+ * somebody else's, so the import is what settles a match.
222
+ */
223
+ interface RawSqlPattern {
224
+ /** The module the function is imported from. */
225
+ module: string;
226
+ /** The functions that take a statement written as SQL. */
227
+ functions: string[];
228
+ /** Which database the library is talking to, for the boundary binding. */
229
+ storageSystem: "postgres" | "mysql" | "sqlite";
230
+ }
215
231
  type PythonDiscoveryPattern = DecoratedClassRoute | DecoratedFunctionRoute;
216
232
  /** Conventions both kinds of route share. Each one describes what the library does, never a project's choice. */
217
233
  interface RouteConventions {
@@ -370,6 +386,8 @@ interface StorageLookup {
370
386
  readonly definitionAt: (key: string) => Node | undefined;
371
387
  readonly couldMatch: ReadonlySet<string>;
372
388
  readonly leadsToStorage: ReadonlySet<string>;
389
+ /** What a pack says about statements a project writes as SQL itself. */
390
+ readonly rawSql?: readonly RawSqlPattern[];
373
391
  }
374
392
 
375
393
  /**
@@ -551,4 +569,4 @@ declare function extractPythonProject(options: ExtractPythonOptions): Promise<Ex
551
569
  /** Every `.py` file under `root`, depth-first, skipping the usual non-source directories. */
552
570
  declare function findPythonFiles(root: string): string[];
553
571
 
554
- export { 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 PythonDiscoveryPattern, type PythonPack, 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, nodeId, objectReturnedBy, parsePython, resolveAbsoluteModule, resolveCalls, resolveModule, resolveName, resolveRelativeModule, shapeFromName, unitKey };
572
+ export { 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 PythonDiscoveryPattern, type PythonPack, 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, nodeId, objectReturnedBy, parsePython, resolveAbsoluteModule, resolveCalls, resolveModule, resolveName, resolveRelativeModule, shapeFromName, unitKey };
package/dist/index.d.ts CHANGED
@@ -189,6 +189,8 @@ interface PythonPack {
189
189
  projectModules?: string[];
190
190
  /** What the library's own database queries look like. The README says how one is matched. */
191
191
  storage?: StoragePattern[];
192
+ /** How the library lets a project hand the database SQL it wrote itself. */
193
+ rawSql?: RawSqlPattern[];
192
194
  }
193
195
  /**
194
196
  * A call talks to the database when the method behind it says it returns one
@@ -212,6 +214,20 @@ interface StoragePattern {
212
214
  /** Which database the library is talking to, for the boundary binding. */
213
215
  storageSystem: "postgres" | "mysql" | "sqlite";
214
216
  }
217
+ /**
218
+ * The function a library gives a project for handing the database a
219
+ * statement written as SQL, and where it comes from. SQLAlchemy exports
220
+ * `text` from `sqlalchemy`. A local function of the same name is
221
+ * somebody else's, so the import is what settles a match.
222
+ */
223
+ interface RawSqlPattern {
224
+ /** The module the function is imported from. */
225
+ module: string;
226
+ /** The functions that take a statement written as SQL. */
227
+ functions: string[];
228
+ /** Which database the library is talking to, for the boundary binding. */
229
+ storageSystem: "postgres" | "mysql" | "sqlite";
230
+ }
215
231
  type PythonDiscoveryPattern = DecoratedClassRoute | DecoratedFunctionRoute;
216
232
  /** Conventions both kinds of route share. Each one describes what the library does, never a project's choice. */
217
233
  interface RouteConventions {
@@ -370,6 +386,8 @@ interface StorageLookup {
370
386
  readonly definitionAt: (key: string) => Node | undefined;
371
387
  readonly couldMatch: ReadonlySet<string>;
372
388
  readonly leadsToStorage: ReadonlySet<string>;
389
+ /** What a pack says about statements a project writes as SQL itself. */
390
+ readonly rawSql?: readonly RawSqlPattern[];
373
391
  }
374
392
 
375
393
  /**
@@ -551,4 +569,4 @@ declare function extractPythonProject(options: ExtractPythonOptions): Promise<Ex
551
569
  /** Every `.py` file under `root`, depth-first, skipping the usual non-source directories. */
552
570
  declare function findPythonFiles(root: string): string[];
553
571
 
554
- export { 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 PythonDiscoveryPattern, type PythonPack, 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, nodeId, objectReturnedBy, parsePython, resolveAbsoluteModule, resolveCalls, resolveModule, resolveName, resolveRelativeModule, shapeFromName, unitKey };
572
+ export { 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 PythonDiscoveryPattern, type PythonPack, 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, nodeId, objectReturnedBy, parsePython, resolveAbsoluteModule, resolveCalls, resolveModule, resolveName, resolveRelativeModule, shapeFromName, unitKey };
package/dist/index.js CHANGED
@@ -15703,7 +15703,7 @@ function date4(params) {
15703
15703
  // ../../../node_modules/zod/v4/classic/external.js
15704
15704
  config(en_default());
15705
15705
 
15706
- // ../../ir-core/dist/chunk-WKR5SU4N.js
15706
+ // ../../ir-core/dist/chunk-TEXSN75M.js
15707
15707
  function gqlIdentityKey(typeName, fieldName) {
15708
15708
  return `gql:${typeName}.${fieldName}`;
15709
15709
  }
@@ -16072,27 +16072,41 @@ var runtimeConfigSemantics = defineBoundarySemantics({
16072
16072
  identityKey: () => null
16073
16073
  }
16074
16074
  });
16075
- var StorageRelationalSemanticsSchema = external_exports.object({
16076
- name: external_exports.literal("storage-relational"),
16077
- storageSystem: external_exports.enum(["postgres", "mysql", "sqlite"]),
16075
+ var StorageSemanticsSchema = external_exports.object({
16076
+ name: external_exports.literal("storage"),
16078
16077
  /**
16079
- * The ORM or driver scope. A single-database setup uses `"default"`,
16080
- * and a monorepo with several schemas gives each one its own value.
16078
+ * Which store this is: postgres, mysql, sqlite, dynamodb. Two
16079
+ * products' containers can share a name, so this keeps them apart,
16080
+ * and it is what a finding message calls the store.
16081
+ */
16082
+ storageSystem: external_exports.string(),
16083
+ /**
16084
+ * The ORM, schema, or deployment scope. A single-database setup uses
16085
+ * `"default"`, and a monorepo with several schemas gives each one its
16086
+ * own value.
16081
16087
  */
16082
16088
  scope: external_exports.string(),
16083
16089
  /**
16084
- * The table or model name as the schema declares it, or null when
16085
- * the source states one this reader could not settle. A null table
16086
- * pairs with nothing, rather than with whatever its source text
16087
- * happens to spell.
16090
+ * The table, bucket, collection, or index, as the source declares
16091
+ * it, or null when the source states one this reader could not
16092
+ * settle. A null container pairs with nothing, rather than with
16093
+ * whatever its source text happens to spell.
16094
+ */
16095
+ container: external_exports.string().nullable(),
16096
+ /**
16097
+ * A secondary way into the container, a DynamoDB global secondary
16098
+ * index or an Elasticsearch alias, each with its own key fields.
16099
+ * Null means the container's own primary way in. A query through an
16100
+ * index and a query through the table are different accesses, so
16101
+ * they pair separately.
16088
16102
  */
16089
- table: external_exports.string().nullable()
16103
+ accessPath: external_exports.string().nullable()
16090
16104
  });
16091
- var storageRelationalSemantics = defineBoundarySemantics({
16092
- name: "storage-relational",
16093
- schema: StorageRelationalSemanticsSchema,
16105
+ var storageSemantics = defineBoundarySemantics({
16106
+ name: "storage",
16107
+ schema: StorageSemanticsSchema,
16094
16108
  behavior: {
16095
- /** A query returns rows, not a status and a body. */
16109
+ /** A query returns rows or items, not a status and a body. */
16096
16110
  exchangesHttpResponses: false,
16097
16111
  reportsUnpairedItself: false,
16098
16112
  identityKey: () => null
@@ -16104,7 +16118,7 @@ var SemanticsSchema = external_exports.discriminatedUnion("name", [
16104
16118
  graphqlResolverSemantics.schema,
16105
16119
  graphqlOperationSemantics.schema,
16106
16120
  runtimeConfigSemantics.schema,
16107
- storageRelationalSemantics.schema,
16121
+ storageSemantics.schema,
16108
16122
  messageBusSemantics.schema
16109
16123
  ]);
16110
16124
  var DEFINITIONS = [
@@ -16113,7 +16127,7 @@ var DEFINITIONS = [
16113
16127
  graphqlResolverSemantics,
16114
16128
  graphqlOperationSemantics,
16115
16129
  runtimeConfigSemantics,
16116
- storageRelationalSemantics,
16130
+ storageSemantics,
16117
16131
  messageBusSemantics
16118
16132
  ];
16119
16133
  var BY_NAME = new Map(
@@ -16253,19 +16267,97 @@ function namedOrNull(value, field2) {
16253
16267
  }
16254
16268
  return value;
16255
16269
  }
16256
- function storageRelationalBinding(opts) {
16270
+ function storageBinding(opts) {
16257
16271
  return {
16258
- transport: opts.storageSystem,
16272
+ transport: opts.transport ?? opts.storageSystem,
16259
16273
  semantics: {
16260
- name: "storage-relational",
16274
+ name: "storage",
16261
16275
  storageSystem: opts.storageSystem,
16262
16276
  scope: opts.scope,
16263
- table: namedOrNull(opts.table, "storage table")
16277
+ container: namedOrNull(opts.container, "storage container"),
16278
+ accessPath: opts.accessPath ?? null
16264
16279
  },
16265
16280
  recognition: opts.recognition
16266
16281
  };
16267
16282
  }
16268
16283
 
16284
+ // src/rawSql.ts
16285
+ import { readSqlAccess, sqlFromParts } from "@suss/sql";
16286
+ function rawSqlEffects(calls, options) {
16287
+ if (options.patterns.length === 0) {
16288
+ return [];
16289
+ }
16290
+ const effects = [];
16291
+ for (const call of calls) {
16292
+ const pattern = patternFor(call, options);
16293
+ if (pattern === void 0) {
16294
+ continue;
16295
+ }
16296
+ const statement = statementIn(call);
16297
+ if (statement === null) {
16298
+ continue;
16299
+ }
16300
+ for (const access of readSqlAccess(statement, {
16301
+ dialect: pattern.storageSystem
16302
+ })) {
16303
+ effects.push({
16304
+ type: "interaction",
16305
+ binding: storageBinding({
16306
+ recognition: `python-${pattern.module}`,
16307
+ storageSystem: pattern.storageSystem,
16308
+ scope: pattern.module,
16309
+ container: access.table
16310
+ }),
16311
+ callee: field(call, "function")?.text ?? "",
16312
+ interaction: {
16313
+ class: "storage-access",
16314
+ kind: access.kind,
16315
+ fields: access.fields,
16316
+ ...access.selector.length > 0 ? { selector: access.selector } : {},
16317
+ operation: field(call, "function")?.text ?? ""
16318
+ }
16319
+ });
16320
+ }
16321
+ }
16322
+ return effects;
16323
+ }
16324
+ function patternFor(call, options) {
16325
+ const callee = field(call, "function");
16326
+ if (callee === null || callee.type !== "identifier") {
16327
+ return void 0;
16328
+ }
16329
+ const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
16330
+ if (from === void 0) {
16331
+ return void 0;
16332
+ }
16333
+ const module = String(from[1]);
16334
+ return options.patterns.find(
16335
+ (pattern) => pattern.module === module && pattern.functions.includes(callee.text)
16336
+ );
16337
+ }
16338
+ function statementIn(call) {
16339
+ const args = field(call, "arguments");
16340
+ const first = args?.namedChildren.find((child) => child !== null) ?? null;
16341
+ if (first === null || first.type !== "string") {
16342
+ return null;
16343
+ }
16344
+ const parts = [""];
16345
+ for (const child of first.namedChildren) {
16346
+ if (child === null) {
16347
+ continue;
16348
+ }
16349
+ if (child.type === "string_content") {
16350
+ parts[parts.length - 1] += child.text;
16351
+ continue;
16352
+ }
16353
+ if (child.type === "interpolation") {
16354
+ parts.push("");
16355
+ }
16356
+ }
16357
+ const statement = sqlFromParts(parts);
16358
+ return statement.trim() === "" ? null : statement;
16359
+ }
16360
+
16269
16361
  // src/facts/resolve.ts
16270
16362
  import { deriveOnDemand, evaluate } from "@suss/datalog";
16271
16363
 
@@ -17116,11 +17208,11 @@ function effectFor(pattern, chain, options) {
17116
17208
  ];
17117
17209
  return {
17118
17210
  type: "interaction",
17119
- binding: storageRelationalBinding({
17211
+ binding: storageBinding({
17120
17212
  recognition: "python-storage",
17121
17213
  storageSystem: pattern.storageSystem,
17122
17214
  scope: pattern.module,
17123
- table: chain.subject
17215
+ container: chain.subject
17124
17216
  }),
17125
17217
  callee: chain.last.text,
17126
17218
  interaction: {
@@ -17863,10 +17955,17 @@ function buildRouteUnit(options) {
17863
17955
  () => readReturnAnnotation(definitionNode, enclosingScope, ctx)
17864
17956
  );
17865
17957
  const effects = invocationEffects(definitionNode);
17866
- const storage = storageLookup === void 0 ? [] : storageEffects(bodyCalls(definitionNode), {
17867
- ...storageLookup,
17868
- filePath: storageLookup.factsPath
17869
- });
17958
+ const storage = storageLookup === void 0 ? [] : [
17959
+ ...storageEffects(bodyCalls(definitionNode), {
17960
+ ...storageLookup,
17961
+ filePath: storageLookup.factsPath
17962
+ }),
17963
+ ...rawSqlEffects(bodyCalls(definitionNode), {
17964
+ facts: storageLookup.facts,
17965
+ filePath: storageLookup.factsPath,
17966
+ patterns: storageLookup.rawSql ?? []
17967
+ })
17968
+ ];
17870
17969
  const perReturn = branchesFromReturns(
17871
17970
  readsReturnedStatus,
17872
17971
  definitionNode,
@@ -19222,6 +19321,7 @@ async function extractPythonProject(options) {
19222
19321
  (pack) => pack.discovery.some((pattern) => pattern.routerComposition !== void 0)
19223
19322
  );
19224
19323
  const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
19324
+ const rawSqlPatterns = options.packs.flatMap((pack) => pack.rawSql ?? []);
19225
19325
  const needsValues = mountsRouters || storagePatterns.length > 0;
19226
19326
  const definitions = /* @__PURE__ */ new Map();
19227
19327
  for (const { file: file2, root, module: moduleBinding } of bound) {
@@ -19251,14 +19351,15 @@ async function extractPythonProject(options) {
19251
19351
  absoluteFile: file2,
19252
19352
  routerIndex,
19253
19353
  gapHandling,
19254
- ...storagePatterns.length > 0 ? {
19354
+ ...storagePatterns.length > 0 || rawSqlPatterns.length > 0 ? {
19255
19355
  storage: {
19256
19356
  facts: db,
19257
19357
  factsPath: file2,
19258
19358
  patterns: storagePatterns,
19259
19359
  definitionAt: (key) => definitions.get(key),
19260
19360
  couldMatch,
19261
- leadsToStorage
19361
+ leadsToStorage,
19362
+ rawSql: rawSqlPatterns
19262
19363
  }
19263
19364
  } : {}
19264
19365
  });
@@ -19269,8 +19370,31 @@ async function extractPythonProject(options) {
19269
19370
  emitEntryFact(db, file2, raw.identity.range, raw.identity.name);
19270
19371
  }
19271
19372
  }
19373
+ stampModuleImports(summaries, db, displayPathOf);
19272
19374
  return { summaries, facts: db };
19273
19375
  }
19376
+ function stampModuleImports(summaries, db, displayPathOf) {
19377
+ const importsByFile = /* @__PURE__ */ new Map();
19378
+ for (const [from, , to] of db.facts("pyImportResolved")) {
19379
+ if (typeof from !== "string" || typeof to !== "string") {
19380
+ continue;
19381
+ }
19382
+ const key = displayPathOf(from);
19383
+ const seen = importsByFile.get(key) ?? /* @__PURE__ */ new Set();
19384
+ seen.add(displayPathOf(to));
19385
+ importsByFile.set(key, seen);
19386
+ }
19387
+ for (const summary of summaries) {
19388
+ const imports = importsByFile.get(summary.location.file);
19389
+ if (imports === void 0 || imports.size === 0) {
19390
+ continue;
19391
+ }
19392
+ summary.metadata = {
19393
+ ...summary.metadata ?? {},
19394
+ moduleImports: [...imports].sort()
19395
+ };
19396
+ }
19397
+ }
19274
19398
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
19275
19399
  "__pycache__",
19276
19400
  ".venv",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/adapter-python",
3
- "version": "0.5.11",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "Python language adapter for suss — parses source with tree-sitter (WASM), resolves names with a lexical binder, and emits raw extractions for the suss pipeline.",
6
6
  "keywords": [
@@ -47,9 +47,10 @@
47
47
  "typecheck": "tsc --noEmit"
48
48
  },
49
49
  "dependencies": {
50
- "@suss/behavioral-ir": "0.5.11",
51
- "@suss/datalog": "0.5.11",
52
- "@suss/extractor": "0.5.11",
50
+ "@suss/behavioral-ir": "0.7.0",
51
+ "@suss/datalog": "0.7.0",
52
+ "@suss/extractor": "0.7.0",
53
+ "@suss/sql": "0.7.0",
53
54
  "web-tree-sitter": "^0.26.11"
54
55
  },
55
56
  "devDependencies": {