pymlsapi 0.1.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.
pymlsapi/__init__.py ADDED
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ from pymlsapi.async_client import AsyncMLS, AsyncMlsApiClient
4
+ from pymlsapi.client import MLS, MlsApiClient
5
+ from pymlsapi.config import ClientConfig
6
+ from pymlsapi.errors import (
7
+ AuthenticationError,
8
+ InsufficientCreditsError,
9
+ InvalidRequestError,
10
+ JobTimeoutError,
11
+ MlsApiError,
12
+ NotFoundError,
13
+ PermissionDeniedError,
14
+ RateLimitError,
15
+ StudioJobFailedError,
16
+ )
17
+ from pymlsapi.models import (
18
+ AdCreativesResult,
19
+ Address,
20
+ ArchitecturalRenderResult,
21
+ BaseListing,
22
+ ContentGenerationResponse,
23
+ Coordinates,
24
+ CustomStudioResult,
25
+ DeclutterResult,
26
+ DeStageEmptyResult,
27
+ ExteriorEnhanceResult,
28
+ FloorPlanAnalysisResponse,
29
+ HouseTourResult,
30
+ IngestJob,
31
+ InteriorStyle,
32
+ JobStatus,
33
+ PropertyIntelligence,
34
+ PropertySpecifications,
35
+ Render3dResult,
36
+ ReplaceFurnitureResult,
37
+ ReplaceMaterialResult,
38
+ RestyleResult,
39
+ RoomType,
40
+ SocialPlatform,
41
+ SocialPublishResult,
42
+ StagingResult,
43
+ StudioJob,
44
+ TwilightResult,
45
+ UploadResult,
46
+ UpscaleResult,
47
+ VideoEnhanceResult,
48
+ VideoTransitionResult,
49
+ VideoWalkthroughResult,
50
+ WallColorsResult,
51
+ )
52
+
53
+ __version__ = "0.1.0"
54
+
55
+ __all__ = [
56
+ "MLS",
57
+ "AdCreativesResult",
58
+ "Address",
59
+ "ArchitecturalRenderResult",
60
+ "AsyncMLS",
61
+ "AsyncMlsApiClient",
62
+ "AuthenticationError",
63
+ "BaseListing",
64
+ "ClientConfig",
65
+ "ContentGenerationResponse",
66
+ "Coordinates",
67
+ "CustomStudioResult",
68
+ "DeStageEmptyResult",
69
+ "DeclutterResult",
70
+ "ExteriorEnhanceResult",
71
+ "FloorPlanAnalysisResponse",
72
+ "HouseTourResult",
73
+ "IngestJob",
74
+ "InsufficientCreditsError",
75
+ "InteriorStyle",
76
+ "InvalidRequestError",
77
+ "JobStatus",
78
+ "JobTimeoutError",
79
+ "MlsApiClient",
80
+ "MlsApiError",
81
+ "NotFoundError",
82
+ "PermissionDeniedError",
83
+ "PropertyIntelligence",
84
+ "PropertySpecifications",
85
+ "RateLimitError",
86
+ "Render3dResult",
87
+ "ReplaceFurnitureResult",
88
+ "ReplaceMaterialResult",
89
+ "RestyleResult",
90
+ "RoomType",
91
+ "SocialPlatform",
92
+ "SocialPublishResult",
93
+ "StagingResult",
94
+ "StudioJob",
95
+ "StudioJobFailedError",
96
+ "TwilightResult",
97
+ "UploadResult",
98
+ "UpscaleResult",
99
+ "VideoEnhanceResult",
100
+ "VideoTransitionResult",
101
+ "VideoWalkthroughResult",
102
+ "WallColorsResult",
103
+ ]
@@ -0,0 +1,67 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from pymlsapi.async_http import AsyncHttpClient
6
+ from pymlsapi.config import (
7
+ DEFAULT_JOB_TIMEOUT_SECONDS,
8
+ DEFAULT_MAX_RETRIES,
9
+ DEFAULT_POLL_INTERVAL_SECONDS,
10
+ DEFAULT_TIMEOUT_SECONDS,
11
+ ClientConfig,
12
+ )
13
+ from pymlsapi.resources.account import AsyncAccountResource
14
+ from pymlsapi.resources.content import AsyncContentResource
15
+ from pymlsapi.resources.intelligence import AsyncIntelligenceResource
16
+ from pymlsapi.resources.listings import AsyncListingsResource
17
+ from pymlsapi.resources.studio import AsyncStudioResource
18
+
19
+
20
+ class AsyncMlsApiClient:
21
+ """Official asynchronous Python client for mlsapi.dev.
22
+
23
+ High-concurrency asyncio client for real-time MLS listings, property intelligence,
24
+ marketing copy, and the complete suite of Studio Visual AI generative tools.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ api_key: Optional[str] = None,
30
+ environment: str = "live",
31
+ base_url: Optional[str] = None,
32
+ timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS,
33
+ max_retries: int = DEFAULT_MAX_RETRIES,
34
+ poll_interval: float = DEFAULT_POLL_INTERVAL_SECONDS,
35
+ job_timeout_seconds: float = DEFAULT_JOB_TIMEOUT_SECONDS,
36
+ ) -> None:
37
+ self.config = ClientConfig.create(
38
+ api_key=api_key,
39
+ environment=environment,
40
+ base_url=base_url,
41
+ timeout_seconds=timeout_seconds,
42
+ max_retries=max_retries,
43
+ poll_interval=poll_interval,
44
+ job_timeout_seconds=job_timeout_seconds,
45
+ )
46
+ self._http = AsyncHttpClient(self.config)
47
+
48
+ # Namespaced resources
49
+ self.listings = AsyncListingsResource(self._http)
50
+ self.intelligence = AsyncIntelligenceResource(self._http)
51
+ self.content = AsyncContentResource(self._http)
52
+ self.studio = AsyncStudioResource(self._http)
53
+ self.account = AsyncAccountResource(self._http)
54
+
55
+ async def close(self) -> None:
56
+ """Close the underlying HTTP client transport."""
57
+ await self._http.aclose()
58
+
59
+ async def __aenter__(self) -> AsyncMlsApiClient:
60
+ return self
61
+
62
+ async def __aexit__(self, *args: object) -> None:
63
+ await self.close()
64
+
65
+
66
+ # Convenient alias
67
+ AsyncMLS = AsyncMlsApiClient
pymlsapi/async_http.py ADDED
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import random
5
+ from typing import Any, Dict, Optional
6
+
7
+ import httpx
8
+
9
+ from pymlsapi.config import ClientConfig
10
+ from pymlsapi.errors import MlsApiError
11
+ from pymlsapi.http import _handle_error_response
12
+
13
+
14
+ class AsyncHttpClient:
15
+ """Asynchronous HTTP transport engine with automatic retries and exponential backoff."""
16
+
17
+ def __init__(self, config: ClientConfig) -> None:
18
+ self.config = config
19
+ headers = {
20
+ "Authorization": f"Bearer {config.api_key}",
21
+ "x-api-key": config.api_key,
22
+ "User-Agent": "pymlsapi/0.1.0",
23
+ "Accept": "application/json",
24
+ }
25
+ self._client = httpx.AsyncClient(
26
+ base_url=config.base_url,
27
+ headers=headers,
28
+ timeout=config.timeout_seconds,
29
+ )
30
+
31
+ async def aclose(self) -> None:
32
+ await self._client.aclose()
33
+
34
+ async def __aenter__(self) -> AsyncHttpClient:
35
+ return self
36
+
37
+ async def __aexit__(self, *args: object) -> None:
38
+ await self.aclose()
39
+
40
+ async def request(
41
+ self,
42
+ method: str,
43
+ path: str,
44
+ *,
45
+ params: Optional[Dict[str, Any]] = None,
46
+ json: Optional[Any] = None,
47
+ files: Optional[Dict[str, Any]] = None,
48
+ data: Optional[Dict[str, Any]] = None,
49
+ ) -> Any:
50
+ url = path if path.startswith("http") else path.lstrip("/")
51
+ attempts = 0
52
+ max_retries = self.config.max_retries
53
+
54
+ while True:
55
+ attempts += 1
56
+ try:
57
+ response = await self._client.request(
58
+ method=method,
59
+ url=url,
60
+ params=params,
61
+ json=json,
62
+ files=files,
63
+ data=data,
64
+ )
65
+
66
+ if response.status_code in (429, 500, 502, 503, 504) and attempts <= max_retries:
67
+ backoff = (2 ** (attempts - 1)) * 0.5 + random.uniform(0.1, 0.4)
68
+ await asyncio.sleep(backoff)
69
+ continue
70
+
71
+ if response.is_error:
72
+ _handle_error_response(response)
73
+
74
+ if response.status_code == 204:
75
+ return None
76
+
77
+ return response.json()
78
+
79
+ except (httpx.ConnectError, httpx.ReadTimeout, httpx.WriteTimeout) as exc:
80
+ if attempts <= max_retries:
81
+ backoff = (2 ** (attempts - 1)) * 0.5 + random.uniform(0.1, 0.4)
82
+ await asyncio.sleep(backoff)
83
+ continue
84
+ raise MlsApiError(f"Network error during {method} {url}: {exc}") from exc
85
+
86
+ async def get(self, path: str, *, params: Optional[Dict[str, Any]] = None) -> Any:
87
+ return await self.request("GET", path, params=params)
88
+
89
+ async def post(
90
+ self,
91
+ path: str,
92
+ *,
93
+ json: Optional[Any] = None,
94
+ files: Optional[Dict[str, Any]] = None,
95
+ data: Optional[Dict[str, Any]] = None,
96
+ params: Optional[Dict[str, Any]] = None,
97
+ ) -> Any:
98
+ return await self.request("POST", path, json=json, files=files, data=data, params=params)
99
+
100
+ async def put(self, path: str, *, json: Optional[Any] = None) -> Any:
101
+ return await self.request("PUT", path, json=json)
102
+
103
+ async def delete(self, path: str, *, params: Optional[Dict[str, Any]] = None) -> Any:
104
+ return await self.request("DELETE", path, params=params)
pymlsapi/client.py ADDED
@@ -0,0 +1,67 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from pymlsapi.config import (
6
+ DEFAULT_JOB_TIMEOUT_SECONDS,
7
+ DEFAULT_MAX_RETRIES,
8
+ DEFAULT_POLL_INTERVAL_SECONDS,
9
+ DEFAULT_TIMEOUT_SECONDS,
10
+ ClientConfig,
11
+ )
12
+ from pymlsapi.http import HttpClient
13
+ from pymlsapi.resources.account import AccountResource
14
+ from pymlsapi.resources.content import ContentResource
15
+ from pymlsapi.resources.intelligence import IntelligenceResource
16
+ from pymlsapi.resources.listings import ListingsResource
17
+ from pymlsapi.resources.studio import StudioResource
18
+
19
+
20
+ class MlsApiClient:
21
+ """Official synchronous Python client for mlsapi.dev.
22
+
23
+ Access real-time MLS listings, property intelligence, marketing copy,
24
+ and the complete suite of Studio Visual AI generative tools.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ api_key: Optional[str] = None,
30
+ environment: str = "live",
31
+ base_url: Optional[str] = None,
32
+ timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS,
33
+ max_retries: int = DEFAULT_MAX_RETRIES,
34
+ poll_interval: float = DEFAULT_POLL_INTERVAL_SECONDS,
35
+ job_timeout_seconds: float = DEFAULT_JOB_TIMEOUT_SECONDS,
36
+ ) -> None:
37
+ self.config = ClientConfig.create(
38
+ api_key=api_key,
39
+ environment=environment,
40
+ base_url=base_url,
41
+ timeout_seconds=timeout_seconds,
42
+ max_retries=max_retries,
43
+ poll_interval=poll_interval,
44
+ job_timeout_seconds=job_timeout_seconds,
45
+ )
46
+ self._http = HttpClient(self.config)
47
+
48
+ # Namespaced resources
49
+ self.listings = ListingsResource(self._http)
50
+ self.intelligence = IntelligenceResource(self._http)
51
+ self.content = ContentResource(self._http)
52
+ self.studio = StudioResource(self._http)
53
+ self.account = AccountResource(self._http)
54
+
55
+ def close(self) -> None:
56
+ """Close the underlying HTTP client transport."""
57
+ self._http.close()
58
+
59
+ def __enter__(self) -> MlsApiClient:
60
+ return self
61
+
62
+ def __exit__(self, *args: object) -> None:
63
+ self.close()
64
+
65
+
66
+ # Convenient alias
67
+ MLS = MlsApiClient
pymlsapi/config.py ADDED
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from typing import Optional
6
+
7
+ DEFAULT_LIVE_URL = "https://mlsapi.dev"
8
+ DEFAULT_TEST_URL = "https://api.mlsapi.dev"
9
+ DEFAULT_TIMEOUT_SECONDS = 60.0
10
+ DEFAULT_MAX_RETRIES = 3
11
+ DEFAULT_POLL_INTERVAL_SECONDS = 2.0
12
+ DEFAULT_JOB_TIMEOUT_SECONDS = 90.0
13
+
14
+
15
+ @dataclass
16
+ class ClientConfig:
17
+ """Configuration options for the MLS API Client."""
18
+
19
+ api_key: str
20
+ environment: str = "live"
21
+ base_url: str = DEFAULT_LIVE_URL
22
+ timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS
23
+ max_retries: int = DEFAULT_MAX_RETRIES
24
+ poll_interval: float = DEFAULT_POLL_INTERVAL_SECONDS
25
+ job_timeout_seconds: float = DEFAULT_JOB_TIMEOUT_SECONDS
26
+
27
+ @classmethod
28
+ def create(
29
+ cls,
30
+ api_key: Optional[str] = None,
31
+ environment: str = "live",
32
+ base_url: Optional[str] = None,
33
+ timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS,
34
+ max_retries: int = DEFAULT_MAX_RETRIES,
35
+ poll_interval: float = DEFAULT_POLL_INTERVAL_SECONDS,
36
+ job_timeout_seconds: float = DEFAULT_JOB_TIMEOUT_SECONDS,
37
+ ) -> ClientConfig:
38
+ resolved_key = api_key or os.environ.get("MLSAPI_KEY")
39
+ if not resolved_key:
40
+ raise ValueError(
41
+ "MLS API key is required. Pass `api_key` to client or set MLSAPI_KEY environment variable."
42
+ )
43
+
44
+ if base_url is None:
45
+ base_url = DEFAULT_LIVE_URL if environment == "live" else DEFAULT_TEST_URL
46
+
47
+ # Strip trailing slashes
48
+ base_url = base_url.rstrip("/")
49
+
50
+ return cls(
51
+ api_key=resolved_key,
52
+ environment=environment,
53
+ base_url=base_url,
54
+ timeout_seconds=timeout_seconds,
55
+ max_retries=max_retries,
56
+ poll_interval=poll_interval,
57
+ job_timeout_seconds=job_timeout_seconds,
58
+ )
pymlsapi/errors.py ADDED
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Optional
4
+
5
+
6
+ class MlsApiError(Exception):
7
+ """Base exception for all mlsapi.dev SDK errors."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ status_code: Optional[int] = None,
13
+ code: Optional[str] = None,
14
+ raw_response: Optional[Any] = None,
15
+ ) -> None:
16
+ super().__init__(message)
17
+ self.message = message
18
+ self.status_code = status_code
19
+ self.code = code or "UNKNOWN_ERROR"
20
+ self.raw_response = raw_response
21
+
22
+ def __str__(self) -> str:
23
+ parts = []
24
+ if self.status_code:
25
+ parts.append(f"HTTP {self.status_code}")
26
+ if self.code and self.code != "UNKNOWN_ERROR":
27
+ parts.append(f"[{self.code}]")
28
+ parts.append(self.message)
29
+ return " - ".join(parts)
30
+
31
+
32
+ class AuthenticationError(MlsApiError):
33
+ """Raised on HTTP 401 when the provided API key is invalid, missing, or revoked."""
34
+
35
+
36
+ class PermissionDeniedError(MlsApiError):
37
+ """Raised on HTTP 403 when the API key lacks necessary permissions or plan tier."""
38
+
39
+
40
+ class NotFoundError(MlsApiError):
41
+ """Raised on HTTP 404 when the requested MLS listing, job, or resource is not found."""
42
+
43
+
44
+ class InvalidRequestError(MlsApiError):
45
+ """Raised on HTTP 400 when request parameters are malformed or missing."""
46
+
47
+
48
+ class InsufficientCreditsError(MlsApiError):
49
+ """Raised on HTTP 402 when workspace credit balance is insufficient for the requested action."""
50
+
51
+
52
+ class RateLimitError(MlsApiError):
53
+ """Raised on HTTP 429 when concurrency or rate limits have been exceeded."""
54
+
55
+ def __init__(
56
+ self,
57
+ message: str,
58
+ status_code: int = 429,
59
+ code: str = "RATE_LIMIT_EXCEEDED",
60
+ retry_after_seconds: Optional[float] = None,
61
+ raw_response: Optional[Any] = None,
62
+ ) -> None:
63
+ super().__init__(message, status_code, code, raw_response)
64
+ self.retry_after_seconds = retry_after_seconds
65
+
66
+
67
+ class StudioJobFailedError(MlsApiError):
68
+ """Raised when an asynchronous Studio AI job completes with a failed status."""
69
+
70
+ def __init__(
71
+ self,
72
+ job_id: str,
73
+ message: str,
74
+ error_details: Optional[Any] = None,
75
+ ) -> None:
76
+ super().__init__(
77
+ f"Studio job '{job_id}' failed: {message}",
78
+ status_code=500,
79
+ code="JOB_FAILED",
80
+ raw_response=error_details,
81
+ )
82
+ self.job_id = job_id
83
+
84
+
85
+ class JobTimeoutError(MlsApiError):
86
+ """Raised when polling for an asynchronous job exceeds the configured timeout."""
87
+
88
+ def __init__(self, job_id: str, timeout_seconds: float) -> None:
89
+ super().__init__(
90
+ f"Job '{job_id}' did not complete within {timeout_seconds:.1f} seconds",
91
+ status_code=408,
92
+ code="JOB_TIMEOUT",
93
+ )
94
+ self.job_id = job_id
95
+ self.timeout_seconds = timeout_seconds
pymlsapi/http.py ADDED
@@ -0,0 +1,155 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ import time
5
+ from typing import Any, Dict, Optional
6
+
7
+ import httpx
8
+
9
+ from pymlsapi.config import ClientConfig
10
+ from pymlsapi.errors import (
11
+ AuthenticationError,
12
+ InsufficientCreditsError,
13
+ InvalidRequestError,
14
+ MlsApiError,
15
+ NotFoundError,
16
+ PermissionDeniedError,
17
+ RateLimitError,
18
+ )
19
+
20
+
21
+ def _handle_error_response(response: httpx.Response) -> None:
22
+ status = response.status_code
23
+ try:
24
+ body = response.json()
25
+ except Exception:
26
+ body = {"message": response.text}
27
+
28
+ msg = body.get("error", {})
29
+ if isinstance(msg, dict):
30
+ message = msg.get("message") or str(body)
31
+ code = msg.get("code", "API_ERROR")
32
+ else:
33
+ message = str(msg) if msg else body.get("message", response.text or "Unknown error")
34
+ code = body.get("code", "API_ERROR")
35
+
36
+ if status == 401:
37
+ raise AuthenticationError(message, status_code=status, code=code, raw_response=body)
38
+ elif status == 402:
39
+ raise InsufficientCreditsError(message, status_code=status, code=code, raw_response=body)
40
+ elif status == 403:
41
+ raise PermissionDeniedError(message, status_code=status, code=code, raw_response=body)
42
+ elif status == 404:
43
+ raise NotFoundError(message, status_code=status, code=code, raw_response=body)
44
+ elif status == 400:
45
+ raise InvalidRequestError(message, status_code=status, code=code, raw_response=body)
46
+ elif status == 429:
47
+ retry_after = None
48
+ if "retry-after" in response.headers:
49
+ try:
50
+ retry_after = float(response.headers["retry-after"])
51
+ except ValueError:
52
+ pass
53
+ raise RateLimitError(
54
+ message,
55
+ status_code=status,
56
+ code=code,
57
+ retry_after_seconds=retry_after,
58
+ raw_response=body,
59
+ )
60
+ else:
61
+ raise MlsApiError(message, status_code=status, code=code, raw_response=body)
62
+
63
+
64
+ class HttpClient:
65
+ """Synchronous HTTP transport engine with automatic retries and exponential backoff."""
66
+
67
+ def __init__(self, config: ClientConfig) -> None:
68
+ self.config = config
69
+ headers = {
70
+ "Authorization": f"Bearer {config.api_key}",
71
+ "x-api-key": config.api_key,
72
+ "User-Agent": "pymlsapi/0.1.0",
73
+ "Accept": "application/json",
74
+ }
75
+ self._client = httpx.Client(
76
+ base_url=config.base_url,
77
+ headers=headers,
78
+ timeout=config.timeout_seconds,
79
+ )
80
+
81
+ def close(self) -> None:
82
+ self._client.close()
83
+
84
+ def __enter__(self) -> HttpClient:
85
+ return self
86
+
87
+ def __exit__(self, *args: object) -> None:
88
+ self.close()
89
+
90
+ def request(
91
+ self,
92
+ method: str,
93
+ path: str,
94
+ *,
95
+ params: Optional[Dict[str, Any]] = None,
96
+ json: Optional[Any] = None,
97
+ files: Optional[Dict[str, Any]] = None,
98
+ data: Optional[Dict[str, Any]] = None,
99
+ ) -> Any:
100
+ url = path if path.startswith("http") else path.lstrip("/")
101
+ attempts = 0
102
+ max_retries = self.config.max_retries
103
+
104
+ while True:
105
+ attempts += 1
106
+ try:
107
+ response = self._client.request(
108
+ method=method,
109
+ url=url,
110
+ params=params,
111
+ json=json,
112
+ files=files,
113
+ data=data,
114
+ )
115
+
116
+ if response.status_code in (429, 500, 502, 503, 504) and attempts <= max_retries:
117
+ # Exponential backoff with jitter
118
+ backoff = (2 ** (attempts - 1)) * 0.5 + random.uniform(0.1, 0.4)
119
+ time.sleep(backoff)
120
+ continue
121
+
122
+ if response.is_error:
123
+ _handle_error_response(response)
124
+
125
+ if response.status_code == 204:
126
+ return None
127
+
128
+ return response.json()
129
+
130
+ except (httpx.ConnectError, httpx.ReadTimeout, httpx.WriteTimeout) as exc:
131
+ if attempts <= max_retries:
132
+ backoff = (2 ** (attempts - 1)) * 0.5 + random.uniform(0.1, 0.4)
133
+ time.sleep(backoff)
134
+ continue
135
+ raise MlsApiError(f"Network error during {method} {url}: {exc}") from exc
136
+
137
+ def get(self, path: str, *, params: Optional[Dict[str, Any]] = None) -> Any:
138
+ return self.request("GET", path, params=params)
139
+
140
+ def post(
141
+ self,
142
+ path: str,
143
+ *,
144
+ json: Optional[Any] = None,
145
+ files: Optional[Dict[str, Any]] = None,
146
+ data: Optional[Dict[str, Any]] = None,
147
+ params: Optional[Dict[str, Any]] = None,
148
+ ) -> Any:
149
+ return self.request("POST", path, json=json, files=files, data=data, params=params)
150
+
151
+ def put(self, path: str, *, json: Optional[Any] = None) -> Any:
152
+ return self.request("PUT", path, json=json)
153
+
154
+ def delete(self, path: str, *, params: Optional[Dict[str, Any]] = None) -> Any:
155
+ return self.request("DELETE", path, params=params)