rbx 3.20.0.dev175__tar.gz → 3.21.0.dev177__tar.gz

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 (46) hide show
  1. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/PKG-INFO +1 -1
  2. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/pyproject.toml +2 -2
  3. rbx-3.21.0.dev177/rbx/__init__.py +1 -0
  4. rbx-3.21.0.dev177/rbx/clients/place.py +277 -0
  5. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx.egg-info/PKG-INFO +1 -1
  6. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx.egg-info/SOURCES.txt +1 -0
  7. rbx-3.20.0.dev175/rbx/__init__.py +0 -1
  8. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/LICENSE +0 -0
  9. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/README.md +0 -0
  10. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/auth/__init__.py +0 -0
  11. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/auth/decorators.py +0 -0
  12. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/auth/id_token.py +0 -0
  13. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/auth/keystore.py +0 -0
  14. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/auth/mock.py +0 -0
  15. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/aws/__init__.py +0 -0
  16. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/aws/s3.py +0 -0
  17. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/buildtools/__init__.py +0 -0
  18. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/buildtools/cli.py +0 -0
  19. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/buildtools/tasks/__init__.py +0 -0
  20. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/buildtools/tasks/ec2.py +0 -0
  21. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/buildtools/tasks/image.py +0 -0
  22. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/buildtools/tasks/misc.py +0 -0
  23. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/clients/__init__.py +0 -0
  24. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/clients/adsquare.py +0 -0
  25. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/clients/broadsign.py +0 -0
  26. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/clients/oxr.py +0 -0
  27. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/clients/panels.py +0 -0
  28. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/clients/reporting.py +0 -0
  29. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/clients/retry.py +0 -0
  30. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/exceptions.py +0 -0
  31. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/gcp/__init__.py +0 -0
  32. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/gcp/cloud_tasks.py +0 -0
  33. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/gcp/pubsub.py +0 -0
  34. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/gcp/storage.py +0 -0
  35. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/logging.py +0 -0
  36. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/settings.py +0 -0
  37. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/utils/__init__.py +0 -0
  38. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/utils/mdm.py +0 -0
  39. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/utils/vast.py +0 -0
  40. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/web/__init__.py +0 -0
  41. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx/web/handlers.py +0 -0
  42. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx.egg-info/dependency_links.txt +0 -0
  43. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx.egg-info/entry_points.txt +0 -0
  44. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx.egg-info/requires.txt +0 -0
  45. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/rbx.egg-info/top_level.txt +0 -0
  46. {rbx-3.20.0.dev175 → rbx-3.21.0.dev177}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rbx
3
- Version: 3.20.0.dev175
3
+ Version: 3.21.0.dev177
4
4
  Summary: A collection of common tools for Scoota services.
5
5
  Author-email: The Scoota Engineering Team <engineering@scoota.com>
6
6
  License:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rbx"
7
- version = "3.20.0.dev175"
7
+ version = "3.21.0.dev177"
8
8
  description = "A collection of common tools for Scoota services."
9
9
  authors = [
10
10
  { name = "The Scoota Engineering Team", email = "engineering@scoota.com" }
@@ -80,7 +80,7 @@ homepage = "https://github.com/rockabox/rbx"
80
80
  repository = "https://github.com/rockabox/rbx.git"
81
81
 
82
82
  [tool.bumpversion]
83
- current_version = "3.20.0.dev175"
83
+ current_version = "3.21.0.dev177"
84
84
  commit = true
85
85
  parse = """
86
86
  (?P<major>\\d+)\\.
@@ -0,0 +1 @@
1
+ __version__ = "3.21.0.dev177"
@@ -0,0 +1,277 @@
1
+ import logging
2
+ from typing import Dict, Any, Optional
3
+
4
+ import requests
5
+
6
+ from requests.auth import AuthBase
7
+ from . import Client
8
+ from ..exceptions import ClientError, ServerError
9
+
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class BearerAuth(AuthBase):
15
+ """Authentication class for Bearer token authorization.
16
+
17
+ Adds the Bearer token to the Authorization header of requests.
18
+ """
19
+
20
+ def __init__(self, token):
21
+ """Initialize with the token.
22
+
23
+ Args:
24
+ token (str): The authentication token
25
+ """
26
+ self.token = token
27
+
28
+ def __call__(self, r):
29
+ """Add Authorization header to the request.
30
+
31
+ Args:
32
+ r (Request): The request object
33
+
34
+ Returns:
35
+ Request: The modified request with Authorization header
36
+ """
37
+ if self.token:
38
+ r.headers["Authorization"] = "Bearer " + self.token
39
+ return r
40
+
41
+
42
+ class PlaceExchangeClient(Client):
43
+ """Client for Place Exchange Creative API.
44
+
45
+ Handles authentication and API communication with Place Exchange.
46
+ This client only handles the HTTP requests and authentication,
47
+ not the formatting of data or interpretation of responses.
48
+
49
+ API documentation: https://api.placeexchange.com/v3/orgs/{id}/ads
50
+ """
51
+
52
+ ENDPOINT = "https://api.placeexchange.com/"
53
+ AUTH_PATH = "v3/token"
54
+ DEFAULT_TIMEOUT = 30
55
+ TOKEN = "access_token"
56
+ MAX_PAGE_SIZE = 4000
57
+
58
+ def __init__(self, org_id: str):
59
+ """Initialize the Place Exchange client with authentication credentials.
60
+
61
+ Args:
62
+ org_id: Organization ID for Place Exchange API
63
+ """
64
+ super().__init__()
65
+ self.org_id = org_id
66
+
67
+ @property
68
+ def auth(self):
69
+ """Get the authentication object for requests.
70
+
71
+ Returns:
72
+ BearerAuth: Authentication object with the token
73
+ """
74
+ return BearerAuth(self.token)
75
+
76
+ @property
77
+ def is_authenticated(self):
78
+ """Check if the client is authenticated."""
79
+ return self.token is not None
80
+
81
+ def refresh_access_token(self):
82
+ """Refresh the access token.
83
+
84
+ Place Exchange doesn't support refresh tokens, so we just get a new token.
85
+ """
86
+ self.logout()
87
+ self.login(self.credentials["username"], self.credentials["password"])
88
+
89
+ def get_creative(self, creative_id: str):
90
+ """Get a creative by name.
91
+
92
+ Args:
93
+ creative_id: The name of the ad to get
94
+
95
+ Returns:
96
+ The raw API response data
97
+
98
+ Raises:
99
+ ServerError: On server-side errors or invalid responses
100
+ ClientError: On client-side errors
101
+ """
102
+ path = f"v3/orgs/{self.org_id}/ads/{creative_id}"
103
+
104
+ try:
105
+ return self.request(
106
+ method="get",
107
+ path=path,
108
+ )
109
+ except ClientError as e:
110
+ if e.status_code == 404:
111
+ return None
112
+ raise
113
+
114
+ def submit_creative(self, payload: Dict[str, Any]):
115
+ """Create a new creative.
116
+
117
+ Args:
118
+ payload: The creative data to submit
119
+
120
+ Returns:
121
+ The raw API response data
122
+
123
+ Raises:
124
+ ServerError: On server-side errors or invalid responses
125
+ ClientError: On client-side errors
126
+ """
127
+ path = f"v3/orgs/{self.org_id}/ads"
128
+ headers = {"Content-Type": "application/json"}
129
+
130
+ return self.request(
131
+ method="post",
132
+ path=path,
133
+ headers=headers,
134
+ data=payload,
135
+ )
136
+
137
+ def update_creative(self, creative_id: str, payload: Dict[str, Any]):
138
+ """Update an existing creative.
139
+
140
+ Args:
141
+ creative_id: The name of the creative to update
142
+ payload: The creative data to update
143
+
144
+ Returns:
145
+ The raw API response data
146
+
147
+ Raises:
148
+ ServerError: On server-side errors or invalid responses
149
+ ClientError: On client-side errors
150
+ """
151
+ path = f"v3/orgs/{self.org_id}/ads/{creative_id}"
152
+ headers = {"Content-Type": "application/json"}
153
+
154
+ return self.request(
155
+ method="patch",
156
+ path=path,
157
+ headers=headers,
158
+ data=payload,
159
+ )
160
+
161
+ def get_creative_adapprovals(self, creative_id: str):
162
+ """Get approval information for a creative from individual publishers.
163
+
164
+ Calls the adapprovals endpoint to retrieve approval status for a creative
165
+ from each publisher. The response contains a list of objects, one per publisher,
166
+ with information about the approval status.
167
+
168
+ Args:
169
+ creative_id: The ID of the creative to get approvals for
170
+
171
+ Returns:
172
+ The raw API response data containing a list of approval objects.
173
+ Each object contains:
174
+ - owned_by: A string containing the publisher's ID
175
+ - audit: An object with status, feedback, and lastmod fields
176
+ - status: Integer representing approval status (1-5, 500+ for Exchange-specific)
177
+ 1 - Pending Audit
178
+ 2 - Pre-Approved
179
+ 3 - Approved
180
+ 4 - Denied
181
+ 5 - Changed
182
+ - feedback: Array of strings with explanations for rejection or changes
183
+ - lastmod: Date/time of last modification in ISO 8601 format
184
+
185
+ Raises:
186
+ ServerError: On server-side errors or invalid responses
187
+ ClientError: On client-side errors
188
+ """
189
+ path = f"v3/orgs/{self.org_id}/ads/{creative_id}/adapprovals"
190
+ params = {"page": self.MAX_PAGE_SIZE}
191
+
192
+ return self.request(
193
+ method="get",
194
+ path=path,
195
+ params=params,
196
+ )
197
+
198
+ def get_publishers(self):
199
+ """Get publishers data from Place Exchange sellers.json file.
200
+
201
+ Fetches the sellers.json file from Place Exchange website and returns
202
+ the raw JSON response.
203
+
204
+ Returns:
205
+ Dict containing the raw JSON response from the sellers.json endpoint
206
+
207
+ Raises:
208
+ ServerError: On server-side errors or invalid responses
209
+ ClientError: On client-side errors
210
+ """
211
+
212
+ url = "https://www.placeexchange.com/sellers.json"
213
+ headers = {
214
+ # Pretend to be a modern browser to avoid bot/WAF blocks
215
+ "User-Agent": (
216
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
217
+ "(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
218
+ ),
219
+ "Accept": "application/json, text/plain, */*",
220
+ }
221
+
222
+ try:
223
+ response = requests.get(url, timeout=self.DEFAULT_TIMEOUT, headers=headers)
224
+ response.raise_for_status() # Raise exception for 4XX/5XX responses
225
+
226
+ return response.json()
227
+
228
+ except requests.HTTPError as e:
229
+ logger.error(f"HTTP error fetching publishers from {url}: {str(e)}")
230
+ raise ServerError(
231
+ message=f"Failed to fetch publishers: {response.text if response is not None else str(e)}",
232
+ url=url,
233
+ )
234
+ except requests.RequestException as e:
235
+ logger.error(f"Error fetching publishers from {url}: {str(e)}")
236
+ raise ServerError(message=f"Failed to fetch publishers: {str(e)}", url=url)
237
+ except ValueError as e:
238
+ logger.error(f"Error parsing JSON from {url}: {str(e)}")
239
+ raise ServerError(
240
+ message=f"Failed to parse publishers data: {str(e)}", url=url
241
+ )
242
+
243
+ def get_creatives(
244
+ self,
245
+ lastmod: Optional[str] = None,
246
+ page: Optional[int] = None,
247
+ page_size: Optional[int] = None,
248
+ ):
249
+ """Get a collection of all ads/creatives for the organization.
250
+
251
+ Args:
252
+ lastmod: Date/time filter in ISO format (YYYY-MM-DDTHH:MM:SSZ)
253
+ page: Page number to retrieve
254
+ page_size: Number of entities per page (max 4000)
255
+
256
+ Returns:
257
+ API response data with timestamp, count, and ads array
258
+
259
+ Raises:
260
+ ServerError: On server-side errors or invalid responses
261
+ ClientError: On client-side errors
262
+ """
263
+ path = f"v3/orgs/{self.org_id}/ads"
264
+ params = {}
265
+
266
+ if lastmod is not None:
267
+ params["lastmod"] = lastmod
268
+ if page is not None:
269
+ params["page"] = page
270
+ if page_size is not None:
271
+ params["page_size"] = page_size
272
+
273
+ return self.request(
274
+ method="get",
275
+ path=path,
276
+ params=params if params else None,
277
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rbx
3
- Version: 3.20.0.dev175
3
+ Version: 3.21.0.dev177
4
4
  Summary: A collection of common tools for Scoota services.
5
5
  Author-email: The Scoota Engineering Team <engineering@scoota.com>
6
6
  License:
@@ -29,6 +29,7 @@ rbx/clients/adsquare.py
29
29
  rbx/clients/broadsign.py
30
30
  rbx/clients/oxr.py
31
31
  rbx/clients/panels.py
32
+ rbx/clients/place.py
32
33
  rbx/clients/reporting.py
33
34
  rbx/clients/retry.py
34
35
  rbx/gcp/__init__.py
@@ -1 +0,0 @@
1
- __version__ = "3.20.0.dev175"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes