snap-python 0.1.1__py3-none-any.whl → 0.1.3__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.
@@ -1,14 +1,22 @@
1
1
  import functools
2
+ import uuid
2
3
 
3
4
  import retry
4
- from httpx import AsyncClient
5
+ from httpx import AsyncClient, Response
5
6
 
6
7
  from snap_python.schemas.store.categories import (
7
8
  VALID_CATEGORY_FIELDS,
8
9
  CategoryResponse,
9
10
  SingleCategoryResponse,
10
11
  )
11
- from snap_python.schemas.store.info import VALID_SNAP_INFO_FIELDS, InfoResponse
12
+ from snap_python.schemas.store.info import (
13
+ VALID_SNAP_INFO_FIELDS,
14
+ InfoResponse,
15
+ )
16
+ from snap_python.schemas.store.refresh import (
17
+ VALID_SNAP_REFRESH_FIELDS,
18
+ RefreshRevisionResponse,
19
+ )
12
20
  from snap_python.schemas.store.search import (
13
21
  VALID_SEARCH_CATEGORY_FIELDS,
14
22
  ArchSearchResponse,
@@ -64,7 +72,9 @@ class StoreEndpoints:
64
72
  response.raise_for_status()
65
73
  return response.json()
66
74
 
67
- async def get_snap_info(self, snap_name: str, fields: list[str] | None = None):
75
+ async def get_snap_info(
76
+ self, snap_name: str, fields: list[str] | None = None
77
+ ) -> InfoResponse:
68
78
  """
69
79
  Get information about a snap.
70
80
 
@@ -305,3 +315,84 @@ class StoreEndpoints:
305
315
  response_json = response.json()
306
316
  response_json["arch"] = arch
307
317
  return ArchSearchResponse.model_validate(response_json)
318
+
319
+ async def snap_refresh(
320
+ self, snap_name: str, payload: dict, extra_headers: dict = None
321
+ ) -> Response:
322
+ """Query the Store's "snap_refresh" endpoint.
323
+
324
+ Useful for more than refreshing a snap. Notably some snap information is only retrievable from this endpoint. E.g. old revision information
325
+
326
+ :param snap_name: The name of the snap.
327
+ :type snap_name: str
328
+ """
329
+ route = "/snaps/refresh"
330
+ if extra_headers is None:
331
+ extra_headers = {}
332
+ query = {}
333
+ response = await self.store_client.post(
334
+ f"{self.base_url}{route}", json=payload, headers=extra_headers, params=query
335
+ )
336
+
337
+ if not response.is_success:
338
+ # TODO: Add logging here
339
+ pass
340
+ return response
341
+
342
+ async def get_snap_revision_info(
343
+ self, snap_name: str, revision: int, arch: str, fields=None
344
+ ) -> RefreshRevisionResponse:
345
+ """Get information about a snap revision.
346
+
347
+ :param snap_name: The name of the snap.
348
+ :type snap_name: str
349
+ :param revision: The revision of the snap.
350
+ :type revision: int
351
+ :param arch: The architecture of the snap to retrieve details about (e.g. amd64, arm64, riscv64, etc).
352
+ :type arch: str
353
+
354
+ :returns: The snap revision information.
355
+ :rtype: RefreshRevisionResponse
356
+ """
357
+
358
+ # cast revision to int
359
+ revision = int(revision)
360
+ extra_headers = {"Snap-Device-Architecture": arch}
361
+ snap_info = await self.get_snap_info(snap_name=snap_name)
362
+
363
+ # I don't think this matters for the "context" field, so using info from the first available
364
+ channel_map_item = snap_info.channel_map[0]
365
+
366
+ payload = {
367
+ "context": [
368
+ {
369
+ "tracking-channel": "stable",
370
+ "snap-id": snap_info.snap_id,
371
+ "revision": channel_map_item.revision,
372
+ "instance-key": str(uuid.uuid4()),
373
+ }
374
+ ],
375
+ "actions": [
376
+ {
377
+ "action": "download",
378
+ "name": snap_name,
379
+ "revision": revision,
380
+ "instance-key": str(uuid.uuid4()),
381
+ }
382
+ ],
383
+ }
384
+
385
+ if fields is not None:
386
+ if not all(field in VALID_SNAP_REFRESH_FIELDS for field in fields):
387
+ raise ValueError(
388
+ f"Invalid fields. Allowed fields: {VALID_SNAP_REFRESH_FIELDS}"
389
+ )
390
+ payload["fields"] = fields
391
+
392
+ response = await self.snap_refresh(
393
+ snap_name=snap_name,
394
+ payload=payload,
395
+ extra_headers=extra_headers,
396
+ )
397
+ response.raise_for_status()
398
+ return RefreshRevisionResponse.model_validate_json(response.content)
@@ -4,7 +4,7 @@
4
4
 
5
5
  from typing import List, Optional
6
6
 
7
- from pydantic import AwareDatetime, BaseModel, ConfigDict, Field
7
+ from pydantic import AliasChoices, AwareDatetime, BaseModel, ConfigDict, Field
8
8
 
9
9
  from snap_python.schemas.snaps import StoreSnap
10
10
 
@@ -47,7 +47,11 @@ class Channel(BaseModel):
47
47
 
48
48
  architecture: str
49
49
  name: str
50
- released_at: Optional[AwareDatetime] = Field(None, alias="released-at")
50
+ released_at: Optional[AwareDatetime] = Field(
51
+ None,
52
+ alias=AliasChoices("released-at", "released_at"),
53
+ serialization_alias="released-at",
54
+ )
51
55
  risk: str
52
56
  track: str
53
57
 
@@ -56,7 +60,9 @@ class Delta(BaseModel):
56
60
  model_config = ConfigDict(extra="forbid", exclude_unset=True)
57
61
 
58
62
  format: str
59
- sha3_384: str = Field(..., alias="sha3-384")
63
+ sha3_384: Optional[str] = Field(
64
+ None, alias=AliasChoices("sha3-384", "sha3_384"), serialization_alias="sha3-384"
65
+ )
60
66
  size: float
61
67
  source: float
62
68
  target: float
@@ -67,7 +73,9 @@ class Download(BaseModel):
67
73
  model_config = ConfigDict(extra="forbid", exclude_unset=True)
68
74
 
69
75
  deltas: List[Delta]
70
- sha3_384: str = Field(..., alias="sha3-384")
76
+ sha3_384: Optional[str] = Field(
77
+ None, alias=AliasChoices("sha3-384", "sha3_384"), serialization_alias="sha3-384"
78
+ )
71
79
  size: float
72
80
  url: str
73
81
 
@@ -80,7 +88,9 @@ class Epoch(BaseModel):
80
88
 
81
89
 
82
90
  class Download1(BaseModel):
83
- sha3_384: Optional[str] = Field(None, alias="sha3-384")
91
+ sha3_384: Optional[str] = Field(
92
+ None, alias=AliasChoices("sha3-384", "sha3_384"), serialization_alias="sha3-384"
93
+ )
84
94
  size: Optional[int] = None
85
95
  url: Optional[str] = None
86
96
 
@@ -89,7 +99,11 @@ class Resource(BaseModel):
89
99
  model_config = ConfigDict(extra="forbid", exclude_unset=True)
90
100
 
91
101
  architectures: Optional[List[str]] = None
92
- created_at: Optional[str] = Field(None, alias="created-at")
102
+ created_at: Optional[str] = Field(
103
+ None,
104
+ alias=AliasChoices("created-at", "created_at"),
105
+ serialization_alias="created-at",
106
+ )
93
107
  description: Optional[str] = None
94
108
  download: Optional[Download1] = None
95
109
  name: Optional[str] = None
@@ -104,14 +118,26 @@ class ChannelMapItem(BaseModel):
104
118
  architectures: Optional[List[str]] = None
105
119
  base: Optional[str] = None
106
120
  channel: Channel
107
- common_ids: Optional[List[str]] = Field(None, alias="common-ids")
121
+ common_ids: Optional[List[str]] = Field(
122
+ None,
123
+ alias=AliasChoices("common-ids", "common_ids"),
124
+ serialization_alias="common-ids",
125
+ )
108
126
  confinement: Optional[str] = None
109
- created_at: Optional[AwareDatetime] = Field(None, alias="created-at")
127
+ created_at: Optional[AwareDatetime] = Field(
128
+ None,
129
+ alias=AliasChoices("created-at", "created_at"),
130
+ serialization_alias="created-at",
131
+ )
110
132
  download: Optional[Download] = None
111
133
  epoch: Optional[Epoch] = None
112
134
  resources: Optional[List[Resource]] = None
113
135
  revision: Optional[int] = None
114
- snap_yaml: Optional[str] = Field(None, alias="snap-yaml")
136
+ snap_yaml: Optional[str] = Field(
137
+ None,
138
+ alias=AliasChoices("snap-yaml", "snap_yaml"),
139
+ serialization_alias="snap-yaml",
140
+ )
115
141
  type: Optional[str] = None
116
142
  version: Optional[str] = None
117
143
 
@@ -126,9 +152,23 @@ class ErrorListItem(BaseModel):
126
152
  class InfoResponse(BaseModel):
127
153
  model_config = ConfigDict(extra="forbid", exclude_unset=True)
128
154
 
129
- channel_map: List[ChannelMapItem] = Field(..., alias="channel-map")
130
- default_track: Optional[str] = Field(None, alias="default-track")
131
- error_list: Optional[List[ErrorListItem]] = Field(None, alias="error-list")
155
+ channel_map: List[ChannelMapItem] = Field(
156
+ ...,
157
+ alias=AliasChoices("channel-map", "channel_map"),
158
+ serialization_alias="channel-map",
159
+ )
160
+ default_track: Optional[str] = Field(
161
+ None,
162
+ alias=AliasChoices("default-track", "default_track"),
163
+ serialization_alias="default-track",
164
+ )
165
+ error_list: Optional[List[ErrorListItem]] = Field(
166
+ None,
167
+ alias=AliasChoices("error-list", "error_list"),
168
+ serialization_alias="error-list",
169
+ )
132
170
  name: str
133
171
  snap: StoreSnap
134
- snap_id: str = Field(..., alias="snap-id")
172
+ snap_id: str = Field(
173
+ ..., alias=AliasChoices("snap-id", "snap_id"), serialization_alias="snap-id"
174
+ )
@@ -0,0 +1,122 @@
1
+ from typing import List, Optional
2
+
3
+ from pydantic import AliasChoices, AwareDatetime, BaseModel, ConfigDict, Field
4
+
5
+ from snap_python.schemas.snaps import Snap
6
+ from snap_python.schemas.store.info import Download, ErrorListItem, Resource
7
+
8
+ VALID_SNAP_REFRESH_FIELDS = [
9
+ "architectures",
10
+ "base",
11
+ "common-ids",
12
+ "confinement",
13
+ "contact",
14
+ "created-at",
15
+ "description",
16
+ "download",
17
+ "epoch",
18
+ "gated-snap-ids",
19
+ "license",
20
+ "links",
21
+ "media",
22
+ "name",
23
+ "prices",
24
+ "private",
25
+ "publisher",
26
+ "resources",
27
+ "revision",
28
+ "snap-id",
29
+ "snap-yaml",
30
+ "summary",
31
+ "title",
32
+ "type",
33
+ "version",
34
+ "website",
35
+ ]
36
+
37
+
38
+ class SnapRefreshFields(BaseModel):
39
+ architectures: Optional[list[str]] = None
40
+ base: Optional[str] = None
41
+ common_ids: Optional[list[str]] = Field(
42
+ None, alias=AliasChoices("common-ids", "common_ids")
43
+ )
44
+ confinement: Optional[str] = None
45
+ created_at: Optional[AwareDatetime] = Field(
46
+ None,
47
+ alias=AliasChoices("created-at", "created_at"),
48
+ serialization_alias="created-at",
49
+ )
50
+ download: Optional[Download] = None
51
+ epoch: Optional[dict[str, list[int]]] = None
52
+ gated_snap_ids: Optional[list[str]] = Field(
53
+ None, alias=AliasChoices("gated-snap-ids", "gated_snap_ids")
54
+ )
55
+ license: Optional[str] = None
56
+ prices: Optional[dict[str, str]] = None
57
+ resources: list[Resource] = Field(default_factory=list)
58
+ revision: Optional[int] = None
59
+ snap_id: Optional[str] = Field(
60
+ None, alias=AliasChoices("snap-id", "snap_id"), serialization_alias="snap-id"
61
+ )
62
+ snap_yaml: Optional[str] = Field(
63
+ None,
64
+ alias=AliasChoices("snap-yaml", "snap_yaml"),
65
+ serialization_alias="snap-yaml",
66
+ )
67
+ type: Optional[str] = None
68
+ version: Optional[str] = None
69
+
70
+
71
+ class StoreRefreshSnap(Snap, SnapRefreshFields):
72
+ pass
73
+
74
+
75
+ class RefreshResultError(BaseModel):
76
+ code: str
77
+ message: str
78
+ extra: Optional[dict] = None
79
+
80
+
81
+ class RefreshResultData(BaseModel):
82
+ model_config = ConfigDict(extra="forbid", exclude_unset=True)
83
+
84
+ default_track: Optional[str] = Field(
85
+ None,
86
+ alias=AliasChoices("default-track", "default_track"),
87
+ serialization_alias="default-track",
88
+ )
89
+
90
+ name: str
91
+ snap: StoreRefreshSnap
92
+ instance_key: str = Field(
93
+ ...,
94
+ alias=AliasChoices("instance-key", "instance_key"),
95
+ serialization_alias="instance-key",
96
+ )
97
+ released_at: Optional[AwareDatetime] = Field(
98
+ None,
99
+ alias=AliasChoices("released-at", "released_at"),
100
+ serialization_alias="released-at",
101
+ )
102
+ result: str
103
+
104
+ snap_id: str = Field(
105
+ ..., alias=AliasChoices("snap-id", "snap_id"), serialization_alias="snap-id"
106
+ )
107
+ error: RefreshResultError | None = None
108
+
109
+ @property
110
+ def is_error(self) -> bool:
111
+ if self.error:
112
+ return True
113
+ return False
114
+
115
+
116
+ class RefreshRevisionResponse(BaseModel):
117
+ error_list: Optional[List[ErrorListItem]] = Field(
118
+ None,
119
+ alias=AliasChoices("error-list", "error_list"),
120
+ serialization_alias="error-list",
121
+ )
122
+ results: List[RefreshResultData]
@@ -142,6 +142,6 @@ class ArchSearchItem(BaseModel):
142
142
 
143
143
  class ArchSearchResponse(BaseModel):
144
144
  results: list[ArchSearchItem] = Field(
145
- alias=AliasPath("_embedded", "clickindex:package")
145
+ alias=AliasChoices(AliasPath("_embedded", "clickindex:package"), "results")
146
146
  )
147
147
  arch: str
snap_python/scrape.py ADDED
@@ -0,0 +1,129 @@
1
+ import pathlib
2
+
3
+ from snap_python.client import SnapClient
4
+ from snap_python.schemas.store.info import ChannelMapItem
5
+ from snap_python.schemas.store.refresh import VALID_SNAP_REFRESH_FIELDS
6
+
7
+
8
+ def get_highest_revision(channel_map: list[ChannelMapItem]) -> ChannelMapItem:
9
+ """Find the ChannelMapItem with the highest revision number from a list of ChannelMapItems.
10
+
11
+ Find the highest revision in a provided channel map
12
+
13
+ Parameters:
14
+ channel_map (list[ChannelMapItem]): A list of ChannelMapItem objects to search through.
15
+
16
+ Returns:
17
+ ChannelMapItem: The item with the highest revision number
18
+
19
+ Example:
20
+
21
+ >>> items = [ChannelMapItem(revision=1), ChannelMapItem(revision=5), ChannelMapItem(revision=3)]
22
+ >>> highest = get_highest_revision(items)
23
+ >>> highest.revision
24
+ 5
25
+
26
+ """
27
+
28
+ return max(channel_map, key=lambda x: x.revision)
29
+
30
+
31
+ async def download_snap_file(
32
+ snap_client: SnapClient,
33
+ snap_revision_download: str,
34
+ snap_revision_download_path: pathlib.Path,
35
+ ):
36
+ """Asynchronously downloads a snap file from the Snap Store.
37
+
38
+ This function streams the snap file from the specified URL and writes it to
39
+ the specified path on disk.
40
+
41
+ Parameters:
42
+ snap_client (SnapClient): The client for interacting with the Snap Store.
43
+ snap_revision_download (str): The URL to download the snap file from.
44
+ snap_revision_download_path (pathlib.Path): The path where the downloaded snap file will be saved.
45
+
46
+ Returns:
47
+ None
48
+
49
+ """
50
+
51
+ store_client = snap_client.store.store_client
52
+ async with store_client.stream(
53
+ "GET", snap_revision_download, follow_redirects=True
54
+ ) as response:
55
+ with open(snap_revision_download_path, "wb") as f:
56
+ async for chunk in response.aiter_bytes():
57
+ f.write(chunk)
58
+
59
+
60
+ async def get_all_snap_content(
61
+ snap_client: SnapClient,
62
+ snap_name: str,
63
+ output_dir: pathlib.Path | str,
64
+ start_revision: int = 1,
65
+ with_snap_files: bool = False,
66
+ ):
67
+ """
68
+ Asynchronously fetches and downloads all content for a specified snap package across multiple revisions.
69
+
70
+ This function retrieves metadata for each revision of a snap package and optionally downloads
71
+ the snap files themselves. All content is organized into directories by revision number.
72
+
73
+ Parameters:
74
+ snap_client (SnapClient): Initialized Snap client instance for API interactions.
75
+ snap_name (str): Name of the snap package to process.
76
+ output_dir (pathlib.Path | str): Directory where snap content will be saved.
77
+ start_revision (int, optional): First revision to process. Defaults to 1.
78
+ with_snap_files (bool, optional): Whether to download the actual snap package files. Defaults to False.
79
+
80
+ Returns:
81
+ None
82
+
83
+ Notes:
84
+ - Creates a directory structure where each revision has its own subdirectory
85
+ - Saves metadata as data.json in each revision directory
86
+ - When with_snap_files=True, also downloads the snap package file for each revision
87
+
88
+ """
89
+ if not isinstance(output_dir, pathlib.Path):
90
+ output_dir = pathlib.Path(output_dir)
91
+
92
+ if not output_dir.exists():
93
+ output_dir.mkdir(parents=True)
94
+
95
+ current_snap_info = await snap_client.store.get_snap_info(
96
+ snap_name, fields=["name", "channel-map", "revision"]
97
+ )
98
+ current_snap_channel = get_highest_revision(current_snap_info.channel_map)
99
+
100
+ print(
101
+ f"Processing snap {snap_name} from revision {start_revision} to {current_snap_channel.revision}"
102
+ )
103
+
104
+ for revision in range(1, current_snap_channel.revision + 1):
105
+ print(f"Processing revision {revision}")
106
+ revision_dir = output_dir / str(revision)
107
+ revision_dir.mkdir(exist_ok=True)
108
+ # get snap revision info
109
+ snap_revision_info = await snap_client.store.get_snap_revision_info(
110
+ snap_name, revision, arch="amd64", fields=VALID_SNAP_REFRESH_FIELDS
111
+ )
112
+
113
+ snap_revision_data = snap_revision_info.results[0]
114
+
115
+ with open(revision_dir / "data.json", "w") as f:
116
+ f.write(snap_revision_data.model_dump_json(indent=2))
117
+
118
+ # download snap revision
119
+ snap_revision_download = snap_revision_data.snap.download.url
120
+
121
+ # download the file
122
+ snap_revision_download_path = (
123
+ revision_dir / snap_revision_download.split("/")[-1]
124
+ )
125
+
126
+ if with_snap_files:
127
+ await download_snap_file(
128
+ snap_client, snap_revision_download, snap_revision_download_path
129
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: snap-python
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Interact with snapd and snapstore(s) from Python
5
5
  License: MIT
6
6
  Author: Alexander Lukens
@@ -3,21 +3,23 @@ snap_python/client.py,sha256=rZAfzcQNhdtTyjQml1N-9-nAianFyEwOaI4H_0FMaFE,6612
3
3
  snap_python/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  snap_python/components/config.py,sha256=IO_gCrumphEZioINFmXxb8I5OVsKC9cgSa28sQbZ700,1775
5
5
  snap_python/components/snaps.py,sha256=FQ362L83jmhu3MLKszOU1w5jGP8l5-WNBlUTLnR6IW8,13176
6
- snap_python/components/store.py,sha256=8E613XeKWt9kc7bAtKPhYq1PULmxZU0RnrULKW2HGQQ,10778
6
+ snap_python/components/store.py,sha256=8r1WUOBu2vPpDXGSSgK9f1HKU3Rfl6wAPu6siTnEcPU,13803
7
7
  snap_python/schemas/changes.py,sha256=7SYUFXAQq9arl7hAA3YQ0YubMurPJtknTOVxP8ryqew,1919
8
8
  snap_python/schemas/common.py,sha256=GLtC9aBo3O8vLKX9PNvzE-_DKN02OGpwsS7Ik2WK9-8,2608
9
9
  snap_python/schemas/config.py,sha256=xXTGhk6TpwI8RlR6_qnfWlBHrBnTwr3hRCO5qCnN-6U,107
10
10
  snap_python/schemas/snaps.py,sha256=cO0UwghFZwfIlJtAjYDdNgfLKwU7953B9bgolpXcUL0,2980
11
11
  snap_python/schemas/store/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  snap_python/schemas/store/categories.py,sha256=PewotRqoPs7cMKtlZKsHVNfnJoItmNKMekS1Be-46l0,1082
13
- snap_python/schemas/store/info.py,sha256=TuqG5wZc89e_Epai1xkcpA1nXNMKBOKng0B40xm_PfU,3333
13
+ snap_python/schemas/store/info.py,sha256=K2y1NOrSLZEszGJur5L0W_1ap4P038cK4ThSjLcyQLA,4407
14
14
  snap_python/schemas/store/json/category_by_name_schema.json,sha256=3-95m6V2WXaPCu_DZw-STa9YHPEbr7CI8fbhjaq881s,1674
15
15
  snap_python/schemas/store/json/category_schema.json,sha256=c4h_eyyBbChOiNby3hYfjAh9i_1MDbPJnHHU4rGOGss,1898
16
16
  snap_python/schemas/store/json/snaps_find_schema.json,sha256=TfKpnievs5QOc7ah97ATMCQTOEfKspONTZnwSwq_0fI,8866
17
17
  snap_python/schemas/store/json/snaps_info_schema.json,sha256=udoOovb6Ea7EnE_R9RuZ1CpY1wOJ3-K8xP4-8zKHgrQ,15888
18
- snap_python/schemas/store/search.py,sha256=u92aFLLuvDwpfMNHlZo7O2-k-kj7cgzAOtvxxsvi6mU,3718
18
+ snap_python/schemas/store/refresh.py,sha256=HAlt-rKfy7vmZSCujMXtW9HHXlVYbHGFwy_BKfD1uCU,3190
19
+ snap_python/schemas/store/search.py,sha256=-Fd1G821-V6JVxvbcgfnzXy8VQoWnS9MR1oLdRurqu0,3743
20
+ snap_python/scrape.py,sha256=ZDJAROp8jwossaAA78gBFTKQzrfnO11RgyyOZqevNd0,4576
19
21
  snap_python/utils.py,sha256=cmUooPg5DzNL197Xxvu7LGD4Gwk3xxsT68wbVytRtV0,1240
20
- snap_python-0.1.1.dist-info/LICENSE,sha256=lYXNZszxC_l2rlBvrHYs-esDegxWh8MKtjrdxA24uOk,1072
21
- snap_python-0.1.1.dist-info/METADATA,sha256=CKofDQQsSyKxoIVDf4ELXETRIva_1w6Hxoe--UJh0S8,1883
22
- snap_python-0.1.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
23
- snap_python-0.1.1.dist-info/RECORD,,
22
+ snap_python-0.1.3.dist-info/LICENSE,sha256=lYXNZszxC_l2rlBvrHYs-esDegxWh8MKtjrdxA24uOk,1072
23
+ snap_python-0.1.3.dist-info/METADATA,sha256=rnVfkQFtCUJNz5018Yh4_VOwZTTeo1SveVswcZka7bI,1883
24
+ snap_python-0.1.3.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
25
+ snap_python-0.1.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.0.1
2
+ Generator: poetry-core 2.1.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any