snap-python 0.1.3__py3-none-any.whl → 0.1.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/components/snaps.py +138 -3
- snap_python/components/store.py +98 -0
- snap_python/schemas/snaps.py +25 -1
- snap_python/schemas/store/search.py +33 -1
- {snap_python-0.1.3.dist-info → snap_python-0.1.5.dist-info}/METADATA +1 -1
- {snap_python-0.1.3.dist-info → snap_python-0.1.5.dist-info}/RECORD +8 -8
- {snap_python-0.1.3.dist-info → snap_python-0.1.5.dist-info}/WHEEL +1 -1
- {snap_python-0.1.3.dist-info → snap_python-0.1.5.dist-info}/LICENSE +0 -0
snap_python/components/snaps.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
5
6
|
import httpx
|
|
6
7
|
|
|
7
8
|
from snap_python.schemas.changes import ChangesResponse
|
|
8
|
-
from snap_python.schemas.common import AsyncResponse
|
|
9
|
+
from snap_python.schemas.common import AsyncResponse, BaseErrorResult
|
|
9
10
|
from snap_python.schemas.snaps import (
|
|
11
|
+
AppsResponse,
|
|
10
12
|
InstalledSnapListResponse,
|
|
11
13
|
SingleInstalledSnapResponse,
|
|
12
14
|
)
|
|
@@ -34,14 +36,13 @@ class SnapsEndpoints:
|
|
|
34
36
|
"GET", self.common_endpoint
|
|
35
37
|
)
|
|
36
38
|
|
|
37
|
-
response = InstalledSnapListResponse.model_validate_json(response.content)
|
|
38
39
|
if response.status_code > 299:
|
|
39
40
|
raise httpx.HTTPStatusError(
|
|
40
41
|
request=response.request,
|
|
41
42
|
response=response,
|
|
42
43
|
message=f"Invalid status code in response: {response.status_code}",
|
|
43
44
|
)
|
|
44
|
-
return response
|
|
45
|
+
return InstalledSnapListResponse.model_validate_json(response.content)
|
|
45
46
|
|
|
46
47
|
async def get_snap_info(self, snap: str) -> SingleInstalledSnapResponse:
|
|
47
48
|
"""
|
|
@@ -331,3 +332,137 @@ class SnapsEndpoints:
|
|
|
331
332
|
previous_changes = changes
|
|
332
333
|
return changes
|
|
333
334
|
return response
|
|
335
|
+
|
|
336
|
+
async def disable_snap(
|
|
337
|
+
self,
|
|
338
|
+
snap: str,
|
|
339
|
+
wait: bool = False,
|
|
340
|
+
) -> AsyncResponse | ChangesResponse:
|
|
341
|
+
"""
|
|
342
|
+
Disables a snap package.
|
|
343
|
+
|
|
344
|
+
:param snap: The name of the snap package to disable.
|
|
345
|
+
:type snap: str
|
|
346
|
+
:param wait: Whether to wait for the disable operation to complete. Defaults to False.
|
|
347
|
+
:type wait: bool, optional
|
|
348
|
+
|
|
349
|
+
:returns: The response from the disable operation.
|
|
350
|
+
:rtype: AsyncResponse | ChangesResponse
|
|
351
|
+
|
|
352
|
+
:raises SnapdAPIError: If there is an error during the snap disable operation.
|
|
353
|
+
"""
|
|
354
|
+
request_data = {
|
|
355
|
+
"action": "disable",
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
raw_response: httpx.Response = await self._client.request(
|
|
359
|
+
"POST", f"{self.common_endpoint}/{snap}", json=request_data
|
|
360
|
+
)
|
|
361
|
+
response = AsyncResponse.model_validate_json(raw_response.content)
|
|
362
|
+
|
|
363
|
+
if wait:
|
|
364
|
+
changes_id = response.change
|
|
365
|
+
previous_changes = None
|
|
366
|
+
while True:
|
|
367
|
+
try:
|
|
368
|
+
changes = await self._client.get_changes_by_id(changes_id)
|
|
369
|
+
except httpx.HTTPError:
|
|
370
|
+
if going_to_reload_daemon(previous_changes):
|
|
371
|
+
logger.debug("Waiting for daemon to reload")
|
|
372
|
+
await asyncio.sleep(0.1)
|
|
373
|
+
continue
|
|
374
|
+
raise
|
|
375
|
+
if changes.ready:
|
|
376
|
+
break
|
|
377
|
+
if isinstance(changes.result, BaseErrorResult):
|
|
378
|
+
raise SnapdAPIError(
|
|
379
|
+
f"Error in snap disable: {changes.result.message}"
|
|
380
|
+
)
|
|
381
|
+
if changes.result.err:
|
|
382
|
+
raise SnapdAPIError(f"Error in snap disable: {changes.result.err}")
|
|
383
|
+
await asyncio.sleep(0.1)
|
|
384
|
+
previous_changes = changes
|
|
385
|
+
return changes
|
|
386
|
+
|
|
387
|
+
return response
|
|
388
|
+
|
|
389
|
+
async def enable_snap(
|
|
390
|
+
self,
|
|
391
|
+
snap: str,
|
|
392
|
+
wait: bool = False,
|
|
393
|
+
) -> AsyncResponse | ChangesResponse:
|
|
394
|
+
"""
|
|
395
|
+
enables a snap package.
|
|
396
|
+
|
|
397
|
+
:param snap: The name of the snap package to enable.
|
|
398
|
+
:type snap: str
|
|
399
|
+
:param wait: Whether to wait for the enable operation to complete. Defaults to False.
|
|
400
|
+
:type wait: bool, optional
|
|
401
|
+
|
|
402
|
+
:returns: The response from the enable operation.
|
|
403
|
+
:rtype: AsyncResponse | ChangesResponse
|
|
404
|
+
|
|
405
|
+
:raises SnapdAPIError: If there is an error during the snap enable operation.
|
|
406
|
+
"""
|
|
407
|
+
request_data = {
|
|
408
|
+
"action": "enable",
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
raw_response: httpx.Response = await self._client.request(
|
|
412
|
+
"POST", f"{self.common_endpoint}/{snap}", json=request_data
|
|
413
|
+
)
|
|
414
|
+
response = AsyncResponse.model_validate_json(raw_response.content)
|
|
415
|
+
|
|
416
|
+
if wait:
|
|
417
|
+
changes_id = response.change
|
|
418
|
+
previous_changes = None
|
|
419
|
+
while True:
|
|
420
|
+
try:
|
|
421
|
+
changes = await self._client.get_changes_by_id(changes_id)
|
|
422
|
+
except httpx.HTTPError:
|
|
423
|
+
if going_to_reload_daemon(previous_changes):
|
|
424
|
+
logger.debug("Waiting for daemon to reload")
|
|
425
|
+
await asyncio.sleep(0.1)
|
|
426
|
+
continue
|
|
427
|
+
raise
|
|
428
|
+
if changes.ready:
|
|
429
|
+
break
|
|
430
|
+
if isinstance(changes.result, BaseErrorResult):
|
|
431
|
+
raise SnapdAPIError(
|
|
432
|
+
f"Error in snap enable: {changes.result.message}"
|
|
433
|
+
)
|
|
434
|
+
if changes.result.err:
|
|
435
|
+
raise SnapdAPIError(f"Error in snap enable: {changes.result.err}")
|
|
436
|
+
await asyncio.sleep(0.1)
|
|
437
|
+
previous_changes = changes
|
|
438
|
+
return changes
|
|
439
|
+
|
|
440
|
+
return response
|
|
441
|
+
|
|
442
|
+
async def get_snap_apps(
|
|
443
|
+
self,
|
|
444
|
+
snap: str,
|
|
445
|
+
services_only: bool = False,
|
|
446
|
+
) -> AppsResponse:
|
|
447
|
+
"""
|
|
448
|
+
Retrieves the applications associated with a specific snap.
|
|
449
|
+
|
|
450
|
+
:param snap: The name of the snap to retrieve applications for.
|
|
451
|
+
:type snap: str
|
|
452
|
+
:param services_only: Whether to retrieve only service applications.
|
|
453
|
+
:type services_only: bool
|
|
454
|
+
|
|
455
|
+
:returns: The response containing the list of applications for the specified snap.
|
|
456
|
+
:rtype: AppsResponse
|
|
457
|
+
"""
|
|
458
|
+
|
|
459
|
+
payload: dict[str, Any] = {"names": [snap]}
|
|
460
|
+
if services_only:
|
|
461
|
+
payload["select"] = "service"
|
|
462
|
+
|
|
463
|
+
response: httpx.Response = await self._client.request(
|
|
464
|
+
"GET", "apps", params=payload
|
|
465
|
+
)
|
|
466
|
+
if response.status_code != 200:
|
|
467
|
+
raise SnapdAPIError(f"Failed to retrieve apps for snap: {snap}")
|
|
468
|
+
return AppsResponse.model_validate_json(response.content)
|
snap_python/components/store.py
CHANGED
|
@@ -20,6 +20,7 @@ from snap_python.schemas.store.refresh import (
|
|
|
20
20
|
from snap_python.schemas.store.search import (
|
|
21
21
|
VALID_SEARCH_CATEGORY_FIELDS,
|
|
22
22
|
ArchSearchResponse,
|
|
23
|
+
PaginatedSnapSearchResponse,
|
|
23
24
|
SearchResponse,
|
|
24
25
|
)
|
|
25
26
|
|
|
@@ -396,3 +397,100 @@ class StoreEndpoints:
|
|
|
396
397
|
)
|
|
397
398
|
response.raise_for_status()
|
|
398
399
|
return RefreshRevisionResponse.model_validate_json(response.content)
|
|
400
|
+
|
|
401
|
+
async def get_many_snap_revision_info(
|
|
402
|
+
self,
|
|
403
|
+
snap_name: str,
|
|
404
|
+
from_revision: int,
|
|
405
|
+
to_revision: int,
|
|
406
|
+
arch: str,
|
|
407
|
+
fields=None,
|
|
408
|
+
) -> RefreshRevisionResponse:
|
|
409
|
+
"""Get information about a snap revision.
|
|
410
|
+
|
|
411
|
+
:param snap_name: The name of the snap.
|
|
412
|
+
:type snap_name: str
|
|
413
|
+
:param revision: The revision of the snap.
|
|
414
|
+
:type revision: int
|
|
415
|
+
:param arch: The architecture of the snap to retrieve details about (e.g. amd64, arm64, riscv64, etc).
|
|
416
|
+
:type arch: str
|
|
417
|
+
|
|
418
|
+
:returns: The snap revision information.
|
|
419
|
+
:rtype: RefreshRevisionResponse
|
|
420
|
+
"""
|
|
421
|
+
|
|
422
|
+
# cast revision to int
|
|
423
|
+
from_revision = int(from_revision)
|
|
424
|
+
to_revision = int(to_revision)
|
|
425
|
+
extra_headers = {"Snap-Device-Architecture": arch}
|
|
426
|
+
snap_info = await self.get_snap_info(snap_name=snap_name)
|
|
427
|
+
|
|
428
|
+
# I don't think this matters for the "context" field, so using info from the first available
|
|
429
|
+
channel_map_item = snap_info.channel_map[0]
|
|
430
|
+
|
|
431
|
+
payload = {
|
|
432
|
+
"context": [
|
|
433
|
+
{
|
|
434
|
+
"tracking-channel": "stable",
|
|
435
|
+
"snap-id": snap_info.snap_id,
|
|
436
|
+
"revision": channel_map_item.revision,
|
|
437
|
+
"instance-key": str(uuid.uuid4()),
|
|
438
|
+
}
|
|
439
|
+
],
|
|
440
|
+
"actions": [],
|
|
441
|
+
}
|
|
442
|
+
for revision in range(from_revision, to_revision + 1):
|
|
443
|
+
payload["actions"].append(
|
|
444
|
+
{
|
|
445
|
+
"action": "download",
|
|
446
|
+
"name": snap_name,
|
|
447
|
+
"revision": revision,
|
|
448
|
+
"instance-key": str(uuid.uuid4()),
|
|
449
|
+
}
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
if fields is not None:
|
|
453
|
+
if not all(field in VALID_SNAP_REFRESH_FIELDS for field in fields):
|
|
454
|
+
raise ValueError(
|
|
455
|
+
f"Invalid fields. Allowed fields: {VALID_SNAP_REFRESH_FIELDS}"
|
|
456
|
+
)
|
|
457
|
+
payload["fields"] = fields
|
|
458
|
+
|
|
459
|
+
response = await self.snap_refresh(
|
|
460
|
+
snap_name=snap_name,
|
|
461
|
+
payload=payload,
|
|
462
|
+
extra_headers=extra_headers,
|
|
463
|
+
)
|
|
464
|
+
response.raise_for_status()
|
|
465
|
+
return RefreshRevisionResponse.model_validate_json(response.content)
|
|
466
|
+
|
|
467
|
+
async def get_snap_search_paginated(
|
|
468
|
+
self,
|
|
469
|
+
q: str = "",
|
|
470
|
+
scope: str = "wide",
|
|
471
|
+
arch: str = "wide",
|
|
472
|
+
page: int = 1,
|
|
473
|
+
limit: int = 100,
|
|
474
|
+
confinement: str = "strict,classic",
|
|
475
|
+
) -> PaginatedSnapSearchResponse:
|
|
476
|
+
payload = {
|
|
477
|
+
"scope": scope,
|
|
478
|
+
"arch": arch,
|
|
479
|
+
"page": page,
|
|
480
|
+
"size": limit,
|
|
481
|
+
"confinement": confinement,
|
|
482
|
+
}
|
|
483
|
+
if q:
|
|
484
|
+
payload["q"] = q
|
|
485
|
+
|
|
486
|
+
response = await self.store_client.get(
|
|
487
|
+
f"{self._raw_base_url}/api/v1/snaps/search",
|
|
488
|
+
params=payload,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
response.raise_for_status()
|
|
492
|
+
response_json = response.json()
|
|
493
|
+
response_json["page"] = page
|
|
494
|
+
response_json["limit"] = limit
|
|
495
|
+
|
|
496
|
+
return PaginatedSnapSearchResponse.model_validate(response_json)
|
snap_python/schemas/snaps.py
CHANGED
|
@@ -32,7 +32,8 @@ class InstalledSnapFields(Revision):
|
|
|
32
32
|
validation_alias=AliasChoices("ignore-validation", "ignore_validation"),
|
|
33
33
|
serialization_alias="ignore-validation",
|
|
34
34
|
)
|
|
35
|
-
install_date: AwareDatetime = Field(
|
|
35
|
+
install_date: Optional[AwareDatetime] = Field(
|
|
36
|
+
default=None,
|
|
36
37
|
validation_alias=AliasChoices("install-date", "install_date"),
|
|
37
38
|
serialization_alias="install-date",
|
|
38
39
|
)
|
|
@@ -98,3 +99,26 @@ class InstalledSnapListResponse(BaseResponse):
|
|
|
98
99
|
|
|
99
100
|
def __len__(self):
|
|
100
101
|
return len(self.result)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class SnapAppInfo(BaseModel):
|
|
105
|
+
snap: Optional[str] = None
|
|
106
|
+
name: str
|
|
107
|
+
desktop_file: Optional[str] = Field(
|
|
108
|
+
default=None,
|
|
109
|
+
validation_alias=AliasChoices("desktop-file", "desktop_file"),
|
|
110
|
+
serialization_alias="desktop-file",
|
|
111
|
+
)
|
|
112
|
+
daemon: Optional[str] = None
|
|
113
|
+
enabled: Optional[bool] = None
|
|
114
|
+
active: Optional[bool] = None
|
|
115
|
+
common_id: Optional[str] = Field(
|
|
116
|
+
default=None,
|
|
117
|
+
validation_alias=AliasChoices("common-id", "common_id"),
|
|
118
|
+
serialization_alias="common-id",
|
|
119
|
+
)
|
|
120
|
+
activators: Optional[list[dict]] = None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class AppsResponse(BaseResponse):
|
|
124
|
+
result: list[SnapAppInfo]
|
|
@@ -7,6 +7,7 @@ from pydantic import (
|
|
|
7
7
|
BaseModel,
|
|
8
8
|
ConfigDict,
|
|
9
9
|
Field,
|
|
10
|
+
computed_field,
|
|
10
11
|
)
|
|
11
12
|
|
|
12
13
|
from snap_python.schemas.common import Revision
|
|
@@ -144,4 +145,35 @@ class ArchSearchResponse(BaseModel):
|
|
|
144
145
|
results: list[ArchSearchItem] = Field(
|
|
145
146
|
alias=AliasChoices(AliasPath("_embedded", "clickindex:package"), "results")
|
|
146
147
|
)
|
|
147
|
-
arch: str
|
|
148
|
+
arch: str = "wide"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class PaginatedResponse(BaseModel):
|
|
152
|
+
total: int
|
|
153
|
+
page: int = Field(ge=0)
|
|
154
|
+
limit: int = Field(ge=1, le=100)
|
|
155
|
+
|
|
156
|
+
@computed_field
|
|
157
|
+
@property
|
|
158
|
+
def next_page(self) -> Optional[int]:
|
|
159
|
+
if self.page * self.limit + self.limit < self.total:
|
|
160
|
+
return self.page + 1
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
@computed_field
|
|
164
|
+
@property
|
|
165
|
+
def previous_page(self) -> Optional[int]:
|
|
166
|
+
if self.page > 0:
|
|
167
|
+
return self.page - 1
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
@computed_field
|
|
171
|
+
@property
|
|
172
|
+
def has_more(self) -> bool:
|
|
173
|
+
return self.page * self.limit + self.limit < self.total
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class PaginatedSnapSearchResponse(PaginatedResponse):
|
|
177
|
+
results: list[ArchSearchItem] = Field(
|
|
178
|
+
alias=AliasChoices(AliasPath("_embedded", "clickindex:package"), "results")
|
|
179
|
+
)
|
|
@@ -2,12 +2,12 @@ snap_python/__init__.py,sha256=gV43Nempk7HFKD1UCvxUhmP9x6dBmPjotcITREr81Vg,222
|
|
|
2
2
|
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
|
-
snap_python/components/snaps.py,sha256=
|
|
6
|
-
snap_python/components/store.py,sha256=
|
|
5
|
+
snap_python/components/snaps.py,sha256=NfZAgoNvsvf-aJa-ZZJoJxS-KrwrG6gBe1ScN2WM5To,18003
|
|
6
|
+
snap_python/components/store.py,sha256=lxN-6cVslAeen-yfZqMPQ2iNvDUNBCClWIvLn5PZi2Q,16971
|
|
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
|
-
snap_python/schemas/snaps.py,sha256=
|
|
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
13
|
snap_python/schemas/store/info.py,sha256=K2y1NOrSLZEszGJur5L0W_1ap4P038cK4ThSjLcyQLA,4407
|
|
@@ -16,10 +16,10 @@ snap_python/schemas/store/json/category_schema.json,sha256=c4h_eyyBbChOiNby3hYfj
|
|
|
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
|
-
snap_python/schemas/store/search.py,sha256
|
|
19
|
+
snap_python/schemas/store/search.py,sha256=4ghZb8xY6IESxZuAvzSjM9RaEwZAH3zjiC06zLc0zvU,4561
|
|
20
20
|
snap_python/scrape.py,sha256=ZDJAROp8jwossaAA78gBFTKQzrfnO11RgyyOZqevNd0,4576
|
|
21
21
|
snap_python/utils.py,sha256=cmUooPg5DzNL197Xxvu7LGD4Gwk3xxsT68wbVytRtV0,1240
|
|
22
|
-
snap_python-0.1.
|
|
23
|
-
snap_python-0.1.
|
|
24
|
-
snap_python-0.1.
|
|
25
|
-
snap_python-0.1.
|
|
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,,
|
|
File without changes
|