snap-python 0.0.3__py3-none-any.whl → 0.0.5__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
@@ -27,6 +27,7 @@ class SnapClient(AbstractSnapsClient):
27
27
  tcp_location: str = None,
28
28
  store_base_url: str = "https://api.snapcraft.io",
29
29
  store_headers: dict[str, str] = None,
30
+ prompt_for_authentication: bool = False,
30
31
  ):
31
32
  if tcp_location and snapd_socket_location:
32
33
  raise ValueError(
@@ -40,14 +41,19 @@ class SnapClient(AbstractSnapsClient):
40
41
  self._transport = httpx.AsyncHTTPTransport(
41
42
  uds=snapd_socket_location or SNAPD_SOCKET
42
43
  )
43
-
44
44
  if store_headers is None:
45
45
  store_headers = {"Snap-Device-Series": "16", "X-Ubuntu-Series": "16"}
46
46
 
47
+ snapd_headers = {}
48
+ if prompt_for_authentication:
49
+ snapd_headers = {"X-Allow-Interaction": "true"}
50
+
47
51
  self.version = version
48
52
  self.store_base_url = store_base_url
49
53
  self.store_headers = store_headers
50
- self.snapd_client = httpx.AsyncClient(transport=self._transport)
54
+ self.snapd_client = httpx.AsyncClient(
55
+ transport=self._transport, headers=snapd_headers
56
+ )
51
57
  self.snaps = SnapsEndpoints(self)
52
58
  self.store = StoreEndpoints(
53
59
  base_url=self.store_base_url,
@@ -6,7 +6,10 @@ import httpx
6
6
 
7
7
  from snap_python.schemas.changes import ChangesResponse
8
8
  from snap_python.schemas.common import AsyncResponse
9
- from snap_python.schemas.snaps import SingleSnapResponse, SnapListResponse
9
+ from snap_python.schemas.snaps import (
10
+ InstalledSnapListResponse,
11
+ SingleInstalledSnapResponse,
12
+ )
10
13
  from snap_python.utils import AbstractSnapsClient, going_to_reload_daemon
11
14
 
12
15
  logger = logging.getLogger("snap_python.components.snaps")
@@ -17,12 +20,12 @@ class SnapsEndpoints:
17
20
  self._client = client
18
21
  self.common_endpoint = "snaps"
19
22
 
20
- async def list_installed_snaps(self) -> SnapListResponse:
23
+ async def list_installed_snaps(self) -> InstalledSnapListResponse:
21
24
  response: httpx.Response = await self._client.request(
22
25
  "GET", self.common_endpoint
23
26
  )
24
27
 
25
- response = SnapListResponse.model_validate_json(response.content)
28
+ response = InstalledSnapListResponse.model_validate_json(response.content)
26
29
  if response.status_code > 299:
27
30
  raise httpx.HTTPStatusError(
28
31
  request=response.request,
@@ -31,7 +34,7 @@ class SnapsEndpoints:
31
34
  )
32
35
  return response
33
36
 
34
- async def get_snap_info(self, snap: str) -> SingleSnapResponse:
37
+ async def get_snap_info(self, snap: str) -> SingleInstalledSnapResponse:
35
38
  try:
36
39
  response: httpx.Response = await self._client.request(
37
40
  "GET", f"{self.common_endpoint}/{snap}"
@@ -44,7 +47,7 @@ class SnapsEndpoints:
44
47
  )
45
48
  response = e.response
46
49
 
47
- return SingleSnapResponse.model_validate_json(response.content)
50
+ return SingleInstalledSnapResponse.model_validate_json(response.content)
48
51
 
49
52
  async def is_snap_installed(self, snap: str) -> bool:
50
53
  snap_info = await self.get_snap_info(snap)
@@ -57,6 +60,7 @@ class SnapsEndpoints:
57
60
  snap: str,
58
61
  channel: str = "stable",
59
62
  classic: bool = False,
63
+ dangerous: bool = False,
60
64
  devmode: bool = False,
61
65
  ignore_validation: bool = False,
62
66
  jailmode: bool = False,
@@ -73,6 +77,7 @@ class SnapsEndpoints:
73
77
  snap (str): name of the snap to install
74
78
  channel (str, optional): Channel to install. Defaults to "stable".
75
79
  classic (bool, optional): Install with classic confinement. Defaults to False.
80
+ dangerous (bool, optional): install the given snap files even if there are no pre-acknowledged signatures for them, meaning they are not verified and could be dangerous if true (optional, implied by devmode). Defaults to False.
76
81
  devmode (bool, optional): Install with devmode. Defaults to False.
77
82
  ignore_validation (bool, optional): _description_. Defaults to False.
78
83
  jailmode (bool, optional): Install snap with jailmode. Defaults to False.
@@ -90,6 +95,7 @@ class SnapsEndpoints:
90
95
  "action": "install",
91
96
  "channel": channel,
92
97
  "classic": classic,
98
+ "dangerous": dangerous,
93
99
  "devmode": devmode,
94
100
  "ignore_validation": ignore_validation,
95
101
  "jailmode": jailmode,
@@ -100,7 +106,10 @@ class SnapsEndpoints:
100
106
  # sideload
101
107
  if not Path(filename).exists():
102
108
  raise FileNotFoundError(f"File {filename} does not exist")
103
- request_data["dangerous"] = True
109
+ if request_data.get("dangerous") is not True:
110
+ raise ValueError(
111
+ "Cannot sideload snap without dangerous flag set to True"
112
+ )
104
113
  raw_response: httpx.Response = await self._client.request(
105
114
  "POST",
106
115
  f"{self.common_endpoint}",
@@ -62,3 +62,39 @@ class Media(BaseModel):
62
62
 
63
63
  class AsyncResponse(BaseResponse):
64
64
  change: str
65
+
66
+
67
+ class Download(BaseModel):
68
+ model_config = ConfigDict(extra="forbid", exclude_unset=True)
69
+
70
+ size: float
71
+
72
+
73
+ class Revision(BaseModel):
74
+ model_config = ConfigDict(extra="forbid", exclude_unset=True)
75
+
76
+ base: Optional[SnapBaseVersion] = None
77
+ channel: Optional[str] = None
78
+ common_ids: Optional[list[str]] = Field(
79
+ None, validation_alias=AliasChoices("common-ids", "common_ids")
80
+ )
81
+ confinement: Optional[SnapConfinement] = None
82
+ download: Optional[Download] = None
83
+ revision: Optional[int | str] = None
84
+ type: Optional[str] = None
85
+ version: Optional[str] = None
86
+
87
+
88
+ class Publisher(BaseModel):
89
+ model_config = ConfigDict(extra="forbid", exclude_unset=True)
90
+
91
+ display_name: str = Field(
92
+ ...,
93
+ validation_alias=AliasChoices("display-name", "display_name"),
94
+ description="Display name corresponding to the publisher.",
95
+ )
96
+ id: str = Field(..., description="The publisher id.")
97
+ username: str = Field(..., description="Username belonging to the publisher.")
98
+ validation: Optional[str] = Field(
99
+ None, description="Indicates if the account has been validated."
100
+ )
@@ -1,27 +1,33 @@
1
+ from typing import Optional
2
+
1
3
  from pydantic import AliasChoices, AwareDatetime, BaseModel, ConfigDict, Field
2
4
 
3
5
  from snap_python.schemas.common import (
4
6
  BaseErrorResult,
5
7
  BaseResponse,
6
8
  Media,
9
+ Publisher,
10
+ Revision,
7
11
  SnapApp,
8
- SnapBaseVersion,
9
- SnapConfinement,
10
12
  )
13
+ from snap_python.schemas.store.categories import Category
11
14
 
12
15
 
13
- class Snap(BaseModel):
14
- model_config = ConfigDict(extra="allow")
16
+ class SnapHealth(BaseModel):
17
+ status: str
18
+ revision: str
19
+ timestamp: AwareDatetime
20
+
21
+
22
+ class InstalledSnapFields(Revision):
15
23
  apps: list[SnapApp] = Field(default_factory=list)
16
- base: SnapBaseVersion | None = None
17
- channel: str
18
- confinement: SnapConfinement
19
- contact: str
20
- description: str
21
- developer: str
22
- devmode: bool
23
- icon: str | None = None
24
- id: str
24
+ developer: Optional[str] = None
25
+ devmode: Optional[bool] = None
26
+ health: Optional[SnapHealth] = None
27
+ icon: Optional[str] = None
28
+ id: Optional[str] = Field(
29
+ None, validation_alias=AliasChoices("id", "snap-id"), serialization_alias="id"
30
+ )
25
31
  ignore_validation: bool = Field(
26
32
  validation_alias=AliasChoices("ignore-validation", "ignore_validation"),
27
33
  serialization_alias="ignore-validation",
@@ -35,36 +41,60 @@ class Snap(BaseModel):
35
41
  serialization_alias="installed-size",
36
42
  )
37
43
  jailmode: bool
38
- license: str = "unset"
39
- links: dict[str, list[str]] | None = None
40
- media: list[Media] = Field(default_factory=list)
41
44
  mounted_from: str = Field(
42
45
  validation_alias=AliasChoices("mounted-from", "mounted_from"),
43
46
  serialization_alias="mounted-from",
44
47
  )
45
- name: str
46
- private: bool
47
- publisher: dict[str, str] | None = None
48
- revision: str
49
48
  status: str
50
- summary: str
51
- title: str | None = None
52
- tracking_channel: str | None = Field(
49
+ tracking_channel: Optional[str] = Field(
53
50
  validation_alias=AliasChoices("tracking-channel", "tracking_channel"),
54
51
  serialization_alias="tracking-channel",
55
52
  default=None,
56
53
  )
57
- type: str
58
- version: str
59
- website: str | None = None
60
54
 
61
55
 
62
- class SingleSnapResponse(BaseResponse):
63
- result: Snap | BaseErrorResult
56
+ class StoreSnapFields(BaseModel):
57
+ gated_snap_ids: Optional[list[str]] = Field(
58
+ None, alias=AliasChoices("gated-snap-ids", "gated_snap_ids")
59
+ )
60
+ categories: Optional[list[Category]] = None
61
+ prices: Optional[dict[str, str]] = None
62
+ snap_id: Optional[str] = Field(None, alias=AliasChoices("snap-id", "snap_id"))
63
+ store_url: Optional[str] = Field(None, alias=AliasChoices("store-url", "store_url"))
64
+ trending: Optional[bool] = None
65
+
66
+
67
+ class Snap(BaseModel):
68
+ model_config = ConfigDict(extra="forbid")
69
+
70
+ contact: Optional[str] = None
71
+ description: Optional[str] = None
72
+ license: str = "unset"
73
+ links: Optional[dict[str, list[str]]] = None
74
+ media: Optional[list[Media]] = None
75
+ name: Optional[str] = None
76
+ private: Optional[bool] = None
77
+ publisher: Optional[Publisher] = Field(None, description="The publisher.")
78
+ summary: Optional[str] = None
79
+ title: Optional[str] = None
80
+ unlisted: Optional[bool] = None
81
+ website: Optional[str] = None
82
+
83
+
84
+ class StoreSnap(Snap, StoreSnapFields):
85
+ pass
86
+
87
+
88
+ class InstalledSnap(Snap, InstalledSnapFields):
89
+ pass
90
+
91
+
92
+ class SingleInstalledSnapResponse(BaseResponse):
93
+ result: InstalledSnap | BaseErrorResult
64
94
 
65
95
 
66
- class SnapListResponse(BaseResponse):
67
- result: list[Snap]
96
+ class InstalledSnapListResponse(BaseResponse):
97
+ result: list[InstalledSnap]
68
98
 
69
99
  def __len__(self):
70
100
  return len(self.result)
@@ -6,7 +6,7 @@ from typing import List, Optional
6
6
 
7
7
  from pydantic import AwareDatetime, BaseModel, ConfigDict, Field
8
8
 
9
- from snap_python.schemas.store.search import Snap
9
+ from snap_python.schemas.snaps import StoreSnap
10
10
 
11
11
  VALID_SNAP_INFO_FIELDS = [
12
12
  "architectures",
@@ -130,5 +130,5 @@ class InfoResponse(BaseModel):
130
130
  default_track: Optional[str] = Field(None, alias="default-track")
131
131
  error_list: Optional[List[ErrorListItem]] = Field(None, alias="error-list")
132
132
  name: str
133
- snap: Snap
133
+ snap: StoreSnap
134
134
  snap_id: str = Field(..., alias="snap-id")
@@ -1,13 +1,9 @@
1
- # generated by datamodel-codegen:
2
- # filename: snaps_find_schema.json
3
- # timestamp: 2024-09-17T01:18:24+00:00
4
-
5
-
6
1
  from typing import Any, Dict, List, Optional
7
2
 
8
3
  from pydantic import AliasChoices, AwareDatetime, BaseModel, ConfigDict, Field
9
4
 
10
- from snap_python.schemas.store.categories import Category, Media
5
+ from snap_python.schemas.common import Revision
6
+ from snap_python.schemas.snaps import InstalledSnap, Snap, StoreSnap, StoreSnapFields
11
7
 
12
8
  VALID_SEARCH_CATEGORY_FIELDS = [
13
9
  "base",
@@ -40,65 +36,6 @@ class ErrorListItem(BaseModel):
40
36
  message: str
41
37
 
42
38
 
43
- class Download(BaseModel):
44
- model_config = ConfigDict(extra="forbid", exclude_unset=True)
45
-
46
- size: float
47
-
48
-
49
- class Revision(BaseModel):
50
- model_config = ConfigDict(extra="forbid", exclude_unset=True)
51
-
52
- base: Optional[str] = None
53
- channel: Optional[str] = None
54
- common_ids: Optional[List[str]] = Field(None, alias="common-ids")
55
- confinement: Optional[str] = None
56
- download: Optional[Download] = None
57
- revision: Optional[float] = None
58
- type: Optional[str] = None
59
- version: Optional[str] = None
60
-
61
-
62
- class Publisher(BaseModel):
63
- model_config = ConfigDict(extra="forbid", exclude_unset=True)
64
-
65
- display_name: str = Field(
66
- ...,
67
- alias="display-name",
68
- description="Display name corresponding to the publisher.",
69
- )
70
- id: str = Field(..., description="The publisher id.")
71
- username: str = Field(..., description="Username belonging to the publisher.")
72
- validation: Optional[str] = Field(
73
- None, description="Indicates if the account has been validated."
74
- )
75
-
76
-
77
- class Snap(BaseModel):
78
- model_config = ConfigDict(extra="forbid", exclude_unset=True)
79
-
80
- categories: Optional[List[Category]] = None
81
- contact: Optional[str] = None
82
- description: Optional[str] = ""
83
- gated_snap_ids: Optional[List[str]] = Field(
84
- None, alias=AliasChoices("gated-snap-ids", "gated_snap_ids")
85
- )
86
- license: Optional[str] = None
87
- links: Optional[Dict[str, Any]] = None
88
- media: Optional[List[Media]] = None
89
- name: Optional[str] = None
90
- prices: Optional[Dict[str, Any]] = None
91
- private: Optional[bool] = None
92
- publisher: Optional[Publisher] = Field(None, description="The publisher.")
93
- snap_id: Optional[str] = Field(None, alias=AliasChoices("snap-id", "snap_id"))
94
- store_url: Optional[str] = Field(None, alias=AliasChoices("store-url", "store_url"))
95
- summary: Optional[str] = None
96
- title: Optional[str] = None
97
- trending: Optional[bool] = None
98
- unlisted: Optional[bool] = None
99
- website: Optional[str] = None
100
-
101
-
102
39
  class SnapDetails(BaseModel):
103
40
  aliases: Optional[List[Dict]] = None
104
41
  anon_download_url: str
@@ -145,16 +82,41 @@ class SnapDetails(BaseModel):
145
82
 
146
83
 
147
84
  class SearchResult(BaseModel):
148
- model_config = ConfigDict(extra="forbid", exclude_unset=True, exclude_none=True)
85
+ model_config = ConfigDict(extra="forbid")
149
86
 
150
87
  name: str
151
88
  revision: Optional[Revision] = None
152
- snap: Snap
89
+ snap: StoreSnap
153
90
  snap_id: str = Field(alias=AliasChoices("snap-id", "snap_id"))
154
91
 
92
+ @classmethod
93
+ def from_installed_snap(cls, installed_snap: InstalledSnap):
94
+ snap_info: dict = installed_snap.model_dump(mode="json")
95
+ revision_snap_fields = Revision.model_fields.keys()
96
+ revision_info = {}
97
+ for field in revision_snap_fields:
98
+ if field in snap_info:
99
+ revision_info[field] = snap_info.pop(field)
100
+
101
+ snap_id = snap_info.pop("id")
102
+ snap_name = snap_info.get("name")
103
+
104
+ # get set of acceptable fields for StoreSnap
105
+ store_snap_fields = set(StoreSnapFields.model_fields.keys()).union(
106
+ Snap.model_fields.keys()
107
+ )
108
+ store_snap_info = {
109
+ key: value for key, value in snap_info.items() if key in store_snap_fields
110
+ }
111
+
112
+ snap_info = {"snap": store_snap_info, "snap-id": snap_id, "name": snap_name}
113
+ snap_info["revision"] = revision_info
114
+
115
+ return cls.model_validate(snap_info)
116
+
155
117
 
156
118
  class SearchResponse(BaseModel):
157
- model_config = ConfigDict(extra="forbid", exclude_unset=True, exclude_none=True)
119
+ model_config = ConfigDict(extra="forbid")
158
120
 
159
121
  error_list: Optional[List[ErrorListItem]] = Field(None, alias="error-list")
160
122
  results: List[SearchResult]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: snap-python
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: An api to interact with snapd and snap stores
5
5
  Home-page: https://github.com/alexlukens/snap-python
6
6
  License: MIT
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
15
16
  Requires-Dist: httpx (>=0.27.2,<0.28.0)
16
17
  Requires-Dist: pydantic (>=2.9.2,<3.0.0)
17
18
  Requires-Dist: retry (>=0.9.2,<0.10.0)
@@ -38,6 +39,15 @@ Here is a simple example of how to use snap_python:
38
39
  # TODO
39
40
  ```
40
41
 
42
+ ## Create a Release
43
+
44
+ ```bash
45
+ git tag -a v0.x.x -m "Release v0.x.x"
46
+ gh release create v0.x.x --title "Release v0.x.x"
47
+ ```
48
+
49
+ The automation will build a wheel and push it to pypi
50
+
41
51
  ## License
42
52
 
43
53
  This project is licensed under the MIT License. See the LICENSE file for more details.
@@ -1,23 +1,23 @@
1
1
  snap_python/__init__.py,sha256=gV43Nempk7HFKD1UCvxUhmP9x6dBmPjotcITREr81Vg,222
2
- snap_python/client.py,sha256=PsmO9EIszUsYc6zYOOijFjFr1jdHQR2dxl3by2bWUMo,5150
2
+ snap_python/client.py,sha256=skmAjn_-aOrnV5aRw74vRwLbYDi-dqaV4mNSa69_JA4,5369
3
3
  snap_python/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  snap_python/components/config.py,sha256=IO_gCrumphEZioINFmXxb8I5OVsKC9cgSa28sQbZ700,1775
5
- snap_python/components/snaps.py,sha256=Hw1dPlgmkAo9q3YQUOnCa3Dz6b7lnA5JkkdZF71HSrs,6631
5
+ snap_python/components/snaps.py,sha256=hY054pk7CwMvannJcuHG_MxYCUsvUs-gXAWvsB8o40M,7150
6
6
  snap_python/components/store.py,sha256=_o_2OYMvGbq_bLEQXw9zHjBrCipQ92qZaSkwdAfngAc,6451
7
7
  snap_python/schemas/changes.py,sha256=7SYUFXAQq9arl7hAA3YQ0YubMurPJtknTOVxP8ryqew,1919
8
- snap_python/schemas/common.py,sha256=n0npzv0CAaSK6GPY1Y9PxywWRR0KE7Wf2JML8G5e9Dk,1459
8
+ snap_python/schemas/common.py,sha256=GLtC9aBo3O8vLKX9PNvzE-_DKN02OGpwsS7Ik2WK9-8,2608
9
9
  snap_python/schemas/config.py,sha256=xXTGhk6TpwI8RlR6_qnfWlBHrBnTwr3hRCO5qCnN-6U,107
10
- snap_python/schemas/snaps.py,sha256=L6iXB4j9MiSR57siIkxP5MbB7I0wmDBC1dHmGs0QkY8,1960
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=AKe93ichq8gOIQQxSaEEWHmFHeeONOqe5hfOADJP1gU,3330
13
+ snap_python/schemas/store/info.py,sha256=TuqG5wZc89e_Epai1xkcpA1nXNMKBOKng0B40xm_PfU,3333
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=ewsc00BV4S1AIZMCj-HCvB7lOznGpWImHv7YZbkRBhc,4496
18
+ snap_python/schemas/store/search.py,sha256=z0SutS4LQJybrNY9WqgU5CWOXJi-VeL2K3EKxveFpQc,3300
19
19
  snap_python/utils.py,sha256=pE-mVtoq5KMZH_Xg1O9lJlAmktLyuIdAVBEomuLZjWI,752
20
- snap_python-0.0.3.dist-info/LICENSE,sha256=lYXNZszxC_l2rlBvrHYs-esDegxWh8MKtjrdxA24uOk,1072
21
- snap_python-0.0.3.dist-info/METADATA,sha256=N-hc-RSJiouLpnb7QGXB2OAHUBdrU_YctJalRJqfxY8,1096
22
- snap_python-0.0.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
23
- snap_python-0.0.3.dist-info/RECORD,,
20
+ snap_python-0.0.5.dist-info/LICENSE,sha256=lYXNZszxC_l2rlBvrHYs-esDegxWh8MKtjrdxA24uOk,1072
21
+ snap_python-0.0.5.dist-info/METADATA,sha256=g7i8TJybXyJDFEJHDj5Ix5f_9yelR9mpFa75pjhUutQ,1324
22
+ snap_python-0.0.5.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
23
+ snap_python-0.0.5.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any