snap-python 0.1.5__py3-none-any.whl → 0.1.6__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.
snap_python/client.py CHANGED
@@ -61,7 +61,6 @@ class SnapClient(AbstractSnapsClient):
61
61
  headers=self.store_headers,
62
62
  )
63
63
  self.config = ConfigEndpoints(self)
64
- self.snapd_client = httpx.AsyncClient(transport=self._transport)
65
64
 
66
65
  @retry(httpx.HTTPError, tries=3, delay=1, backoff=1)
67
66
  async def request(self, method: str, endpoint: str, **kwargs) -> httpx.Response:
@@ -23,6 +23,10 @@ from snap_python.schemas.store.search import (
23
23
  PaginatedSnapSearchResponse,
24
24
  SearchResponse,
25
25
  )
26
+ from snap_python.schemas.store.track import (
27
+ TrackRiskMap,
28
+ channel_map_to_current_track_map,
29
+ )
26
30
 
27
31
 
28
32
  class StoreEndpoints:
@@ -165,7 +169,7 @@ class StoreEndpoints:
165
169
  async def find(
166
170
  self,
167
171
  query: str | None = None,
168
- fields: str | None = None,
172
+ fields: list[str] | None = None,
169
173
  name_startswith: str | None = None,
170
174
  architecture: str | None = None,
171
175
  common_id: str | None = None,
@@ -183,7 +187,7 @@ class StoreEndpoints:
183
187
  :param query: The search query.
184
188
  :type query: str, optional
185
189
  :param fields: The fields to include in the response.
186
- :type fields: str, optional
190
+ :type fields: list[str], optional
187
191
  :param name_startswith: Filter snaps by name prefix.
188
192
  :type name_startswith: str, optional
189
193
  :param architecture: Filter snaps by architecture.
@@ -494,3 +498,28 @@ class StoreEndpoints:
494
498
  response_json["limit"] = limit
495
499
 
496
500
  return PaginatedSnapSearchResponse.model_validate(response_json)
501
+
502
+ async def get_track_risk_map(
503
+ self, snap_name: str
504
+ ) -> dict[str, dict[str, TrackRiskMap]]:
505
+ """Get the track risk map for a snap.
506
+
507
+ :param snap_name: The name of the snap.
508
+ :type snap_name: str
509
+
510
+ :returns: A dictionary mapping tracks to their risk maps.
511
+ :rtype: dict[str, TrackRiskMap]
512
+ """
513
+ snap_info = await self.get_snap_info(
514
+ snap_name=snap_name,
515
+ fields=[
516
+ "channel-map",
517
+ "architectures",
518
+ "base",
519
+ "revision",
520
+ "confinement",
521
+ "version",
522
+ "created-at",
523
+ ],
524
+ )
525
+ return channel_map_to_current_track_map(snap_info.channel_map)
@@ -1,10 +1,12 @@
1
- # generated by datamodel-codegen:
2
- # filename: snaps_info_schema.json
3
- # timestamp: 2024-10-17T01:47:02+00:00
4
-
5
1
  from typing import List, Optional
6
2
 
7
- from pydantic import AliasChoices, AwareDatetime, BaseModel, ConfigDict, Field
3
+ from pydantic import (
4
+ AliasChoices,
5
+ AwareDatetime,
6
+ BaseModel,
7
+ ConfigDict,
8
+ Field,
9
+ )
8
10
 
9
11
  from snap_python.schemas.snaps import StoreSnap
10
12
 
@@ -0,0 +1,132 @@
1
+ import logging
2
+ from typing import Optional
3
+
4
+ from pydantic import (
5
+ AwareDatetime,
6
+ BaseModel,
7
+ field_validator,
8
+ )
9
+
10
+ from snap_python.schemas.store.info import Channel, ChannelMapItem
11
+
12
+ logger = logging.getLogger("snap_python.schemas.store.track")
13
+
14
+
15
+ class TrackRevisionDetails(BaseModel):
16
+ arch: str
17
+ base: str
18
+ channel: Channel
19
+ confinement: str
20
+ created_at: AwareDatetime
21
+ released_at: AwareDatetime
22
+ revision: int
23
+ risk: str
24
+ track: str
25
+ version: str
26
+
27
+ @field_validator("arch")
28
+ @classmethod
29
+ def validate_arch(cls, value):
30
+ valid_architectures = {
31
+ "amd64",
32
+ "arm64",
33
+ "armhf",
34
+ "i386",
35
+ "ppc64el",
36
+ "s390x",
37
+ "riscv64",
38
+ }
39
+ if value not in valid_architectures:
40
+ raise ValueError(
41
+ f"Invalid architecture: {value}. Must be one of {', '.join(valid_architectures)}."
42
+ )
43
+ return value
44
+
45
+
46
+ class TrackRiskMap(BaseModel):
47
+ amd64: Optional[TrackRevisionDetails] = None
48
+ arm64: Optional[TrackRevisionDetails] = None
49
+ armhf: Optional[TrackRevisionDetails] = None
50
+ i386: Optional[TrackRevisionDetails] = None
51
+ ppc64el: Optional[TrackRevisionDetails] = None
52
+ s390x: Optional[TrackRevisionDetails] = None
53
+ riscv64: Optional[TrackRevisionDetails] = None
54
+
55
+ @property
56
+ def architectures(self) -> list[str]:
57
+ """Return a list of architectures that have revisions."""
58
+ return [
59
+ arch for arch, details in self.model_dump().items() if details is not None
60
+ ]
61
+
62
+
63
+ def channel_map_to_current_track_map(
64
+ channel_map: list[ChannelMapItem],
65
+ ) -> dict[str, dict[str, TrackRiskMap]]:
66
+ """Convert a list of ChannelMapItem to a map of TrackRiskMap.
67
+
68
+ track -> risk -> arch
69
+ """
70
+ # track -> risk -> arch
71
+ current_track_map: dict[str, dict[str, TrackRiskMap]] = {}
72
+
73
+ all_tracks = set(item.channel.track for item in channel_map)
74
+ logger.debug(f"Found tracks: {', '.join(all_tracks)}")
75
+
76
+ for track in all_tracks:
77
+ current_track_map[track] = {}
78
+
79
+ for item in channel_map:
80
+ track = item.channel.track
81
+ risk = item.channel.risk
82
+
83
+ if not item.architectures:
84
+ logger.error(
85
+ f"Warning: Channel {item} has no architectures defined. Skipping."
86
+ )
87
+ continue
88
+ assert (
89
+ len(item.architectures) == 1
90
+ ), "Channel must have exactly one architecture."
91
+
92
+ assert (
93
+ item.revision is not None
94
+ ), f"Channel {item} must have a revision defined."
95
+ assert item.channel is not None, f"Channel {item} must have a channel defined."
96
+ assert item.base is not None, f"Channel {item} must have a base defined."
97
+ assert (
98
+ item.confinement is not None
99
+ ), f"Channel {item} must have a confinement defined."
100
+ assert item.version is not None, f"Channel {item} must have a version defined."
101
+ assert (
102
+ item.created_at is not None
103
+ ), f"Channel {item} must have a created_at defined."
104
+ assert (
105
+ item.channel.released_at is not None
106
+ ), f"Channel {item} must have a released_at defined."
107
+
108
+ arch = item.architectures[0]
109
+
110
+ revision_details = TrackRevisionDetails(
111
+ arch=arch,
112
+ base=item.base,
113
+ channel=item.channel,
114
+ confinement=item.confinement,
115
+ created_at=item.created_at,
116
+ released_at=item.channel.released_at,
117
+ revision=item.revision,
118
+ risk=risk,
119
+ track=track,
120
+ version=item.version,
121
+ )
122
+
123
+ if risk not in current_track_map[track]:
124
+ current_track_map[track][risk] = TrackRiskMap()
125
+ setattr(current_track_map[track][risk], arch, revision_details)
126
+
127
+ # re validate the track map to ensure it has all required fields
128
+ for track, risk_map in current_track_map.items():
129
+ for risk in risk_map:
130
+ risk_map[risk] = TrackRiskMap.model_validate(risk_map[risk])
131
+
132
+ return current_track_map
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: snap-python
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: Interact with snapd and snapstore(s) from Python
5
5
  License: MIT
6
6
  Author: Alexander Lukens
@@ -1,25 +1,26 @@
1
1
  snap_python/__init__.py,sha256=gV43Nempk7HFKD1UCvxUhmP9x6dBmPjotcITREr81Vg,222
2
- snap_python/client.py,sha256=rZAfzcQNhdtTyjQml1N-9-nAianFyEwOaI4H_0FMaFE,6612
2
+ snap_python/client.py,sha256=HrgLu0ebhPci8cN3GmfOWc9-ccri80Pk4Oys2RVdoQY,6539
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=NfZAgoNvsvf-aJa-ZZJoJxS-KrwrG6gBe1ScN2WM5To,18003
6
- snap_python/components/store.py,sha256=lxN-6cVslAeen-yfZqMPQ2iNvDUNBCClWIvLn5PZi2Q,16971
6
+ snap_python/components/store.py,sha256=gryTStqw4Wu7S7book4arqzfyzFcITRSCbW5BK89ce4,17839
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=GTDi6_n5P7o9h7-cDllkkFYn8wS9EQ6VZ32rwYJD07A,3657
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=K2y1NOrSLZEszGJur5L0W_1ap4P038cK4ThSjLcyQLA,4407
13
+ snap_python/schemas/store/info.py,sha256=C5T88QMgMJZzG5BwRXGeSDVMlElefjv30i-Gqm8qRyA,4318
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
18
  snap_python/schemas/store/refresh.py,sha256=HAlt-rKfy7vmZSCujMXtW9HHXlVYbHGFwy_BKfD1uCU,3190
19
19
  snap_python/schemas/store/search.py,sha256=4ghZb8xY6IESxZuAvzSjM9RaEwZAH3zjiC06zLc0zvU,4561
20
+ snap_python/schemas/store/track.py,sha256=peOjBVccukjud7_Wn_M2hX6KvoJhyhX74FX5HeeHpm8,4099
20
21
  snap_python/scrape.py,sha256=ZDJAROp8jwossaAA78gBFTKQzrfnO11RgyyOZqevNd0,4576
21
22
  snap_python/utils.py,sha256=cmUooPg5DzNL197Xxvu7LGD4Gwk3xxsT68wbVytRtV0,1240
22
- snap_python-0.1.5.dist-info/LICENSE,sha256=lYXNZszxC_l2rlBvrHYs-esDegxWh8MKtjrdxA24uOk,1072
23
- snap_python-0.1.5.dist-info/METADATA,sha256=0JlTOj5NPMtPMKnOmNriwFlE-p99r39erih9VLo4eQw,1883
24
- snap_python-0.1.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
25
- snap_python-0.1.5.dist-info/RECORD,,
23
+ snap_python-0.1.6.dist-info/LICENSE,sha256=lYXNZszxC_l2rlBvrHYs-esDegxWh8MKtjrdxA24uOk,1072
24
+ snap_python-0.1.6.dist-info/METADATA,sha256=cfN_x0M8MSEtVq0Du6RlDQTrt6xzbd144tH6jyr_w34,1883
25
+ snap_python-0.1.6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
26
+ snap_python-0.1.6.dist-info/RECORD,,