charmarr-lib-core 0.12.2__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.
Files changed (32) hide show
  1. charmarr_lib/core/__init__.py +126 -0
  2. charmarr_lib/core/_arr/__init__.py +72 -0
  3. charmarr_lib/core/_arr/_arr_client.py +154 -0
  4. charmarr_lib/core/_arr/_base_client.py +314 -0
  5. charmarr_lib/core/_arr/_config_builders.py +214 -0
  6. charmarr_lib/core/_arr/_config_xml.py +121 -0
  7. charmarr_lib/core/_arr/_protocols.py +54 -0
  8. charmarr_lib/core/_arr/_reconcilers.py +269 -0
  9. charmarr_lib/core/_arr/_recyclarr.py +150 -0
  10. charmarr_lib/core/_juju/__init__.py +27 -0
  11. charmarr_lib/core/_juju/_pebble.py +102 -0
  12. charmarr_lib/core/_juju/_reconciler.py +137 -0
  13. charmarr_lib/core/_juju/_secrets.py +44 -0
  14. charmarr_lib/core/_k8s/__init__.py +43 -0
  15. charmarr_lib/core/_k8s/_hardware.py +191 -0
  16. charmarr_lib/core/_k8s/_permission_check.py +310 -0
  17. charmarr_lib/core/_k8s/_storage.py +253 -0
  18. charmarr_lib/core/_variant.py +37 -0
  19. charmarr_lib/core/_version.py +3 -0
  20. charmarr_lib/core/constants.py +29 -0
  21. charmarr_lib/core/enums.py +55 -0
  22. charmarr_lib/core/interfaces/__init__.py +78 -0
  23. charmarr_lib/core/interfaces/_base.py +103 -0
  24. charmarr_lib/core/interfaces/_download_client.py +125 -0
  25. charmarr_lib/core/interfaces/_flaresolverr.py +69 -0
  26. charmarr_lib/core/interfaces/_media_indexer.py +131 -0
  27. charmarr_lib/core/interfaces/_media_manager.py +111 -0
  28. charmarr_lib/core/interfaces/_media_server.py +74 -0
  29. charmarr_lib/core/interfaces/_media_storage.py +99 -0
  30. charmarr_lib_core-0.12.2.dist-info/METADATA +136 -0
  31. charmarr_lib_core-0.12.2.dist-info/RECORD +32 -0
  32. charmarr_lib_core-0.12.2.dist-info/WHEEL +4 -0
@@ -0,0 +1,99 @@
1
+ # Copyright 2025 The Charmarr Project
2
+ # See LICENSE file for licensing details.
3
+
4
+ """Media storage interface for shared PVC and PUID/PGID coordination."""
5
+
6
+ from typing import Any
7
+
8
+ from ops import EventBase, EventSource, ObjectEvents
9
+ from pydantic import BaseModel, Field
10
+
11
+ from charmarr_lib.core.interfaces._base import (
12
+ EventObservingMixin,
13
+ RelationInterfaceBase,
14
+ )
15
+
16
+
17
+ class MediaStorageProviderData(BaseModel):
18
+ """Data published by charmarr-storage charm."""
19
+
20
+ pvc_name: str = Field(description="Name of the shared PVC to mount")
21
+ mount_path: str = Field(
22
+ default="/data", description="Mount path for the shared storage inside containers"
23
+ )
24
+ puid: int = Field(default=1000, description="User ID for file ownership (LinuxServer PUID)")
25
+ pgid: int = Field(default=1000, description="Group ID for file ownership (LinuxServer PGID)")
26
+
27
+
28
+ class MediaStorageRequirerData(BaseModel):
29
+ """Data published by apps mounting storage."""
30
+
31
+ instance_name: str = Field(description="Juju application name")
32
+
33
+
34
+ class MediaStorageChangedEvent(EventBase):
35
+ """Event emitted when media-storage relation state changes."""
36
+
37
+ pass
38
+
39
+
40
+ class MediaStorageProvider(
41
+ RelationInterfaceBase[MediaStorageProviderData, MediaStorageRequirerData]
42
+ ):
43
+ """Provider side of media-storage interface (passive - no events)."""
44
+
45
+ def __init__(self, charm: Any, relation_name: str = "media-storage") -> None:
46
+ super().__init__(charm, relation_name)
47
+
48
+ def _get_remote_data_model(self) -> type[MediaStorageRequirerData]:
49
+ return MediaStorageRequirerData
50
+
51
+ def publish_data(self, data: MediaStorageProviderData) -> None:
52
+ """Publish provider data to all relations."""
53
+ self._publish_to_all_relations(data)
54
+
55
+ def clear_data(self) -> None:
56
+ """Clear provider data from all relations."""
57
+ if not self._charm.unit.is_leader():
58
+ return
59
+
60
+ for relation in self._charm.model.relations.get(self._relation_name, []):
61
+ if "config" in relation.data[self._charm.app]:
62
+ del relation.data[self._charm.app]["config"]
63
+
64
+ def get_connected_apps(self) -> list[str]:
65
+ """Get list of connected application names (for logging/metrics)."""
66
+ return [r.instance_name for r in self._get_all_remote_app_data()]
67
+
68
+
69
+ class MediaStorageRequirerEvents(ObjectEvents):
70
+ """Events emitted by MediaStorageRequirer."""
71
+
72
+ changed = EventSource(MediaStorageChangedEvent)
73
+
74
+
75
+ class MediaStorageRequirer(
76
+ EventObservingMixin, RelationInterfaceBase[MediaStorageRequirerData, MediaStorageProviderData]
77
+ ):
78
+ """Requirer side of media-storage interface."""
79
+
80
+ on = MediaStorageRequirerEvents() # type: ignore[assignment]
81
+
82
+ def __init__(self, charm: Any, relation_name: str = "media-storage") -> None:
83
+ super().__init__(charm, relation_name)
84
+ self._setup_event_observation()
85
+
86
+ def _get_remote_data_model(self) -> type[MediaStorageProviderData]:
87
+ return MediaStorageProviderData
88
+
89
+ def publish_data(self, data: MediaStorageRequirerData) -> None:
90
+ """Publish requirer data to the relation."""
91
+ self._publish_to_single_relation(data)
92
+
93
+ def get_provider(self) -> MediaStorageProviderData | None:
94
+ """Get storage provider data if available."""
95
+ return self._get_single_provider_data()
96
+
97
+ def is_ready(self) -> bool:
98
+ """Check if storage is available."""
99
+ return self.get_provider() is not None
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: charmarr-lib-core
3
+ Version: 0.12.2
4
+ Summary: Core charm libraries for Charmarr media automation
5
+ Author-email: Adhitya Ravi <adhityashan95@gmail.com>
6
+ License: LGPL-3.0-or-later
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Python: >=3.12
13
+ Requires-Dist: charmarr-lib-krm>=0.1
14
+ Requires-Dist: httpx>=0.27
15
+ Requires-Dist: lightkube>=0.15
16
+ Requires-Dist: ops>=2.0
17
+ Requires-Dist: pydantic>=2.0
18
+ Provides-Extra: dev
19
+ Requires-Dist: mypy>=1.8; extra == 'dev'
20
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
21
+ Requires-Dist: pytest>=8.0; extra == 'dev'
22
+ Requires-Dist: ruff>=0.1; extra == 'dev'
23
+ Description-Content-Type: text/markdown
24
+
25
+ <p align="center">
26
+ <img src="../assets/charmarr-charmarr-lib.png" width="350" alt="Charmarr Lib">
27
+ </p>
28
+
29
+ <h1 align="center">charmarr-lib-core</h1>
30
+
31
+ Core charm libraries for Charmarr media automation.
32
+
33
+ ## Features
34
+
35
+ - Juju relation interfaces for media automation
36
+ - API clients for *arr applications (Radarr, Sonarr, Prowlarr, etc.)
37
+ - Reconcilers for managing application configuration
38
+ - Pydantic models for type-safe data validation
39
+ - K8s storage utilities for StatefulSet patching
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install charmarr-lib-core
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ ### Interfaces
50
+
51
+ ```python
52
+ from charmarr_lib.core.interfaces import (
53
+ # Media Indexer (Prowlarr <-> Radarr/Sonarr/Lidarr)
54
+ MediaIndexerProvider,
55
+ MediaIndexerRequirer,
56
+ MediaIndexerProviderData,
57
+ MediaIndexerRequirerData,
58
+ # Download Client (qBittorrent/SABnzbd <-> Radarr/Sonarr/Lidarr)
59
+ DownloadClientProvider,
60
+ DownloadClientRequirer,
61
+ DownloadClientProviderData,
62
+ DownloadClientRequirerData,
63
+ # Media Storage (shared PVC <-> all apps)
64
+ MediaStorageProvider,
65
+ MediaStorageRequirer,
66
+ MediaStorageProviderData,
67
+ MediaStorageRequirerData,
68
+ # Media Manager (Radarr/Sonarr <-> Overseerr/Jellyseerr)
69
+ MediaManagerProvider,
70
+ MediaManagerRequirer,
71
+ MediaManagerProviderData,
72
+ MediaManagerRequirerData,
73
+ QualityProfile,
74
+ )
75
+ ```
76
+
77
+ ### API Clients
78
+
79
+ ```python
80
+ from charmarr_lib.core import (
81
+ # Radarr/Sonarr/Lidarr client (v3 API)
82
+ ArrApiClient,
83
+ # Prowlarr client (v1 API)
84
+ ProwlarrApiClient,
85
+ # Errors
86
+ ArrApiError,
87
+ ArrApiConnectionError,
88
+ ArrApiResponseError,
89
+ )
90
+
91
+ # Connect to Radarr
92
+ with ArrApiClient("http://radarr:7878", api_key) as client:
93
+ profiles = client.get_quality_profiles()
94
+ client.add_root_folder("/movies")
95
+ ```
96
+
97
+ ### Reconcilers
98
+
99
+ ```python
100
+ from charmarr_lib.core import (
101
+ reconcile_download_clients,
102
+ reconcile_media_manager_connections,
103
+ reconcile_root_folder,
104
+ reconcile_external_url,
105
+ )
106
+
107
+ # Sync download clients from relations to Radarr
108
+ reconcile_download_clients(
109
+ api_client=arr_client,
110
+ desired_clients=download_client_data_from_relations,
111
+ category="radarr",
112
+ media_manager=MediaManager.RADARR,
113
+ get_secret=lambda sid: model.get_secret(id=sid).get_content(),
114
+ )
115
+ ```
116
+
117
+ ### Storage Utilities
118
+
119
+ ```python
120
+ from charmarr_lib.core import (
121
+ reconcile_storage_volume,
122
+ is_storage_mounted,
123
+ K8sResourceManager,
124
+ ReconcileResult,
125
+ )
126
+
127
+ manager = K8sResourceManager()
128
+ result = reconcile_storage_volume(
129
+ manager=manager,
130
+ statefulset_name="radarr",
131
+ container_name="radarr", # from charmcraft.yaml
132
+ namespace="media",
133
+ pvc_name="media-storage",
134
+ mount_path="/data",
135
+ )
136
+ ```
@@ -0,0 +1,32 @@
1
+ charmarr_lib/core/__init__.py,sha256=9iTHLea8kbXNPBOAcBkx4wQK3T3wa5LPAW0TGm9qop4,3230
2
+ charmarr_lib/core/_variant.py,sha256=5SLyJn3fEsbG4K8iAIklwJTSkehkHzpJg-_-RpzkoMg,1356
3
+ charmarr_lib/core/_version.py,sha256=L6cX0RHoWTs-DaGZVEELehp3559HrfGryRfTWHaRXWg,93
4
+ charmarr_lib/core/constants.py,sha256=cn1TUDzQ79bD_olGxnxutKg-ccfhYPOVZa8P8UCdbo4,1260
5
+ charmarr_lib/core/enums.py,sha256=EIhDNYxTH2QNHzlmyTCFysnNIdAuMPff8vIKqcXUAfk,1120
6
+ charmarr_lib/core/_arr/__init__.py,sha256=hS7TGAbWVpyNBX51yLNVetF5-aYgkT_DmOWUPjQ7e1M,1842
7
+ charmarr_lib/core/_arr/_arr_client.py,sha256=ZZCJccFGibRKrt1TW5Romw8GXGTsqAUy7RPhAdw2jKM,4544
8
+ charmarr_lib/core/_arr/_base_client.py,sha256=9qEJHKhu2C_R2PR54MRLejMhOIklPm2WZ6xATxaN3Zw,9831
9
+ charmarr_lib/core/_arr/_config_builders.py,sha256=ON3tyn9bsBhggD-NHzXrWdMYyT643x5xm8Y1in2KlKU,8418
10
+ charmarr_lib/core/_arr/_config_xml.py,sha256=picvSBFoUgACc9A-Gi_P-G3qb5onklwsDAdIOKu08Zk,3510
11
+ charmarr_lib/core/_arr/_protocols.py,sha256=UqnNwSJpbWx29_7K_2svLKClMaj1ccQdjyYjZUKmXZg,1625
12
+ charmarr_lib/core/_arr/_reconcilers.py,sha256=xdoA-tuggTC-HTFM5g5yi_3OpqX6f0CNS8LdwpQbZYA,9041
13
+ charmarr_lib/core/_arr/_recyclarr.py,sha256=Qo55BwgDjfJ3OxQMz9WP98QqY_YlK-qq6j2Di_8AEDM,4666
14
+ charmarr_lib/core/_juju/__init__.py,sha256=c33kTuwUtmauzM9Z2RTRPFcw4d9JhMU__JQoZdjazIA,734
15
+ charmarr_lib/core/_juju/_pebble.py,sha256=2BYTl1bvVJYH_3NIRIeI_hiNjg2TesQtDlKIgYPrfko,3422
16
+ charmarr_lib/core/_juju/_reconciler.py,sha256=8fqmfI-y7oAI8Y9WecKq_tcWRsRW9RnNcXK5kiFkv7g,4780
17
+ charmarr_lib/core/_juju/_secrets.py,sha256=_silLiY3l7hOh5a_QpRH0U86oBqaIFmq6QMel97U7iw,1384
18
+ charmarr_lib/core/_k8s/__init__.py,sha256=Y_UXl8074Cxo4MdHVJRpp9-h-cp7fu32NFeXQAdK0YM,1398
19
+ charmarr_lib/core/_k8s/_hardware.py,sha256=ZAf48vznw5_35w0LaCa24d1RaXiCrQFphIDvetDgqko,6711
20
+ charmarr_lib/core/_k8s/_permission_check.py,sha256=fFW5RMZf1T2wANPBbcSbHqpjbwmjvyIAuetODtnz9NA,9105
21
+ charmarr_lib/core/_k8s/_storage.py,sha256=Z4znpev2wNHG04JvmCi7ekB2kRGxvpLiZXAkYIJkhls,8786
22
+ charmarr_lib/core/interfaces/__init__.py,sha256=tTbQTrYcUCBGVS3qspRpEpUrJwvw_U828Yz7c7D9lkY,2211
23
+ charmarr_lib/core/interfaces/_base.py,sha256=QEOMva4qzEmnm6NivzJFYc5jgFkWwWKbkMaxSFkTLuU,4185
24
+ charmarr_lib/core/interfaces/_download_client.py,sha256=y02oLszgSSoz86-1Oa8n-LbbFsRhSwfTmlAZqt60E10,4304
25
+ charmarr_lib/core/interfaces/_flaresolverr.py,sha256=Jqdw6cS-vBdKcKuK3v4aGB9Ln9VNIRrwZOj-3m6B4BE,2152
26
+ charmarr_lib/core/interfaces/_media_indexer.py,sha256=rEbVXOtdFzi4VWcJ8C7MvLHryy3JBOtbt4DpxKQM5cY,4300
27
+ charmarr_lib/core/interfaces/_media_manager.py,sha256=BDL5gQFWkiq6zjfjgx_jF1x22rlQ5k_qpERAZrrjeLM,4073
28
+ charmarr_lib/core/interfaces/_media_server.py,sha256=vkdVkKeDyWS-mk1KZqtKm4Pca95IP2-dM-drgtOswe8,2432
29
+ charmarr_lib/core/interfaces/_media_storage.py,sha256=affcBNnZ6AssXt_NQDfWD9gzPtnbMYh00Pg-GuG5CzY,3451
30
+ charmarr_lib_core-0.12.2.dist-info/METADATA,sha256=1tOcrh_tWevgvA8HMDYIkrhY2Uzak8Jd5UQ7e8zk0NM,3561
31
+ charmarr_lib_core-0.12.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
32
+ charmarr_lib_core-0.12.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any