pyios-firebase 0.2.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.
Files changed (65) hide show
  1. pyios_firebase/__init__.py +26 -0
  2. pyios_firebase/analytics/__init__.py +434 -0
  3. pyios_firebase/analytics/names.py +251 -0
  4. pyios_firebase/app_check/__init__.py +483 -0
  5. pyios_firebase/auth/__init__.py +131 -0
  6. pyios_firebase/auth/_apple_name_cache.py +97 -0
  7. pyios_firebase/auth/_common.py +121 -0
  8. pyios_firebase/auth/_ui.py +21 -0
  9. pyios_firebase/auth/apple.py +326 -0
  10. pyios_firebase/auth/auth.py +592 -0
  11. pyios_firebase/auth/credentials.py +551 -0
  12. pyios_firebase/auth/errors.py +321 -0
  13. pyios_firebase/auth/facebook.py +240 -0
  14. pyios_firebase/auth/google.py +167 -0
  15. pyios_firebase/auth/multi_factor.py +394 -0
  16. pyios_firebase/auth/results.py +361 -0
  17. pyios_firebase/auth/user.py +457 -0
  18. pyios_firebase/core/__init__.py +32 -0
  19. pyios_firebase/crashlytics/__init__.py +463 -0
  20. pyios_firebase/database/__init__.py +62 -0
  21. pyios_firebase/database/database.py +144 -0
  22. pyios_firebase/database/errors.py +80 -0
  23. pyios_firebase/database/query.py +569 -0
  24. pyios_firebase/database/reference.py +428 -0
  25. pyios_firebase/database/snapshot.py +218 -0
  26. pyios_firebase/database/values.py +271 -0
  27. pyios_firebase/firestore/__init__.py +59 -0
  28. pyios_firebase/firestore/_limits.py +48 -0
  29. pyios_firebase/firestore/_query_state.py +228 -0
  30. pyios_firebase/firestore/_write.py +18 -0
  31. pyios_firebase/firestore/aggregate.py +137 -0
  32. pyios_firebase/firestore/collection_reference.py +100 -0
  33. pyios_firebase/firestore/document_change.py +63 -0
  34. pyios_firebase/firestore/document_reference.py +229 -0
  35. pyios_firebase/firestore/document_snapshot.py +102 -0
  36. pyios_firebase/firestore/enums.py +66 -0
  37. pyios_firebase/firestore/field_value.py +94 -0
  38. pyios_firebase/firestore/filter.py +319 -0
  39. pyios_firebase/firestore/firestore.py +184 -0
  40. pyios_firebase/firestore/listener_registration.py +55 -0
  41. pyios_firebase/firestore/query.py +679 -0
  42. pyios_firebase/firestore/query_snapshot.py +103 -0
  43. pyios_firebase/firestore/snapshot_metadata.py +36 -0
  44. pyios_firebase/firestore/transaction.py +130 -0
  45. pyios_firebase/firestore/write_batch.py +125 -0
  46. pyios_firebase/functions/__init__.py +329 -0
  47. pyios_firebase/messaging/__init__.py +37 -0
  48. pyios_firebase/messaging/messaging.py +435 -0
  49. pyios_firebase/messaging/notifications.py +292 -0
  50. pyios_firebase/performance/__init__.py +486 -0
  51. pyios_firebase/py.typed +0 -0
  52. pyios_firebase/remote_config/__init__.py +660 -0
  53. pyios_firebase/storage/__init__.py +41 -0
  54. pyios_firebase/storage/errors.py +103 -0
  55. pyios_firebase/storage/metadata.py +202 -0
  56. pyios_firebase/storage/reference.py +402 -0
  57. pyios_firebase/storage/storage.py +120 -0
  58. pyios_firebase/storage/task.py +198 -0
  59. pyios_firebase/types.py +93 -0
  60. pyios_firebase/util.py +413 -0
  61. pyios_firebase-0.2.0.dist-info/METADATA +1969 -0
  62. pyios_firebase-0.2.0.dist-info/RECORD +65 -0
  63. pyios_firebase-0.2.0.dist-info/WHEEL +5 -0
  64. pyios_firebase-0.2.0.dist-info/licenses/LICENSE +21 -0
  65. pyios_firebase-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,26 @@
1
+ """Use Firebase from Python on iOS, through Rubicon-ObjC.
2
+
3
+ One subpackage per Firebase product. Import only the ones you use: each one
4
+ needs its Firebase framework in the app.
5
+
6
+ - ``pyios_firebase.core``: ``configure()``
7
+ - ``pyios_firebase.analytics``: Google Analytics
8
+ - ``pyios_firebase.app_check``: App Check providers and tokens
9
+ - ``pyios_firebase.auth``: Firebase Authentication
10
+ - ``pyios_firebase.crashlytics``: Crashlytics
11
+ - ``pyios_firebase.database``: Realtime Database
12
+ - ``pyios_firebase.firestore``: Cloud Firestore
13
+ - ``pyios_firebase.functions``: Cloud Functions (HTTPS callables)
14
+ - ``pyios_firebase.messaging``: Cloud Messaging and notifications
15
+ - ``pyios_firebase.performance``: Performance Monitoring
16
+ - ``pyios_firebase.remote_config``: Remote Config
17
+ - ``pyios_firebase.storage``: Cloud Storage
18
+ - ``pyios_firebase.types``: types for your own annotations
19
+ """
20
+
21
+ from .core import configure
22
+ from .types import FirebaseError, QueryDict
23
+
24
+ __all__ = ("FirebaseError", "QueryDict", "__version__", "configure")
25
+
26
+ __version__ = "0.2.0"
@@ -0,0 +1,434 @@
1
+ """Google Analytics for Firebase: log events and user properties.
2
+
3
+ from pyios_firebase.analytics import Analytics, Event, Param
4
+
5
+ Analytics.log_event(Event.SELECT_CONTENT, {
6
+ Param.CONTENT_TYPE: "image",
7
+ Param.ITEM_ID: "P12453",
8
+ })
9
+ Analytics.log_screen_view("Checkout", "CheckoutScreen")
10
+ Analytics.set_user_property("premium", "plan")
11
+
12
+ Screen views are not logged automatically for Kivy screens (the SDK only sees
13
+ the one ``UIViewController``): call ``log_screen_view`` when a screen opens.
14
+
15
+ Names and values are checked first: a bad one raises ``ValueError`` or
16
+ ``TypeError`` instead of being dropped quietly by the SDK.
17
+
18
+ To see events as they are logged, run the app with ``-FIRDebugEnabled`` (in
19
+ Xcode: Product > Scheme > Edit Scheme > Arguments) and open DebugView in the
20
+ Firebase console.
21
+
22
+ Needs the ``FirebaseAnalytics`` framework.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ __all__ = (
28
+ "Analytics",
29
+ "ConsentStatus",
30
+ "ConsentType",
31
+ "Event",
32
+ "Param",
33
+ "UserProperty",
34
+ )
35
+
36
+ import math
37
+ import re
38
+ from collections.abc import Callable, Mapping
39
+ from ctypes import c_int64
40
+ from typing import Any, Final
41
+
42
+ from rubicon.objc import ns_from_py, objc_id
43
+
44
+ from ..types import FirebaseError, ObjCObject
45
+ from ..util import error_from_pointer, objc_class, one_shot_block
46
+ from .names import Event, Param, UserProperty
47
+
48
+
49
+ class ConsentType:
50
+ """What a consent covers (``FIRConsentType``)."""
51
+
52
+ AD_STORAGE: Final = "ad_storage"
53
+ """Storage (such as device identifiers) for advertising."""
54
+ ANALYTICS_STORAGE: Final = "analytics_storage"
55
+ """Storage (such as app identifiers) for analytics."""
56
+ AD_USER_DATA: Final = "ad_user_data"
57
+ """Sending user data to Google for advertising."""
58
+ AD_PERSONALIZATION: Final = "ad_personalization"
59
+ """Personalized advertising (remarketing)."""
60
+
61
+
62
+ class ConsentStatus:
63
+ """A consent's status (``FIRConsentStatus``)."""
64
+
65
+ GRANTED: Final = "granted"
66
+ DENIED: Final = "denied"
67
+
68
+
69
+ _NAME: Final = re.compile(r"[A-Za-z][A-Za-z0-9_]*\Z")
70
+ _RESERVED_PREFIXES: Final = ("firebase_", "google_", "ga_")
71
+ _RESERVED_EVENTS: Final = frozenset(
72
+ [
73
+ "ad_activeview",
74
+ "ad_click",
75
+ "ad_exposure",
76
+ "ad_query",
77
+ "ad_reward",
78
+ "adunit_exposure",
79
+ "app_clear_data",
80
+ "app_exception",
81
+ "app_remove",
82
+ "app_store_refund",
83
+ "app_store_subscription_cancel",
84
+ "app_store_subscription_convert",
85
+ "app_store_subscription_renew",
86
+ "app_update",
87
+ "app_upgrade",
88
+ "dynamic_link_app_open",
89
+ "dynamic_link_app_update",
90
+ "dynamic_link_first_open",
91
+ "error",
92
+ "firebase_campaign",
93
+ "first_open",
94
+ "first_visit",
95
+ "notification_dismiss",
96
+ "notification_foreground",
97
+ "notification_open",
98
+ "notification_receive",
99
+ "os_update",
100
+ "session_start",
101
+ "session_start_with_rollout",
102
+ "user_engagement",
103
+ ]
104
+ )
105
+ _RESERVED_USER_PROPERTIES: Final = frozenset(
106
+ {"first_open_time", "last_deep_link_referrer", "user_id"}
107
+ )
108
+ _MAX_PARAMETERS: Final = 25
109
+ _CONSENT_TYPES: Final = frozenset(
110
+ v for k, v in vars(ConsentType).items() if not k.startswith("_")
111
+ )
112
+ _CONSENT_STATUSES: Final = frozenset({ConsentStatus.GRANTED, ConsentStatus.DENIED})
113
+
114
+
115
+ def _check_name(name: str, what: str, max_length: int) -> None:
116
+ if not isinstance(name, str):
117
+ raise TypeError(f"{what} must be a str, not {type(name).__name__}")
118
+ if not 0 < len(name) <= max_length or not _NAME.match(name):
119
+ raise ValueError(
120
+ f"invalid {what} {name!r}: 1 to {max_length} letters, digits and "
121
+ "underscores, starting with a letter"
122
+ )
123
+ if name.startswith(_RESERVED_PREFIXES):
124
+ raise ValueError(
125
+ f"invalid {what} {name!r}: the prefixes firebase_, google_ and ga_ "
126
+ "are reserved"
127
+ )
128
+
129
+
130
+ def _parameter_value(value: Any, path: str) -> Any:
131
+ if isinstance(value, bool):
132
+ raise TypeError(f"{path}: Analytics has no bool parameters; use 1 / 0 or a str")
133
+ if isinstance(value, float) and not math.isfinite(value):
134
+ raise ValueError(f"{path}: cannot log {value!r}")
135
+ if isinstance(value, str | int | float):
136
+ return value
137
+ if isinstance(value, list | tuple): # Param.ITEMS: a list of item dicts
138
+ items = []
139
+ for i, item in enumerate(value):
140
+ if not isinstance(item, Mapping):
141
+ raise TypeError(f"{path}[{i}]: items must be dicts")
142
+ items.append(_parameters(item, f"{path}[{i}]", limit=False))
143
+ return items
144
+ raise TypeError(
145
+ f"{path}: parameter values must be str, int or float (or a list of "
146
+ f"item dicts), not {type(value).__name__}"
147
+ )
148
+
149
+
150
+ def _parameters(
151
+ parameters: Mapping[str, Any], path: str = "", *, limit: bool = True
152
+ ) -> dict[str, Any]:
153
+ if not isinstance(parameters, Mapping):
154
+ raise TypeError(f"parameters must be a dict, not {type(parameters).__name__}")
155
+ if limit and len(parameters) > _MAX_PARAMETERS:
156
+ raise ValueError(
157
+ f"an event can have at most {_MAX_PARAMETERS} parameters, "
158
+ f"not {len(parameters)}"
159
+ )
160
+ result: dict[str, Any] = {}
161
+ for key, value in parameters.items():
162
+ _check_name(key, "parameter name", 40)
163
+ result[key] = _parameter_value(value, f"{path}.{key}" if path else key)
164
+ return result
165
+
166
+
167
+ def _analytics() -> ObjCObject:
168
+ return objc_class("FIRAnalytics")
169
+
170
+
171
+ class Analytics:
172
+ """Google Analytics. Every method is a static method, as in the SDK::
173
+
174
+ Analytics.log_event("level_up", {"level": 3})
175
+ """
176
+
177
+ @staticmethod
178
+ def log_event(name: str, parameters: Mapping[str, Any] | None = None) -> None:
179
+ """Log an event.
180
+
181
+ Args:
182
+ name: the event name: an ``Event`` constant, or your own (1 to 40
183
+ letters, digits and underscores, starting with a letter).
184
+ parameters: up to 25 parameters: ``Param`` constants or your own
185
+ names, to ``str``, ``int`` or ``float`` values. ``Param.ITEMS``
186
+ takes a list of item dicts. String values over 100 characters
187
+ are cut by Analytics.
188
+
189
+ Raises:
190
+ ValueError: an invalid or reserved name, or too many parameters.
191
+ TypeError: a value of another type (``bool`` too: use 1 / 0).
192
+ """
193
+ _check_name(name, "event name", 40)
194
+ if name in _RESERVED_EVENTS:
195
+ raise ValueError(f"{name!r} is a reserved event name (Analytics drops it)")
196
+ native = None if parameters is None else ns_from_py(_parameters(parameters))
197
+ _analytics().logEventWithName_parameters_(name, native)
198
+
199
+ @staticmethod
200
+ def log_screen_view(
201
+ screen_name: str,
202
+ screen_class: str | None = None,
203
+ parameters: Mapping[str, Any] | None = None,
204
+ ) -> None:
205
+ """Log that a screen was shown (the ``screen_view`` event).
206
+
207
+ Args:
208
+ screen_name: the screen's name, for example ``"Checkout"``.
209
+ screen_class: the screen's class, for example the Kivy ``Screen``
210
+ class name.
211
+ parameters: more parameters for the event.
212
+
213
+ Raises:
214
+ ValueError: an invalid parameter name.
215
+ TypeError: a parameter value of another type.
216
+ """
217
+ params: dict[str, Any] = {Param.SCREEN_NAME: screen_name}
218
+ if screen_class is not None:
219
+ params[Param.SCREEN_CLASS] = screen_class
220
+ params.update(parameters or {})
221
+ Analytics.log_event(Event.SCREEN_VIEW, params)
222
+
223
+ @staticmethod
224
+ def set_user_property(value: str | None, name: str) -> None:
225
+ """Set a user property, to filter and compare users in reports. Register
226
+ it in the Firebase console (Custom definitions) to see it there.
227
+
228
+ Args:
229
+ value: the value (up to 36 characters), or ``None`` to remove it.
230
+ name: the property name (1 to 24 letters, digits and underscores,
231
+ starting with a letter).
232
+
233
+ Raises:
234
+ ValueError: an invalid or reserved name, or a value over 36 characters.
235
+ TypeError: ``value`` is not a ``str`` or ``None``.
236
+ """
237
+ _check_name(name, "user property name", 24)
238
+ if name in _RESERVED_USER_PROPERTIES:
239
+ raise ValueError(f"{name!r} is a reserved user property name")
240
+ if value is not None:
241
+ if not isinstance(value, str):
242
+ raise TypeError(
243
+ f"user property values are str, not {type(value).__name__}"
244
+ )
245
+ if len(value) > 36:
246
+ raise ValueError("a user property value can be at most 36 characters")
247
+ _analytics().setUserPropertyString_forName_(value, name)
248
+
249
+ @staticmethod
250
+ def set_user_id(user_id: str | None) -> None:
251
+ """Set the user ID (your own ID for the user; never personal data such
252
+ as an email address).
253
+
254
+ Args:
255
+ user_id: up to 256 characters, or ``None`` to remove it.
256
+
257
+ Raises:
258
+ ValueError: an empty ID or one over 256 characters.
259
+ """
260
+ if user_id is not None and not 0 < len(user_id) <= 256:
261
+ raise ValueError("a user ID must be 1 to 256 characters")
262
+ _analytics().setUserID_(user_id)
263
+
264
+ @staticmethod
265
+ def set_analytics_collection_enabled(enabled: bool) -> None:
266
+ """Turn collection on or off. The setting is kept across launches. To
267
+ start the app with it off, set ``FIREBASE_ANALYTICS_COLLECTION_ENABLED``
268
+ to ``NO`` in ``Info.plist``.
269
+
270
+ Args:
271
+ enabled: ``False`` to stop collecting.
272
+ """
273
+ _analytics().setAnalyticsCollectionEnabled_(bool(enabled))
274
+
275
+ @staticmethod
276
+ def set_session_timeout_interval(seconds: float) -> None:
277
+ """How long the app can be inactive before the session ends (default
278
+ 1800 s).
279
+
280
+ Args:
281
+ seconds: the timeout.
282
+ """
283
+ _analytics().setSessionTimeoutInterval_(float(seconds))
284
+
285
+ @staticmethod
286
+ def session_id(
287
+ callback: Callable[[int | None, FirebaseError | None], None],
288
+ ) -> None:
289
+ """The current session's ID.
290
+
291
+ Args:
292
+ callback: gets ``(session_id, None)``, or ``(None, error)`` (for
293
+ example with collection off). It runs on a background thread.
294
+ """
295
+
296
+ def done(session: int, error_ptr: Any) -> None:
297
+ error = error_from_pointer(error_ptr)
298
+ callback(None if error is not None else int(session), error)
299
+
300
+ _analytics().sessionIDWithCompletion_(one_shot_block(done, c_int64, objc_id))
301
+
302
+ @staticmethod
303
+ def app_instance_id() -> str | None:
304
+ """This app install's ID, or ``None`` when ``ANALYTICS_STORAGE``
305
+ consent is denied."""
306
+ value = _analytics().appInstanceID()
307
+ return None if value is None else str(value)
308
+
309
+ @staticmethod
310
+ def reset_analytics_data() -> None:
311
+ """Clear the data on this device and make a new app instance ID."""
312
+ _analytics().resetAnalyticsData()
313
+
314
+ @staticmethod
315
+ def set_default_event_parameters(parameters: Mapping[str, Any] | None) -> None:
316
+ """Parameters added to every event logged from now on (kept across
317
+ launches).
318
+
319
+ Args:
320
+ parameters: parameter names to values (a ``None`` value removes
321
+ that default), or ``None`` to remove all defaults.
322
+
323
+ Raises:
324
+ ValueError: an invalid name.
325
+ TypeError: a value of another type.
326
+ """
327
+ if parameters is None:
328
+ _analytics().setDefaultEventParameters_(None)
329
+ return
330
+ checked = _parameters(
331
+ {k: v for k, v in parameters.items() if v is not None}, limit=False
332
+ )
333
+ native = objc_class("NSMutableDictionary").dictionary()
334
+ for key, value in parameters.items():
335
+ if value is None:
336
+ _check_name(key, "parameter name", 40)
337
+ native.setObject_forKey_(objc_class("NSNull").null(), key)
338
+ else:
339
+ native.setObject_forKey_(ns_from_py(checked[key]), key)
340
+ _analytics().setDefaultEventParameters_(native)
341
+
342
+ @staticmethod
343
+ def set_consent(consent: Mapping[str, str]) -> None:
344
+ """Set the user's consent (Google consent mode). Types left out keep
345
+ their status.
346
+
347
+ ::
348
+
349
+ Analytics.set_consent({
350
+ ConsentType.ANALYTICS_STORAGE: ConsentStatus.GRANTED,
351
+ ConsentType.AD_STORAGE: ConsentStatus.DENIED,
352
+ })
353
+
354
+ Args:
355
+ consent: ``ConsentType`` to ``ConsentStatus``.
356
+
357
+ Raises:
358
+ ValueError: an unknown type or status.
359
+ """
360
+ for kind, status in consent.items():
361
+ if kind not in _CONSENT_TYPES:
362
+ raise ValueError(f"unknown consent type {kind!r}: use ConsentType")
363
+ if status not in _CONSENT_STATUSES:
364
+ raise ValueError(
365
+ f"unknown consent status {status!r}: use ConsentStatus"
366
+ )
367
+ _analytics().setConsent_(ns_from_py(dict(consent)))
368
+
369
+ @staticmethod
370
+ def initiate_on_device_conversion_measurement(
371
+ *,
372
+ email_address: str | None = None,
373
+ phone_number: str | None = None,
374
+ hashed_email_address: bytes | None = None,
375
+ hashed_phone_number: bytes | None = None,
376
+ ) -> None:
377
+ """Google Ads on-device conversion measurement, from what the user
378
+ consented to share. Give exactly one argument. Needs the
379
+ ``GoogleAdsOnDeviceConversion`` package in the app (otherwise it does
380
+ nothing).
381
+
382
+ Args:
383
+ email_address: the email address.
384
+ phone_number: the phone number in E.164 format (``"+15555550100"``).
385
+ hashed_email_address: the SHA-256 of the normalized email address.
386
+ hashed_phone_number: the SHA-256 of the phone number in E.164 format.
387
+
388
+ Raises:
389
+ ValueError: not exactly one argument.
390
+ """
391
+ given = [
392
+ email_address,
393
+ phone_number,
394
+ hashed_email_address,
395
+ hashed_phone_number,
396
+ ]
397
+ if sum(value is not None for value in given) != 1:
398
+ raise ValueError(
399
+ "give exactly one of email_address, phone_number, "
400
+ "hashed_email_address, hashed_phone_number"
401
+ )
402
+ fir = _analytics()
403
+ if email_address is not None:
404
+ fir.initiateOnDeviceConversionMeasurementWithEmailAddress_(email_address)
405
+ elif phone_number is not None:
406
+ fir.initiateOnDeviceConversionMeasurementWithPhoneNumber_(phone_number)
407
+ elif hashed_email_address is not None:
408
+ fir.initiateOnDeviceConversionMeasurementWithHashedEmailAddress_(
409
+ ns_from_py(bytes(hashed_email_address))
410
+ )
411
+ elif hashed_phone_number is not None:
412
+ fir.initiateOnDeviceConversionMeasurementWithHashedPhoneNumber_(
413
+ ns_from_py(bytes(hashed_phone_number))
414
+ )
415
+
416
+ @staticmethod
417
+ def handle_open_url(url: str) -> None:
418
+ """Tell Analytics the app was opened from a URL (campaign attribution).
419
+ Only needed when ``FirebaseAppDelegateProxyEnabled`` is ``NO``.
420
+
421
+ Args:
422
+ url: the URL the app was opened with.
423
+ """
424
+ _analytics().handleOpenURL_(objc_class("NSURL").URLWithString_(url))
425
+
426
+ @staticmethod
427
+ def handle_user_activity(user_activity: ObjCObject) -> None:
428
+ """Tell Analytics about a universal link the app was opened with. Only
429
+ needed when ``FirebaseAppDelegateProxyEnabled`` is ``NO``.
430
+
431
+ Args:
432
+ user_activity: the ``NSUserActivity``.
433
+ """
434
+ _analytics().handleUserActivity_(user_activity)