snap-python 0.0.1__tar.gz → 0.0.3__tar.gz
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-0.0.1 → snap_python-0.0.3}/PKG-INFO +7 -3
- {snap_python-0.0.1 → snap_python-0.0.3}/pyproject.toml +6 -3
- snap_python-0.0.3/src/snap_python/__init__.py +7 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/client.py +53 -1
- snap_python-0.0.3/src/snap_python/components/config.py +54 -0
- snap_python-0.0.3/src/snap_python/components/snaps.py +175 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/schemas/changes.py +23 -2
- snap_python-0.0.3/src/snap_python/schemas/config.py +5 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/schemas/snaps.py +8 -2
- {snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/utils.py +13 -0
- snap_python-0.0.1/src/snap_python/components/snaps.py +0 -92
- snap_python-0.0.1/src/snap_python/schemas/store/__init__.py +0 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/LICENSE +0 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/README.md +0 -0
- {snap_python-0.0.1/src/snap_python → snap_python-0.0.3/src/snap_python/components}/__init__.py +0 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/components/store.py +0 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/schemas/common.py +0 -0
- {snap_python-0.0.1/src/snap_python/components → snap_python-0.0.3/src/snap_python/schemas/store}/__init__.py +0 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/schemas/store/categories.py +0 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/schemas/store/info.py +0 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/schemas/store/json/category_by_name_schema.json +0 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/schemas/store/json/category_schema.json +0 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/schemas/store/json/snaps_find_schema.json +0 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/schemas/store/json/snaps_info_schema.json +0 -0
- {snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/schemas/store/search.py +0 -0
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: snap-python
|
|
3
|
-
Version: 0.0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: An api to interact with snapd and snap stores
|
|
5
|
+
Home-page: https://github.com/alexlukens/snap-python
|
|
5
6
|
License: MIT
|
|
6
7
|
Author: Alexander Lukens
|
|
7
8
|
Author-email: alexdlukens@gmail.com
|
|
8
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
9
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
15
|
Requires-Dist: httpx (>=0.27.2,<0.28.0)
|
|
13
16
|
Requires-Dist: pydantic (>=2.9.2,<3.0.0)
|
|
14
17
|
Requires-Dist: retry (>=0.9.2,<0.10.0)
|
|
18
|
+
Project-URL: Repository, https://github.com/alexlukens/snap-python
|
|
15
19
|
Description-Content-Type: text/markdown
|
|
16
20
|
|
|
17
21
|
# snap_python
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "snap-python"
|
|
3
|
-
version = "0.0.
|
|
4
|
-
description = ""
|
|
3
|
+
version = "0.0.3"
|
|
4
|
+
description = "An api to interact with snapd and snap stores"
|
|
5
5
|
authors = ["Alexander Lukens <alexdlukens@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
7
7
|
readme = "README.md"
|
|
8
|
+
repository = "https://github.com/alexlukens/snap-python"
|
|
8
9
|
|
|
9
10
|
[tool.poetry.dependencies]
|
|
10
|
-
python = "^3.
|
|
11
|
+
python = "^3.10"
|
|
11
12
|
httpx = "^0.27.2"
|
|
12
13
|
pydantic = "^2.9.2"
|
|
13
14
|
retry = "^0.9.2"
|
|
@@ -20,6 +21,8 @@ ruff = "^0.7.4"
|
|
|
20
21
|
pre-commit = "^4.0.1"
|
|
21
22
|
ipykernel = "^6.29.5"
|
|
22
23
|
pytest-cov = "^6.0.0"
|
|
24
|
+
sphinx = "^8.1.3"
|
|
25
|
+
sphinx-rtd-theme = "^3.0.2"
|
|
23
26
|
|
|
24
27
|
[build-system]
|
|
25
28
|
requires = ["poetry-core"]
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import logging
|
|
3
|
+
from typing import AsyncGenerator
|
|
2
4
|
|
|
3
5
|
import httpx
|
|
6
|
+
from retry import retry
|
|
4
7
|
|
|
8
|
+
from snap_python.components.config import ConfigEndpoints
|
|
5
9
|
from snap_python.components.snaps import SnapsEndpoints
|
|
6
10
|
from snap_python.components.store import StoreEndpoints
|
|
7
11
|
from snap_python.schemas.changes import ChangesResponse
|
|
@@ -14,6 +18,8 @@ logger.setLevel(logging.DEBUG)
|
|
|
14
18
|
|
|
15
19
|
|
|
16
20
|
class SnapClient(AbstractSnapsClient):
|
|
21
|
+
"""Core class for interacting with the Snap Store and Snapd."""
|
|
22
|
+
|
|
17
23
|
def __init__(
|
|
18
24
|
self,
|
|
19
25
|
version: str = "v2",
|
|
@@ -48,16 +54,42 @@ class SnapClient(AbstractSnapsClient):
|
|
|
48
54
|
version=self.version,
|
|
49
55
|
headers=self.store_headers,
|
|
50
56
|
)
|
|
57
|
+
self.config = ConfigEndpoints(self)
|
|
51
58
|
|
|
59
|
+
@retry(httpx.HTTPError, tries=3, delay=1, backoff=1)
|
|
52
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
|
+
|
|
53
73
|
response = await self.snapd_client.request(
|
|
54
74
|
method, f"{self._base_url}/{self.version}/{endpoint}", **kwargs
|
|
55
75
|
)
|
|
56
|
-
logger.debug(f"response: {response.content}")
|
|
57
76
|
response.raise_for_status()
|
|
58
77
|
return response
|
|
59
78
|
|
|
79
|
+
@retry(httpx.HTTPError, tries=3, delay=1, backoff=1)
|
|
60
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
|
+
|
|
61
93
|
response = await self.snapd_client.request(method, endpoint, **kwargs)
|
|
62
94
|
|
|
63
95
|
response.raise_for_status()
|
|
@@ -84,3 +116,23 @@ class SnapClient(AbstractSnapsClient):
|
|
|
84
116
|
pass
|
|
85
117
|
|
|
86
118
|
return ChangesResponse.model_validate_json(response.content)
|
|
119
|
+
|
|
120
|
+
async def get_changes_by_id_generator(
|
|
121
|
+
self, change_id: str
|
|
122
|
+
) -> AsyncGenerator[ChangesResponse, None]:
|
|
123
|
+
while True:
|
|
124
|
+
try:
|
|
125
|
+
response = await self.request("GET", f"changes/{change_id}")
|
|
126
|
+
except httpx.HTTPStatusError as e:
|
|
127
|
+
logger.warning(
|
|
128
|
+
f"Bad response status code for get_changes_by_id: {e.response.status_code}"
|
|
129
|
+
)
|
|
130
|
+
response = e.response
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
change_response = ChangesResponse.model_validate_json(response.content)
|
|
134
|
+
yield change_response
|
|
135
|
+
|
|
136
|
+
await asyncio.sleep(
|
|
137
|
+
0.01
|
|
138
|
+
) # Add a delay to avoid spamming the server with requests
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from snap_python.schemas.changes import ChangesResponse
|
|
7
|
+
from snap_python.schemas.common import AsyncResponse
|
|
8
|
+
from snap_python.schemas.config import ConfigResponse
|
|
9
|
+
from snap_python.utils import AbstractSnapsClient
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("snap_python.components.snaps")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConfigEndpoints:
|
|
15
|
+
def __init__(self, client: AbstractSnapsClient) -> None:
|
|
16
|
+
self._client = client
|
|
17
|
+
self.common_endpoint = "snaps"
|
|
18
|
+
|
|
19
|
+
async def get_configuration(
|
|
20
|
+
self, snap: str, keys: list[str] | None = None
|
|
21
|
+
) -> ConfigResponse:
|
|
22
|
+
if keys:
|
|
23
|
+
response: httpx.Response = await self._client.request(
|
|
24
|
+
"GET",
|
|
25
|
+
f"{self.common_endpoint}/{snap}/conf",
|
|
26
|
+
params={"keys": ",".join(keys)},
|
|
27
|
+
)
|
|
28
|
+
else:
|
|
29
|
+
response: httpx.Response = await self._client.request(
|
|
30
|
+
"GET", f"{self.common_endpoint}/{snap}/conf"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return ConfigResponse.model_validate_json(response.content)
|
|
34
|
+
|
|
35
|
+
async def set_configuration(
|
|
36
|
+
self, snap: str, configuration: dict, wait: bool = True
|
|
37
|
+
) -> AsyncResponse | ChangesResponse:
|
|
38
|
+
# TODO: should check that snap supports configure hook before trying to set configuration
|
|
39
|
+
response: httpx.Response = await self._client.request(
|
|
40
|
+
"PUT", f"{self.common_endpoint}/{snap}/conf", json=configuration
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
async_response = AsyncResponse.model_validate_json(response.content)
|
|
44
|
+
if not wait:
|
|
45
|
+
return async_response
|
|
46
|
+
while True:
|
|
47
|
+
changes = await self._client.get_changes_by_id(async_response.change)
|
|
48
|
+
|
|
49
|
+
if changes.ready:
|
|
50
|
+
break
|
|
51
|
+
|
|
52
|
+
await asyncio.sleep(0.1)
|
|
53
|
+
|
|
54
|
+
return changes
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from snap_python.schemas.changes import ChangesResponse
|
|
8
|
+
from snap_python.schemas.common import AsyncResponse
|
|
9
|
+
from snap_python.schemas.snaps import SingleSnapResponse, SnapListResponse
|
|
10
|
+
from snap_python.utils import AbstractSnapsClient, going_to_reload_daemon
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("snap_python.components.snaps")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SnapsEndpoints:
|
|
16
|
+
def __init__(self, client: AbstractSnapsClient) -> None:
|
|
17
|
+
self._client = client
|
|
18
|
+
self.common_endpoint = "snaps"
|
|
19
|
+
|
|
20
|
+
async def list_installed_snaps(self) -> SnapListResponse:
|
|
21
|
+
response: httpx.Response = await self._client.request(
|
|
22
|
+
"GET", self.common_endpoint
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
response = SnapListResponse.model_validate_json(response.content)
|
|
26
|
+
if response.status_code > 299:
|
|
27
|
+
raise httpx.HTTPStatusError(
|
|
28
|
+
request=response.request,
|
|
29
|
+
response=response,
|
|
30
|
+
message=f"Invalid status code in response: {response.status_code}",
|
|
31
|
+
)
|
|
32
|
+
return response
|
|
33
|
+
|
|
34
|
+
async def get_snap_info(self, snap: str) -> SingleSnapResponse:
|
|
35
|
+
try:
|
|
36
|
+
response: httpx.Response = await self._client.request(
|
|
37
|
+
"GET", f"{self.common_endpoint}/{snap}"
|
|
38
|
+
)
|
|
39
|
+
except httpx.HTTPStatusError as e:
|
|
40
|
+
logger.debug(
|
|
41
|
+
"Bad status code from get_snap_info on snap %s: %s",
|
|
42
|
+
snap,
|
|
43
|
+
e.response.status_code,
|
|
44
|
+
)
|
|
45
|
+
response = e.response
|
|
46
|
+
|
|
47
|
+
return SingleSnapResponse.model_validate_json(response.content)
|
|
48
|
+
|
|
49
|
+
async def is_snap_installed(self, snap: str) -> bool:
|
|
50
|
+
snap_info = await self.get_snap_info(snap)
|
|
51
|
+
if snap_info.status == "OK":
|
|
52
|
+
return True
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
async def install_snap(
|
|
56
|
+
self,
|
|
57
|
+
snap: str,
|
|
58
|
+
channel: str = "stable",
|
|
59
|
+
classic: bool = False,
|
|
60
|
+
devmode: bool = False,
|
|
61
|
+
ignore_validation: bool = False,
|
|
62
|
+
jailmode: bool = False,
|
|
63
|
+
revision: int = None,
|
|
64
|
+
filename: str = None,
|
|
65
|
+
wait: bool = False,
|
|
66
|
+
) -> AsyncResponse | ChangesResponse:
|
|
67
|
+
"""Install or sideload a snap
|
|
68
|
+
|
|
69
|
+
To sideload a snap, provide the filename parameter with the path to the snap file
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
snap (str): name of the snap to install
|
|
74
|
+
channel (str, optional): Channel to install. Defaults to "stable".
|
|
75
|
+
classic (bool, optional): Install with classic confinement. Defaults to False.
|
|
76
|
+
devmode (bool, optional): Install with devmode. Defaults to False.
|
|
77
|
+
ignore_validation (bool, optional): _description_. Defaults to False.
|
|
78
|
+
jailmode (bool, optional): Install snap with jailmode. Defaults to False.
|
|
79
|
+
revision (int, optional): install a specific revision of the snap. Defaults to None.
|
|
80
|
+
filename (str, optional): Path to snap to sideload. Defaults to None.
|
|
81
|
+
wait (bool, optional): Whether to wait for snap to install. If not waiting, will return async response with change id. Defaults to False.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
Exception: If error occurs during snap install
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
AsyncResponse | ChangesResponse: If wait is True, will return ChangesResponse. Otherwise, will return AsyncResponse
|
|
88
|
+
"""
|
|
89
|
+
request_data = {
|
|
90
|
+
"action": "install",
|
|
91
|
+
"channel": channel,
|
|
92
|
+
"classic": classic,
|
|
93
|
+
"devmode": devmode,
|
|
94
|
+
"ignore_validation": ignore_validation,
|
|
95
|
+
"jailmode": jailmode,
|
|
96
|
+
}
|
|
97
|
+
if revision:
|
|
98
|
+
request_data["revision"] = revision
|
|
99
|
+
if filename:
|
|
100
|
+
# sideload
|
|
101
|
+
if not Path(filename).exists():
|
|
102
|
+
raise FileNotFoundError(f"File {filename} does not exist")
|
|
103
|
+
request_data["dangerous"] = True
|
|
104
|
+
raw_response: httpx.Response = await self._client.request(
|
|
105
|
+
"POST",
|
|
106
|
+
f"{self.common_endpoint}",
|
|
107
|
+
data=request_data,
|
|
108
|
+
files={"snap": open(filename, "rb")},
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
# install from default snap store
|
|
112
|
+
raw_response: httpx.Response = await self._client.request(
|
|
113
|
+
"POST", f"{self.common_endpoint}/{snap}", json=request_data
|
|
114
|
+
)
|
|
115
|
+
response = AsyncResponse.model_validate_json(raw_response.content)
|
|
116
|
+
if wait:
|
|
117
|
+
changes_id = response.change
|
|
118
|
+
previous_changes = None
|
|
119
|
+
while True:
|
|
120
|
+
try:
|
|
121
|
+
changes = await self._client.get_changes_by_id(changes_id)
|
|
122
|
+
logger.debug(f"Progress: {changes.result.overall_progress}")
|
|
123
|
+
except httpx.HTTPError as e:
|
|
124
|
+
if going_to_reload_daemon(previous_changes):
|
|
125
|
+
logger.debug("Waiting for daemon to reload")
|
|
126
|
+
await asyncio.sleep(0.1)
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
if changes.ready:
|
|
130
|
+
break
|
|
131
|
+
if changes.result.err:
|
|
132
|
+
raise Exception(f"Error in snap install: {changes.result.err}")
|
|
133
|
+
await asyncio.sleep(0.1)
|
|
134
|
+
previous_changes = changes
|
|
135
|
+
return changes
|
|
136
|
+
return response
|
|
137
|
+
|
|
138
|
+
async def remove_snap(
|
|
139
|
+
self,
|
|
140
|
+
snap: str,
|
|
141
|
+
purge: bool = False,
|
|
142
|
+
terminate: bool = False,
|
|
143
|
+
wait: bool = False,
|
|
144
|
+
) -> AsyncResponse | ChangesResponse:
|
|
145
|
+
request_data = {
|
|
146
|
+
"action": "remove",
|
|
147
|
+
"purge": purge,
|
|
148
|
+
"terminate": terminate,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
raw_response: httpx.Response = await self._client.request(
|
|
152
|
+
"POST", f"{self.common_endpoint}/{snap}", json=request_data
|
|
153
|
+
)
|
|
154
|
+
response = AsyncResponse.model_validate_json(raw_response.content)
|
|
155
|
+
|
|
156
|
+
if wait:
|
|
157
|
+
changes_id = response.change
|
|
158
|
+
previous_changes = None
|
|
159
|
+
while True:
|
|
160
|
+
try:
|
|
161
|
+
changes = await self._client.get_changes_by_id(changes_id)
|
|
162
|
+
except httpx.HTTPError as e:
|
|
163
|
+
if going_to_reload_daemon(previous_changes):
|
|
164
|
+
logger.debug("Waiting for daemon to reload")
|
|
165
|
+
await asyncio.sleep(0.1)
|
|
166
|
+
continue
|
|
167
|
+
if changes.ready:
|
|
168
|
+
break
|
|
169
|
+
if changes.result.err:
|
|
170
|
+
raise Exception(f"Error in snap remove: {changes.result.err}")
|
|
171
|
+
await asyncio.sleep(0.1)
|
|
172
|
+
previous_changes = changes
|
|
173
|
+
return changes
|
|
174
|
+
|
|
175
|
+
return response
|
|
@@ -3,12 +3,23 @@ from pydantic import AliasChoices, AwareDatetime, BaseModel, Field
|
|
|
3
3
|
from snap_python.schemas.common import BaseErrorResult, BaseResponse
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
class MaintenanceInfo(BaseModel):
|
|
7
|
+
kind: str
|
|
8
|
+
message: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProgressInfo(BaseModel):
|
|
12
|
+
label: str
|
|
13
|
+
done: int
|
|
14
|
+
total: int
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
class Task(BaseModel):
|
|
7
18
|
id: str
|
|
8
19
|
kind: str
|
|
9
20
|
summary: str
|
|
10
21
|
status: str
|
|
11
|
-
progress:
|
|
22
|
+
progress: ProgressInfo
|
|
12
23
|
spawn_time: AwareDatetime = Field(
|
|
13
24
|
validation_alias=AliasChoices("spawn-time", "spawn_time"),
|
|
14
25
|
serialization_alias="spawn-time",
|
|
@@ -40,12 +51,22 @@ class ChangesResult(BaseModel):
|
|
|
40
51
|
err: str | None = None
|
|
41
52
|
data: dict | None = None
|
|
42
53
|
|
|
54
|
+
@property
|
|
55
|
+
def overall_progress(self) -> ProgressInfo:
|
|
56
|
+
if not self.tasks:
|
|
57
|
+
return ProgressInfo(label="Overall", done=0, total=-1)
|
|
58
|
+
total = sum(task.progress.total for task in self.tasks)
|
|
59
|
+
done = sum(task.progress.done for task in self.tasks)
|
|
60
|
+
|
|
61
|
+
return ProgressInfo(label="Overall", done=done, total=total)
|
|
62
|
+
|
|
43
63
|
|
|
44
64
|
class ChangesResponse(BaseResponse):
|
|
45
65
|
result: ChangesResult | BaseErrorResult
|
|
66
|
+
maintenance: MaintenanceInfo | None = None
|
|
46
67
|
|
|
47
68
|
@property
|
|
48
69
|
def ready(self) -> bool:
|
|
49
70
|
if isinstance(self.result, BaseErrorResult):
|
|
50
|
-
return
|
|
71
|
+
return True
|
|
51
72
|
return self.result.ready
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from pydantic import AliasChoices, AwareDatetime, BaseModel, ConfigDict, Field
|
|
2
2
|
|
|
3
3
|
from snap_python.schemas.common import (
|
|
4
|
+
BaseErrorResult,
|
|
4
5
|
BaseResponse,
|
|
5
6
|
Media,
|
|
6
7
|
SnapApp,
|
|
@@ -43,20 +44,25 @@ class Snap(BaseModel):
|
|
|
43
44
|
)
|
|
44
45
|
name: str
|
|
45
46
|
private: bool
|
|
46
|
-
publisher: dict[str, str]
|
|
47
|
+
publisher: dict[str, str] | None = None
|
|
47
48
|
revision: str
|
|
48
49
|
status: str
|
|
49
50
|
summary: str
|
|
50
51
|
title: str | None = None
|
|
51
|
-
tracking_channel: str = Field(
|
|
52
|
+
tracking_channel: str | None = Field(
|
|
52
53
|
validation_alias=AliasChoices("tracking-channel", "tracking_channel"),
|
|
53
54
|
serialization_alias="tracking-channel",
|
|
55
|
+
default=None,
|
|
54
56
|
)
|
|
55
57
|
type: str
|
|
56
58
|
version: str
|
|
57
59
|
website: str | None = None
|
|
58
60
|
|
|
59
61
|
|
|
62
|
+
class SingleSnapResponse(BaseResponse):
|
|
63
|
+
result: Snap | BaseErrorResult
|
|
64
|
+
|
|
65
|
+
|
|
60
66
|
class SnapListResponse(BaseResponse):
|
|
61
67
|
result: list[Snap]
|
|
62
68
|
|
|
@@ -17,3 +17,16 @@ class AbstractSnapsClient(ABC): # pragma: no cover
|
|
|
17
17
|
@abstractmethod
|
|
18
18
|
async def get_changes_by_id(self, change_id: str) -> ChangesResponse:
|
|
19
19
|
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def going_to_reload_daemon(changes: ChangesResponse | None) -> bool:
|
|
23
|
+
if changes is None or not isinstance(changes, ChangesResponse):
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
if changes.maintenance and changes.maintenance.kind in [
|
|
27
|
+
"daemon-reload",
|
|
28
|
+
"daemon-restart",
|
|
29
|
+
]:
|
|
30
|
+
return True
|
|
31
|
+
|
|
32
|
+
return False
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
|
|
3
|
-
import httpx
|
|
4
|
-
|
|
5
|
-
from snap_python.schemas.changes import ChangesResponse
|
|
6
|
-
from snap_python.schemas.common import AsyncResponse
|
|
7
|
-
from snap_python.schemas.snaps import SnapListResponse
|
|
8
|
-
from snap_python.utils import AbstractSnapsClient
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class SnapsEndpoints:
|
|
12
|
-
def __init__(self, client: AbstractSnapsClient) -> None:
|
|
13
|
-
self._client = client
|
|
14
|
-
self.common_endpoint = "snaps"
|
|
15
|
-
|
|
16
|
-
async def list_installed_snaps(self) -> SnapListResponse:
|
|
17
|
-
response: httpx.Response = await self._client.request(
|
|
18
|
-
"GET", self.common_endpoint
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
response = SnapListResponse.model_validate_json(response.content)
|
|
22
|
-
if response.status_code > 299:
|
|
23
|
-
raise httpx.HTTPStatusError(
|
|
24
|
-
request=response.request,
|
|
25
|
-
response=response,
|
|
26
|
-
message=f"Invalid status code in response: {response.status_code}",
|
|
27
|
-
)
|
|
28
|
-
return response
|
|
29
|
-
|
|
30
|
-
async def install_snap(
|
|
31
|
-
self,
|
|
32
|
-
snap: str,
|
|
33
|
-
channel: str = "stable",
|
|
34
|
-
classic: bool = False,
|
|
35
|
-
devmode: bool = False,
|
|
36
|
-
ignore_validation: bool = False,
|
|
37
|
-
jailmode: bool = False,
|
|
38
|
-
revision: int = None,
|
|
39
|
-
wait: bool = False,
|
|
40
|
-
) -> AsyncResponse | ChangesResponse:
|
|
41
|
-
request_data = {
|
|
42
|
-
"action": "install",
|
|
43
|
-
"channel": channel,
|
|
44
|
-
"classic": classic,
|
|
45
|
-
"devmode": devmode,
|
|
46
|
-
"ignore_validation": ignore_validation,
|
|
47
|
-
"jailmode": jailmode,
|
|
48
|
-
}
|
|
49
|
-
if revision:
|
|
50
|
-
request_data["revision"] = revision
|
|
51
|
-
raw_response: httpx.Response = await self._client.request(
|
|
52
|
-
"POST", f"{self.common_endpoint}/{snap}", json=request_data
|
|
53
|
-
)
|
|
54
|
-
response = AsyncResponse.model_validate_json(raw_response.content)
|
|
55
|
-
if wait:
|
|
56
|
-
changes_id = response.change
|
|
57
|
-
while True:
|
|
58
|
-
changes = await self._client.get_changes_by_id(changes_id)
|
|
59
|
-
if changes.ready:
|
|
60
|
-
break
|
|
61
|
-
if changes.result.err:
|
|
62
|
-
raise Exception(f"Error in snap install: {changes.result.err}")
|
|
63
|
-
await asyncio.sleep(2.0)
|
|
64
|
-
return changes
|
|
65
|
-
return response
|
|
66
|
-
|
|
67
|
-
async def remove_snap(
|
|
68
|
-
self, snap: str, purge: bool, terminate: bool, wait: bool = False
|
|
69
|
-
) -> AsyncResponse | ChangesResponse:
|
|
70
|
-
request_data = {
|
|
71
|
-
"action": "remove",
|
|
72
|
-
"purge": purge,
|
|
73
|
-
"terminate": terminate,
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
raw_response: httpx.Response = await self._client.request(
|
|
77
|
-
"POST", f"{self.common_endpoint}/{snap}", json=request_data
|
|
78
|
-
)
|
|
79
|
-
response = AsyncResponse.model_validate_json(raw_response.content)
|
|
80
|
-
|
|
81
|
-
if wait:
|
|
82
|
-
changes_id = response.change
|
|
83
|
-
while True:
|
|
84
|
-
changes = await self._client.get_changes_by_id(changes_id)
|
|
85
|
-
if changes.ready:
|
|
86
|
-
break
|
|
87
|
-
if changes.result.err:
|
|
88
|
-
raise Exception(f"Error in snap remove: {changes.result.err}")
|
|
89
|
-
await asyncio.sleep(2.0)
|
|
90
|
-
return changes
|
|
91
|
-
|
|
92
|
-
return response
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snap_python-0.0.1/src/snap_python → snap_python-0.0.3/src/snap_python/components}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/schemas/store/json/category_schema.json
RENAMED
|
File without changes
|
{snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/schemas/store/json/snaps_find_schema.json
RENAMED
|
File without changes
|
{snap_python-0.0.1 → snap_python-0.0.3}/src/snap_python/schemas/store/json/snaps_info_schema.json
RENAMED
|
File without changes
|
|
File without changes
|