tracktolib 0.62.0__py3-none-any.whl → 0.63.0__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.
File without changes
@@ -0,0 +1,352 @@
1
+ import os
2
+ from typing import Any
3
+
4
+ try:
5
+ import niquests
6
+ except ImportError:
7
+ raise ImportError('Please install niquests or tracktolib with "notion" to use this module')
8
+
9
+ from .models import (
10
+ Block,
11
+ BlockListResponse,
12
+ Database,
13
+ IntrospectTokenResponse,
14
+ Page,
15
+ PageListResponse,
16
+ RevokeTokenResponse,
17
+ SearchResponse,
18
+ TokenResponse,
19
+ User,
20
+ UserListResponse,
21
+ )
22
+
23
+ __all__ = (
24
+ # Auth helpers
25
+ "get_notion_headers",
26
+ # OAuth
27
+ "create_token",
28
+ "introspect_token",
29
+ "revoke_token",
30
+ "refresh_token",
31
+ # Users
32
+ "fetch_users",
33
+ "fetch_user",
34
+ "fetch_me",
35
+ # Pages
36
+ "fetch_page",
37
+ "create_page",
38
+ "update_page",
39
+ # Databases
40
+ "fetch_database",
41
+ "query_database",
42
+ # Blocks
43
+ "fetch_block",
44
+ "fetch_block_children",
45
+ "fetch_append_block_children",
46
+ # Search
47
+ "fetch_search",
48
+ )
49
+
50
+ NOTION_API_URL = "https://api.notion.com"
51
+
52
+
53
+ def _get_notion_token() -> str:
54
+ """Get Notion token from config or environment."""
55
+ token = os.environ.get("NOTION_TOKEN")
56
+ if not token:
57
+ raise ValueError("Notion token not found. Set NOTION_TOKEN env var.")
58
+ return token
59
+
60
+
61
+ def get_notion_headers(api_version: str = "2025-09-03", token: str | None = None):
62
+ """Get headers for Notion API requests."""
63
+ _token = token or _get_notion_token()
64
+ return {
65
+ "Authorization": f"Bearer {_token}",
66
+ "Notion-Version": api_version,
67
+ }
68
+
69
+
70
+ # OAuth endpoints
71
+
72
+
73
+ async def create_token(
74
+ session: niquests.AsyncSession,
75
+ client_id: str,
76
+ client_secret: str,
77
+ code: str,
78
+ redirect_uri: str | None = None,
79
+ ) -> TokenResponse:
80
+ """Create an access token from an OAuth authorization code."""
81
+ payload: dict[str, str] = {
82
+ "grant_type": "authorization_code",
83
+ "code": code,
84
+ }
85
+ if redirect_uri:
86
+ payload["redirect_uri"] = redirect_uri
87
+
88
+ response = await session.post(f"{NOTION_API_URL}/v1/oauth/token", json=payload, auth=(client_id, client_secret))
89
+ response.raise_for_status()
90
+ return response.json() # type: ignore[return-value]
91
+
92
+
93
+ async def introspect_token(
94
+ session: niquests.AsyncSession,
95
+ client_id: str,
96
+ client_secret: str,
97
+ token: str,
98
+ ) -> IntrospectTokenResponse:
99
+ """Get a token's active status, scope, and issued time."""
100
+ payload = {"token": token}
101
+ response = await session.post(
102
+ f"{NOTION_API_URL}/v1/oauth/introspect", json=payload, auth=(client_id, client_secret)
103
+ )
104
+ response.raise_for_status()
105
+ return response.json() # type: ignore[return-value]
106
+
107
+
108
+ async def revoke_token(
109
+ session: niquests.AsyncSession,
110
+ client_id: str,
111
+ client_secret: str,
112
+ token: str,
113
+ ) -> RevokeTokenResponse:
114
+ """Revoke an access token."""
115
+ payload = {"token": token}
116
+
117
+ response = await session.post(f"{NOTION_API_URL}/v1/oauth/revoke", json=payload, auth=(client_id, client_secret))
118
+ response.raise_for_status()
119
+ return response.json() # type: ignore[return-value]
120
+
121
+
122
+ async def refresh_token(
123
+ session: niquests.AsyncSession,
124
+ client_id: str,
125
+ client_secret: str,
126
+ refresh_token_value: str,
127
+ ) -> TokenResponse:
128
+ """Refresh an access token, generating new access and refresh tokens."""
129
+ payload = {
130
+ "grant_type": "refresh_token",
131
+ "refresh_token": refresh_token_value,
132
+ }
133
+
134
+ response = await session.post(f"{NOTION_API_URL}/v1/oauth/token", json=payload, auth=(client_id, client_secret))
135
+ response.raise_for_status()
136
+ return response.json() # type: ignore[return-value]
137
+
138
+
139
+ # Users endpoints
140
+
141
+
142
+ async def fetch_users(
143
+ session: niquests.AsyncSession,
144
+ *,
145
+ start_cursor: str | None = None,
146
+ page_size: int | None = None,
147
+ ) -> UserListResponse:
148
+ """List all users in the workspace."""
149
+ params: dict[str, str] = {}
150
+ if start_cursor:
151
+ params["start_cursor"] = start_cursor
152
+ if page_size:
153
+ params["page_size"] = str(page_size)
154
+
155
+ response = await session.get(f"{NOTION_API_URL}/v1/users", params=params or None)
156
+ response.raise_for_status()
157
+ return response.json() # type: ignore[return-value]
158
+
159
+
160
+ async def fetch_user(session: niquests.AsyncSession, user_id: str) -> User:
161
+ """Retrieve a user by ID."""
162
+ response = await session.get(f"{NOTION_API_URL}/v1/users/{user_id}")
163
+ response.raise_for_status()
164
+ return response.json() # type: ignore[return-value]
165
+
166
+
167
+ async def fetch_me(session: niquests.AsyncSession) -> User:
168
+ """Retrieve the bot user associated with the token."""
169
+ response = await session.get(f"{NOTION_API_URL}/v1/users/me")
170
+ response.raise_for_status()
171
+ return response.json() # type: ignore[return-value]
172
+
173
+
174
+ # Pages endpoints
175
+
176
+
177
+ async def fetch_page(session: niquests.AsyncSession, page_id: str) -> Page:
178
+ """Retrieve a page by ID."""
179
+ response = await session.get(f"{NOTION_API_URL}/v1/pages/{page_id}")
180
+ response.raise_for_status()
181
+ return response.json() # type: ignore[return-value]
182
+
183
+
184
+ async def create_page(
185
+ session: niquests.AsyncSession,
186
+ *,
187
+ parent: dict[str, Any],
188
+ properties: dict[str, Any],
189
+ children: list[dict[str, Any]] | None = None,
190
+ icon: dict[str, Any] | None = None,
191
+ cover: dict[str, Any] | None = None,
192
+ ) -> Page:
193
+ """Create a new page."""
194
+ payload: dict[str, Any] = {
195
+ "parent": parent,
196
+ "properties": properties,
197
+ }
198
+ if children:
199
+ payload["children"] = children
200
+ if icon:
201
+ payload["icon"] = icon
202
+ if cover:
203
+ payload["cover"] = cover
204
+
205
+ response = await session.post(f"{NOTION_API_URL}/v1/pages", json=payload)
206
+ response.raise_for_status()
207
+ return response.json() # type: ignore[return-value]
208
+
209
+
210
+ async def update_page(
211
+ session: niquests.AsyncSession,
212
+ page_id: str,
213
+ *,
214
+ properties: dict[str, Any] | None = None,
215
+ archived: bool | None = None,
216
+ icon: dict[str, Any] | None = None,
217
+ cover: dict[str, Any] | None = None,
218
+ ) -> Page:
219
+ """Update a page's properties."""
220
+ payload: dict[str, Any] = {}
221
+ if properties is not None:
222
+ payload["properties"] = properties
223
+ if archived is not None:
224
+ payload["archived"] = archived
225
+ if icon is not None:
226
+ payload["icon"] = icon
227
+ if cover is not None:
228
+ payload["cover"] = cover
229
+
230
+ response = await session.patch(f"{NOTION_API_URL}/v1/pages/{page_id}", json=payload)
231
+ response.raise_for_status()
232
+ return response.json() # type: ignore[return-value]
233
+
234
+
235
+ # Databases endpoints
236
+
237
+
238
+ async def fetch_database(session: niquests.AsyncSession, database_id: str) -> Database:
239
+ """Retrieve a database by ID."""
240
+ response = await session.get(f"{NOTION_API_URL}/v1/databases/{database_id}")
241
+ response.raise_for_status()
242
+ return response.json() # type: ignore[return-value]
243
+
244
+
245
+ async def query_database(
246
+ session: niquests.AsyncSession,
247
+ database_id: str,
248
+ *,
249
+ filter: dict[str, Any] | None = None,
250
+ sorts: list[dict[str, Any]] | None = None,
251
+ start_cursor: str | None = None,
252
+ page_size: int | None = None,
253
+ ) -> PageListResponse:
254
+ """Query a database."""
255
+ payload: dict[str, Any] = {}
256
+ if filter:
257
+ payload["filter"] = filter
258
+ if sorts:
259
+ payload["sorts"] = sorts
260
+ if start_cursor:
261
+ payload["start_cursor"] = start_cursor
262
+ if page_size:
263
+ payload["page_size"] = page_size
264
+
265
+ response = await session.post(f"{NOTION_API_URL}/v1/databases/{database_id}/query", json=payload or None)
266
+ response.raise_for_status()
267
+ return response.json() # type: ignore[return-value]
268
+
269
+
270
+ # Blocks endpoints
271
+
272
+
273
+ async def fetch_block(session: niquests.AsyncSession, block_id: str) -> Block:
274
+ """Retrieve a block by ID."""
275
+ response = await session.get(f"{NOTION_API_URL}/v1/blocks/{block_id}")
276
+ response.raise_for_status()
277
+ return response.json() # type: ignore[return-value]
278
+
279
+
280
+ async def fetch_block_children(
281
+ session: niquests.AsyncSession,
282
+ block_id: str,
283
+ *,
284
+ start_cursor: str | None = None,
285
+ page_size: int | None = None,
286
+ ) -> BlockListResponse:
287
+ """Retrieve a block's children."""
288
+ params: dict[str, str] = {}
289
+ if start_cursor:
290
+ params["start_cursor"] = start_cursor
291
+ if page_size:
292
+ params["page_size"] = str(page_size)
293
+
294
+ response = await session.get(f"{NOTION_API_URL}/v1/blocks/{block_id}/children", params=params or None)
295
+ response.raise_for_status()
296
+ return response.json() # type: ignore[return-value]
297
+
298
+
299
+ async def fetch_append_block_children(
300
+ session: niquests.AsyncSession,
301
+ block_id: str,
302
+ children: list[dict[str, Any]],
303
+ ) -> BlockListResponse:
304
+ """Append children blocks to a parent block."""
305
+ payload = {"children": children}
306
+
307
+ response = await session.patch(f"{NOTION_API_URL}/v1/blocks/{block_id}/children", json=payload)
308
+ response.raise_for_status()
309
+ return response.json() # type: ignore[return-value]
310
+
311
+
312
+ # Search endpoint
313
+
314
+
315
+ async def fetch_search(
316
+ session: niquests.AsyncSession,
317
+ *,
318
+ query: str | None = None,
319
+ filter: dict[str, Any] | None = None,
320
+ sort: dict[str, Any] | None = None,
321
+ start_cursor: str | None = None,
322
+ page_size: int | None = None,
323
+ ) -> SearchResponse:
324
+ """Search pages and databases."""
325
+ payload: dict[str, Any] = {}
326
+ if query:
327
+ payload["query"] = query
328
+ if filter:
329
+ payload["filter"] = filter
330
+ if sort:
331
+ payload["sort"] = sort
332
+ if start_cursor:
333
+ payload["start_cursor"] = start_cursor
334
+ if page_size:
335
+ payload["page_size"] = page_size
336
+
337
+ response = await session.post(f"{NOTION_API_URL}/v1/search", json=payload or None)
338
+ response.raise_for_status()
339
+ return response.json() # type: ignore[return-value]
340
+
341
+
342
+ if __name__ == "__main__":
343
+ import asyncio
344
+
345
+ async def main():
346
+ async with niquests.AsyncSession() as session:
347
+ session.headers.update(get_notion_headers())
348
+ me = await fetch_me(session)
349
+ print("Me:", me)
350
+ # print(await fetch_search(session, filter="aaa"))
351
+
352
+ asyncio.run(main())
@@ -0,0 +1,281 @@
1
+ """Notion API models based on official notion-sdk-js types."""
2
+
3
+ from typing import Any, Literal, NotRequired, TypedDict
4
+
5
+ # Base types
6
+
7
+
8
+ class PartialUser(TypedDict):
9
+ """Partial user object."""
10
+
11
+ id: str
12
+ object: Literal["user"]
13
+
14
+
15
+ class PersonDetails(TypedDict):
16
+ """Person details."""
17
+
18
+ email: NotRequired[str]
19
+
20
+
21
+ class PersonUserObjectResponse(TypedDict):
22
+ """Person user type details."""
23
+
24
+ type: Literal["person"]
25
+ person: PersonDetails
26
+
27
+
28
+ class BotUserObjectResponse(TypedDict):
29
+ """Bot user type details."""
30
+
31
+ type: Literal["bot"]
32
+ bot: dict[str, Any]
33
+
34
+
35
+ class UserObjectResponseCommon(TypedDict):
36
+ """Common user object fields."""
37
+
38
+ id: str
39
+ object: Literal["user"]
40
+ name: str | None
41
+ avatar_url: str | None
42
+
43
+
44
+ # User object is common fields + either person or bot
45
+ class User(UserObjectResponseCommon):
46
+ """Full user object."""
47
+
48
+ type: Literal["person", "bot"]
49
+ person: NotRequired[PersonDetails]
50
+ bot: NotRequired[dict[str, Any]]
51
+
52
+
53
+ # OAuth types
54
+
55
+
56
+ class UserOwner(TypedDict):
57
+ """User owner for OAuth."""
58
+
59
+ type: Literal["user"]
60
+ user: User | PartialUser
61
+
62
+
63
+ class WorkspaceOwner(TypedDict):
64
+ """Workspace owner for OAuth."""
65
+
66
+ type: Literal["workspace"]
67
+ workspace: Literal[True]
68
+
69
+
70
+ Owner = UserOwner | WorkspaceOwner
71
+
72
+
73
+ class TokenResponse(TypedDict):
74
+ """Response from creating or refreshing an access token."""
75
+
76
+ access_token: str
77
+ token_type: Literal["bearer"]
78
+ refresh_token: str | None
79
+ bot_id: str
80
+ workspace_icon: str | None
81
+ workspace_name: str | None
82
+ workspace_id: str
83
+ owner: Owner
84
+ duplicated_template_id: str | None
85
+ request_id: NotRequired[str]
86
+
87
+
88
+ class IntrospectTokenResponse(TypedDict):
89
+ """Response from introspecting a token."""
90
+
91
+ active: bool
92
+ scope: NotRequired[str]
93
+ iat: NotRequired[int]
94
+ request_id: NotRequired[str]
95
+
96
+
97
+ class RevokeTokenResponse(TypedDict):
98
+ """Response from revoking a token."""
99
+
100
+ request_id: NotRequired[str]
101
+
102
+
103
+ # Rich text types
104
+
105
+
106
+ class RichTextItemResponse(TypedDict):
107
+ """Rich text item."""
108
+
109
+ type: str
110
+ plain_text: str
111
+ href: str | None
112
+ annotations: dict[str, Any]
113
+ text: NotRequired[dict[str, Any]]
114
+ mention: NotRequired[dict[str, Any]]
115
+ equation: NotRequired[dict[str, Any]]
116
+
117
+
118
+ # Parent types
119
+
120
+
121
+ class PageParent(TypedDict):
122
+ """Page parent."""
123
+
124
+ type: Literal["page_id"]
125
+ page_id: str
126
+
127
+
128
+ class DatabaseParent(TypedDict):
129
+ """Database parent."""
130
+
131
+ type: Literal["database_id"]
132
+ database_id: str
133
+
134
+
135
+ class WorkspaceParent(TypedDict):
136
+ """Workspace parent."""
137
+
138
+ type: Literal["workspace"]
139
+ workspace: Literal[True]
140
+
141
+
142
+ class BlockParent(TypedDict):
143
+ """Block parent."""
144
+
145
+ type: Literal["block_id"]
146
+ block_id: str
147
+
148
+
149
+ Parent = PageParent | DatabaseParent | WorkspaceParent | BlockParent
150
+
151
+
152
+ # Page types
153
+
154
+
155
+ class Page(TypedDict):
156
+ """Page object response."""
157
+
158
+ object: Literal["page"]
159
+ id: str
160
+ created_time: str
161
+ last_edited_time: str
162
+ created_by: PartialUser
163
+ last_edited_by: PartialUser
164
+ archived: bool
165
+ in_trash: bool
166
+ is_locked: bool
167
+ url: str
168
+ public_url: str | None
169
+ parent: Parent
170
+ properties: dict[str, Any]
171
+ icon: dict[str, Any] | None
172
+ cover: dict[str, Any] | None
173
+
174
+
175
+ class PartialPage(TypedDict):
176
+ """Partial page object."""
177
+
178
+ object: Literal["page"]
179
+ id: str
180
+
181
+
182
+ # Database types
183
+
184
+
185
+ class Database(TypedDict):
186
+ """Database object response."""
187
+
188
+ object: Literal["database"]
189
+ id: str
190
+ title: list[RichTextItemResponse]
191
+ description: list[RichTextItemResponse]
192
+ parent: Parent
193
+ is_inline: bool
194
+ in_trash: bool
195
+ is_locked: bool
196
+ created_time: str
197
+ last_edited_time: str
198
+ icon: dict[str, Any] | None
199
+ cover: dict[str, Any] | None
200
+ properties: dict[str, Any]
201
+ url: str
202
+ public_url: str | None
203
+ archived: bool
204
+ created_by: PartialUser
205
+ last_edited_by: PartialUser
206
+
207
+
208
+ class PartialDatabase(TypedDict):
209
+ """Partial database object."""
210
+
211
+ object: Literal["database"]
212
+ id: str
213
+
214
+
215
+ # Block types
216
+
217
+
218
+ class Block(TypedDict):
219
+ """Block object response."""
220
+
221
+ object: Literal["block"]
222
+ id: str
223
+ parent: Parent
224
+ type: str
225
+ created_time: str
226
+ last_edited_time: str
227
+ created_by: PartialUser
228
+ last_edited_by: PartialUser
229
+ has_children: bool
230
+ archived: bool
231
+ in_trash: bool
232
+
233
+
234
+ class PartialBlock(TypedDict):
235
+ """Partial block object."""
236
+
237
+ object: Literal["block"]
238
+ id: str
239
+
240
+
241
+ # List response types
242
+
243
+
244
+ class UserListResponse(TypedDict):
245
+ """Response from listing users."""
246
+
247
+ object: Literal["list"]
248
+ type: Literal["user"]
249
+ results: list[User]
250
+ next_cursor: str | None
251
+ has_more: bool
252
+
253
+
254
+ class PageListResponse(TypedDict):
255
+ """Response from querying a database."""
256
+
257
+ object: Literal["list"]
258
+ type: Literal["page_or_data_source"]
259
+ results: list[Page | PartialPage]
260
+ next_cursor: str | None
261
+ has_more: bool
262
+
263
+
264
+ class BlockListResponse(TypedDict):
265
+ """Response from listing block children."""
266
+
267
+ object: Literal["list"]
268
+ type: Literal["block"]
269
+ results: list[Block | PartialBlock]
270
+ next_cursor: str | None
271
+ has_more: bool
272
+
273
+
274
+ class SearchResponse(TypedDict):
275
+ """Response from search."""
276
+
277
+ object: Literal["list"]
278
+ type: Literal["page_or_data_source"]
279
+ results: list[Page | PartialPage | Database | PartialDatabase]
280
+ next_cursor: str | None
281
+ has_more: bool
tracktolib/pg/query.py CHANGED
@@ -225,10 +225,14 @@ def get_update_fields(
225
225
 
226
226
  @dataclass
227
227
  class PGUpdateQuery(PGQuery):
228
- """Value to start the arguments from:
229
- For instance, with a value of 10, the first argument will be $11
228
+ """
229
+ Postgresql UPDATE query generator
230
230
  """
231
231
 
232
+ """
233
+ Value to start the arguments from:
234
+ For instance, with a value of 10, the first argument will be $11
235
+ """
232
236
  start_from: int | None = None
233
237
  """Keys to use for the WHERE clause. Theses fields will not be updated"""
234
238
  where_keys: list[str] | None = None
@@ -239,6 +243,8 @@ class PGUpdateQuery(PGQuery):
239
243
  return_keys: bool = False
240
244
  """Values to update using merge (like {}::jsonb || {}::jsonb)"""
241
245
  merge_keys: list[str] | None = None
246
+ """If True, the query is for many items and values will be a list of tuples"""
247
+ is_many: bool = False
242
248
 
243
249
  _update_fields: str | None = field(init=False, default=None)
244
250
  _values: list | None = field(init=False, default=None)
@@ -264,7 +270,7 @@ class PGUpdateQuery(PGQuery):
264
270
  def values(self):
265
271
  if not self._values:
266
272
  raise ValueError("No values found")
267
- if len(self.items) == 1:
273
+ if len(self.items) == 1 and not self.is_many:
268
274
  return self._values
269
275
  _where_keys = self.where_keys or []
270
276
  _keys_not_where = [k for k in self.keys if k not in _where_keys]
@@ -516,7 +522,13 @@ async def update_many(
516
522
  query_callback: QueryCallback[PGUpdateQuery] | None = None,
517
523
  ):
518
524
  query = PGUpdateQuery(
519
- table=table, items=items, start_from=start_from, where_keys=keys, where=where, merge_keys=merge_keys
525
+ table=table,
526
+ items=items,
527
+ start_from=start_from,
528
+ where_keys=keys,
529
+ where=where,
530
+ merge_keys=merge_keys,
531
+ is_many=True,
520
532
  )
521
533
  if query_callback is not None:
522
534
  query_callback(query)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tracktolib
3
- Version: 0.62.0
3
+ Version: 0.63.0
4
4
  Summary: Utility library for python
5
5
  Keywords: utility
6
6
  Author-email: julien.brayere@tracktor.fr
@@ -10,6 +10,7 @@ Requires-Dist: fastapi>=0.103.2 ; extra == 'api'
10
10
  Requires-Dist: pydantic>=2 ; extra == 'api'
11
11
  Requires-Dist: httpx>=0.25.0 ; extra == 'http'
12
12
  Requires-Dist: python-json-logger>=3.2.1 ; extra == 'logs'
13
+ Requires-Dist: niquests>=3.15.2 ; extra == 'notion'
13
14
  Requires-Dist: asyncpg>=0.27.0 ; extra == 'pg'
14
15
  Requires-Dist: rich>=13.6.0 ; extra == 'pg'
15
16
  Requires-Dist: psycopg>=3.1.12 ; extra == 'pg-sync'
@@ -21,6 +22,7 @@ Requires-Python: >=3.12, <4.0
21
22
  Provides-Extra: api
22
23
  Provides-Extra: http
23
24
  Provides-Extra: logs
25
+ Provides-Extra: notion
24
26
  Provides-Extra: pg
25
27
  Provides-Extra: pg-sync
26
28
  Provides-Extra: s3
@@ -2,8 +2,11 @@ tracktolib/__init__.py,sha256=Q9d6h2lNjcYzxvfJ3zlNcpiP_Ak0T3TBPWINzZNrhu0,173
2
2
  tracktolib/api.py,sha256=ZLMgjH3Y8r3MpXc8m3IuZbzTj3fgrZKZORtSVgbuP-M,10221
3
3
  tracktolib/http_utils.py,sha256=c10JGmHaBw3VSDMYhz2dvVw2lo4PUAq1xMub74I7xDc,2625
4
4
  tracktolib/logs.py,sha256=D2hx6urXl5l4PBGP8mCpcT4GX7tJeFfNY-7oBfHczBU,2191
5
+ tracktolib/notion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ tracktolib/notion/fetch.py,sha256=zIhFo9T18eS75l0wZN_WaXD51OUxTsgvFuvqk3R87o4,9974
7
+ tracktolib/notion/models.py,sha256=D-px7Ht2xoXKKZICuEkZhRwVBvVCaKCvhpzPtVVYawI,5390
5
8
  tracktolib/pg/__init__.py,sha256=Ul_hgwvTXZvQBt7sHKi4ZI-0DDpnXmoFtmVkGRy-1J0,366
6
- tracktolib/pg/query.py,sha256=u1j-LYjPsAjJtvg5j1_L8896UJZBGjlcLe_OKugGzRo,16441
9
+ tracktolib/pg/query.py,sha256=_wL9MQU_z8Sk0ZOYGVE0TjUbwBZ1OJJuEq2jlmWoeeM,16693
7
10
  tracktolib/pg/utils.py,sha256=ygQn63EBDaEGB0p7P2ibellO2mv-StafanpXKcCUiZU,6324
8
11
  tracktolib/pg_sync.py,sha256=MKDaV7dYsRy59Y0EE5RGZL0DlZ-RUdBeaN9eSBwiQJg,6718
9
12
  tracktolib/pg_utils.py,sha256=ArYNdf9qsdYdzGEWmev8tZpyx8_1jaGGdkfYkauM7UM,2582
@@ -12,6 +15,6 @@ tracktolib/s3/minio.py,sha256=wMEjkSes9Fp39fD17IctALpD6zB2xwDRQEmO7Vzan3g,1387
12
15
  tracktolib/s3/s3.py,sha256=0HbSAPoaup5-W4LK54zRCjrQ5mr8OWR-N9WjW99Q4aw,5937
13
16
  tracktolib/tests.py,sha256=gKE--epQjgMZGXc5ydbl4zjOdmwztJS42UMV0p4hXEA,399
14
17
  tracktolib/utils.py,sha256=ysTBF9V35fVXQVBPk0kfE_84SGRxzrayqmg9RbtoJq4,5761
15
- tracktolib-0.62.0.dist-info/WHEEL,sha256=ZHijuPszqKbNczrBXkSuoxdxocbxgFghqnequ9ZQlVk,79
16
- tracktolib-0.62.0.dist-info/METADATA,sha256=A9tLiSx166euWV-WAQayKHLQzZHPvr9IOwWQklLM88E,3053
17
- tracktolib-0.62.0.dist-info/RECORD,,
18
+ tracktolib-0.63.0.dist-info/WHEEL,sha256=z-mOpxbJHqy3cq6SvUThBZdaLGFZzdZPtgWLcP2NKjQ,79
19
+ tracktolib-0.63.0.dist-info/METADATA,sha256=q3HcfLHVoybHIDc4Bgayt3kYd5v1spd62OCGyxjCfZw,3128
20
+ tracktolib-0.63.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.10
2
+ Generator: uv 0.9.15
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any