appstream-python 0.8.1__py3-none-any.whl → 0.9.0__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.
- appstream_python/Collection.py +4 -3
- appstream_python/Component.py +149 -81
- appstream_python/Release.py +41 -26
- appstream_python/Shared.py +58 -45
- appstream_python/StandardConstants.py +20 -3
- appstream_python/py.typed +0 -0
- appstream_python/version.txt +1 -1
- {appstream_python-0.8.1.dist-info → appstream_python-0.9.0.dist-info}/METADATA +9 -7
- appstream_python-0.9.0.dist-info/RECORD +14 -0
- {appstream_python-0.8.1.dist-info → appstream_python-0.9.0.dist-info}/WHEEL +1 -1
- {appstream_python-0.8.1.dist-info → appstream_python-0.9.0.dist-info/licenses}/LICENSE +1 -1
- appstream_python-0.8.1.dist-info/RECORD +0 -13
- {appstream_python-0.8.1.dist-info → appstream_python-0.9.0.dist-info}/top_level.txt +0 -0
appstream_python/Collection.py
CHANGED
|
@@ -5,11 +5,12 @@ import gzip
|
|
|
5
5
|
|
|
6
6
|
class AppstreamCollection:
|
|
7
7
|
"Represents a Collection of multiple AppStream files"
|
|
8
|
+
|
|
8
9
|
def __init__(self) -> None:
|
|
9
10
|
self._components: dict[str, AppstreamComponent] = {}
|
|
10
11
|
self._categories: dict[str, list[str]] = {}
|
|
11
12
|
|
|
12
|
-
def _add_appstream_tag(self, tag: etree.
|
|
13
|
+
def _add_appstream_tag(self, tag: etree._Element) -> None:
|
|
13
14
|
component_data = AppstreamComponent()
|
|
14
15
|
component_data.parse_component_tag(tag)
|
|
15
16
|
self.add_component(component_data)
|
|
@@ -54,7 +55,7 @@ class AppstreamCollection:
|
|
|
54
55
|
"""Returns a list with all available component id's"""
|
|
55
56
|
return list(self._components.keys())
|
|
56
57
|
|
|
57
|
-
def get_component(self, component_id: str) -> AppstreamComponent:
|
|
58
|
+
def get_component(self, component_id: str) -> AppstreamComponent | None:
|
|
58
59
|
"""Returns the component with the given id"""
|
|
59
60
|
return self._components.get(component_id, None)
|
|
60
61
|
|
|
@@ -69,7 +70,7 @@ class AppstreamCollection:
|
|
|
69
70
|
|
|
70
71
|
return category_list
|
|
71
72
|
|
|
72
|
-
def get_collection_tag(self) -> etree.
|
|
73
|
+
def get_collection_tag(self) -> etree._Element:
|
|
73
74
|
"Gets the XML from the Collection"
|
|
74
75
|
components_tag = etree.Element("components")
|
|
75
76
|
|
appstream_python/Component.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
from .Shared import TranslateableTag, TranslateableList, Description
|
|
2
|
-
from typing import Any, Optional,
|
|
2
|
+
from typing import Any, IO, Optional, Self, TypedDict, Union, cast
|
|
3
3
|
from .Release import ReleaseList
|
|
4
4
|
from ._helper import assert_func
|
|
5
5
|
from .StandardConstants import *
|
|
6
6
|
from lxml import etree
|
|
7
7
|
import dataclasses
|
|
8
|
-
import io
|
|
9
8
|
import os
|
|
10
9
|
|
|
11
10
|
|
|
@@ -33,6 +32,7 @@ def _compare_relation_value(operator: RELATION_COMPARISON_OPERATOR_LITERAL, firs
|
|
|
33
32
|
|
|
34
33
|
class InternetRelationDict(TypedDict):
|
|
35
34
|
"the type for the content of the Internet attribute"
|
|
35
|
+
|
|
36
36
|
value: INTERNET_RELATION_VALUE_LITERAL
|
|
37
37
|
bandwidth_mbitps: Optional[int]
|
|
38
38
|
|
|
@@ -44,7 +44,7 @@ class Image:
|
|
|
44
44
|
self.url: str = ""
|
|
45
45
|
"The image URL"
|
|
46
46
|
|
|
47
|
-
self.type:
|
|
47
|
+
self.type: IMAGE_TYPE_LITERAL = "source"
|
|
48
48
|
"The image type"
|
|
49
49
|
|
|
50
50
|
self.width: Optional[int] = None
|
|
@@ -56,22 +56,25 @@ class Image:
|
|
|
56
56
|
self.language: Optional[str] = None
|
|
57
57
|
"The language"
|
|
58
58
|
|
|
59
|
-
def load_tag(self, tag: etree.
|
|
59
|
+
def load_tag(self, tag: etree._Element) -> None:
|
|
60
60
|
"Loads a image tag"
|
|
61
|
-
self.url = tag.text.strip()
|
|
62
|
-
|
|
61
|
+
self.url = (tag.text or "").strip()
|
|
62
|
+
tag_type = tag.get("type", "source")
|
|
63
|
+
if tag_type not in IMAGE_TYPE:
|
|
64
|
+
raise ValueError(f"Invalid image type {tag_type}")
|
|
65
|
+
self.type = cast(IMAGE_TYPE_LITERAL, tag_type)
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self.width =
|
|
67
|
+
tag_width = tag.get("width")
|
|
68
|
+
self.width = None
|
|
69
|
+
if tag_width is not None:
|
|
70
|
+
self.width = int(tag_width)
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
self.height =
|
|
72
|
+
tag_height = tag.get("height")
|
|
73
|
+
self.height = None
|
|
74
|
+
if tag_height is not None:
|
|
75
|
+
self.height = int(tag_height)
|
|
73
76
|
|
|
74
|
-
self.language = tag.get(
|
|
77
|
+
self.language = tag.get(_XML_LANG)
|
|
75
78
|
|
|
76
79
|
|
|
77
80
|
class Screenshot:
|
|
@@ -95,7 +98,7 @@ class Screenshot:
|
|
|
95
98
|
"Returns the thumbnail images"
|
|
96
99
|
return [image for image in self.images if image.type == "thumbnail"]
|
|
97
100
|
|
|
98
|
-
def load_tag(self, tag: etree.
|
|
101
|
+
def load_tag(self, tag: etree._Element) -> None:
|
|
99
102
|
"Load a screenshot tag"
|
|
100
103
|
for i in tag.findall("image"):
|
|
101
104
|
img = Image()
|
|
@@ -112,7 +115,9 @@ class DisplayLength:
|
|
|
112
115
|
self.px: int = px
|
|
113
116
|
"The logical pixels"
|
|
114
117
|
|
|
115
|
-
|
|
118
|
+
if compare not in RELATION_COMPARISON_OPERATOR:
|
|
119
|
+
raise ValueError(f"Invalid compare {compare}")
|
|
120
|
+
self.compare: RELATION_COMPARISON_OPERATOR_LITERAL = cast(RELATION_COMPARISON_OPERATOR_LITERAL, compare)
|
|
116
121
|
"Compare"
|
|
117
122
|
|
|
118
123
|
def compare_px(self, value: int) -> bool:
|
|
@@ -124,7 +129,7 @@ class DisplayLength:
|
|
|
124
129
|
"""
|
|
125
130
|
return _compare_relation_value(self.compare, value, self.px)
|
|
126
131
|
|
|
127
|
-
def get_tag(self) -> etree.
|
|
132
|
+
def get_tag(self) -> etree._Element:
|
|
128
133
|
"""
|
|
129
134
|
Returns the XML Tag
|
|
130
135
|
|
|
@@ -139,16 +144,22 @@ class DisplayLength:
|
|
|
139
144
|
return tag
|
|
140
145
|
|
|
141
146
|
@classmethod
|
|
142
|
-
def from_tag(cls
|
|
147
|
+
def from_tag(cls, tag: etree._Element) -> Self:
|
|
143
148
|
"Creates the Object from an XML Tag"
|
|
144
149
|
display_length = cls()
|
|
145
150
|
|
|
151
|
+
if tag.text is None:
|
|
152
|
+
raise ValueError("Missing text for tag {tag}")
|
|
153
|
+
|
|
146
154
|
try:
|
|
147
155
|
display_length.px = int(tag.text)
|
|
148
156
|
except ValueError:
|
|
149
157
|
display_length.px = cls.string_to_px(tag.text)
|
|
150
158
|
|
|
151
|
-
|
|
159
|
+
tag_compare = tag.get("compare", "ge")
|
|
160
|
+
if tag_compare not in RELATION_COMPARISON_OPERATOR:
|
|
161
|
+
raise ValueError(f"Invalid compare {tag_compare}")
|
|
162
|
+
display_length.compare = cast(RELATION_COMPARISON_OPERATOR_LITERAL, tag_compare)
|
|
152
163
|
|
|
153
164
|
return display_length
|
|
154
165
|
|
|
@@ -198,13 +209,13 @@ class Developer:
|
|
|
198
209
|
id: Optional[str] = None
|
|
199
210
|
name: TranslateableTag = dataclasses.field(default_factory=lambda: TranslateableTag())
|
|
200
211
|
|
|
201
|
-
def load_tag(self, tag: etree.
|
|
212
|
+
def load_tag(self, tag: etree._Element) -> None:
|
|
202
213
|
"Loads the data from a XML tag"
|
|
203
214
|
self.id = tag.get("id")
|
|
204
215
|
|
|
205
216
|
self.name.load_tags(tag.findall("name"))
|
|
206
217
|
|
|
207
|
-
def get_tag(self) -> etree.
|
|
218
|
+
def get_tag(self) -> etree._Element:
|
|
208
219
|
"""
|
|
209
220
|
Returns the XML Tag
|
|
210
221
|
|
|
@@ -260,7 +271,7 @@ class AppstreamComponent:
|
|
|
260
271
|
self.urls: dict[URL_TYPES_LITERAL, str] = {}
|
|
261
272
|
"The URLs"
|
|
262
273
|
|
|
263
|
-
self.launchables: dict[
|
|
274
|
+
self.launchables: dict[LAUNCHABLE_TYPES_LITERAL, str] = {}
|
|
264
275
|
"The launchables"
|
|
265
276
|
|
|
266
277
|
self.oars: dict[OARS_ATTRIBUTE_TYPES_LITERAL, OARS_VALUE_TYPES_LITERAL] = {}
|
|
@@ -290,13 +301,13 @@ class AppstreamComponent:
|
|
|
290
301
|
self.keywords: TranslateableList = TranslateableList()
|
|
291
302
|
"The Keywords"
|
|
292
303
|
|
|
293
|
-
self.controls: dict[CONTROL_TYPES_LITERAL, Optional[
|
|
304
|
+
self.controls: dict[CONTROL_TYPES_LITERAL, Optional[RELATION_LITERAL]] = {}
|
|
294
305
|
"The Controls"
|
|
295
306
|
|
|
296
|
-
self.display_length: dict[
|
|
307
|
+
self.display_length: dict[RELATION_LITERAL, list[DisplayLength]] = {}
|
|
297
308
|
"The Display Length"
|
|
298
309
|
|
|
299
|
-
self.internet: dict[
|
|
310
|
+
self.internet: dict[RELATION_LITERAL, InternetRelationDict] = {}
|
|
300
311
|
|
|
301
312
|
self.kudos: list[str] = []
|
|
302
313
|
"The Kudos"
|
|
@@ -360,13 +371,20 @@ class AppstreamComponent:
|
|
|
360
371
|
lang_list = self.name.get_available_languages() + self.summary.get_available_languages() + self.developer.name.get_available_languages()
|
|
361
372
|
return list(set(lang_list))
|
|
362
373
|
|
|
363
|
-
def _parse_relation_tag(self, tag: etree.
|
|
374
|
+
def _parse_relation_tag(self, tag: etree._Element) -> None:
|
|
364
375
|
"Parses a relation tag"
|
|
365
376
|
relation = tag.tag
|
|
377
|
+
if relation not in RELATION:
|
|
378
|
+
raise ValueError(f"Invalid relation {relation}")
|
|
379
|
+
relation = cast(RELATION_LITERAL, relation)
|
|
366
380
|
|
|
367
381
|
for control_tag in tag.findall("control"):
|
|
368
|
-
if control_tag.text
|
|
369
|
-
|
|
382
|
+
if control_tag.text is None:
|
|
383
|
+
raise ValueError("Empty control tag text {control_tag}")
|
|
384
|
+
control_tag_text = control_tag.text.strip()
|
|
385
|
+
if control_tag_text not in CONTROL_TYPES:
|
|
386
|
+
raise ValueError("Invalid control tag {control_tag_text}")
|
|
387
|
+
self.controls[cast(CONTROL_TYPES_LITERAL, control_tag_text)] = relation
|
|
370
388
|
|
|
371
389
|
for display_length_tag in tag.findall("display_length"):
|
|
372
390
|
if relation in self.display_length:
|
|
@@ -376,23 +394,31 @@ class AppstreamComponent:
|
|
|
376
394
|
|
|
377
395
|
internet_tag = tag.find("internet")
|
|
378
396
|
if internet_tag is not None:
|
|
379
|
-
|
|
397
|
+
internet_tag_text = (internet_tag.text or "").strip()
|
|
398
|
+
if internet_tag_text not in INTERNET_RELATION_VALUE:
|
|
399
|
+
raise ValueError(f"Invalid internet relation {internet_tag_text}")
|
|
400
|
+
internet_dict: InternetRelationDict = {"bandwidth_mbitps": None, "value": cast(INTERNET_RELATION_VALUE_LITERAL, internet_tag_text)}
|
|
380
401
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
402
|
+
internet_bandwidth = internet_tag.get("bandwidth_mbitps")
|
|
403
|
+
if internet_bandwidth is not None:
|
|
404
|
+
try:
|
|
405
|
+
internet_dict["bandwidth_mbitps"] = int(internet_bandwidth)
|
|
406
|
+
except ValueError:
|
|
407
|
+
internet_dict["bandwidth_mbitps"] = None
|
|
385
408
|
|
|
386
409
|
self.internet[relation] = internet_dict
|
|
387
410
|
|
|
388
|
-
def parse_component_tag(self, tag: etree.
|
|
411
|
+
def parse_component_tag(self, tag: etree._Element) -> None:
|
|
389
412
|
"Parses a XML tag"
|
|
390
|
-
|
|
413
|
+
id = tag.find("id")
|
|
414
|
+
if id is not None and id.text is not None:
|
|
415
|
+
self.id = id.text.strip()
|
|
391
416
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
417
|
+
tag_component = tag.xpath("/component")
|
|
418
|
+
if isinstance(tag_component, list) and len(tag_component) > 0:
|
|
419
|
+
self.type = cast(etree._Element, tag_component[0]).get("type", "")
|
|
420
|
+
else:
|
|
421
|
+
self.type = tag.get("type", "")
|
|
396
422
|
|
|
397
423
|
self.name.load_tags(tag.findall("name"))
|
|
398
424
|
|
|
@@ -419,32 +445,55 @@ class AppstreamComponent:
|
|
|
419
445
|
categories_tag = tag.find("categories")
|
|
420
446
|
if categories_tag is not None:
|
|
421
447
|
for i in categories_tag.findall("category"):
|
|
448
|
+
if i.text is None:
|
|
449
|
+
raise ValueError("Empty text for category tag {i}")
|
|
422
450
|
self.categories.append(i.text.strip())
|
|
423
451
|
|
|
424
452
|
for i in tag.findall("url"):
|
|
425
|
-
if i.
|
|
426
|
-
|
|
453
|
+
if i.text is None:
|
|
454
|
+
raise ValueError("Empty text for url tag {i}")
|
|
455
|
+
i_type = i.get("type")
|
|
456
|
+
if i_type not in URL_TYPES:
|
|
457
|
+
raise ValueError("Invalid url type {i_type}")
|
|
458
|
+
self.urls[cast(URL_TYPES_LITERAL, i_type)] = i.text.strip()
|
|
427
459
|
|
|
428
460
|
for i in tag.findall("launchable"):
|
|
429
|
-
if i.
|
|
430
|
-
|
|
461
|
+
if i.text is None:
|
|
462
|
+
raise ValueError("Empty text for launchable tag {i}")
|
|
463
|
+
i_type = i.get("type")
|
|
464
|
+
if i_type not in LAUNCHABLE_TYPES:
|
|
465
|
+
raise ValueError("Invalid launchable type {i_type}")
|
|
466
|
+
self.launchables[cast(LAUNCHABLE_TYPES_LITERAL, i_type)] = i.text.strip()
|
|
431
467
|
|
|
432
468
|
oars_tag = tag.find("content_rating")
|
|
433
469
|
if oars_tag is not None:
|
|
434
470
|
for i in oars_tag.findall("content_attribute"):
|
|
435
|
-
|
|
436
|
-
|
|
471
|
+
i_id_attr = i.get("id")
|
|
472
|
+
if i_id_attr is None:
|
|
473
|
+
raise ValueError("Missing id attribute for content_attribute tag {i}")
|
|
474
|
+
if i.text is None:
|
|
475
|
+
raise ValueError("Empty text for content_attribute tag {i}")
|
|
476
|
+
i_id_val = i.text.strip()
|
|
477
|
+
if i_id_attr not in OARS_ATTRIBUTE_TYPES:
|
|
478
|
+
raise ValueError("Invalid id attribute {i_id_attr}")
|
|
479
|
+
if i_id_val not in OARS_VALUE_TYPES:
|
|
480
|
+
raise ValueError("Invalid id value {i_id_val}")
|
|
481
|
+
self.oars[cast(OARS_ATTRIBUTE_TYPES_LITERAL, i_id_attr)] = cast(OARS_VALUE_TYPES_LITERAL, i_id_val)
|
|
437
482
|
|
|
438
483
|
provides_tag = tag.find("provides")
|
|
439
484
|
if provides_tag is not None:
|
|
440
|
-
for i in provides_tag
|
|
485
|
+
for i in provides_tag:
|
|
486
|
+
if i.text is None:
|
|
487
|
+
raise ValueError("Empty text for provides tag {i}")
|
|
441
488
|
if i.tag in PROVIDES_TYPES:
|
|
442
|
-
self.provides[i.tag].append(i.text.strip())
|
|
489
|
+
self.provides[cast(PROVIDES_TYPES_LITERAL, i.tag)].append(i.text.strip())
|
|
443
490
|
|
|
444
491
|
# For backwards compatibility. See: https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html#tag-mimetypes
|
|
445
492
|
mimetypes_tag = tag.find("mimetypes")
|
|
446
493
|
if mimetypes_tag is not None:
|
|
447
494
|
for i in mimetypes_tag.findall("mimetype"):
|
|
495
|
+
if i.text is None:
|
|
496
|
+
raise ValueError("Empty text for mimetypes tag {i}")
|
|
448
497
|
self.provides["mediatype"].append(i.text.strip())
|
|
449
498
|
|
|
450
499
|
releases_tag = tag.find("releases")
|
|
@@ -460,20 +509,27 @@ class AppstreamComponent:
|
|
|
460
509
|
|
|
461
510
|
project_group_tag = tag.find("project_group")
|
|
462
511
|
if project_group_tag is not None:
|
|
512
|
+
if project_group_tag.text is None:
|
|
513
|
+
raise ValueError("Empty text for project_group tag {project_group_tag}")
|
|
463
514
|
self.project_group = project_group_tag.text.strip()
|
|
464
515
|
|
|
465
516
|
for i in tag.findall("translation"):
|
|
466
|
-
trans_dict = {}
|
|
467
|
-
trans_dict["type"] = i.get("type")
|
|
468
517
|
if i.text is None:
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
518
|
+
continue
|
|
519
|
+
trans_dict = {
|
|
520
|
+
"type": i.get("type", ""),
|
|
521
|
+
"value": "",
|
|
522
|
+
}
|
|
523
|
+
if i.text is None:
|
|
524
|
+
raise ValueError("Empty text for lang tag {i}")
|
|
525
|
+
trans_dict["value"] = i.text.strip()
|
|
472
526
|
self.translation.append(trans_dict)
|
|
473
527
|
|
|
474
528
|
languages_tag = tag.find("languages")
|
|
475
529
|
if languages_tag is not None:
|
|
476
530
|
for i in languages_tag.findall("lang"):
|
|
531
|
+
if i.text is None:
|
|
532
|
+
continue
|
|
477
533
|
try:
|
|
478
534
|
self.languages[i.text.strip()] = int(i.get("percentage") or 100)
|
|
479
535
|
except ValueError:
|
|
@@ -497,45 +553,57 @@ class AppstreamComponent:
|
|
|
497
553
|
kudos_tag = tag.find("kudos")
|
|
498
554
|
if kudos_tag is not None:
|
|
499
555
|
for i in kudos_tag.findall("kudo"):
|
|
556
|
+
if i.text is None:
|
|
557
|
+
raise ValueError("Empty text for kudo tag {i}")
|
|
500
558
|
self.kudos.append(i.text.strip())
|
|
501
559
|
|
|
502
560
|
update_contact_tag = tag.find("update_contact")
|
|
503
561
|
if update_contact_tag is not None:
|
|
562
|
+
if update_contact_tag.text is None:
|
|
563
|
+
raise ValueError("Empty text for update_contact tag {update_contact_tag}")
|
|
504
564
|
self.update_contact = update_contact_tag.text.strip()
|
|
505
565
|
|
|
506
566
|
replaces_tag = tag.find("replaces")
|
|
507
567
|
if replaces_tag is not None:
|
|
508
568
|
for i in replaces_tag.findall("id"):
|
|
569
|
+
if i.text is None:
|
|
570
|
+
raise ValueError("Empty text for replaces tag {replaces_tag}")
|
|
509
571
|
self.replaces.append(i.text)
|
|
510
572
|
|
|
511
573
|
suggests_tag = tag.find("suggests")
|
|
512
574
|
if suggests_tag is not None:
|
|
513
575
|
for i in suggests_tag.findall("id"):
|
|
576
|
+
if i.text is None:
|
|
577
|
+
raise ValueError("Empty text for suggests tag {suggests_tag}")
|
|
514
578
|
self.suggests.append(i.text)
|
|
515
579
|
|
|
516
580
|
custom_tag = tag.find("custom")
|
|
517
581
|
if custom_tag is not None:
|
|
518
582
|
for i in custom_tag.findall("value"):
|
|
583
|
+
if i.text is None:
|
|
584
|
+
raise ValueError("Empty text for custom tag {custom_tag}")
|
|
519
585
|
if key := i.get("key", "").strip():
|
|
520
586
|
self.custom[key] = i.text.strip()
|
|
521
587
|
|
|
522
588
|
for i in tag.findall("extends"):
|
|
589
|
+
if i.text is None:
|
|
590
|
+
raise ValueError("Empty text for extends tag {i}")
|
|
523
591
|
self.extends.append(i.text.strip())
|
|
524
592
|
|
|
525
593
|
@classmethod
|
|
526
|
-
def from_component_tag(cls
|
|
594
|
+
def from_component_tag(cls, root: etree._Element) -> Self:
|
|
527
595
|
"Load an appdata.xml or metainfo.xml file"
|
|
528
596
|
component = cls()
|
|
529
597
|
component.parse_component_tag(root)
|
|
530
598
|
return component
|
|
531
599
|
|
|
532
|
-
def load_file(self, path: Union[str, os.PathLike,
|
|
600
|
+
def load_file(self, path: Union[str, os.PathLike, IO[Any]]) -> None:
|
|
533
601
|
"""Load an appdata.xml or metainfo.xml file"""
|
|
534
602
|
root = etree.parse(path)
|
|
535
|
-
self.parse_component_tag(root)
|
|
603
|
+
self.parse_component_tag(cast(etree._Element, root))
|
|
536
604
|
|
|
537
605
|
@classmethod
|
|
538
|
-
def from_file(cls
|
|
606
|
+
def from_file(cls, path: Union[str, os.PathLike, IO[Any]]) -> Self:
|
|
539
607
|
"Load an appdata.xml or metainfo.xml file"
|
|
540
608
|
component = cls()
|
|
541
609
|
component.load_file(path)
|
|
@@ -547,7 +615,7 @@ class AppstreamComponent:
|
|
|
547
615
|
self.parse_component_tag(root)
|
|
548
616
|
|
|
549
617
|
@classmethod
|
|
550
|
-
def from_bytes(cls
|
|
618
|
+
def from_bytes(cls, data: bytes, encoding: Optional[str] = None) -> Self:
|
|
551
619
|
"Load an appdata.xml or metainfo.xml byte string"
|
|
552
620
|
component = cls()
|
|
553
621
|
component.load_bytes(data, encoding)
|
|
@@ -558,13 +626,13 @@ class AppstreamComponent:
|
|
|
558
626
|
self.load_bytes(text.encode("utf-8"), encoding="utf-8")
|
|
559
627
|
|
|
560
628
|
@classmethod
|
|
561
|
-
def from_string(cls
|
|
629
|
+
def from_string(cls, text: str) -> Self:
|
|
562
630
|
"Load an appdata.xml or metainfo.xml string"
|
|
563
631
|
component = cls()
|
|
564
632
|
component.load_string(text)
|
|
565
633
|
return component
|
|
566
634
|
|
|
567
|
-
def _get_relation_tag(self, parent_tag: etree.
|
|
635
|
+
def _get_relation_tag(self, parent_tag: etree._Element, relation: RELATION_LITERAL) -> None:
|
|
568
636
|
"Craetes a relation tag from the Component."
|
|
569
637
|
relation_tag = etree.SubElement(parent_tag, relation)
|
|
570
638
|
|
|
@@ -584,10 +652,10 @@ class AppstreamComponent:
|
|
|
584
652
|
|
|
585
653
|
internet_tag.text = self.internet[relation]["value"]
|
|
586
654
|
|
|
587
|
-
if len(relation_tag
|
|
655
|
+
if len(relation_tag) == 0:
|
|
588
656
|
parent_tag.remove(relation_tag)
|
|
589
657
|
|
|
590
|
-
def get_component_tag(self) -> etree.
|
|
658
|
+
def get_component_tag(self) -> etree._Element:
|
|
591
659
|
"Creates a XML tag from the Component"
|
|
592
660
|
tag = etree.Element("component")
|
|
593
661
|
tag.set("type", self.type)
|
|
@@ -610,16 +678,16 @@ class AppstreamComponent:
|
|
|
610
678
|
project_license_tag = etree.SubElement(tag, "project_license")
|
|
611
679
|
project_license_tag.text = self.project_license
|
|
612
680
|
|
|
613
|
-
for
|
|
614
|
-
if
|
|
681
|
+
for key_url, value in self.urls.items():
|
|
682
|
+
if key_url in URL_TYPES:
|
|
615
683
|
url_tag = etree.SubElement(tag, "url")
|
|
616
|
-
url_tag.set("type",
|
|
684
|
+
url_tag.set("type", key_url)
|
|
617
685
|
url_tag.text = value
|
|
618
686
|
|
|
619
|
-
for
|
|
620
|
-
if
|
|
687
|
+
for key_launchable, value in self.launchables.items():
|
|
688
|
+
if key_launchable in LAUNCHABLE_TYPES:
|
|
621
689
|
url_tag = etree.SubElement(tag, "launchable")
|
|
622
|
-
url_tag.set("type",
|
|
690
|
+
url_tag.set("type", key_launchable)
|
|
623
691
|
url_tag.text = value
|
|
624
692
|
|
|
625
693
|
oars_tag = etree.SubElement(tag, "content_rating")
|
|
@@ -637,15 +705,15 @@ class AppstreamComponent:
|
|
|
637
705
|
single_categorie_tag.text = i
|
|
638
706
|
|
|
639
707
|
provides_tag = etree.SubElement(tag, "provides")
|
|
640
|
-
for
|
|
641
|
-
if
|
|
708
|
+
for key_provides, value_provides in self.provides.items():
|
|
709
|
+
if key_provides not in PROVIDES_TYPES:
|
|
642
710
|
continue
|
|
643
|
-
for i in
|
|
644
|
-
single_provides_tag = etree.SubElement(provides_tag,
|
|
711
|
+
for i in value_provides:
|
|
712
|
+
single_provides_tag = etree.SubElement(provides_tag, key_provides)
|
|
645
713
|
single_provides_tag.text = i
|
|
646
714
|
|
|
647
715
|
# Don't write empty provides tag
|
|
648
|
-
if len(provides_tag
|
|
716
|
+
if len(provides_tag) == 0:
|
|
649
717
|
tag.remove(provides_tag)
|
|
650
718
|
|
|
651
719
|
if len(self.releases) != 0:
|
|
@@ -655,17 +723,17 @@ class AppstreamComponent:
|
|
|
655
723
|
project_group_tag = etree.SubElement(tag, "project_group")
|
|
656
724
|
project_group_tag.text = self.project_group
|
|
657
725
|
|
|
658
|
-
for
|
|
726
|
+
for i_translation in self.translation:
|
|
659
727
|
translation_tag = etree.SubElement(tag, "translation")
|
|
660
|
-
translation_tag.set("type",
|
|
661
|
-
translation_tag.text =
|
|
728
|
+
translation_tag.set("type", i_translation["type"])
|
|
729
|
+
translation_tag.text = i_translation["value"]
|
|
662
730
|
|
|
663
731
|
if len(self.languages) > 0:
|
|
664
732
|
languages_tag = etree.SubElement(tag, "languages")
|
|
665
|
-
for
|
|
733
|
+
for key_languages, value_languages in self.languages.items():
|
|
666
734
|
single_language_tag = etree.SubElement(languages_tag, "lang")
|
|
667
|
-
single_language_tag.set("percentage", str(
|
|
668
|
-
single_language_tag.text =
|
|
735
|
+
single_language_tag.set("percentage", str(value_languages))
|
|
736
|
+
single_language_tag.text = key_languages
|
|
669
737
|
|
|
670
738
|
self._get_relation_tag(tag, "supports")
|
|
671
739
|
self._get_relation_tag(tag, "recommends")
|
|
@@ -695,9 +763,9 @@ class AppstreamComponent:
|
|
|
695
763
|
|
|
696
764
|
if len(self.custom) > 0:
|
|
697
765
|
custom_tag = etree.SubElement(tag, "custom")
|
|
698
|
-
for
|
|
766
|
+
for key_custom, value in self.custom.items():
|
|
699
767
|
value_tag = etree.SubElement(custom_tag, "value")
|
|
700
|
-
value_tag.set("key",
|
|
768
|
+
value_tag.set("key", key_custom.strip())
|
|
701
769
|
value_tag.text = value.strip()
|
|
702
770
|
|
|
703
771
|
for i in self.extends:
|
appstream_python/Release.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Literal, Self, cast, get_args
|
|
3
3
|
from ._helper import assert_func
|
|
4
4
|
from .Shared import Description
|
|
5
5
|
from lxml import etree
|
|
@@ -29,7 +29,7 @@ class Release:
|
|
|
29
29
|
def __post_init__(self) -> None:
|
|
30
30
|
self.description = copy.deepcopy(self.description)
|
|
31
31
|
|
|
32
|
-
def get_tag(self) -> etree.
|
|
32
|
+
def get_tag(self) -> etree._Element:
|
|
33
33
|
"""
|
|
34
34
|
Returns the XML Tag
|
|
35
35
|
|
|
@@ -53,28 +53,40 @@ class Release:
|
|
|
53
53
|
return tag
|
|
54
54
|
|
|
55
55
|
@classmethod
|
|
56
|
-
def from_tag(cls
|
|
56
|
+
def from_tag(cls, tag: etree._Element) -> Self:
|
|
57
57
|
"Loads a release tag"
|
|
58
58
|
release = cls()
|
|
59
59
|
|
|
60
60
|
release.version = tag.get("version", "")
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
61
|
+
tag_type = tag.get("type", "stable")
|
|
62
|
+
if tag_type not in get_args(ReleaseType):
|
|
63
|
+
raise ValueError(f"Invalid type {tag_type}")
|
|
64
|
+
release.type = cast(ReleaseType, tag_type)
|
|
65
|
+
tag_urgency = tag.get("urgency", "unknown")
|
|
66
|
+
if tag_urgency not in get_args(UrgencyType):
|
|
67
|
+
raise ValueError(f"Invalid urgency {tag_urgency}")
|
|
68
|
+
release.urgency = cast(UrgencyType, tag_urgency)
|
|
69
|
+
|
|
70
|
+
tag_date = tag.get("date")
|
|
71
|
+
if tag_date is not None:
|
|
72
|
+
try:
|
|
73
|
+
release.date = datetime.datetime.fromisoformat(tag_date).date()
|
|
74
|
+
except Exception:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
tag_timestamp = tag.get("timestamp")
|
|
78
|
+
if tag_timestamp is not None:
|
|
79
|
+
try:
|
|
80
|
+
release.date = datetime.date.fromtimestamp(int(tag_timestamp))
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
tag_date_eol = tag.get("date_eol")
|
|
85
|
+
if tag_date_eol is not None:
|
|
86
|
+
try:
|
|
87
|
+
release.date_eol = datetime.datetime.fromisoformat(tag_date_eol).date()
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
78
90
|
|
|
79
91
|
description_tag = tag.find("description")
|
|
80
92
|
if description_tag is not None:
|
|
@@ -107,7 +119,7 @@ class ReleaseList(collections.UserList[Release]):
|
|
|
107
119
|
for single_release in tag.findall("release"):
|
|
108
120
|
self.append(Release.from_tag(single_release))
|
|
109
121
|
|
|
110
|
-
def get_tag(self) -> etree.
|
|
122
|
+
def get_tag(self) -> etree._Element:
|
|
111
123
|
"""
|
|
112
124
|
Returns the XML Tag
|
|
113
125
|
|
|
@@ -140,11 +152,14 @@ class ReleaseList(collections.UserList[Release]):
|
|
|
140
152
|
f.write(etree.tostring(self.get_tag(), pretty_print=True, xml_declaration=True, encoding="utf-8"))
|
|
141
153
|
|
|
142
154
|
@classmethod
|
|
143
|
-
def from_tag(cls
|
|
155
|
+
def from_tag(cls, tag: etree._Element, fetch_external: bool = False) -> Self:
|
|
144
156
|
"Creates the list from an XMl tag"
|
|
145
157
|
release_list = cls()
|
|
146
158
|
|
|
147
|
-
|
|
159
|
+
tag_type = tag.get("type", "embedded")
|
|
160
|
+
if tag_type not in get_args(ReleaseListType):
|
|
161
|
+
raise ValueError(f"Invalid type {tag_type}")
|
|
162
|
+
release_list.type = cast(ReleaseListType, tag_type)
|
|
148
163
|
release_list.url = tag.get("url", "")
|
|
149
164
|
|
|
150
165
|
for single_release in tag.findall("release"):
|
|
@@ -156,18 +171,18 @@ class ReleaseList(collections.UserList[Release]):
|
|
|
156
171
|
return release_list
|
|
157
172
|
|
|
158
173
|
@classmethod
|
|
159
|
-
def from_string(cls
|
|
174
|
+
def from_string(cls, text: str) -> Self:
|
|
160
175
|
"Loads the Releases from a string"
|
|
161
176
|
return cls.from_tag(etree.fromstring(text.encode("utf-8")))
|
|
162
177
|
|
|
163
178
|
@classmethod
|
|
164
|
-
def from_file(cls
|
|
179
|
+
def from_file(cls, path: str | os.PathLike) -> Self:
|
|
165
180
|
"Loads the Releases from a file"
|
|
166
181
|
with open(path, "r", encoding="utf-8") as f:
|
|
167
182
|
return cls.from_string(f.read())
|
|
168
183
|
|
|
169
184
|
@classmethod
|
|
170
|
-
def from_url(cls
|
|
185
|
+
def from_url(cls, url: str) -> Self:
|
|
171
186
|
"Loads the Releases from a URL"
|
|
172
187
|
return cls.from_tag(etree.fromstring(requests.get(url).content))
|
|
173
188
|
|
appstream_python/Shared.py
CHANGED
|
@@ -8,6 +8,7 @@ _XML_LANG = "{http://www.w3.org/XML/1998/namespace}lang"
|
|
|
8
8
|
|
|
9
9
|
class TranslateableTag:
|
|
10
10
|
"Represents a translatable tag"
|
|
11
|
+
|
|
11
12
|
def __init__(self) -> None:
|
|
12
13
|
self._text = ""
|
|
13
14
|
self._translations: dict[str, str] = {}
|
|
@@ -24,8 +25,10 @@ class TranslateableTag:
|
|
|
24
25
|
"""Returns the translated text"""
|
|
25
26
|
return self._translations.get(lang, None)
|
|
26
27
|
|
|
27
|
-
def get_translated_text_default(self, lang: str) -> Optional[str]:
|
|
28
|
+
def get_translated_text_default(self, lang: Optional[str]) -> Optional[str]:
|
|
28
29
|
"""Returns the translated text. Returns the default text, if the translation does not exists"""
|
|
30
|
+
if lang is None:
|
|
31
|
+
return self._text
|
|
29
32
|
return self._translations.get(lang, self._text)
|
|
30
33
|
|
|
31
34
|
def set_translated_text(self, lang: str, text: str) -> None:
|
|
@@ -36,28 +39,29 @@ class TranslateableTag:
|
|
|
36
39
|
"""Returns a list with all languages of the tag"""
|
|
37
40
|
return list(self._translations.keys())
|
|
38
41
|
|
|
39
|
-
def load_tags(self, tag_list: list[etree.
|
|
42
|
+
def load_tags(self, tag_list: list[etree._Element]) -> None:
|
|
40
43
|
"""Load a list of Tags"""
|
|
41
44
|
for i in tag_list:
|
|
42
|
-
|
|
45
|
+
i_lang = i.get(_XML_LANG)
|
|
46
|
+
if i_lang is None:
|
|
43
47
|
if i.text is not None:
|
|
44
48
|
self._text = i.text.strip()
|
|
45
49
|
else:
|
|
46
50
|
self._text = ""
|
|
47
51
|
else:
|
|
48
52
|
if i.text is not None:
|
|
49
|
-
self._translations[
|
|
53
|
+
self._translations[i_lang] = i.text.strip()
|
|
50
54
|
else:
|
|
51
|
-
self._translations[
|
|
55
|
+
self._translations[i_lang] = ""
|
|
52
56
|
|
|
53
|
-
def write_tags(self, parent_tag: etree.
|
|
57
|
+
def write_tags(self, parent_tag: etree._Element, tag_name: str) -> None:
|
|
54
58
|
"""Writes a Tag"""
|
|
55
59
|
default_tag = etree.SubElement(parent_tag, tag_name)
|
|
56
60
|
default_tag.text = self._text
|
|
57
61
|
|
|
58
62
|
for key, value in self._translations.items():
|
|
59
63
|
translation_tag = etree.SubElement(parent_tag, tag_name)
|
|
60
|
-
translation_tag.set(
|
|
64
|
+
translation_tag.set(_XML_LANG, key)
|
|
61
65
|
translation_tag.text = value
|
|
62
66
|
|
|
63
67
|
def clear(self) -> None:
|
|
@@ -90,37 +94,43 @@ class TranslateableList:
|
|
|
90
94
|
"Returns a list with the default items"
|
|
91
95
|
return list(self._translated_data.keys())
|
|
92
96
|
|
|
93
|
-
def get_translated_list(self, lang: str) -> list[str]:
|
|
97
|
+
def get_translated_list(self, lang: Optional[str]) -> list[str]:
|
|
94
98
|
"Returns the translated list for the given language"
|
|
95
|
-
if lang in self._translated_lists:
|
|
99
|
+
if lang is not None and lang in self._translated_lists:
|
|
96
100
|
return self._translated_lists[lang]
|
|
97
101
|
|
|
98
102
|
return_list: list[str] = []
|
|
99
103
|
for untranslated_text, translations in self._translated_data.items():
|
|
100
|
-
|
|
104
|
+
translated_text = untranslated_text
|
|
105
|
+
if lang is not None:
|
|
106
|
+
translated_text = translations.get(lang, untranslated_text)
|
|
107
|
+
return_list.append(translated_text)
|
|
101
108
|
return return_list
|
|
102
109
|
|
|
103
|
-
def load_tag(self, tag: etree.
|
|
110
|
+
def load_tag(self, tag: etree._Element) -> None:
|
|
104
111
|
"Loads an Tag. Only for internal use."
|
|
105
|
-
|
|
112
|
+
tag_lang = tag.get(_XML_LANG)
|
|
113
|
+
if tag_lang is None:
|
|
106
114
|
current_text = ""
|
|
107
|
-
for i in tag
|
|
108
|
-
if i.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
for i in tag:
|
|
116
|
+
if i.text is None:
|
|
117
|
+
continue
|
|
118
|
+
i_lang = i.get(_XML_LANG)
|
|
119
|
+
if i_lang is None:
|
|
120
|
+
current_text = i.text.strip()
|
|
121
|
+
self._translated_data[current_text] = {}
|
|
114
122
|
else:
|
|
115
123
|
if current_text in self._translated_data:
|
|
116
|
-
self._translated_data[current_text][
|
|
124
|
+
self._translated_data[current_text][i_lang] = i.text.strip()
|
|
117
125
|
else:
|
|
118
|
-
if
|
|
119
|
-
self._translated_lists[
|
|
120
|
-
for i in tag
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
if tag_lang not in self._translated_lists:
|
|
127
|
+
self._translated_lists[tag_lang] = []
|
|
128
|
+
for i in tag:
|
|
129
|
+
if i.text is None:
|
|
130
|
+
continue
|
|
131
|
+
self._translated_lists[tag_lang].append(i.text.strip())
|
|
132
|
+
|
|
133
|
+
def write_all_tag(self, parent_tag: etree._Element, tag_name: str) -> None:
|
|
124
134
|
"Writes the XML tags. Onnly for internal use."
|
|
125
135
|
for untranslated_text, translations in self._translated_data.items():
|
|
126
136
|
default_tag = etree.SubElement(parent_tag, tag_name)
|
|
@@ -130,13 +140,13 @@ class TranslateableList:
|
|
|
130
140
|
translated_tag.set(_XML_LANG, lang)
|
|
131
141
|
translated_tag.text = translated_text
|
|
132
142
|
|
|
133
|
-
def write_untranslated_tags(self, parent_tag: etree.
|
|
143
|
+
def write_untranslated_tags(self, parent_tag: etree._Element, tag_name: str) -> None:
|
|
134
144
|
"Writes the untranslated XML tags. Onnly for internal use."
|
|
135
145
|
for untranslated_text in self._translated_data.keys():
|
|
136
146
|
default_tag = etree.SubElement(parent_tag, tag_name)
|
|
137
147
|
default_tag.text = untranslated_text
|
|
138
148
|
|
|
139
|
-
def write_translated_tags(self, parent_tag: etree.
|
|
149
|
+
def write_translated_tags(self, parent_tag: etree._Element, tag_name: str, lang: str) -> None:
|
|
140
150
|
"Writes the translated XML tags. Onnly for internal use."
|
|
141
151
|
for untranslated_text, translations in self._translated_data.items():
|
|
142
152
|
child_tag = etree.SubElement(parent_tag, tag_name)
|
|
@@ -163,17 +173,17 @@ class DescriptionItem:
|
|
|
163
173
|
|
|
164
174
|
def get_type(self) -> Literal["paragraph", "unordered-list", "ordered-list"]:
|
|
165
175
|
"Retutns the Type of the Item"
|
|
166
|
-
|
|
176
|
+
raise NotImplementedError
|
|
167
177
|
|
|
168
|
-
def load_tags(self, tag_list: list[etree.
|
|
178
|
+
def load_tags(self, tag_list: list[etree._Element]) -> None:
|
|
169
179
|
"Loads teh XML tags into the Elemnt. Only for internal use."
|
|
170
180
|
raise NotImplementedError
|
|
171
181
|
|
|
172
|
-
def get_tags(self, parent_tag: etree.
|
|
182
|
+
def get_tags(self, parent_tag: etree._Element) -> None:
|
|
173
183
|
"Get the XML Tag from the Element. Only for internal use."
|
|
174
184
|
raise NotImplementedError()
|
|
175
185
|
|
|
176
|
-
def get_translated_tag(self, lang: Optional[str]) -> etree.
|
|
186
|
+
def get_translated_tag(self, lang: Optional[str]) -> etree._Element:
|
|
177
187
|
"Loads the tag for a given language"
|
|
178
188
|
raise NotImplementedError()
|
|
179
189
|
|
|
@@ -193,15 +203,15 @@ class DescriptionParagraph(DescriptionItem):
|
|
|
193
203
|
"Retutns the Type of the Item"
|
|
194
204
|
return "paragraph"
|
|
195
205
|
|
|
196
|
-
def load_tags(self, tag_list: list[etree.
|
|
206
|
+
def load_tags(self, tag_list: list[etree._Element]) -> None:
|
|
197
207
|
"Loads teh XML tags into the Elemnt. Only for internal use."
|
|
198
208
|
self.content.load_tags(tag_list)
|
|
199
209
|
|
|
200
|
-
def get_tags(self, parent_tag: etree.
|
|
210
|
+
def get_tags(self, parent_tag: etree._Element) -> None:
|
|
201
211
|
"Get the XML Tag from the Element. Only for internal use."
|
|
202
212
|
self.content.write_tags(parent_tag, "p")
|
|
203
213
|
|
|
204
|
-
def get_translated_tag(self, lang: Optional[str]) -> etree.
|
|
214
|
+
def get_translated_tag(self, lang: Optional[str]) -> etree._Element:
|
|
205
215
|
"Loads the tag for a given language"
|
|
206
216
|
paragraph_tag = etree.Element("p")
|
|
207
217
|
if lang is None:
|
|
@@ -212,7 +222,7 @@ class DescriptionParagraph(DescriptionItem):
|
|
|
212
222
|
|
|
213
223
|
def to_plain_text(self, lang: Optional[str] = None) -> str:
|
|
214
224
|
"Returns the content as plain text"
|
|
215
|
-
return self.content.get_translated_text_default(lang).strip()
|
|
225
|
+
return (self.content.get_translated_text_default(lang) or "").strip()
|
|
216
226
|
|
|
217
227
|
def __repr__(self) -> str:
|
|
218
228
|
return f"<DescriptionParagraph default='{self.content.get_default_text()}'>"
|
|
@@ -240,16 +250,19 @@ class DescriptionList(DescriptionItem):
|
|
|
240
250
|
else:
|
|
241
251
|
return "ordered-list"
|
|
242
252
|
|
|
243
|
-
def load_tags(self, tag_list: list[etree.
|
|
253
|
+
def load_tags(self, tag_list: etree._Element | list[etree._Element]) -> None:
|
|
244
254
|
"Loads the XML tags into the Elemnt. Only for internal use."
|
|
245
|
-
|
|
255
|
+
if not isinstance(tag_list, list):
|
|
256
|
+
tag_list = [tag_list]
|
|
257
|
+
for tag in tag_list:
|
|
258
|
+
self.content.load_tag(tag)
|
|
246
259
|
|
|
247
|
-
def get_tags(self, parent_tag: etree.
|
|
260
|
+
def get_tags(self, parent_tag: etree._Element) -> None:
|
|
248
261
|
"Get the XML Tag from the Element. Only for internal use."
|
|
249
262
|
list_tag = etree.SubElement(parent_tag, self._list_type)
|
|
250
263
|
self.content.write_all_tag(list_tag, "li")
|
|
251
264
|
|
|
252
|
-
def get_translated_tag(self, lang: Optional[str] = None) -> etree.
|
|
265
|
+
def get_translated_tag(self, lang: Optional[str] = None) -> etree._Element:
|
|
253
266
|
"Loads the tag for a given language"
|
|
254
267
|
list_tag = etree.Element(self._list_type)
|
|
255
268
|
if lang is None:
|
|
@@ -291,12 +304,12 @@ class Description:
|
|
|
291
304
|
self.items: list[DescriptionItem] = []
|
|
292
305
|
"""All Description Items"""
|
|
293
306
|
|
|
294
|
-
def load_tags(self, tag: etree.
|
|
307
|
+
def load_tags(self, tag: etree._Element) -> None:
|
|
295
308
|
"Load a XML tag. Onyl for internal use."
|
|
296
|
-
paragraph_list: list[etree.
|
|
297
|
-
for i in tag
|
|
309
|
+
paragraph_list: list[etree._Element] = []
|
|
310
|
+
for i in tag:
|
|
298
311
|
if i.tag == "p":
|
|
299
|
-
if i.get(
|
|
312
|
+
if i.get(_XML_LANG) is not None:
|
|
300
313
|
paragraph_list.append(i)
|
|
301
314
|
else:
|
|
302
315
|
if len(paragraph_list) != 0:
|
|
@@ -321,7 +334,7 @@ class Description:
|
|
|
321
334
|
paragraph_item.load_tags(paragraph_list)
|
|
322
335
|
self.items.append(paragraph_item)
|
|
323
336
|
|
|
324
|
-
def get_tags(self, parent_tag: etree.
|
|
337
|
+
def get_tags(self, parent_tag: etree._Element) -> None:
|
|
325
338
|
"Writes a Description tag. Only for internal use."
|
|
326
339
|
description_tag = etree.SubElement(parent_tag, "description")
|
|
327
340
|
for i in self.items:
|
|
@@ -66,6 +66,12 @@ PROVIDES_TYPES_LITERAL = Literal[
|
|
|
66
66
|
"id"
|
|
67
67
|
]
|
|
68
68
|
|
|
69
|
+
RELATION_LITERAL = Literal[
|
|
70
|
+
"requires",
|
|
71
|
+
"recommends",
|
|
72
|
+
"supports"
|
|
73
|
+
]
|
|
74
|
+
|
|
69
75
|
RELATION_COMPARISON_OPERATOR_LITERAL = Literal[
|
|
70
76
|
"eq",
|
|
71
77
|
"ne",
|
|
@@ -88,11 +94,16 @@ CONTROL_TYPES_LITERAL = Literal[
|
|
|
88
94
|
]
|
|
89
95
|
|
|
90
96
|
INTERNET_RELATION_VALUE_LITERAL = Literal[
|
|
91
|
-
"always"
|
|
92
|
-
"offline-only"
|
|
97
|
+
"always",
|
|
98
|
+
"offline-only",
|
|
93
99
|
"first-run"
|
|
94
100
|
]
|
|
95
101
|
|
|
102
|
+
IMAGE_TYPE_LITERAL = Literal[
|
|
103
|
+
"source",
|
|
104
|
+
"thumbnail"
|
|
105
|
+
]
|
|
106
|
+
|
|
96
107
|
URL_TYPES = list(get_args(URL_TYPES_LITERAL))
|
|
97
108
|
"All URL types"
|
|
98
109
|
|
|
@@ -108,11 +119,17 @@ OARS_VALUE_TYPES = list(get_args(OARS_VALUE_TYPES_LITERAL))
|
|
|
108
119
|
PROVIDES_TYPES = list(get_args(PROVIDES_TYPES_LITERAL))
|
|
109
120
|
"The list with all types for provides"
|
|
110
121
|
|
|
122
|
+
RELATION = list(get_args(RELATION_LITERAL))
|
|
123
|
+
"The list with all relations"
|
|
124
|
+
|
|
111
125
|
RELATION_COMPARISON_OPERATOR = list(get_args(RELATION_COMPARISON_OPERATOR_LITERAL))
|
|
112
|
-
"The
|
|
126
|
+
"The available Operators for the relation compare attribute"
|
|
113
127
|
|
|
114
128
|
CONTROL_TYPES = list(get_args(CONTROL_TYPES_LITERAL))
|
|
115
129
|
"The list with all possible values for control"
|
|
116
130
|
|
|
117
131
|
INTERNET_RELATION_VALUE = list(get_args(INTERNET_RELATION_VALUE_LITERAL))
|
|
118
132
|
"The list with all possible values for internet"
|
|
133
|
+
|
|
134
|
+
IMAGE_TYPE = list(get_args(IMAGE_TYPE_LITERAL))
|
|
135
|
+
"The list with all possible values for image type"
|
|
File without changes
|
appstream_python/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.9.0
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: appstream-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: A library for dealing with Freedesktop Appstream data
|
|
5
5
|
Author-email: JakobDev <jakobdev@gmx.de>
|
|
6
|
-
License: BSD-2-Clause
|
|
6
|
+
License-Expression: BSD-2-Clause
|
|
7
7
|
Project-URL: Documentation, https://appstream-python.readthedocs.io
|
|
8
8
|
Project-URL: Issues, https://codeberg.org/JakobDev/appstream-python/issues
|
|
9
9
|
Project-URL: Source, https://codeberg.org/JakobDev/appstream-python
|
|
@@ -12,20 +12,22 @@ Keywords: JakobDev,AppStream,Freedesktop,Linux
|
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: Environment :: Other Environment
|
|
15
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
16
15
|
Classifier: Operating System :: POSIX
|
|
17
16
|
Classifier: Operating System :: POSIX :: BSD
|
|
18
17
|
Classifier: Operating System :: POSIX :: Linux
|
|
19
18
|
Classifier: Programming Language :: Python :: 3
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
23
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
24
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
24
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.11
|
|
25
26
|
Description-Content-Type: text/markdown
|
|
26
27
|
License-File: LICENSE
|
|
27
|
-
Requires-Dist: requests
|
|
28
28
|
Requires-Dist: lxml
|
|
29
|
+
Requires-Dist: requests
|
|
30
|
+
Dynamic: license-file
|
|
29
31
|
|
|
30
32
|
# appstream-python
|
|
31
33
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
appstream_python/Collection.py,sha256=KlJxeUELQXcUGubL9nkztI5zH8JZAxAnOIqCLJFvZo8,3482
|
|
2
|
+
appstream_python/Component.py,sha256=o-oZnDoiSDQcOGLQulC_pFQHdwy4AjfR_Xky_nkUnlg,28058
|
|
3
|
+
appstream_python/Release.py,sha256=0z9D_OGpsFoXlTvUeRQMYc2s1_nqzNzepMOFddqEH9w,6028
|
|
4
|
+
appstream_python/Shared.py,sha256=kkdRva9l-Fgu8lhdDW7D-07-GCvzZUqb6vOxNoVtb-E,14111
|
|
5
|
+
appstream_python/StandardConstants.py,sha256=CFI2KcZBPh9a8t-5o7paFTaOPeCaIVTtN4XG3USXLXo,2698
|
|
6
|
+
appstream_python/__init__.py,sha256=eB0PvIoWrH0ZkNq2yY2h9xujdNqJMKmyM01KDpm6Yis,212
|
|
7
|
+
appstream_python/_helper.py,sha256=T1On8-taSPoKgpz9u8briuRO0uurcBL415o8g2Gc678,326
|
|
8
|
+
appstream_python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
appstream_python/version.txt,sha256=nYyU8a0-qWseKsSRT9pMuTx2tKPg2Mxt2JdtbAsifRU,6
|
|
10
|
+
appstream_python-0.9.0.dist-info/licenses/LICENSE,sha256=weEYp8N1kpgppN3rkAAKxi69mEuhuDX7SipDP9Lz5bU,1323
|
|
11
|
+
appstream_python-0.9.0.dist-info/METADATA,sha256=Zy60EAcnfS9QpUwrqLjAokJ0lpOKX77EuD5g7d1Ybqw,1414
|
|
12
|
+
appstream_python-0.9.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
13
|
+
appstream_python-0.9.0.dist-info/top_level.txt,sha256=TEnvfaZcEAhAofLtB0lquKH1fwr9C-9dxpGN405dZiQ,17
|
|
14
|
+
appstream_python-0.9.0.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
appstream_python/Collection.py,sha256=1WG1jyQCUEtaU8jv_iOjMZDibUjSSv9VkWkVUz9TtTA,3472
|
|
2
|
-
appstream_python/Component.py,sha256=8HxrSdkiMv3Ff-pfFLbMxxDC4A20gzIyE4g7QkxiHb0,24302
|
|
3
|
-
appstream_python/Release.py,sha256=sLQbv0T7FLYZYcfGivveVOKFD6rptoCAK0lbYCFYeiE,5409
|
|
4
|
-
appstream_python/Shared.py,sha256=zHeUDicNbuP_jXx3VTSNGuq7XFGUryZg0zMVCgGdXgo,13959
|
|
5
|
-
appstream_python/StandardConstants.py,sha256=-SzBKeXWFgY_6wc3SgptDlj88gOE_ztXBeBM3Xx3ko4,2376
|
|
6
|
-
appstream_python/__init__.py,sha256=eB0PvIoWrH0ZkNq2yY2h9xujdNqJMKmyM01KDpm6Yis,212
|
|
7
|
-
appstream_python/_helper.py,sha256=T1On8-taSPoKgpz9u8briuRO0uurcBL415o8g2Gc678,326
|
|
8
|
-
appstream_python/version.txt,sha256=qvZyHcN8QLQjOsz8CB8ld2_zvR0qS51c6nYNHCz4ZmU,6
|
|
9
|
-
appstream_python-0.8.1.dist-info/LICENSE,sha256=KHWQLS2VDTyyF0CMgSrCU2j758vzhIUXA4viB-DWlTI,1318
|
|
10
|
-
appstream_python-0.8.1.dist-info/METADATA,sha256=2f82WuWQSonypjpDg6zB4_Q25GJJ8IO2U4J1O7debok,1330
|
|
11
|
-
appstream_python-0.8.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
12
|
-
appstream_python-0.8.1.dist-info/top_level.txt,sha256=TEnvfaZcEAhAofLtB0lquKH1fwr9C-9dxpGN405dZiQ,17
|
|
13
|
-
appstream_python-0.8.1.dist-info/RECORD,,
|
|
File without changes
|