snap-python 0.0.2__py3-none-any.whl → 0.0.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.
snap_python/__init__.py CHANGED
@@ -5,14 +5,3 @@ log_level = os.getenv("SNAP_PYTHON_LOG_LEVEL", "DEBUG").upper()
5
5
  numeric_level = getattr(logging, log_level, logging.DEBUG)
6
6
  logger = logging.getLogger("snap_python")
7
7
  logger.setLevel(numeric_level)
8
-
9
- ch = logging.StreamHandler()
10
- ch.setLevel(numeric_level)
11
- formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
12
- ch.setFormatter(formatter)
13
- logger.addHandler(ch)
14
-
15
- fh = logging.FileHandler("snap_python.log")
16
- fh.setLevel(numeric_level)
17
- fh.setFormatter(formatter)
18
- logger.addHandler(fh)
snap_python/client.py CHANGED
@@ -18,6 +18,8 @@ logger.setLevel(logging.DEBUG)
18
18
 
19
19
 
20
20
  class SnapClient(AbstractSnapsClient):
21
+ """Core class for interacting with the Snap Store and Snapd."""
22
+
21
23
  def __init__(
22
24
  self,
23
25
  version: str = "v2",
@@ -56,6 +58,18 @@ class SnapClient(AbstractSnapsClient):
56
58
 
57
59
  @retry(httpx.HTTPError, tries=3, delay=1, backoff=1)
58
60
  async def request(self, method: str, endpoint: str, **kwargs) -> httpx.Response:
61
+ """
62
+ Sends an HTTP request to the specified endpoint using the given method and parameters.
63
+ Args:
64
+ method (str): The HTTP method to use for the request (e.g., 'GET', 'POST').
65
+ endpoint (str): The API endpoint to send the request to.
66
+ **kwargs: Additional keyword arguments to pass to the request.
67
+ Returns:
68
+ httpx.Response: The response object from the HTTP request.
69
+ Raises:
70
+ httpx.HTTPStatusError: If the response contains an HTTP status code indicating an error.
71
+ """
72
+
59
73
  response = await self.snapd_client.request(
60
74
  method, f"{self._base_url}/{self.version}/{endpoint}", **kwargs
61
75
  )
@@ -64,6 +78,18 @@ class SnapClient(AbstractSnapsClient):
64
78
 
65
79
  @retry(httpx.HTTPError, tries=3, delay=1, backoff=1)
66
80
  async def request_raw(self, method: str, endpoint: str, **kwargs) -> httpx.Response:
81
+ """
82
+ Sends an HTTP request using the specified method and endpoint.
83
+ Args:
84
+ method (str): The HTTP method to use for the request (e.g., 'GET', 'POST').
85
+ endpoint (str): The endpoint URL to send the request to.
86
+ **kwargs: Additional keyword arguments to pass to the request.
87
+ Returns:
88
+ httpx.Response: The response object resulting from the HTTP request.
89
+ Raises:
90
+ httpx.HTTPStatusError: If the response contains an HTTP status code indicating an error.
91
+ """
92
+
67
93
  response = await self.snapd_client.request(method, endpoint, **kwargs)
68
94
 
69
95
  response.raise_for_status()
@@ -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)
@@ -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,26 @@
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 InstalledSnapFields(Revision):
15
17
  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
18
+ developer: Optional[str] = None
19
+ devmode: Optional[bool] = None
20
+ icon: Optional[str] = None
21
+ id: Optional[str] = Field(
22
+ None, validation_alias=AliasChoices("id", "snap-id"), serialization_alias="id"
23
+ )
25
24
  ignore_validation: bool = Field(
26
25
  validation_alias=AliasChoices("ignore-validation", "ignore_validation"),
27
26
  serialization_alias="ignore-validation",
@@ -35,36 +34,60 @@ class Snap(BaseModel):
35
34
  serialization_alias="installed-size",
36
35
  )
37
36
  jailmode: bool
38
- license: str = "unset"
39
- links: dict[str, list[str]] | None = None
40
- media: list[Media] = Field(default_factory=list)
41
37
  mounted_from: str = Field(
42
38
  validation_alias=AliasChoices("mounted-from", "mounted_from"),
43
39
  serialization_alias="mounted-from",
44
40
  )
45
- name: str
46
- private: bool
47
- publisher: dict[str, str] | None = None
48
- revision: str
49
41
  status: str
50
- summary: str
51
- title: str | None = None
52
- tracking_channel: str | None = Field(
42
+ tracking_channel: Optional[str] = Field(
53
43
  validation_alias=AliasChoices("tracking-channel", "tracking_channel"),
54
44
  serialization_alias="tracking-channel",
55
45
  default=None,
56
46
  )
57
- type: str
58
- version: str
59
- website: str | None = None
60
47
 
61
48
 
62
- class SingleSnapResponse(BaseResponse):
63
- result: Snap | BaseErrorResult
49
+ class StoreSnapFields(BaseModel):
50
+ gated_snap_ids: Optional[list[str]] = Field(
51
+ None, alias=AliasChoices("gated-snap-ids", "gated_snap_ids")
52
+ )
53
+ categories: Optional[list[Category]] = None
54
+ prices: Optional[dict[str, str]] = None
55
+ snap_id: Optional[str] = Field(None, alias=AliasChoices("snap-id", "snap_id"))
56
+ store_url: Optional[str] = Field(None, alias=AliasChoices("store-url", "store_url"))
57
+ trending: Optional[bool] = None
58
+
59
+
60
+ class Snap(BaseModel):
61
+ model_config = ConfigDict(extra="forbid")
62
+
63
+ contact: Optional[str] = None
64
+ description: Optional[str] = None
65
+ license: str = "unset"
66
+ links: Optional[dict[str, list[str]]] = None
67
+ media: Optional[list[Media]] = None
68
+ name: Optional[str] = None
69
+ private: Optional[bool] = None
70
+ publisher: Optional[Publisher] = Field(None, description="The publisher.")
71
+ summary: Optional[str] = None
72
+ title: Optional[str] = None
73
+ unlisted: Optional[bool] = None
74
+ website: Optional[str] = None
75
+
76
+
77
+ class StoreSnap(Snap, StoreSnapFields):
78
+ pass
79
+
80
+
81
+ class InstalledSnap(Snap, InstalledSnapFields):
82
+ pass
83
+
84
+
85
+ class SingleInstalledSnapResponse(BaseResponse):
86
+ result: InstalledSnap | BaseErrorResult
64
87
 
65
88
 
66
- class SnapListResponse(BaseResponse):
67
- result: list[Snap]
89
+ class InstalledSnapListResponse(BaseResponse):
90
+ result: list[InstalledSnap]
68
91
 
69
92
  def __len__(self):
70
93
  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, 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,39 @@ 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.pop("name")
103
+
104
+ # get set of acceptable fields for StoreSnap
105
+ store_snap_fields = StoreSnapFields.model_fields.keys()
106
+ snap_info = {
107
+ key: value for key, value in snap_info.items() if key in store_snap_fields
108
+ }
109
+
110
+ snap_info = {"snap": snap_info, "snap-id": snap_id, "name": snap_name}
111
+ snap_info["revision"] = revision_info
112
+
113
+ return cls.model_validate(snap_info)
114
+
155
115
 
156
116
  class SearchResponse(BaseModel):
157
- model_config = ConfigDict(extra="forbid", exclude_unset=True, exclude_none=True)
117
+ model_config = ConfigDict(extra="forbid")
158
118
 
159
119
  error_list: Optional[List[ErrorListItem]] = Field(None, alias="error-list")
160
120
  results: List[SearchResult]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: snap-python
3
- Version: 0.0.2
3
+ Version: 0.0.4
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
@@ -1,23 +1,23 @@
1
- snap_python/__init__.py,sha256=wsEFXtK9EflFT0l7EuICfsX31OgUq--7cpQ-YXiWCbc,535
2
- snap_python/client.py,sha256=a5xyEC3RzFEiMr-p_1KBFhDfCFROuErSMkmqTh8p2fQ,3953
1
+ snap_python/__init__.py,sha256=gV43Nempk7HFKD1UCvxUhmP9x6dBmPjotcITREr81Vg,222
2
+ snap_python/client.py,sha256=PsmO9EIszUsYc6zYOOijFjFr1jdHQR2dxl3by2bWUMo,5150
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=RkCxxHfArWATx7bsNst4v0yvJPqVQtaKJkLm_bO70qE,6698
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=y1v-FjZ4ZpcqmLxXbULP6Wa85RMPjXQpbKGf0CEHVIg,2846
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=GFfK8qfYnCD1UziU5kWmmjac03qNYWWkmIGcKZoSb90,3223
19
19
  snap_python/utils.py,sha256=pE-mVtoq5KMZH_Xg1O9lJlAmktLyuIdAVBEomuLZjWI,752
20
- snap_python-0.0.2.dist-info/LICENSE,sha256=lYXNZszxC_l2rlBvrHYs-esDegxWh8MKtjrdxA24uOk,1072
21
- snap_python-0.0.2.dist-info/METADATA,sha256=azhCpe9YohlK3uhjIdY_wgDUdAid7tPNQtcakwpoZ7Q,1096
22
- snap_python-0.0.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
23
- snap_python-0.0.2.dist-info/RECORD,,
20
+ snap_python-0.0.4.dist-info/LICENSE,sha256=lYXNZszxC_l2rlBvrHYs-esDegxWh8MKtjrdxA24uOk,1072
21
+ snap_python-0.0.4.dist-info/METADATA,sha256=CsfubdQSuEypyBzgGd42EHi1EyNmjwO78b88DEsh1RI,1096
22
+ snap_python-0.0.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
23
+ snap_python-0.0.4.dist-info/RECORD,,