airalo-sdk 1.0.1__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.
airalo/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ """
2
+ Airalo Python SDK
3
+
4
+ A Python SDK for integrating with Airalo's Partner API.
5
+ """
6
+
7
+ from .config import Config
8
+ from .airalo import Airalo
9
+ from .exceptions.airalo_exception import (
10
+ AiraloException,
11
+ ConfigurationError,
12
+ AuthenticationError,
13
+ ValidationError,
14
+ APIError,
15
+ NetworkError,
16
+ )
17
+
18
+ __version__ = "1.0.1"
19
+
20
+ __all__ = [
21
+ "Airalo",
22
+ "Config",
23
+ "AiraloException",
24
+ "ConfigurationError",
25
+ "AuthenticationError",
26
+ "ValidationError",
27
+ "APIError",
28
+ "NetworkError",
29
+ ]
airalo/airalo.py ADDED
@@ -0,0 +1,620 @@
1
+ """
2
+ Airalo Main Client Module
3
+
4
+ This module provides the main Airalo client class for SDK operations.
5
+ """
6
+
7
+ from typing import Any, Dict, List, Optional, Union
8
+
9
+ from .config import Config
10
+ from .helpers.signature import Signature
11
+ from .services.sim_service import SimService
12
+ from .services.oauth_service import OAuthService
13
+ from .services.order_service import OrderService
14
+ from .services.topup_service import TopupService
15
+ from .resources.http_resource import HttpResource
16
+ from .services.packages_service import PackagesService
17
+ from .exceptions.airalo_exception import AiraloException
18
+ from .resources.multi_http_resource import MultiHttpResource
19
+ from .services.future_order_service import FutureOrderService
20
+ from .services.compatibility_devices_service import CompatibilityDevicesService
21
+ from .services.installation_instructions_service import InstallationInstructionsService
22
+ from .services.exchange_rates_service import ExchangeRatesService
23
+ from .services.voucher_service import VoucherService
24
+
25
+
26
+ class Airalo:
27
+ """
28
+ Main Airalo SDK client.
29
+
30
+ Provides access to all Airalo API operations through a single interface.
31
+ """
32
+
33
+ # Class-level pool for resource sharing
34
+ _pool: Dict[str, Any] = {}
35
+
36
+ def __init__(self, config: Union[Dict[str, Any], Config, str]):
37
+ """
38
+ Initialize Airalo client.
39
+
40
+ Args:
41
+ config: Configuration data (dict, Config object, or JSON string)
42
+
43
+ Raises:
44
+ AiraloException: If initialization fails
45
+ """
46
+ try:
47
+ self._init_resources(config)
48
+ self._init_services()
49
+
50
+ # Store resources in pool for reuse
51
+ if not self._pool:
52
+ self._pool = {
53
+ "config": self._config,
54
+ "curl": self._http,
55
+ "multi_curl": self._multi_http,
56
+ "signature": self._signature,
57
+ "oauth": self._oauth,
58
+ "installation_instructions": self._installation_instructions,
59
+ "topup": self._topup,
60
+ "future_order": self._future_order,
61
+ "compatibility_devices": self._compatibility_devices,
62
+ "sim": self._sim,
63
+ "exchange_rates": self._exchange_rates,
64
+ "voucher": self._voucher,
65
+ }
66
+ except Exception as e:
67
+ self._pool = {}
68
+ raise AiraloException(f"Airalo SDK initialization failed: {str(e)}")
69
+
70
+ def _init_resources(self, config: Union[Dict[str, Any], Config, str]) -> None:
71
+ """
72
+ Initialize core resources.
73
+
74
+ Args:
75
+ config: Configuration data
76
+ """
77
+ # Initialize configuration
78
+ if isinstance(config, Config):
79
+ self._config = config
80
+ else:
81
+ self._config = self._pool.get("config") or Config(config)
82
+
83
+ # Initialize HTTP resources
84
+ self._http = self._pool.get("curl") or HttpResource(self._config)
85
+ self._multi_http = self._pool.get("multi_curl") or MultiHttpResource(
86
+ self._config
87
+ )
88
+
89
+ # Initialize signature helper
90
+ self._signature = self._pool.get("signature") or Signature(
91
+ self._config.get("client_secret")
92
+ )
93
+
94
+ def _init_services(self) -> None:
95
+ """
96
+ Initialize service classes.
97
+
98
+ Raises:
99
+ AiraloException: If authentication fails
100
+ """
101
+ # Initialize OAuth service
102
+ self._oauth = self._pool.get("oauth") or OAuthService(
103
+ self._config, self._http, self._signature
104
+ )
105
+
106
+ # Get access token
107
+ self._access_token = self._oauth.get_access_token()
108
+ if not self._access_token:
109
+ raise AiraloException("Failed to obtain access token")
110
+
111
+ # Initialize other services
112
+ self._packages = self._pool.get("packages") or PackagesService(
113
+ self._config, self._http, self._access_token
114
+ )
115
+ self._order = self._pool.get("order") or OrderService(
116
+ self._config,
117
+ self._http,
118
+ self._multi_http,
119
+ self._signature,
120
+ self._access_token,
121
+ )
122
+ self._topup = self._pool.get("topup") or TopupService(
123
+ self._config, self._http, self._signature, self._access_token
124
+ )
125
+ self._installation_instructions = self._pool.get(
126
+ "installation_instructions"
127
+ ) or InstallationInstructionsService(
128
+ self._config, self._http, self._access_token
129
+ )
130
+ self._future_order = self._pool.get("future_order") or FutureOrderService(
131
+ self._config, self._http, self._signature, self._access_token
132
+ )
133
+ self._compatibility_devices = self._pool.get(
134
+ "compatibility_devices"
135
+ ) or CompatibilityDevicesService(self._config, self._http, self._access_token)
136
+ self._sim = self._pool.get("sim") or SimService(
137
+ self._config, self._http, self._multi_http, self._access_token
138
+ )
139
+ self._exchange_rates = self._pool.get("exchange_rates") or ExchangeRatesService(
140
+ self._config, self._http, self._access_token
141
+ )
142
+ self._voucher = self._pool.get("voucher") or VoucherService(
143
+ self._config, self._http, self._signature, self._access_token
144
+ )
145
+
146
+ # =====================================================
147
+ # OAuth Methods
148
+ # =====================================================
149
+
150
+ def get_access_token(self) -> Optional[str]:
151
+ """
152
+ Get current access token.
153
+
154
+ Returns:
155
+ Access token or None
156
+ """
157
+ return self._access_token
158
+
159
+ def refresh_token(self) -> Optional[str]:
160
+ """
161
+ Refresh access token.
162
+
163
+ Returns:
164
+ New access token or None
165
+ """
166
+ self._access_token = self._oauth.refresh_token()
167
+ return self._access_token
168
+
169
+ # =====================================================
170
+ # Package Methods
171
+ # =====================================================
172
+
173
+ def get_all_packages(
174
+ self,
175
+ flat: bool = False,
176
+ limit: Optional[int] = None,
177
+ page: Optional[int] = None,
178
+ ) -> Optional[Dict]:
179
+ """
180
+ Get all available packages.
181
+
182
+ Args:
183
+ flat: If True, return flattened response
184
+ limit: Number of results per page
185
+ page: Page number
186
+
187
+ Returns:
188
+ Packages data or None
189
+ """
190
+ return self._packages.get_all_packages(flat, limit, page)
191
+
192
+ def get_sim_packages(
193
+ self,
194
+ flat: bool = False,
195
+ limit: Optional[int] = None,
196
+ page: Optional[int] = None,
197
+ ) -> Optional[Dict]:
198
+ """
199
+ Get SIM-only packages.
200
+
201
+ Args:
202
+ flat: If True, return flattened response
203
+ limit: Number of results per page
204
+ page: Page number
205
+
206
+ Returns:
207
+ Packages data or None
208
+ """
209
+ return self._packages.get_sim_packages(flat, limit, page)
210
+
211
+ def get_local_packages(
212
+ self,
213
+ flat: bool = False,
214
+ limit: Optional[int] = None,
215
+ page: Optional[int] = None,
216
+ ) -> Optional[Dict]:
217
+ """
218
+ Get local packages.
219
+
220
+ Args:
221
+ flat: If True, return flattened response
222
+ limit: Number of results per page
223
+ page: Page number
224
+
225
+ Returns:
226
+ Packages data or None
227
+ """
228
+ return self._packages.get_local_packages(flat, limit, page)
229
+
230
+ def get_global_packages(
231
+ self,
232
+ flat: bool = False,
233
+ limit: Optional[int] = None,
234
+ page: Optional[int] = None,
235
+ ) -> Optional[Dict]:
236
+ """
237
+ Get global packages.
238
+
239
+ Args:
240
+ flat: If True, return flattened response
241
+ limit: Number of results per page
242
+ page: Page number
243
+
244
+ Returns:
245
+ Packages data or None
246
+ """
247
+ return self._packages.get_global_packages(flat, limit, page)
248
+
249
+ def get_country_packages(
250
+ self, country_code: str, flat: bool = False, limit: Optional[int] = None
251
+ ) -> Optional[Dict]:
252
+ """
253
+ Get packages for a specific country.
254
+
255
+ Args:
256
+ country_code: ISO country code
257
+ flat: If True, return flattened response
258
+ limit: Number of results
259
+
260
+ Returns:
261
+ Packages data or None
262
+ """
263
+ return self._packages.get_country_packages(country_code, flat, limit)
264
+
265
+ # =====================================================
266
+ # Order Methods
267
+ # =====================================================
268
+
269
+ def order(
270
+ self, package_id: str, quantity: int, description: Optional[str] = None
271
+ ) -> Optional[Dict]:
272
+ """
273
+ Create an order.
274
+
275
+ Args:
276
+ package_id: Package ID to order
277
+ quantity: Number of SIMs
278
+ description: Order description
279
+
280
+ Returns:
281
+ Order data or None
282
+ """
283
+ return self._order.create_order(
284
+ {
285
+ "package_id": package_id,
286
+ "quantity": quantity,
287
+ "type": "sim",
288
+ "description": description or "Order placed via Airalo Python SDK",
289
+ }
290
+ )
291
+
292
+ def order_with_email_sim_share(
293
+ self,
294
+ package_id: str,
295
+ quantity: int,
296
+ esim_cloud: Dict[str, Any],
297
+ description: Optional[str] = None,
298
+ ) -> Optional[Dict]:
299
+ """
300
+ Create an order with email SIM sharing.
301
+
302
+ Args:
303
+ package_id: Package ID to order
304
+ quantity: Number of SIMs
305
+ esim_cloud: Email sharing configuration
306
+ description: Order description
307
+
308
+ Returns:
309
+ Order data or None
310
+ """
311
+ return self._order.create_order_with_email_sim_share(
312
+ {
313
+ "package_id": package_id,
314
+ "quantity": quantity,
315
+ "type": "sim",
316
+ "description": description or "Order placed via Airalo Python SDK",
317
+ },
318
+ esim_cloud,
319
+ )
320
+
321
+ def order_async(
322
+ self,
323
+ package_id: str,
324
+ quantity: int,
325
+ webhook_url: Optional[str] = None,
326
+ description: Optional[str] = None,
327
+ ) -> Optional[Dict]:
328
+ """
329
+ Create an asynchronous order.
330
+
331
+ Args:
332
+ package_id: Package ID to order
333
+ quantity: Number of SIMs
334
+ webhook_url: Webhook URL for notifications
335
+ description: Order description
336
+
337
+ Returns:
338
+ Order data or None
339
+ """
340
+ return self._order.create_order_async(
341
+ {
342
+ "package_id": package_id,
343
+ "quantity": quantity,
344
+ "type": "sim",
345
+ "webhook_url": webhook_url,
346
+ "description": description or "Order placed via Airalo Python SDK",
347
+ }
348
+ )
349
+
350
+ def order_bulk(
351
+ self,
352
+ packages: Union[Dict[str, int], List[Dict]],
353
+ description: Optional[str] = None,
354
+ ) -> Optional[Dict]:
355
+ """
356
+ Create bulk orders.
357
+
358
+ Args:
359
+ packages: Either dict of package_id: quantity or list of dicts
360
+ description: Order description
361
+
362
+ Returns:
363
+ Order data or None
364
+ """
365
+ if not packages:
366
+ return None
367
+ return self._order.create_order_bulk(packages, description)
368
+
369
+ def order_bulk_with_email_sim_share(
370
+ self,
371
+ packages: Union[Dict[str, int], List[Dict]],
372
+ esim_cloud: Dict[str, Any],
373
+ description: Optional[str] = None,
374
+ ) -> Optional[Dict]:
375
+ """
376
+ Create bulk orders with email SIM sharing.
377
+
378
+ Args:
379
+ packages: Package IDs and quantities
380
+ esim_cloud: Email sharing configuration
381
+ description: Order description
382
+
383
+ Returns:
384
+ Order data or None
385
+ """
386
+ if not packages:
387
+ return None
388
+ return self._order.create_order_bulk_with_email_sim_share(
389
+ packages, esim_cloud, description
390
+ )
391
+
392
+ def order_async_bulk(
393
+ self,
394
+ packages: Union[Dict[str, int], List[Dict]],
395
+ webhook_url: Optional[str] = None,
396
+ description: Optional[str] = None,
397
+ ) -> Optional[Dict]:
398
+ """
399
+ Create bulk asynchronous orders.
400
+
401
+ Args:
402
+ packages: Package IDs and quantities
403
+ webhook_url: Webhook URL for notifications
404
+ description: Order description
405
+
406
+ Returns:
407
+ Order data or None
408
+ """
409
+ if not packages:
410
+ return None
411
+ return self._order.create_order_async_bulk(packages, webhook_url, description)
412
+
413
+ def topup(
414
+ self, package_id: str, iccid: str, description: Optional[str] = None
415
+ ) -> Optional[Dict]:
416
+ """
417
+ Create a top-up.
418
+
419
+ Args:
420
+ package_id: Package ID to top-up
421
+ iccid: ICCID of the SIM to top-up
422
+ description: Optional description for the top-up
423
+ Returns:
424
+ Top-up data or None
425
+ """
426
+ return self._topup.create_topup(
427
+ {
428
+ "package_id": package_id,
429
+ "iccid": iccid,
430
+ "description": description or "Topup placed via Airalo Python SDK",
431
+ }
432
+ )
433
+
434
+ # =====================================================
435
+ # Utility Methods
436
+ # =====================================================
437
+
438
+ def get_config(self) -> Config:
439
+ """
440
+ Get current configuration.
441
+
442
+ Returns:
443
+ Configuration object
444
+ """
445
+ return self._config
446
+
447
+ def get_environment(self) -> str:
448
+ """
449
+ Get current environment.
450
+
451
+ Returns:
452
+ Environment name ('sandbox' or 'production')
453
+ """
454
+ return self._config.get_environment()
455
+
456
+ def clear_cache(self) -> None:
457
+ """Clear all cached data."""
458
+ from .helpers.cached import Cached
459
+
460
+ Cached.clear_cache()
461
+
462
+ def __repr__(self) -> str:
463
+ """String representation of Airalo client."""
464
+ return f"<Airalo(env='{self.get_environment()}')>"
465
+
466
+ # =====================================================
467
+ # Installation Instruction Methods
468
+ # =====================================================
469
+
470
+ def get_installation_instructions(
471
+ self, params: Optional[Dict[str, Any]] = None
472
+ ) -> Optional[Any]:
473
+ """
474
+ Get installation instructions for a given ICCID and language.
475
+
476
+ Args:
477
+ params: Dictionary with at least 'iccid' key, optionally 'language'.
478
+
479
+ Returns:
480
+ Response data as dictionary or None
481
+ """
482
+ return self._installation_instructions.get_instructions(params or {})
483
+
484
+ # =====================================================
485
+ # Future Order Methods
486
+ # =====================================================
487
+
488
+ def create_future_order(self, payload: Dict[str, Any]) -> Optional[Dict]:
489
+ """
490
+ Create a future order.
491
+
492
+ Args:
493
+ payload: Dictionary containing order details.
494
+
495
+ Returns:
496
+ Response data as dictionary or None.
497
+ """
498
+ return self._future_order.create_future_order(payload)
499
+
500
+ def cancel_future_order(self, payload: Dict[str, Any]) -> Optional[Dict]:
501
+ """
502
+ Cancel a future order.
503
+
504
+ Args:
505
+ payload: Dictionary containing cancellation details.
506
+
507
+ Returns:
508
+ Response data as dictionary or None.
509
+ """
510
+ return self._future_order.cancel_future_order(payload)
511
+
512
+ # =====================================================
513
+ # Compatible devices Methods
514
+ # =====================================================
515
+
516
+ def get_compatible_devices(self) -> Optional[Any]:
517
+ """
518
+ Fetch compatible devices from Airalo API.
519
+
520
+ Returns:
521
+ Response data as dictionary or None
522
+ """
523
+ return self._compatibility_devices.get_compatible_devices()
524
+
525
+ # =====================================================
526
+ # SIM Methods
527
+ # =====================================================
528
+
529
+ def sim_usage(self, iccid: str) -> Optional[Dict]:
530
+ """
531
+ Get SIM usage information.
532
+
533
+ Args:
534
+ iccid: ICCID of the SIM
535
+
536
+ Returns:
537
+ SIM usage data or None
538
+ """
539
+ return self._sim.get_usage(iccid)
540
+
541
+ def sim_usage_bulk(self, iccids: List[str]) -> Optional[Dict]:
542
+ """
543
+ Get usage information for multiple SIMs.
544
+
545
+ Args:
546
+ iccids: List of ICCIDs
547
+
548
+ Returns:
549
+ Dict mapping ICCIDs to usage data
550
+ """
551
+ return self._sim.get_usage_bulk(iccids)
552
+
553
+ def get_sim_topups(self, iccid: str) -> Optional[Dict]:
554
+ """
555
+ Get SIM topup history.
556
+
557
+ Args:
558
+ iccid: ICCID of the SIM
559
+
560
+ Returns:
561
+ Topup history or None
562
+ """
563
+ return self._sim.get_topups(iccid)
564
+
565
+ def get_sim_package_history(self, iccid: str) -> Optional[Dict]:
566
+ """
567
+ Get SIM package history.
568
+
569
+ Args:
570
+ iccid: ICCID of the SIM
571
+
572
+ Returns:
573
+ Package history or None
574
+ """
575
+ return self._sim.get_package_history(iccid)
576
+
577
+ # =====================================================
578
+ # Exchange Rates Methods
579
+ # =====================================================
580
+
581
+ def get_exchange_rates(
582
+ self, params: Optional[Dict[str, str]] = None
583
+ ) -> Optional[Dict]:
584
+ """
585
+ Get exchange rates for given parameters.
586
+
587
+ Args:
588
+ params: Optional dict with keys like 'date' and 'to'
589
+
590
+ Returns:
591
+ Exchange rate data or None
592
+ """
593
+ return self._exchange_rates.exchange_rates(params or {})
594
+
595
+ # Voucher Methods
596
+ # =====================================================
597
+
598
+ def create_voucher(self, payload: Dict[str, Any]) -> Optional[Dict]:
599
+ """
600
+ Create a regular voucher.
601
+
602
+ Args:
603
+ payload: Dictionary with voucher parameters
604
+
605
+ Returns:
606
+ Response data or None
607
+ """
608
+ return self._voucher.create_voucher(payload)
609
+
610
+ def create_esim_voucher(self, payload: Dict[str, Any]) -> Optional[Dict]:
611
+ """
612
+ Create an eSIM voucher.
613
+
614
+ Args:
615
+ payload: Dictionary with eSIM voucher parameters
616
+
617
+ Returns:
618
+ Response data or None
619
+ """
620
+ return self._voucher.create_esim_voucher(payload)