mtx-api-py 0.4.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.
@@ -0,0 +1,374 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+
5
+ from mtx_api.client._mtx_http_client import (
6
+ Method,
7
+ MtxHttpClient,
8
+ array_query_param,
9
+ )
10
+ from mtx_api.models.additional_data import AdditionalDataValue
11
+ from mtx_api.models.errors import (
12
+ MissingArgumentError,
13
+ MissingJurisdictionCodesError,
14
+ MissingRequestIdError,
15
+ MissingServiceIdError,
16
+ )
17
+ from mtx_api.models.request import (
18
+ CreatedRequest,
19
+ CreatedRequestList,
20
+ Request,
21
+ RequestList,
22
+ )
23
+ from mtx_api.models.request_comment import RequestComment
24
+ from mtx_api.models.request_media import RequestMedia
25
+
26
+
27
+ class RequestClient:
28
+ def __init__(self, client: MtxHttpClient):
29
+ self._client: MtxHttpClient = client
30
+
31
+ async def create(
32
+ self,
33
+ *,
34
+ access_token: str | None = None,
35
+ service_id: str,
36
+ jurisdiction_code: str,
37
+ jurisdiction_element_id: str | None = None,
38
+ origin_device_id: str | None = None,
39
+ description: str | None = None,
40
+ public: bool | None = None,
41
+ lat: float | None = None,
42
+ lng: float | None = None,
43
+ address_string: str | None = None,
44
+ email: str | None = None,
45
+ first_name: str | None = None,
46
+ last_name: str | None = None,
47
+ phone: str | None = None,
48
+ twitter_nickname: str | None = None,
49
+ additional_data: Sequence[AdditionalDataValue] | None = None,
50
+ ) -> CreatedRequest:
51
+ """Create a new request in the platform.
52
+
53
+ This function allows creating a new request (issue report) within a specific jurisdiction.
54
+
55
+ Args:
56
+ access_token: Optional token used for authentication. If not provided, the operation
57
+ will be made without authentication.
58
+ service_id: Identifier of the service to request.
59
+ jurisdiction_code: Code of the jurisdiction where the request is made.
60
+ jurisdiction_element_id: Optional ID of a specific element within the jurisdiction.
61
+ origin_device_id: Optional ID of the device making the request.
62
+ Can be found on Jurisdiction detail.
63
+ description: Optional textual description of the request.
64
+ public: Optional boolean. If True, the request is public; if False, it is private.
65
+ lat: Optional latitude coordinate of the request location.
66
+ lng: Optional longitude coordinate of the request location.
67
+ address_string: Optional physical address associated with the request.
68
+ email: Optional email address of the person making the request.
69
+ first_name: Optional first name of the person making the request.
70
+ last_name: Optional last name of the person making the request.
71
+ phone: Optional phone number of the person making the request.
72
+ twitter_nickname: Optional Twitter handle of the person making the request.
73
+ additional_data: Optional sequence of additional data values required by the service.
74
+
75
+ Returns:
76
+ CreatedRequest: The created request instance with its details.
77
+
78
+ Raises:
79
+ MissingJurisdictionCodesError: If jurisdiction_code is missing.
80
+ MissingServiceIdError: If service_id is missing.
81
+ """
82
+ if not jurisdiction_code:
83
+ raise MissingJurisdictionCodesError()
84
+
85
+ if not service_id:
86
+ raise MissingServiceIdError()
87
+
88
+ body: dict[str, object] = {
89
+ "service_id": service_id,
90
+ "jurisdiction_id": jurisdiction_code,
91
+ "description": description,
92
+ "address_string": address_string,
93
+ "jurisdiction_element": jurisdiction_element_id,
94
+ "email": email,
95
+ "first_name": first_name,
96
+ "last_name": last_name,
97
+ "phone": phone,
98
+ "twitter_nickname": twitter_nickname,
99
+ "device_type": origin_device_id,
100
+ "additionalData": (
101
+ [ad.model_dump(by_alias=True) for ad in additional_data]
102
+ if additional_data
103
+ else None
104
+ ),
105
+ }
106
+ if lat is not None and lng is not None:
107
+ body["lat"] = lat
108
+ body["long"] = lng
109
+ if public is not None:
110
+ body["public"] = "true" if public else "false"
111
+
112
+ response = await self._client.http_request(
113
+ method=Method.POST,
114
+ path="requests",
115
+ model=CreatedRequestList,
116
+ access_token=access_token,
117
+ body=body,
118
+ )
119
+ return response.root[0]
120
+
121
+ async def list(
122
+ self,
123
+ *,
124
+ access_token: str | None = None,
125
+ jurisdiction_codes: list[str] | None = None,
126
+ service_request_ids: list[str] | None = None,
127
+ service_ids: list[str] | None = None,
128
+ start_date: str | None = None,
129
+ end_date: str | None = None,
130
+ own: bool | None = None,
131
+ lat: float | None = None,
132
+ lng: float | None = None,
133
+ page: int | None = None,
134
+ limit: int | None = None,
135
+ address_and_service_request_id: str | None = None,
136
+ status: list[str] | None = None,
137
+ typology_ids: list[str] | None = None,
138
+ distance: int | None = None,
139
+ following: bool | None = None,
140
+ order: str | None = None,
141
+ complaints: bool | None = None,
142
+ reiterations: bool | None = None,
143
+ user_reiterated: bool | None = None,
144
+ user_complaint: bool | None = None,
145
+ jurisdiction_element_ids: list[str] | None = None,
146
+ level: int | None = None,
147
+ polygon: list[float] | None = None,
148
+ final_ok: bool | None = None,
149
+ final_not_ok: bool | None = None,
150
+ final_status: bool | None = None,
151
+ interested: bool | None = None,
152
+ timezone: str | None = None,
153
+ ) -> RequestList:
154
+ """List and filter requests from the platform.
155
+
156
+ This function retrieves a list of requests based on multiple filtering criteria.
157
+
158
+ Args:
159
+ access_token: Optional token used for authentication. If not provided, the operation
160
+ will be made without authentication.
161
+ jurisdiction_codes: Optional list of jurisdiction codes to filter by.
162
+ service_request_ids: Optional list of specific request IDs to retrieve.
163
+ service_ids: Optional list of service IDs to filter by.
164
+ start_date: Optional ISO date-time string (inclusive) to filter requests created
165
+ after this date.
166
+ end_date: Optional ISO date-time string (inclusive) to filter requests created
167
+ before this date.
168
+ own: Optional boolean. If True, returns only requests created by the authenticated user.
169
+ lat: Optional latitude to be used for distance filtering or ordering by proximity.
170
+ lng: Optional longitude to be used for distance filtering or ordering by proximity.
171
+ page: Optional page number for pagination.
172
+ limit: Optional number of items per page.
173
+ address_and_service_request_id: Optional free text search for address or
174
+ service request ID.
175
+ status: Optional list of status codes to filter by.
176
+ typology_ids: Optional list of typology IDs to filter by.
177
+ distance: Optional radius in meters to filter requests around the provided lat/lng.
178
+ following: Optional boolean. If True, returns only requests followed by the
179
+ authenticated user.
180
+ order: Optional ordering strategy (e.g., "newest_date_desc").
181
+ complaints: Optional boolean. If True, filters only complaints.
182
+ reiterations: Optional boolean. If True, filters only reiterations.
183
+ user_reiterated: Optional boolean. If True, filters requests reiterated by
184
+ the authenticated user.
185
+ user_complaint: Optional boolean. If True, filters complaints made by the
186
+ authenticated user.
187
+ jurisdiction_element_ids: Optional list of jurisdiction element IDs to filter by.
188
+ level: Optional floor level to filter by.
189
+ polygon: Optional list of float coordinates describing a polygon area to filter requests
190
+ within.
191
+ final_ok: Optional boolean. If True, filters requests with a final status of OK.
192
+ final_not_ok: Optional boolean. If True, filters requests with a final status of NOT OK.
193
+ final_status: Optional boolean. If True, filters requests with any final status.
194
+ interested: Optional boolean. If True, filters requests where the user has expressed
195
+ interest.
196
+ timezone: Optional IANA timezone string used by the backend for date filtering.
197
+
198
+ Returns:
199
+ RequestList: A list of Request items matching the criteria.
200
+ """
201
+
202
+ params = {
203
+ "start_date": start_date,
204
+ "end_date": end_date,
205
+ "own": own,
206
+ "page": page,
207
+ "limit": limit,
208
+ "address_and_service_request_id": address_and_service_request_id,
209
+ "distance": distance,
210
+ "following": following,
211
+ "order": order,
212
+ "complaints": complaints,
213
+ "reiterations": reiterations,
214
+ "user_reiterated": user_reiterated,
215
+ "user_complaint": user_complaint,
216
+ "level": level,
217
+ "final_ok": final_ok,
218
+ "final_not_ok": final_not_ok,
219
+ "final_status": final_status,
220
+ "interested": interested,
221
+ "timezone": timezone,
222
+ "lat": lat,
223
+ "long": lng,
224
+ **array_query_param("jurisdiction_ids", jurisdiction_codes),
225
+ **array_query_param("service_request_ids", service_request_ids),
226
+ **array_query_param("service_ids", service_ids),
227
+ **array_query_param("status", status),
228
+ **array_query_param("typologies", typology_ids),
229
+ **array_query_param("jurisdiction_element_ids", jurisdiction_element_ids),
230
+ **array_query_param("polygon", polygon),
231
+ }
232
+
233
+ return await self._client.http_request(
234
+ method=Method.GET,
235
+ path="requests",
236
+ model=RequestList,
237
+ access_token=access_token,
238
+ query_params=params,
239
+ )
240
+
241
+ async def detail(
242
+ self,
243
+ *,
244
+ access_token: str | None = None,
245
+ request_id: str,
246
+ ) -> Request:
247
+ """Retrieve detailed information about a specific request.
248
+
249
+ Args:
250
+ access_token: Optional token used for authentication. If not provided, the operation
251
+ will be made without authentication.
252
+ request_id: The unique identifier of the request to fetch.
253
+ Note: this is the internal request `id`, not the `service_request_id`.
254
+
255
+ Returns:
256
+ Request: The detailed Request object containing all information.
257
+
258
+ Raises:
259
+ MissingRequestIdError: If request_id is missing.
260
+ """
261
+ if not request_id:
262
+ raise MissingRequestIdError()
263
+ return await self._client.http_request(
264
+ method=Method.GET,
265
+ path=f"requests/{request_id}",
266
+ model=Request,
267
+ access_token=access_token,
268
+ )
269
+
270
+ async def attach_media(
271
+ self,
272
+ *,
273
+ access_token: str | None = None,
274
+ request_id: str,
275
+ jurisdiction_code: str,
276
+ file: tuple[str, bytes],
277
+ ) -> RequestMedia:
278
+ """Attach a media file to an existing request.
279
+
280
+ Args:
281
+ access_token: Optional token used for authentication. If not provided, the operation
282
+ will be made without authentication.
283
+ request_id: The identifier of the request to which the media will be attached.
284
+ Note: this is the internal request `id`, not the `service_request_id`.
285
+ jurisdiction_code: The code of the jurisdiction associated with the request.
286
+ file: A tuple containing the file name (str) and the file content (bytes).
287
+
288
+ Returns:
289
+ RequestMedia: The object representing the attached media.
290
+
291
+ Raises:
292
+ MissingRequestIdError: If request_id is missing.
293
+ MissingJurisdictionCodesError: If jurisdiction_code is missing.
294
+ MissingArgumentError: If file content or file name is empty, or if extension is missing.
295
+ """
296
+ if not request_id:
297
+ raise MissingRequestIdError()
298
+ if not jurisdiction_code:
299
+ raise MissingJurisdictionCodesError()
300
+ (file_name, file_content) = file
301
+ if not file_content:
302
+ raise MissingArgumentError("file content must not be empty")
303
+ if not file_name:
304
+ raise MissingArgumentError("file name must not be empty")
305
+ if "." not in file_name:
306
+ raise MissingArgumentError("file_name has no extension.")
307
+
308
+ return await self._client.http_request(
309
+ method=Method.POST,
310
+ path="requests_medias",
311
+ model=RequestMedia,
312
+ access_token=access_token,
313
+ query_params={
314
+ "jurisdiction_id": jurisdiction_code,
315
+ },
316
+ body={
317
+ "token": request_id,
318
+ },
319
+ files={
320
+ "media": file,
321
+ },
322
+ )
323
+
324
+ async def attach_comment(
325
+ self,
326
+ *,
327
+ access_token: str | None = None,
328
+ request_id: str,
329
+ comment: str | None = None,
330
+ files: Sequence[tuple[str, bytes]] = (),
331
+ ) -> RequestComment:
332
+ """Attach a comment to an existing request.
333
+
334
+ Args:
335
+ access_token: Optional token used for authentication. If not provided, the operation
336
+ will be made without authentication.
337
+ request_id: The identifier of the request to which the comment will be added.
338
+ Note: this is the internal request `id`, not the `service_request_id`.
339
+ comment: Optional text of the comment. Can be empty.
340
+ files: Optional sequence of files to attach with the comment.
341
+ Each file is a tuple containing the file name (str) and the file content (bytes).
342
+
343
+ Returns:
344
+ RequestComment: The created comment object.
345
+
346
+ Raises:
347
+ MissingRequestIdError: If request_id is missing.
348
+ """
349
+ if not request_id:
350
+ raise MissingRequestIdError()
351
+
352
+ return await self._client.http_request(
353
+ method=Method.POST,
354
+ path="requests_comments",
355
+ model=RequestComment,
356
+ access_token=access_token,
357
+ body={
358
+ k: v
359
+ for k, v in {
360
+ "token": request_id,
361
+ "description": comment,
362
+ }.items()
363
+ if v is not None
364
+ },
365
+ files=(
366
+ {
367
+ f"medias_data[{i}]": (file_name, file_content)
368
+ for i, (file_name, file_content) in enumerate(files)
369
+ if file_name and file_content and "." in file_name
370
+ }
371
+ if files
372
+ else None
373
+ ),
374
+ )
@@ -0,0 +1,94 @@
1
+ from mtx_api.client._mtx_http_client import (
2
+ Method,
3
+ MtxHttpClient,
4
+ )
5
+ from mtx_api.models.errors import MissingJurisdictionCodesError, MissingServiceIdError
6
+ from mtx_api.models.service import Service, ServiceList
7
+
8
+
9
+ class ServiceClient:
10
+ def __init__(self, client: MtxHttpClient):
11
+ self._client: MtxHttpClient = client
12
+
13
+ async def list(
14
+ self,
15
+ *,
16
+ access_token: str | None = None,
17
+ jurisdiction_codes: list[str],
18
+ lat: float | None = None,
19
+ lng: float | None = None,
20
+ typology_ids: list[str] | None = None,
21
+ ) -> ServiceList:
22
+ """Retrieves a list of services available in the specified jurisdictions.
23
+
24
+ This function allows searching for services filtering by jurisdiction codes,
25
+ and optionally by geographic location (latitude and longitude) or typology identifiers.
26
+
27
+ Args:
28
+ access_token: Optional token used for authentication. If not provided, the operation
29
+ will be made without authentication.
30
+ jurisdiction_codes: List of jurisdiction codes to filter the services.
31
+ lat: Optional latitude for geolocation filtering.
32
+ lng: Optional longitude for geolocation filtering.
33
+ typology_ids: Optional list of typology identifiers to filter the services.
34
+
35
+ Raises:
36
+ MissingJurisdictionCodesError: If the list of jurisdiction codes is empty.
37
+
38
+ Returns:
39
+ The list of services matching the specified criteria.
40
+ """
41
+ if not jurisdiction_codes:
42
+ raise MissingJurisdictionCodesError()
43
+ return await self._client.http_request(
44
+ method=Method.GET,
45
+ path="services",
46
+ model=ServiceList,
47
+ access_token=access_token,
48
+ query_params={
49
+ "jurisdiction_ids": jurisdiction_codes,
50
+ "lat": lat,
51
+ "lng": lng,
52
+ "typology_ids": typology_ids,
53
+ },
54
+ )
55
+
56
+ async def detail(
57
+ self,
58
+ *,
59
+ access_token: str | None = None,
60
+ service_id: str,
61
+ jurisdiction_code: str,
62
+ ) -> Service:
63
+ """Retrieves the detailed information of a specific service.
64
+
65
+ This function fetches the full details of a service identified by its ID
66
+ within a specific jurisdiction. Details include additional_data and other
67
+ relevant information.
68
+
69
+ Args:
70
+ access_token: Optional token used for authentication. If not provided, the operation
71
+ will be made without authentication.
72
+ service_id: The identifier of the service to fetch details for.
73
+ jurisdiction_code: The jurisdiction code where the service is located.
74
+
75
+ Raises:
76
+ MissingJurisdictionCodesError: If the jurisdiction code is not provided.
77
+ MissingServiceIdError: If the service ID is not provided.
78
+
79
+ Returns:
80
+ The detailed information of the requested service.
81
+ """
82
+ if not jurisdiction_code:
83
+ raise MissingJurisdictionCodesError()
84
+ if not service_id:
85
+ raise MissingServiceIdError()
86
+ return await self._client.http_request(
87
+ method=Method.GET,
88
+ path=f"services/{service_id}",
89
+ model=Service,
90
+ access_token=access_token,
91
+ query_params={
92
+ "jurisdiction_id": jurisdiction_code,
93
+ },
94
+ )
@@ -0,0 +1,64 @@
1
+ from mtx_api.client._mtx_http_client import (
2
+ Method,
3
+ MtxHttpClient,
4
+ )
5
+ from mtx_api.models.errors import MissingJurisdictionCodesError
6
+ from mtx_api.models.typology import TypologyList
7
+
8
+
9
+ class TypologyClient:
10
+ def __init__(self, client: MtxHttpClient):
11
+ self._client: MtxHttpClient = client
12
+
13
+ async def list(
14
+ self,
15
+ *,
16
+ access_token: str | None = None,
17
+ jurisdiction_codes: list[str],
18
+ jurisdiction_element_id: str | None = None,
19
+ typology_ids: list[str] | None = None,
20
+ lat: float | None = None,
21
+ lng: float | None = None,
22
+ page: int | None = None,
23
+ limit: int | None = None,
24
+ ) -> TypologyList:
25
+ """Retrieve a list of typologies from the platform based on jurisdiction codes.
26
+
27
+ This tool fetches typologies available for the specified jurisdiction codes,
28
+ optionally filtering by specific typology IDs, jurisdiction element, or location.
29
+
30
+ Args:
31
+ access_token: Optional token used for authentication. If not provided, the operation
32
+ will be made without authentication.
33
+ jurisdiction_codes: List of jurisdiction codes used to filter the typologies.
34
+ jurisdiction_element_id: Optional ID of a specific jurisdiction element to filter
35
+ results.
36
+ typology_ids: Optional list of specific typology IDs to retrieve.
37
+ lat: Optional latitude coordinate for location-based filtering.
38
+ lng: Optional longitude coordinate for location-based filtering.
39
+ page: Optional page number for result pagination.
40
+ limit: Optional maximum number of items to return per page.
41
+
42
+ Raises:
43
+ MissingJurisdictionCodesError: If `jurisdiction_codes` is empty.
44
+
45
+ Returns:
46
+ TypologyList: A collection of typology items matching the specified criteria.
47
+ """
48
+ if not jurisdiction_codes:
49
+ raise MissingJurisdictionCodesError()
50
+ return await self._client.http_request(
51
+ method=Method.GET,
52
+ path="typologies",
53
+ model=TypologyList,
54
+ access_token=access_token,
55
+ query_params={
56
+ "jurisdiction_ids": jurisdiction_codes,
57
+ "jurisdiction_element_id": jurisdiction_element_id,
58
+ "typology_ids": typology_ids,
59
+ "lng": lng,
60
+ "lat": lat,
61
+ "page": page,
62
+ "limit": limit,
63
+ },
64
+ )
@@ -0,0 +1,135 @@
1
+ from mtx_api.client._mtx_http_client import Method, MtxHttpClient
2
+ from mtx_api.models.auth import Gender, Profile
3
+ from mtx_api.models.base import EmptyResponse
4
+ from mtx_api.models.common import LegalTerms
5
+
6
+
7
+ class UserClient:
8
+ def __init__(self, client: MtxHttpClient):
9
+ self._client: MtxHttpClient = client
10
+
11
+ async def profile(
12
+ self,
13
+ *,
14
+ access_token: str | None = None,
15
+ ) -> Profile:
16
+ """Retrieves the profile information of the currently authenticated user.
17
+
18
+ Use this tool to fetch details about the current user, including personal
19
+ information and account settings.
20
+
21
+ Args:
22
+ access_token: Optional token used for authentication. If not provided, the operation
23
+ will be made without authentication.
24
+
25
+ Returns:
26
+ Profile: A model containing the user's profile details.
27
+ """
28
+ return await self._client.http_request(
29
+ method=Method.GET,
30
+ path="profile",
31
+ model=Profile,
32
+ access_token=access_token,
33
+ )
34
+
35
+ async def update_profile(
36
+ self,
37
+ *,
38
+ access_token: str | None = None,
39
+ email: str | None = None,
40
+ username: str | None = None,
41
+ first_name: str | None = None,
42
+ last_name: str | None = None,
43
+ twitter_nickname: str | None = None,
44
+ phone: str | None = None,
45
+ gender: Gender | None = None,
46
+ birthday: str | None = None,
47
+ ) -> Profile:
48
+ """Updates the profile information of the currently authenticated user.
49
+
50
+ Use this tool to modify the personal details and account information of
51
+ the user. Only the fields provided will be updated; fields set to None
52
+ will remain unchanged.
53
+
54
+ Args:
55
+ access_token: Optional token used for authentication. If not provided,
56
+ the operation will be made without authentication.
57
+ email: Optional new email address for the user.
58
+ username: Optional new username for the user, typically their email address.
59
+ first_name: Optional new first name or given name for the user.
60
+ last_name: Optional new last name or family name for the user.
61
+ twitter_nickname: Optional new shown name or twitter_nickname visible to others.
62
+ phone: Optional new primary phone number for contact purposes.
63
+ gender: Optional new gender identification for the user.
64
+ birthday: Optional new date of birth in format YYYY-MM-DD.
65
+
66
+ Returns:
67
+ Profile: A model containing the updated user's profile details.
68
+ """
69
+ return await self._client.http_request(
70
+ method=Method.PUT,
71
+ path="profile",
72
+ model=Profile,
73
+ access_token=access_token,
74
+ body={
75
+ "email": email,
76
+ "username": username,
77
+ "first_name": first_name,
78
+ "last_name": last_name,
79
+ "twitterNickname": twitter_nickname,
80
+ "phone": phone,
81
+ "gender": gender,
82
+ "birthday": birthday,
83
+ },
84
+ )
85
+
86
+ async def accept_terms(
87
+ self,
88
+ *,
89
+ access_token: str,
90
+ ) -> bool:
91
+ """Accepts the legal terms and conditions for the currently authenticated user.
92
+
93
+ Use this tool to register the acceptance of legal terms for a previously
94
+ registered user. Accepting these terms allows the user to use the platform.
95
+ If not accepted, the user will not be allowed to use the platform.
96
+
97
+ Should be used if a 451 error is received.
98
+
99
+ Args:
100
+ access_token: Optional token used for authentication. If not provided, the operation
101
+ will be made without authentication.
102
+
103
+ Returns:
104
+ bool: True if the terms were successfully accepted.
105
+ """
106
+ await self._client.http_request(
107
+ method=Method.POST,
108
+ path="accept_terms",
109
+ model=EmptyResponse,
110
+ access_token=access_token,
111
+ )
112
+ return True
113
+
114
+ async def legal_terms(
115
+ self,
116
+ ) -> LegalTerms:
117
+ """
118
+ Retrieves the current legal terms and conditions content.
119
+
120
+ Use this function to fetch the complete legal terms and conditions content
121
+ that users must accept to use the platform. The returned content may include
122
+ plain text, formatted text, or links to external legal documents. This is
123
+ typically given to users during registration or when terms have been
124
+ updated and require re-acceptance.
125
+
126
+ Returns:
127
+ LegalTerms: A model containing the legal terms content, which may include
128
+ privacy policy, terms of use, and cookies policy. Each field can
129
+ contain text or links to legal documents.
130
+ """
131
+ return await self._client.http_request(
132
+ method=Method.GET,
133
+ path="legal-terms",
134
+ model=LegalTerms,
135
+ )