pycatlink 1.0.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.
pycatlink/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """CatLink API Library."""
pycatlink/__main__.py ADDED
@@ -0,0 +1,252 @@
1
+ import asyncio
2
+ from collections.abc import Callable, Coroutine
3
+ from functools import wraps
4
+ from typing import Any, ParamSpec, TypeVar
5
+
6
+ import typer
7
+ from dotenv import load_dotenv
8
+
9
+ from pycatlink.c08 import CatlinkC08Device
10
+ from pycatlink.client import CatlinkApiClient
11
+
12
+ from .account import CatlinkAccount
13
+ from .const import (
14
+ ENV_CATLINK_PASSWORD,
15
+ ENV_CATLINK_PHONE,
16
+ ENV_CATLINK_PHONE_INTERNATIONAL_CODE,
17
+ HttpMethod,
18
+ )
19
+ from .models import CatlinkAccountConfig
20
+
21
+ app = typer.Typer()
22
+
23
+ P = ParamSpec("P")
24
+ R = TypeVar("R")
25
+
26
+
27
+ def syncify(f: Callable[P, Coroutine[Any, Any, R]]) -> Callable[P, R]:
28
+ """Decorator to convert async function to sync by running with asyncio.run."""
29
+
30
+ @wraps(f)
31
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
32
+ return asyncio.run(f(*args, **kwargs))
33
+
34
+ return wrapper
35
+
36
+
37
+ @app.command("fetch")
38
+ @syncify
39
+ async def api_fetch(
40
+ phone: str = typer.Option(
41
+ ...,
42
+ help="Catlink account phone number (not starting with 0 or country code).",
43
+ envvar=ENV_CATLINK_PHONE,
44
+ ),
45
+ password: str = typer.Option(
46
+ ...,
47
+ help="Catlink account password.",
48
+ envvar=ENV_CATLINK_PASSWORD,
49
+ ),
50
+ phone_international_code: str = typer.Option(
51
+ ...,
52
+ help="Catlink account phone international code.",
53
+ envvar=ENV_CATLINK_PHONE_INTERNATIONAL_CODE,
54
+ ),
55
+ path: str = typer.Option(
56
+ ...,
57
+ help="API path to fetch data from.",
58
+ ),
59
+ parameters: str = typer.Option(
60
+ "",
61
+ help="Query parameters in key1=value1&key2=value2 format.",
62
+ ),
63
+ ) -> None:
64
+ """GET Request to the Catlink API."""
65
+
66
+ config = CatlinkAccountConfig(
67
+ phone=phone,
68
+ password=password,
69
+ phone_international_code=phone_international_code,
70
+ )
71
+ client = CatlinkApiClient(config)
72
+
73
+ parameters_dict: dict[str, str] = {}
74
+ if parameters:
75
+ for pair in parameters.split("&"):
76
+ if "=" in pair:
77
+ key, value = pair.split("=", 1)
78
+ parameters_dict[key] = value
79
+
80
+ response = await client.request_with_auto_login(
81
+ path=path,
82
+ parameters=parameters_dict,
83
+ method=HttpMethod.GET,
84
+ )
85
+
86
+ typer.echo("Response:")
87
+ typer.echo(response)
88
+
89
+
90
+ @app.command()
91
+ @syncify
92
+ async def pets(
93
+ phone: str = typer.Option(
94
+ ...,
95
+ help="Catlink account phone number (not starting with 0 or country code).",
96
+ envvar=ENV_CATLINK_PHONE,
97
+ ),
98
+ password: str = typer.Option(
99
+ ...,
100
+ help="Catlink account password.",
101
+ envvar=ENV_CATLINK_PASSWORD,
102
+ ),
103
+ phone_international_code: str = typer.Option(
104
+ ...,
105
+ help="Catlink account phone international code.",
106
+ envvar=ENV_CATLINK_PHONE_INTERNATIONAL_CODE,
107
+ ),
108
+ ) -> None:
109
+ """Print the list of pets associated with the account."""
110
+
111
+ config = CatlinkAccountConfig(
112
+ phone=phone,
113
+ password=password,
114
+ phone_international_code=phone_international_code,
115
+ )
116
+
117
+ account = CatlinkAccount(config)
118
+
119
+ typer.echo("Fetching pets...")
120
+
121
+ pets = await account.get_pets()
122
+
123
+ typer.echo(f"Found {len(pets)} pet(s)")
124
+
125
+ for i, pet in enumerate(pets):
126
+ typer.echo(f"Pet {i + 1}")
127
+ typer.echo(pet)
128
+
129
+
130
+ @app.command()
131
+ @syncify
132
+ async def devices(
133
+ phone: str = typer.Option(
134
+ ...,
135
+ help="Catlink account phone number (not starting with 0 or country code).",
136
+ envvar=ENV_CATLINK_PHONE,
137
+ ),
138
+ password: str = typer.Option(
139
+ ...,
140
+ help="Catlink account password.",
141
+ envvar=ENV_CATLINK_PASSWORD,
142
+ ),
143
+ phone_international_code: str = typer.Option(
144
+ ...,
145
+ help="Catlink account phone international code.",
146
+ envvar=ENV_CATLINK_PHONE_INTERNATIONAL_CODE,
147
+ ),
148
+ ) -> None:
149
+ """Print the list of devices associated with the account."""
150
+
151
+ config = CatlinkAccountConfig(
152
+ phone=phone,
153
+ password=password,
154
+ phone_international_code=phone_international_code,
155
+ )
156
+
157
+ account = CatlinkAccount(config)
158
+
159
+ typer.echo("Fetching devices...")
160
+
161
+ devices = await account.get_devices()
162
+
163
+ typer.echo(f"Found {len(devices)} device(s)")
164
+
165
+ for i, device in enumerate(devices):
166
+ typer.echo(f"Device {i + 1}")
167
+ typer.echo(device.device_info)
168
+
169
+
170
+ @app.command()
171
+ @syncify
172
+ async def device(
173
+ phone: str = typer.Option(
174
+ ...,
175
+ help="Catlink account phone number (not starting with 0 or country code).",
176
+ envvar=ENV_CATLINK_PHONE,
177
+ ),
178
+ password: str = typer.Option(
179
+ ...,
180
+ help="Catlink account password.",
181
+ envvar=ENV_CATLINK_PASSWORD,
182
+ ),
183
+ phone_international_code: str = typer.Option(
184
+ ...,
185
+ help="Catlink account phone international code.",
186
+ envvar=ENV_CATLINK_PHONE_INTERNATIONAL_CODE,
187
+ ),
188
+ device_id: str = typer.Argument(
189
+ ...,
190
+ help="ID of the device to get details for.",
191
+ ),
192
+ ) -> None:
193
+ """Print detailed information for a specific device."""
194
+
195
+ config = CatlinkAccountConfig(
196
+ phone=phone,
197
+ password=password,
198
+ phone_international_code=phone_international_code,
199
+ )
200
+
201
+ account = CatlinkAccount(config)
202
+
203
+ typer.echo("Fetching devices...")
204
+
205
+ devices = await account.get_devices()
206
+
207
+ device = next(
208
+ (device for device in devices if str(device.device_info.id) == device_id), None
209
+ )
210
+ if not device:
211
+ typer.echo(f"Device with ID {device_id} not found.")
212
+ raise typer.Exit(code=1)
213
+
214
+ typer.echo(f"Refreshing data for device ID {device_id}...")
215
+
216
+ await device.refresh()
217
+
218
+ typer.echo(f"Info for device ID {device_id}:")
219
+ typer.echo(device.device_info)
220
+
221
+ if not isinstance(device, (CatlinkC08Device,)):
222
+ typer.echo("Detailed information fetching is only supported for C08 devices.")
223
+ raise typer.Exit(code=0)
224
+
225
+ typer.echo(f"Details for device ID {device_id}:")
226
+ typer.echo(device.device_details)
227
+
228
+ typer.echo(f"Stats for device ID {device_id}:")
229
+ typer.echo(device.device_stats)
230
+
231
+ typer.echo(f"Logs for device ID {device_id}:")
232
+ typer.echo(device.device_logs)
233
+
234
+ typer.echo(f"Pet stats for device ID {device_id}:")
235
+ typer.echo(device.pet_stats)
236
+
237
+ typer.echo(f"Linked pets for device ID {device_id}:")
238
+ typer.echo(device.linked_pets)
239
+
240
+ typer.echo(f"Selectable pets for device ID {device_id}:")
241
+ typer.echo(device.selectable_pets)
242
+
243
+ typer.echo(f"Notice configurations for device ID {device_id}:")
244
+ typer.echo(device.notice_configs)
245
+
246
+ typer.echo(f"About device information for device ID {device_id}:")
247
+ typer.echo(device.about_device)
248
+
249
+
250
+ if __name__ == "__main__":
251
+ load_dotenv()
252
+ app()
pycatlink/account.py ADDED
@@ -0,0 +1,75 @@
1
+ """Account module for CatLink API."""
2
+
3
+ from .c08 import CatlinkC08Device
4
+ from .client import CatlinkApiClient
5
+ from .const import (
6
+ API_DEVICE_LIST,
7
+ API_PET_LIST,
8
+ PARAMETER_TYPE,
9
+ RESPONSE_KEY_DATA,
10
+ RESPONSE_KEY_DEVICES,
11
+ RESPONSE_KEY_PETS,
12
+ RESPONSE_KEY_RECORDS,
13
+ TYPE_NONE,
14
+ HttpMethod,
15
+ )
16
+ from .device import CatlinkDevice
17
+ from .models import CatlinkAccountConfig, CatlinkDeviceInfo, CatlinkPet
18
+
19
+
20
+ class CatlinkAccount:
21
+ """Account class for CatLink integration."""
22
+
23
+ def __init__(self, config: CatlinkAccountConfig) -> None:
24
+ """Initialize the account."""
25
+ self.config = config
26
+
27
+ self._client = CatlinkApiClient(config)
28
+
29
+ async def get_devices(self) -> list[CatlinkDevice | CatlinkC08Device]:
30
+ """Get the list of devices associated with the account."""
31
+ response = await self._client.request_with_auto_login(
32
+ path=API_DEVICE_LIST,
33
+ method=HttpMethod.GET,
34
+ parameters={
35
+ PARAMETER_TYPE: TYPE_NONE,
36
+ },
37
+ )
38
+ device_infos = [
39
+ CatlinkDeviceInfo.from_dict(response_element)
40
+ for response_element in response.get(RESPONSE_KEY_DATA, {}).get(
41
+ RESPONSE_KEY_DEVICES, []
42
+ )
43
+ ]
44
+
45
+ return [self._create_device(device_info) for device_info in device_infos]
46
+
47
+ async def get_pets(self) -> list[CatlinkPet]:
48
+ """Get the list of pets associated with the account."""
49
+ response = await self._client.request_with_auto_login(
50
+ path=API_PET_LIST,
51
+ method=HttpMethod.GET,
52
+ parameters={},
53
+ )
54
+ pet_infos = [
55
+ CatlinkPet.from_dict(response_element)
56
+ for response_element in response.get(RESPONSE_KEY_DATA, {})
57
+ .get(RESPONSE_KEY_PETS, {})
58
+ .get(RESPONSE_KEY_RECORDS, [])
59
+ ]
60
+
61
+ return pet_infos
62
+
63
+ def _create_device(
64
+ self, device_info: CatlinkDeviceInfo
65
+ ) -> CatlinkDevice | CatlinkC08Device:
66
+ """Create the appropriate device instance based on device type."""
67
+ if not device_info.device_type:
68
+ return CatlinkDevice(device_info, self._client)
69
+
70
+ device_type_upper = device_info.device_type.upper()
71
+
72
+ if device_type_upper == "C08":
73
+ return CatlinkC08Device(device_info, self._client)
74
+
75
+ return CatlinkDevice(device_info, self._client)