25
25
from firebase_admin import _utils
26
26
27
27
28
-
29
28
PLATFORM_DESKTOP = 'desktop'
30
29
PLATFORM_IOS = 'ios'
31
30
PLATFORM_ANDROID = 'android'
53
52
_LINKS_ATTRIBUTE = '_dynamic_links'
54
53
_LINKS_BASE_URL = 'https://firebasedynamiclinks.googleapis.com/v1/'
55
54
55
+ _INTERNAL_ERROR = 'internal-error'
56
56
_UNKNOWN_ERROR = 'unknown-error'
57
+ _AUTHENTICATION_ERROR = 'authentication-error'
58
+ _BAD_REQUEST = 'invalid-argument'
59
+
60
+ _error_codes = {
61
+ 400 : _BAD_REQUEST ,
62
+ 401 : _AUTHENTICATION_ERROR ,
63
+ 403 : _AUTHENTICATION_ERROR ,
64
+ 500 : _INTERNAL_ERROR ,
65
+ }
66
+
67
+
57
68
def get_link_stats (short_link , stat_options , app = None ):
58
69
""" Returns a ``LinkStats`` object with the event statistics for the given short link
59
70
@@ -70,13 +81,14 @@ def get_link_stats(short_link, stat_options, app=None):
70
81
71
82
Raises:
72
83
ValueError: If any of the arguments are invalid.
73
- short_link must start with the "https" protocol.
74
- stat_options should have duration_days > 0.
84
+ `` short_link`` must start with the "https" protocol.
85
+ `` stat_options`` must have duration_days > 0.
75
86
"""
76
87
return _get_link_service (app ).get_stats (short_link , stat_options )
77
88
89
+
78
90
def _get_link_service (app ):
79
- """Returns an _DynamicLinksService instance for an App.
91
+ """Returns a _DynamicLinksService instance for an App.
80
92
81
93
If the App already has a _DynamicLinksService associated with it, simply returns
82
94
it. Otherwise creates a new _DynamicLinksService, and adds it to the App before
@@ -97,13 +109,14 @@ def _get_link_service(app):
97
109
class LinkStats (object ):
98
110
"""The ``LinkStats`` object is returned by get_link_stats, it contains a list of
99
111
``EventStats``"""
112
+
100
113
def __init__ (self , event_stats ):
101
114
if not isinstance (event_stats , (list , tuple )):
102
115
raise ValueError ('Invalid data argument: {0}. Must be a list or tuple'
103
116
.format (event_stats ))
104
117
if not all (isinstance (es , EventStats ) for es in event_stats ):
105
118
raise ValueError ('Invalid data argument: elements of event stats must be' +
106
- ' "EventStats", found{} ' .format (type (event_stats [0 ])))
119
+ ' "EventStats", found "{}" ' .format (type (event_stats [0 ])))
107
120
self ._stats = event_stats
108
121
109
122
@property
@@ -115,40 +128,42 @@ def event_stats(self):
115
128
"""
116
129
return self ._stats
117
130
131
+
118
132
class EventStats (object ):
119
- """``EventStat`` is a single stat item containing (platform, event, count)"""
133
+ """``EventStat`` is a single stat item containing (platform, event, count)
120
134
121
- def __init__ (self , ** kwargs ):
122
- """Create new instance of EventStats(platform, event, count)
123
- The input values are the strings returned by the REST call.
124
- The internal values stored in the ``EventStats`` object are
125
- the package constants named at the start of this package."""
126
- required = {'platform' , 'event' , 'count' }
127
- params = set (kwargs .keys ())
128
- missing = required - params
129
- unexpected = params - required
130
- if missing :
131
- raise ValueError ('Missing arguments for EventStats: {}' .format (missing ))
132
- if unexpected :
133
- raise ValueError ('Unexpected arguments for EventStats: {}' .format (unexpected ))
134
-
135
- platform = kwargs ['platform' ]
136
- if not isinstance (platform , six .string_types ) or platform not in _platforms .keys ():
137
- raise ValueError ('Invalid Platform value "{}".' .format (platform ))
138
- self ._platform = _platforms [platform ]
135
+ The constructor input values are the strings returned by the REST call.
136
+ e.g. "ANDROID", or "APP_RE_OPEN". See the Dynamic Links `API docs`_ .
137
+ The internal values stored in the ``EventStats`` object are the package
138
+ constants named at the start of this package.
139
139
140
- event = kwargs ['event' ]
141
- if not isinstance (event , six .string_types ) or event not in _event_types .keys ():
142
- raise ValueError ('Invalid Event Type value "{}".' .format (event ))
143
- self ._event = _event_types [event ]
140
+ .. _API docs https://firebase.google.com/docs/reference/dynamic-links/analytics
141
+ """
144
142
145
- count = kwargs ['count' ]
143
+ def __init__ (self , ** kwargs ):
144
+ platform = kwargs .pop ('platform' , None )
145
+ event = kwargs .pop ('event' , None )
146
+ count = kwargs .pop ('count' , None )
147
+
148
+ if not isinstance (platform , six .string_types ) or platform not in _platforms :
149
+ raise ValueError (
150
+ 'Invalid Platform argument value "{}".' .format (platform ))
151
+ if not isinstance (event , six .string_types ) or event not in _event_types :
152
+ raise ValueError (
153
+ 'Invalid Event Type argument value "{}".' .format (event ))
146
154
if (not ((isinstance (count , six .string_types ) # a string
147
155
and count .isdigit ()) # ... that is made of digits(non negative)
148
156
or (not isinstance (count , bool ) # bool is confused as an instance of int
149
- and isinstance (count , (int , float )) # number
157
+ and isinstance (count , (int , float )) # number
150
158
and count >= 0 ))): # non negative
151
- raise ValueError ('Invalid Count, must be a non negative int, "{}".' .format (count ))
159
+ raise ValueError ('Invalid Count argument value, must be a non negative int, "{}".'
160
+ .format (count ))
161
+ if kwargs :
162
+ raise ValueError (
163
+ 'Unexpected arguments for EventStats: {}' .format (kwargs ))
164
+
165
+ self ._platform = _platforms [platform ]
166
+ self ._event = _event_types [event ]
152
167
self ._count = int (count )
153
168
154
169
@property
@@ -181,8 +196,6 @@ def duration_days(self):
181
196
class _DynamicLinksService (object ):
182
197
"""Provides methods for the Firebase dynamic links interaction"""
183
198
184
- INTERNAL_ERROR = 'internal-error'
185
-
186
199
def __init__ (self , app ):
187
200
self ._client = _http_client .JsonHttpClient (
188
201
credential = app .credential .get_credential (),
@@ -193,20 +206,22 @@ def __init__(self, app):
193
206
def _format_request_string (self , short_link , options ):
194
207
days = options .duration_days
195
208
# Complaints about the named second argument needed to replace "/"
196
- url_quoted = urllib .parse .quote (short_link , safe = '' ) # pylint: disable=redundant-keyword-arg
209
+ url_quoted = urllib .parse .quote (short_link , safe = '' ) # pylint: disable=redundant-keyword-arg
197
210
return self ._request_string .format (url_quoted , days )
198
211
199
212
def get_stats (self , short_link , stat_options ):
200
213
"""Returns the LinkStats of the requested short_link for the duration set in options"""
201
214
if (not isinstance (short_link , six .string_types )
202
215
or not short_link .startswith ('https://' )):
203
- raise ValueError ('short_link must be a string and begin with "https://".' )
216
+ raise ValueError (
217
+ 'short_link must be a string and begin with "https://".' )
204
218
if not isinstance (stat_options , StatOptions ):
205
219
raise ValueError ('stat_options must be of type StatOptions.' )
206
220
207
221
request_string = self ._format_request_string (short_link , stat_options )
208
222
try :
209
- resp = self ._client .body ('get' , request_string , timeout = self ._timeout )
223
+ resp = self ._client .body (
224
+ 'get' , request_string , timeout = self ._timeout )
210
225
except requests .exceptions .RequestException as error :
211
226
self ._handle_error (error )
212
227
else :
@@ -218,7 +233,7 @@ def _handle_error(self, error):
218
233
"""Error handler for dynamic links request errors"""
219
234
if error .response is None :
220
235
msg = 'Failed to call dynamic links API: {0}' .format (error )
221
- raise ApiCallError (self . INTERNAL_ERROR , msg , error )
236
+ raise ApiCallError (_INTERNAL_ERROR , msg , error )
222
237
data = {}
223
238
try :
224
239
parsed_body = error .response .json ()
@@ -227,12 +242,13 @@ def _handle_error(self, error):
227
242
except ValueError :
228
243
pass
229
244
error_details = data .get ('error' , {})
230
- code = error_details .get ('code' , _UNKNOWN_ERROR )
245
+ code = error_details .get ('code' )
246
+ code_str = _error_codes .get (code , _UNKNOWN_ERROR )
231
247
msg = error_details .get ('message' )
232
248
if not msg :
233
249
msg = 'Unexpected HTTP response with status: {0}; body: {1}' .format (
234
250
error .response .status_code , error .response .content .decode ())
235
- raise ApiCallError (code , msg , error )
251
+ raise ApiCallError (code_str , msg , error )
236
252
237
253
238
254
class ApiCallError (Exception ):
0 commit comments