weave-python 0.28.2__py3-none-any.whl → 0.30.4__py3-none-any.whl

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.
Files changed (104) hide show
  1. weave/weaveapi/auth/v1/service_pb2.py +184 -88
  2. weave/weaveapi/auth/v1/service_pb2.pyi +1288 -1134
  3. weave/weaveapi/auth/v1/service_pb2_grpc.pyi +248 -335
  4. weave/weaveapi/auth/v1/session_pb2.py +12 -4
  5. weave/weaveapi/auth/v1/session_pb2.pyi +109 -89
  6. weave/weaveapi/auth/v1/session_pb2_grpc.pyi +11 -10
  7. weave/weaveapi/auth/v1/usage_pb2.py +16 -12
  8. weave/weaveapi/auth/v1/usage_pb2.pyi +251 -197
  9. weave/weaveapi/auth/v1/usage_pb2_grpc.pyi +11 -10
  10. weave/weaveapi/auth/v1/user_pb2.py +58 -16
  11. weave/weaveapi/auth/v1/user_pb2.pyi +588 -505
  12. weave/weaveapi/auth/v1/user_pb2_grpc.pyi +11 -10
  13. weave/weaveapi/generate/v1/configuration_pb2.py +29 -17
  14. weave/weaveapi/generate/v1/configuration_pb2.pyi +277 -241
  15. weave/weaveapi/generate/v1/configuration_pb2_grpc.pyi +11 -10
  16. weave/weaveapi/generate/v1/generate_pb2.py +30 -4
  17. weave/weaveapi/generate/v1/generate_pb2.pyi +138 -40
  18. weave/weaveapi/generate/v1/generate_pb2_grpc.pyi +11 -10
  19. weave/weaveapi/generate/v1/service_pb2.py +50 -20
  20. weave/weaveapi/generate/v1/service_pb2.pyi +264 -135
  21. weave/weaveapi/generate/v1/service_pb2_grpc.py +47 -0
  22. weave/weaveapi/generate/v1/service_pb2_grpc.pyi +89 -78
  23. weave/weaveapi/llmx/v1/architecture_pb2.py +3 -3
  24. weave/weaveapi/llmx/v1/architecture_pb2.pyi +664 -555
  25. weave/weaveapi/llmx/v1/architecture_pb2_grpc.pyi +11 -10
  26. weave/weaveapi/llmx/v1/capabilities_pb2.py +86 -54
  27. weave/weaveapi/llmx/v1/capabilities_pb2.pyi +967 -852
  28. weave/weaveapi/llmx/v1/capabilities_pb2_grpc.pyi +11 -10
  29. weave/weaveapi/llmx/v1/model_pb2.py +54 -18
  30. weave/weaveapi/llmx/v1/model_pb2.pyi +533 -455
  31. weave/weaveapi/llmx/v1/model_pb2_grpc.pyi +11 -10
  32. weave/weaveapi/llmx/v1/pricing_pb2.py +22 -18
  33. weave/weaveapi/llmx/v1/pricing_pb2.pyi +194 -172
  34. weave/weaveapi/llmx/v1/pricing_pb2_grpc.pyi +11 -10
  35. weave/weaveapi/llmx/v1/provider_pb2.py +3 -3
  36. weave/weaveapi/llmx/v1/provider_pb2.pyi +84 -59
  37. weave/weaveapi/llmx/v1/provider_pb2_grpc.pyi +11 -10
  38. weave/weaveapi/llmx/v1/service_pb2.py +224 -86
  39. weave/weaveapi/llmx/v1/service_pb2.pyi +1651 -1403
  40. weave/weaveapi/llmx/v1/service_pb2_grpc.pyi +160 -203
  41. weave/weaveapi/mcpregistry/v1/server_pb2.py +14 -8
  42. weave/weaveapi/mcpregistry/v1/server_pb2.pyi +143 -121
  43. weave/weaveapi/mcpregistry/v1/server_pb2_grpc.pyi +11 -10
  44. weave/weaveapi/mcpregistry/v1/service_pb2.py +47 -27
  45. weave/weaveapi/mcpregistry/v1/service_pb2.pyi +132 -122
  46. weave/weaveapi/mcpregistry/v1/service_pb2_grpc.pyi +87 -112
  47. weave/weaveapi/payment/v1/invoice_pb2.py +36 -10
  48. weave/weaveapi/payment/v1/invoice_pb2.pyi +352 -291
  49. weave/weaveapi/payment/v1/invoice_pb2_grpc.pyi +11 -10
  50. weave/weaveapi/payment/v1/service_pb2.py +256 -90
  51. weave/weaveapi/payment/v1/service_pb2.pyi +1381 -1242
  52. weave/weaveapi/payment/v1/service_pb2_grpc.pyi +229 -319
  53. weave/weaveapi/payment/v1/subscription_pb2.py +97 -21
  54. weave/weaveapi/payment/v1/subscription_pb2.pyi +727 -611
  55. weave/weaveapi/payment/v1/subscription_pb2_grpc.pyi +11 -10
  56. weave/weaveapi/storage/v1/auth_pb2.py +3 -3
  57. weave/weaveapi/storage/v1/auth_pb2.pyi +42 -29
  58. weave/weaveapi/storage/v1/auth_pb2_grpc.pyi +11 -10
  59. weave/weaveapi/storage/v1/nosql_database_pb2.py +45 -21
  60. weave/weaveapi/storage/v1/nosql_database_pb2.pyi +438 -372
  61. weave/weaveapi/storage/v1/nosql_database_pb2_grpc.pyi +11 -10
  62. weave/weaveapi/storage/v1/object_store_pb2.py +25 -11
  63. weave/weaveapi/storage/v1/object_store_pb2.pyi +203 -187
  64. weave/weaveapi/storage/v1/object_store_pb2_grpc.pyi +11 -10
  65. weave/weaveapi/storage/v1/service_pb2.py +94 -34
  66. weave/weaveapi/storage/v1/service_pb2.pyi +414 -357
  67. weave/weaveapi/storage/v1/service_pb2_grpc.pyi +88 -107
  68. weave/weaveapi/storage/v1/sql_database_pb2.py +37 -21
  69. weave/weaveapi/storage/v1/sql_database_pb2.pyi +481 -400
  70. weave/weaveapi/storage/v1/sql_database_pb2_grpc.pyi +11 -10
  71. weave/weaveapi/storage/v1/storage_pb2.py +18 -4
  72. weave/weaveapi/storage/v1/storage_pb2.pyi +79 -70
  73. weave/weaveapi/storage/v1/storage_pb2_grpc.pyi +11 -10
  74. weave/weaveapi/synthesize/v1/dataset_pb2.py +10 -8
  75. weave/weaveapi/synthesize/v1/dataset_pb2.pyi +158 -128
  76. weave/weaveapi/synthesize/v1/dataset_pb2_grpc.pyi +11 -10
  77. weave/weaveapi/synthesize/v1/inline_data_pb2.py +4 -4
  78. weave/weaveapi/synthesize/v1/inline_data_pb2.pyi +31 -27
  79. weave/weaveapi/synthesize/v1/inline_data_pb2_grpc.pyi +11 -10
  80. weave/weaveapi/synthesize/v1/relationship_pb2.py +17 -9
  81. weave/weaveapi/synthesize/v1/relationship_pb2.pyi +67 -64
  82. weave/weaveapi/synthesize/v1/relationship_pb2_grpc.pyi +11 -10
  83. weave/weaveapi/synthesize/v1/service_pb2.py +40 -22
  84. weave/weaveapi/synthesize/v1/service_pb2.pyi +202 -168
  85. weave/weaveapi/synthesize/v1/service_pb2_grpc.pyi +67 -79
  86. weave/weaveapi/synthesize/v1/training_pb2.py +17 -11
  87. weave/weaveapi/synthesize/v1/training_pb2.pyi +119 -106
  88. weave/weaveapi/synthesize/v1/training_pb2_grpc.pyi +11 -10
  89. weave/weavesql/llmxdb/capabilities.py +487 -0
  90. weave/weavesql/llmxdb/changes.py +297 -0
  91. weave/weavesql/llmxdb/models.py +594 -0
  92. weave/weavesql/llmxdb/providers.py +348 -0
  93. weave/weavesql/llmxdb/scraper_runs.py +287 -0
  94. weave/weavesql/llmxdb/search.py +721 -0
  95. weave/weavesql/weavedb/dataset.py +75 -0
  96. weave/weavesql/weavedb/models.py +135 -0
  97. weave/weavesql/weavedb/relationships.py +72 -0
  98. weave/weavesql/weavedb/storage.py +113 -0
  99. weave/weavesql/weavedb/synthesizer.py +107 -0
  100. {weave_python-0.28.2.dist-info → weave_python-0.30.4.dist-info}/METADATA +3 -3
  101. weave_python-0.30.4.dist-info/RECORD +131 -0
  102. {weave_python-0.28.2.dist-info → weave_python-0.30.4.dist-info}/WHEEL +1 -1
  103. weave_python-0.28.2.dist-info/RECORD +0 -120
  104. {weave_python-0.28.2.dist-info → weave_python-0.30.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,348 @@
1
+ # Code generated by sqlc. DO NOT EDIT.
2
+ # versions:
3
+ # sqlc v1.28.0
4
+ # source: providers.sql
5
+ import dataclasses
6
+ from typing import AsyncIterator, Iterator, Optional
7
+ import uuid
8
+
9
+ import sqlalchemy
10
+ import sqlalchemy.ext.asyncio
11
+
12
+ from weave.weavesql.llmxdb import models
13
+
14
+
15
+ GET_PROVIDER = """-- name: get_provider \\:one
16
+ SELECT id, slug, name, display_name, description, website_url, api_base_url, documentation_url, logo_url, provider_type, founding_year, headquarters, api_key_required, oauth_required, api_key_env_var, is_active, is_verified, created_at, updated_at
17
+ FROM providers
18
+ WHERE slug = :p1
19
+ AND is_active = true
20
+ """
21
+
22
+
23
+ GET_PROVIDER_BY_ID = """-- name: get_provider_by_id \\:one
24
+ SELECT id, slug, name, display_name, description, website_url, api_base_url, documentation_url, logo_url, provider_type, founding_year, headquarters, api_key_required, oauth_required, api_key_env_var, is_active, is_verified, created_at, updated_at
25
+ FROM providers
26
+ WHERE id = :p1
27
+ """
28
+
29
+
30
+ GET_PROVIDERS = """-- name: get_providers \\:many
31
+ SELECT id, slug, name, display_name, description, website_url, api_base_url, documentation_url, logo_url, provider_type, founding_year, headquarters, api_key_required, oauth_required, api_key_env_var, is_active, is_verified, created_at, updated_at
32
+ FROM providers
33
+ WHERE is_active = true
34
+ ORDER BY name
35
+ """
36
+
37
+
38
+ UPDATE_PROVIDER_STATUS = """-- name: update_provider_status \\:exec
39
+ UPDATE providers
40
+ SET is_active = :p1,
41
+ updated_at = NOW()
42
+ WHERE slug = :p2
43
+ """
44
+
45
+
46
+ UPSERT_PROVIDER = """-- name: upsert_provider \\:one
47
+ INSERT INTO providers (slug, name, display_name, description,
48
+ website_url, api_base_url, documentation_url, logo_url,
49
+ provider_type, api_key_required, api_key_env_var)
50
+ VALUES (:p1, :p2, :p3, :p4,
51
+ :p5, :p6, :p7, :p8,
52
+ :p9, :p10, :p11)
53
+ ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name,
54
+ display_name = EXCLUDED.display_name,
55
+ description = EXCLUDED.description,
56
+ website_url = EXCLUDED.website_url,
57
+ api_base_url = EXCLUDED.api_base_url,
58
+ documentation_url = EXCLUDED.documentation_url,
59
+ logo_url = EXCLUDED.logo_url,
60
+ provider_type = EXCLUDED.provider_type,
61
+ api_key_required = EXCLUDED.api_key_required,
62
+ api_key_env_var = EXCLUDED.api_key_env_var,
63
+ updated_at = NOW()
64
+ RETURNING id, slug, name, display_name, description, website_url, api_base_url, documentation_url, logo_url, provider_type, founding_year, headquarters, api_key_required, oauth_required, api_key_env_var, is_active, is_verified, created_at, updated_at
65
+ """
66
+
67
+
68
+ @dataclasses.dataclass()
69
+ class UpsertProviderParams:
70
+ slug: str
71
+ name: str
72
+ display_name: Optional[str]
73
+ description: Optional[str]
74
+ website_url: Optional[str]
75
+ api_base_url: Optional[str]
76
+ documentation_url: Optional[str]
77
+ logo_url: Optional[str]
78
+ provider_type: Optional[str]
79
+ api_key_required: Optional[bool]
80
+ api_key_env_var: Optional[str]
81
+
82
+
83
+ class Querier:
84
+ def __init__(self, conn: sqlalchemy.engine.Connection):
85
+ self._conn = conn
86
+
87
+ def get_provider(self, *, slug: str) -> Optional[models.Provider]:
88
+ row = self._conn.execute(sqlalchemy.text(GET_PROVIDER), {"p1": slug}).first()
89
+ if row is None:
90
+ return None
91
+ return models.Provider(
92
+ id=row[0],
93
+ slug=row[1],
94
+ name=row[2],
95
+ display_name=row[3],
96
+ description=row[4],
97
+ website_url=row[5],
98
+ api_base_url=row[6],
99
+ documentation_url=row[7],
100
+ logo_url=row[8],
101
+ provider_type=row[9],
102
+ founding_year=row[10],
103
+ headquarters=row[11],
104
+ api_key_required=row[12],
105
+ oauth_required=row[13],
106
+ api_key_env_var=row[14],
107
+ is_active=row[15],
108
+ is_verified=row[16],
109
+ created_at=row[17],
110
+ updated_at=row[18],
111
+ )
112
+
113
+ def get_provider_by_id(self, *, id: uuid.UUID) -> Optional[models.Provider]:
114
+ row = self._conn.execute(
115
+ sqlalchemy.text(GET_PROVIDER_BY_ID), {"p1": id}
116
+ ).first()
117
+ if row is None:
118
+ return None
119
+ return models.Provider(
120
+ id=row[0],
121
+ slug=row[1],
122
+ name=row[2],
123
+ display_name=row[3],
124
+ description=row[4],
125
+ website_url=row[5],
126
+ api_base_url=row[6],
127
+ documentation_url=row[7],
128
+ logo_url=row[8],
129
+ provider_type=row[9],
130
+ founding_year=row[10],
131
+ headquarters=row[11],
132
+ api_key_required=row[12],
133
+ oauth_required=row[13],
134
+ api_key_env_var=row[14],
135
+ is_active=row[15],
136
+ is_verified=row[16],
137
+ created_at=row[17],
138
+ updated_at=row[18],
139
+ )
140
+
141
+ def get_providers(self) -> Iterator[models.Provider]:
142
+ result = self._conn.execute(sqlalchemy.text(GET_PROVIDERS))
143
+ for row in result:
144
+ yield models.Provider(
145
+ id=row[0],
146
+ slug=row[1],
147
+ name=row[2],
148
+ display_name=row[3],
149
+ description=row[4],
150
+ website_url=row[5],
151
+ api_base_url=row[6],
152
+ documentation_url=row[7],
153
+ logo_url=row[8],
154
+ provider_type=row[9],
155
+ founding_year=row[10],
156
+ headquarters=row[11],
157
+ api_key_required=row[12],
158
+ oauth_required=row[13],
159
+ api_key_env_var=row[14],
160
+ is_active=row[15],
161
+ is_verified=row[16],
162
+ created_at=row[17],
163
+ updated_at=row[18],
164
+ )
165
+
166
+ def update_provider_status(self, *, is_active: Optional[bool], slug: str) -> None:
167
+ self._conn.execute(
168
+ sqlalchemy.text(UPDATE_PROVIDER_STATUS), {"p1": is_active, "p2": slug}
169
+ )
170
+
171
+ def upsert_provider(self, arg: UpsertProviderParams) -> Optional[models.Provider]:
172
+ row = self._conn.execute(
173
+ sqlalchemy.text(UPSERT_PROVIDER),
174
+ {
175
+ "p1": arg.slug,
176
+ "p2": arg.name,
177
+ "p3": arg.display_name,
178
+ "p4": arg.description,
179
+ "p5": arg.website_url,
180
+ "p6": arg.api_base_url,
181
+ "p7": arg.documentation_url,
182
+ "p8": arg.logo_url,
183
+ "p9": arg.provider_type,
184
+ "p10": arg.api_key_required,
185
+ "p11": arg.api_key_env_var,
186
+ },
187
+ ).first()
188
+ if row is None:
189
+ return None
190
+ return models.Provider(
191
+ id=row[0],
192
+ slug=row[1],
193
+ name=row[2],
194
+ display_name=row[3],
195
+ description=row[4],
196
+ website_url=row[5],
197
+ api_base_url=row[6],
198
+ documentation_url=row[7],
199
+ logo_url=row[8],
200
+ provider_type=row[9],
201
+ founding_year=row[10],
202
+ headquarters=row[11],
203
+ api_key_required=row[12],
204
+ oauth_required=row[13],
205
+ api_key_env_var=row[14],
206
+ is_active=row[15],
207
+ is_verified=row[16],
208
+ created_at=row[17],
209
+ updated_at=row[18],
210
+ )
211
+
212
+
213
+ class AsyncQuerier:
214
+ def __init__(self, conn: sqlalchemy.ext.asyncio.AsyncConnection):
215
+ self._conn = conn
216
+
217
+ async def get_provider(self, *, slug: str) -> Optional[models.Provider]:
218
+ row = (
219
+ await self._conn.execute(sqlalchemy.text(GET_PROVIDER), {"p1": slug})
220
+ ).first()
221
+ if row is None:
222
+ return None
223
+ return models.Provider(
224
+ id=row[0],
225
+ slug=row[1],
226
+ name=row[2],
227
+ display_name=row[3],
228
+ description=row[4],
229
+ website_url=row[5],
230
+ api_base_url=row[6],
231
+ documentation_url=row[7],
232
+ logo_url=row[8],
233
+ provider_type=row[9],
234
+ founding_year=row[10],
235
+ headquarters=row[11],
236
+ api_key_required=row[12],
237
+ oauth_required=row[13],
238
+ api_key_env_var=row[14],
239
+ is_active=row[15],
240
+ is_verified=row[16],
241
+ created_at=row[17],
242
+ updated_at=row[18],
243
+ )
244
+
245
+ async def get_provider_by_id(self, *, id: uuid.UUID) -> Optional[models.Provider]:
246
+ row = (
247
+ await self._conn.execute(sqlalchemy.text(GET_PROVIDER_BY_ID), {"p1": id})
248
+ ).first()
249
+ if row is None:
250
+ return None
251
+ return models.Provider(
252
+ id=row[0],
253
+ slug=row[1],
254
+ name=row[2],
255
+ display_name=row[3],
256
+ description=row[4],
257
+ website_url=row[5],
258
+ api_base_url=row[6],
259
+ documentation_url=row[7],
260
+ logo_url=row[8],
261
+ provider_type=row[9],
262
+ founding_year=row[10],
263
+ headquarters=row[11],
264
+ api_key_required=row[12],
265
+ oauth_required=row[13],
266
+ api_key_env_var=row[14],
267
+ is_active=row[15],
268
+ is_verified=row[16],
269
+ created_at=row[17],
270
+ updated_at=row[18],
271
+ )
272
+
273
+ async def get_providers(self) -> AsyncIterator[models.Provider]:
274
+ result = await self._conn.stream(sqlalchemy.text(GET_PROVIDERS))
275
+ async for row in result:
276
+ yield models.Provider(
277
+ id=row[0],
278
+ slug=row[1],
279
+ name=row[2],
280
+ display_name=row[3],
281
+ description=row[4],
282
+ website_url=row[5],
283
+ api_base_url=row[6],
284
+ documentation_url=row[7],
285
+ logo_url=row[8],
286
+ provider_type=row[9],
287
+ founding_year=row[10],
288
+ headquarters=row[11],
289
+ api_key_required=row[12],
290
+ oauth_required=row[13],
291
+ api_key_env_var=row[14],
292
+ is_active=row[15],
293
+ is_verified=row[16],
294
+ created_at=row[17],
295
+ updated_at=row[18],
296
+ )
297
+
298
+ async def update_provider_status(
299
+ self, *, is_active: Optional[bool], slug: str
300
+ ) -> None:
301
+ await self._conn.execute(
302
+ sqlalchemy.text(UPDATE_PROVIDER_STATUS), {"p1": is_active, "p2": slug}
303
+ )
304
+
305
+ async def upsert_provider(
306
+ self, arg: UpsertProviderParams
307
+ ) -> Optional[models.Provider]:
308
+ row = (
309
+ await self._conn.execute(
310
+ sqlalchemy.text(UPSERT_PROVIDER),
311
+ {
312
+ "p1": arg.slug,
313
+ "p2": arg.name,
314
+ "p3": arg.display_name,
315
+ "p4": arg.description,
316
+ "p5": arg.website_url,
317
+ "p6": arg.api_base_url,
318
+ "p7": arg.documentation_url,
319
+ "p8": arg.logo_url,
320
+ "p9": arg.provider_type,
321
+ "p10": arg.api_key_required,
322
+ "p11": arg.api_key_env_var,
323
+ },
324
+ )
325
+ ).first()
326
+ if row is None:
327
+ return None
328
+ return models.Provider(
329
+ id=row[0],
330
+ slug=row[1],
331
+ name=row[2],
332
+ display_name=row[3],
333
+ description=row[4],
334
+ website_url=row[5],
335
+ api_base_url=row[6],
336
+ documentation_url=row[7],
337
+ logo_url=row[8],
338
+ provider_type=row[9],
339
+ founding_year=row[10],
340
+ headquarters=row[11],
341
+ api_key_required=row[12],
342
+ oauth_required=row[13],
343
+ api_key_env_var=row[14],
344
+ is_active=row[15],
345
+ is_verified=row[16],
346
+ created_at=row[17],
347
+ updated_at=row[18],
348
+ )
@@ -0,0 +1,287 @@
1
+ # Code generated by sqlc. DO NOT EDIT.
2
+ # versions:
3
+ # sqlc v1.28.0
4
+ # source: scraper_runs.sql
5
+ import dataclasses
6
+ import datetime
7
+ from typing import AsyncIterator, Iterator, Optional
8
+ import uuid
9
+
10
+ import sqlalchemy
11
+ import sqlalchemy.ext.asyncio
12
+
13
+ from weave.weavesql.llmxdb import models
14
+
15
+
16
+ CREATE_SCRAPER_RUN = """-- name: create_scraper_run \\:one
17
+ INSERT INTO scraper_runs (provider_slug, status)
18
+ VALUES (:p1, :p2)
19
+ RETURNING id, provider_slug, status, models_found, models_updated, models_added, started_at, completed_at, error_message, created_at
20
+ """
21
+
22
+
23
+ GET_LAST_SUCCESSFUL_RUN = """-- name: get_last_successful_run \\:one
24
+ SELECT id, provider_slug, status, models_found, models_updated, models_added, started_at, completed_at, error_message, created_at
25
+ FROM scraper_runs
26
+ WHERE provider_slug = :p1
27
+ AND status = 'success'
28
+ ORDER BY completed_at DESC
29
+ LIMIT 1
30
+ """
31
+
32
+
33
+ GET_RECENT_RUNS = """-- name: get_recent_runs \\:many
34
+ SELECT id, provider_slug, status, models_found, models_updated, models_added, started_at, completed_at, error_message, created_at
35
+ FROM scraper_runs
36
+ WHERE (:p1\\:\\:text IS NULL OR provider_slug = :p1)
37
+ ORDER BY created_at DESC
38
+ LIMIT :p2
39
+ """
40
+
41
+
42
+ GET_RUN_STATS = """-- name: get_run_stats \\:one
43
+ SELECT COUNT(*) as total_runs,
44
+ COUNT(*) FILTER (WHERE status = 'success') as successful_runs,
45
+ COUNT(*) FILTER (WHERE status = 'failed') as failed_runs,
46
+ AVG(EXTRACT(EPOCH FROM (completed_at - started_at))) as avg_duration_seconds,
47
+ SUM(models_added) as total_models_added,
48
+ SUM(models_updated) as total_models_updated
49
+ FROM scraper_runs
50
+ WHERE created_at > :p1
51
+ AND (:p2\\:\\:text IS NULL OR provider_slug = :p2)
52
+ """
53
+
54
+
55
+ @dataclasses.dataclass()
56
+ class GetRunStatsRow:
57
+ total_runs: int
58
+ successful_runs: int
59
+ failed_runs: int
60
+ avg_duration_seconds: float
61
+ total_models_added: int
62
+ total_models_updated: int
63
+
64
+
65
+ UPDATE_SCRAPER_RUN = """-- name: update_scraper_run \\:exec
66
+ UPDATE scraper_runs
67
+ SET status = :p1,
68
+ models_found = :p2,
69
+ models_updated = :p3,
70
+ models_added = :p4,
71
+ completed_at = :p5,
72
+ error_message = :p6
73
+ WHERE id = :p7
74
+ """
75
+
76
+
77
+ @dataclasses.dataclass()
78
+ class UpdateScraperRunParams:
79
+ status: Optional[str]
80
+ models_found: Optional[int]
81
+ models_updated: Optional[int]
82
+ models_added: Optional[int]
83
+ completed_at: Optional[datetime.datetime]
84
+ error_message: Optional[str]
85
+ id: uuid.UUID
86
+
87
+
88
+ class Querier:
89
+ def __init__(self, conn: sqlalchemy.engine.Connection):
90
+ self._conn = conn
91
+
92
+ def create_scraper_run(
93
+ self, *, provider_slug: Optional[str], status: Optional[str]
94
+ ) -> Optional[models.ScraperRun]:
95
+ row = self._conn.execute(
96
+ sqlalchemy.text(CREATE_SCRAPER_RUN), {"p1": provider_slug, "p2": status}
97
+ ).first()
98
+ if row is None:
99
+ return None
100
+ return models.ScraperRun(
101
+ id=row[0],
102
+ provider_slug=row[1],
103
+ status=row[2],
104
+ models_found=row[3],
105
+ models_updated=row[4],
106
+ models_added=row[5],
107
+ started_at=row[6],
108
+ completed_at=row[7],
109
+ error_message=row[8],
110
+ created_at=row[9],
111
+ )
112
+
113
+ def get_last_successful_run(
114
+ self, *, provider_slug: Optional[str]
115
+ ) -> Optional[models.ScraperRun]:
116
+ row = self._conn.execute(
117
+ sqlalchemy.text(GET_LAST_SUCCESSFUL_RUN), {"p1": provider_slug}
118
+ ).first()
119
+ if row is None:
120
+ return None
121
+ return models.ScraperRun(
122
+ id=row[0],
123
+ provider_slug=row[1],
124
+ status=row[2],
125
+ models_found=row[3],
126
+ models_updated=row[4],
127
+ models_added=row[5],
128
+ started_at=row[6],
129
+ completed_at=row[7],
130
+ error_message=row[8],
131
+ created_at=row[9],
132
+ )
133
+
134
+ def get_recent_runs(
135
+ self, *, provider_slug: str, limit_count: int
136
+ ) -> Iterator[models.ScraperRun]:
137
+ result = self._conn.execute(
138
+ sqlalchemy.text(GET_RECENT_RUNS), {"p1": provider_slug, "p2": limit_count}
139
+ )
140
+ for row in result:
141
+ yield models.ScraperRun(
142
+ id=row[0],
143
+ provider_slug=row[1],
144
+ status=row[2],
145
+ models_found=row[3],
146
+ models_updated=row[4],
147
+ models_added=row[5],
148
+ started_at=row[6],
149
+ completed_at=row[7],
150
+ error_message=row[8],
151
+ created_at=row[9],
152
+ )
153
+
154
+ def get_run_stats(
155
+ self, *, since: Optional[datetime.datetime], provider_slug: str
156
+ ) -> Optional[GetRunStatsRow]:
157
+ row = self._conn.execute(
158
+ sqlalchemy.text(GET_RUN_STATS), {"p1": since, "p2": provider_slug}
159
+ ).first()
160
+ if row is None:
161
+ return None
162
+ return GetRunStatsRow(
163
+ total_runs=row[0],
164
+ successful_runs=row[1],
165
+ failed_runs=row[2],
166
+ avg_duration_seconds=row[3],
167
+ total_models_added=row[4],
168
+ total_models_updated=row[5],
169
+ )
170
+
171
+ def update_scraper_run(self, arg: UpdateScraperRunParams) -> None:
172
+ self._conn.execute(
173
+ sqlalchemy.text(UPDATE_SCRAPER_RUN),
174
+ {
175
+ "p1": arg.status,
176
+ "p2": arg.models_found,
177
+ "p3": arg.models_updated,
178
+ "p4": arg.models_added,
179
+ "p5": arg.completed_at,
180
+ "p6": arg.error_message,
181
+ "p7": arg.id,
182
+ },
183
+ )
184
+
185
+
186
+ class AsyncQuerier:
187
+ def __init__(self, conn: sqlalchemy.ext.asyncio.AsyncConnection):
188
+ self._conn = conn
189
+
190
+ async def create_scraper_run(
191
+ self, *, provider_slug: Optional[str], status: Optional[str]
192
+ ) -> Optional[models.ScraperRun]:
193
+ row = (
194
+ await self._conn.execute(
195
+ sqlalchemy.text(CREATE_SCRAPER_RUN), {"p1": provider_slug, "p2": status}
196
+ )
197
+ ).first()
198
+ if row is None:
199
+ return None
200
+ return models.ScraperRun(
201
+ id=row[0],
202
+ provider_slug=row[1],
203
+ status=row[2],
204
+ models_found=row[3],
205
+ models_updated=row[4],
206
+ models_added=row[5],
207
+ started_at=row[6],
208
+ completed_at=row[7],
209
+ error_message=row[8],
210
+ created_at=row[9],
211
+ )
212
+
213
+ async def get_last_successful_run(
214
+ self, *, provider_slug: Optional[str]
215
+ ) -> Optional[models.ScraperRun]:
216
+ row = (
217
+ await self._conn.execute(
218
+ sqlalchemy.text(GET_LAST_SUCCESSFUL_RUN), {"p1": provider_slug}
219
+ )
220
+ ).first()
221
+ if row is None:
222
+ return None
223
+ return models.ScraperRun(
224
+ id=row[0],
225
+ provider_slug=row[1],
226
+ status=row[2],
227
+ models_found=row[3],
228
+ models_updated=row[4],
229
+ models_added=row[5],
230
+ started_at=row[6],
231
+ completed_at=row[7],
232
+ error_message=row[8],
233
+ created_at=row[9],
234
+ )
235
+
236
+ async def get_recent_runs(
237
+ self, *, provider_slug: str, limit_count: int
238
+ ) -> AsyncIterator[models.ScraperRun]:
239
+ result = await self._conn.stream(
240
+ sqlalchemy.text(GET_RECENT_RUNS), {"p1": provider_slug, "p2": limit_count}
241
+ )
242
+ async for row in result:
243
+ yield models.ScraperRun(
244
+ id=row[0],
245
+ provider_slug=row[1],
246
+ status=row[2],
247
+ models_found=row[3],
248
+ models_updated=row[4],
249
+ models_added=row[5],
250
+ started_at=row[6],
251
+ completed_at=row[7],
252
+ error_message=row[8],
253
+ created_at=row[9],
254
+ )
255
+
256
+ async def get_run_stats(
257
+ self, *, since: Optional[datetime.datetime], provider_slug: str
258
+ ) -> Optional[GetRunStatsRow]:
259
+ row = (
260
+ await self._conn.execute(
261
+ sqlalchemy.text(GET_RUN_STATS), {"p1": since, "p2": provider_slug}
262
+ )
263
+ ).first()
264
+ if row is None:
265
+ return None
266
+ return GetRunStatsRow(
267
+ total_runs=row[0],
268
+ successful_runs=row[1],
269
+ failed_runs=row[2],
270
+ avg_duration_seconds=row[3],
271
+ total_models_added=row[4],
272
+ total_models_updated=row[5],
273
+ )
274
+
275
+ async def update_scraper_run(self, arg: UpdateScraperRunParams) -> None:
276
+ await self._conn.execute(
277
+ sqlalchemy.text(UPDATE_SCRAPER_RUN),
278
+ {
279
+ "p1": arg.status,
280
+ "p2": arg.models_found,
281
+ "p3": arg.models_updated,
282
+ "p4": arg.models_added,
283
+ "p5": arg.completed_at,
284
+ "p6": arg.error_message,
285
+ "p7": arg.id,
286
+ },
287
+ )