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 +103 -0
- pymlsapi/async_client.py +67 -0
- pymlsapi/async_http.py +104 -0
- pymlsapi/client.py +67 -0
- pymlsapi/config.py +58 -0
- pymlsapi/errors.py +95 -0
- pymlsapi/http.py +155 -0
- pymlsapi/models/__init__.py +100 -0
- pymlsapi/models/common.py +66 -0
- pymlsapi/models/content.py +92 -0
- pymlsapi/models/intelligence.py +65 -0
- pymlsapi/models/listings.py +91 -0
- pymlsapi/models/studio.py +171 -0
- pymlsapi/poller.py +102 -0
- pymlsapi/resources/__init__.py +20 -0
- pymlsapi/resources/account/__init__.py +38 -0
- pymlsapi/resources/account/billing.py +74 -0
- pymlsapi/resources/account/keys.py +46 -0
- pymlsapi/resources/content.py +77 -0
- pymlsapi/resources/intelligence.py +47 -0
- pymlsapi/resources/listings.py +179 -0
- pymlsapi/resources/studio/__init__.py +56 -0
- pymlsapi/resources/studio/creatives.py +124 -0
- pymlsapi/resources/studio/custom.py +100 -0
- pymlsapi/resources/studio/enhance.py +170 -0
- pymlsapi/resources/studio/floorplan.py +166 -0
- pymlsapi/resources/studio/jobs.py +86 -0
- pymlsapi/resources/studio/render.py +96 -0
- pymlsapi/resources/studio/social.py +57 -0
- pymlsapi/resources/studio/staging.py +694 -0
- pymlsapi/resources/studio/upload.py +93 -0
- pymlsapi/resources/studio/video.py +346 -0
- pymlsapi-0.1.0.dist-info/METADATA +570 -0
- pymlsapi-0.1.0.dist-info/RECORD +35 -0
- pymlsapi-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from pymlsapi.async_http import AsyncHttpClient
|
|
6
|
+
from pymlsapi.http import HttpClient
|
|
7
|
+
from pymlsapi.models.studio import CustomStudioResult, StudioJob
|
|
8
|
+
from pymlsapi.resources.studio.jobs import AsyncStudioJobsResource, StudioJobsResource
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CustomResource:
|
|
12
|
+
"""Synchronous custom multimodal generative studio prompt."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, http: HttpClient, jobs: StudioJobsResource) -> None:
|
|
15
|
+
self._http = http
|
|
16
|
+
self._jobs = jobs
|
|
17
|
+
|
|
18
|
+
def generate(
|
|
19
|
+
self,
|
|
20
|
+
prompt: str,
|
|
21
|
+
image_urls: Optional[List[str]] = None,
|
|
22
|
+
negative_prompt: Optional[str] = None,
|
|
23
|
+
webhook_url: Optional[str] = None,
|
|
24
|
+
) -> StudioJob:
|
|
25
|
+
payload: Dict[str, Any] = {"prompt": prompt}
|
|
26
|
+
if image_urls:
|
|
27
|
+
payload["image_urls"] = image_urls
|
|
28
|
+
if negative_prompt:
|
|
29
|
+
payload["negative_prompt"] = negative_prompt
|
|
30
|
+
if webhook_url:
|
|
31
|
+
payload["webhook_url"] = webhook_url
|
|
32
|
+
data = self._http.post("/v1/studio/custom", json=payload)
|
|
33
|
+
return StudioJob.model_validate(data)
|
|
34
|
+
|
|
35
|
+
def generate_and_wait(
|
|
36
|
+
self,
|
|
37
|
+
prompt: str,
|
|
38
|
+
image_urls: Optional[List[str]] = None,
|
|
39
|
+
negative_prompt: Optional[str] = None,
|
|
40
|
+
webhook_url: Optional[str] = None,
|
|
41
|
+
timeout_seconds: float = 90.0,
|
|
42
|
+
poll_interval: float = 2.0,
|
|
43
|
+
on_progress: Optional[Callable[[StudioJob], None]] = None,
|
|
44
|
+
) -> CustomStudioResult:
|
|
45
|
+
job = self.generate(
|
|
46
|
+
prompt, image_urls=image_urls, negative_prompt=negative_prompt, webhook_url=webhook_url
|
|
47
|
+
)
|
|
48
|
+
completed = self._jobs.wait_for(
|
|
49
|
+
job.job_id,
|
|
50
|
+
timeout_seconds=timeout_seconds,
|
|
51
|
+
poll_interval=poll_interval,
|
|
52
|
+
on_progress=on_progress,
|
|
53
|
+
)
|
|
54
|
+
return CustomStudioResult.model_validate(completed.result or {})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class AsyncCustomResource:
|
|
58
|
+
"""Asynchronous custom multimodal generative studio prompt."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, http: AsyncHttpClient, jobs: AsyncStudioJobsResource) -> None:
|
|
61
|
+
self._http = http
|
|
62
|
+
self._jobs = jobs
|
|
63
|
+
|
|
64
|
+
async def generate(
|
|
65
|
+
self,
|
|
66
|
+
prompt: str,
|
|
67
|
+
image_urls: Optional[List[str]] = None,
|
|
68
|
+
negative_prompt: Optional[str] = None,
|
|
69
|
+
webhook_url: Optional[str] = None,
|
|
70
|
+
) -> StudioJob:
|
|
71
|
+
payload: Dict[str, Any] = {"prompt": prompt}
|
|
72
|
+
if image_urls:
|
|
73
|
+
payload["image_urls"] = image_urls
|
|
74
|
+
if negative_prompt:
|
|
75
|
+
payload["negative_prompt"] = negative_prompt
|
|
76
|
+
if webhook_url:
|
|
77
|
+
payload["webhook_url"] = webhook_url
|
|
78
|
+
data = await self._http.post("/v1/studio/custom", json=payload)
|
|
79
|
+
return StudioJob.model_validate(data)
|
|
80
|
+
|
|
81
|
+
async def generate_and_wait(
|
|
82
|
+
self,
|
|
83
|
+
prompt: str,
|
|
84
|
+
image_urls: Optional[List[str]] = None,
|
|
85
|
+
negative_prompt: Optional[str] = None,
|
|
86
|
+
webhook_url: Optional[str] = None,
|
|
87
|
+
timeout_seconds: float = 90.0,
|
|
88
|
+
poll_interval: float = 2.0,
|
|
89
|
+
on_progress: Optional[Callable[[StudioJob], None]] = None,
|
|
90
|
+
) -> CustomStudioResult:
|
|
91
|
+
job = await self.generate(
|
|
92
|
+
prompt, image_urls=image_urls, negative_prompt=negative_prompt, webhook_url=webhook_url
|
|
93
|
+
)
|
|
94
|
+
completed = await self._jobs.wait_for(
|
|
95
|
+
job.job_id,
|
|
96
|
+
timeout_seconds=timeout_seconds,
|
|
97
|
+
poll_interval=poll_interval,
|
|
98
|
+
on_progress=on_progress,
|
|
99
|
+
)
|
|
100
|
+
return CustomStudioResult.model_validate(completed.result or {})
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from pymlsapi.async_http import AsyncHttpClient
|
|
6
|
+
from pymlsapi.http import HttpClient
|
|
7
|
+
from pymlsapi.models.studio import ExteriorEnhanceResult, StudioJob, UpscaleResult
|
|
8
|
+
from pymlsapi.resources.studio.jobs import AsyncStudioJobsResource, StudioJobsResource
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EnhanceResource:
|
|
12
|
+
"""Synchronous exterior curb appeal and 4K upscaling operations."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, http: HttpClient, jobs: StudioJobsResource) -> None:
|
|
15
|
+
self._http = http
|
|
16
|
+
self._jobs = jobs
|
|
17
|
+
|
|
18
|
+
def exterior(
|
|
19
|
+
self,
|
|
20
|
+
photo_url: str,
|
|
21
|
+
enhancements: Optional[List[str]] = None,
|
|
22
|
+
webhook_url: Optional[str] = None,
|
|
23
|
+
) -> StudioJob:
|
|
24
|
+
payload: Dict[str, Any] = {"photo_url": photo_url}
|
|
25
|
+
if enhancements:
|
|
26
|
+
payload["enhancements"] = enhancements
|
|
27
|
+
if webhook_url:
|
|
28
|
+
payload["webhook_url"] = webhook_url
|
|
29
|
+
data = self._http.post("/v1/studio/enhance/exterior", json=payload)
|
|
30
|
+
return StudioJob.model_validate(data)
|
|
31
|
+
|
|
32
|
+
def exterior_and_wait(
|
|
33
|
+
self,
|
|
34
|
+
photo_url: str,
|
|
35
|
+
enhancements: Optional[List[str]] = None,
|
|
36
|
+
webhook_url: Optional[str] = None,
|
|
37
|
+
timeout_seconds: float = 90.0,
|
|
38
|
+
poll_interval: float = 2.0,
|
|
39
|
+
on_progress: Optional[Callable[[StudioJob], None]] = None,
|
|
40
|
+
) -> ExteriorEnhanceResult:
|
|
41
|
+
job = self.exterior(photo_url, enhancements=enhancements, webhook_url=webhook_url)
|
|
42
|
+
completed = self._jobs.wait_for(
|
|
43
|
+
job.job_id,
|
|
44
|
+
timeout_seconds=timeout_seconds,
|
|
45
|
+
poll_interval=poll_interval,
|
|
46
|
+
on_progress=on_progress,
|
|
47
|
+
)
|
|
48
|
+
return ExteriorEnhanceResult.model_validate(completed.result or {})
|
|
49
|
+
|
|
50
|
+
def upscale(
|
|
51
|
+
self,
|
|
52
|
+
image_url: str,
|
|
53
|
+
scale_factor: int = 4,
|
|
54
|
+
enhance_details: bool = True,
|
|
55
|
+
webhook_url: Optional[str] = None,
|
|
56
|
+
) -> StudioJob:
|
|
57
|
+
payload: Dict[str, Any] = {
|
|
58
|
+
"image_url": image_url,
|
|
59
|
+
"scale_factor": scale_factor,
|
|
60
|
+
"enhance_details": enhance_details,
|
|
61
|
+
}
|
|
62
|
+
if webhook_url:
|
|
63
|
+
payload["webhook_url"] = webhook_url
|
|
64
|
+
data = self._http.post("/v1/studio/enhance/upscale", json=payload)
|
|
65
|
+
return StudioJob.model_validate(data)
|
|
66
|
+
|
|
67
|
+
def upscale_and_wait(
|
|
68
|
+
self,
|
|
69
|
+
image_url: str,
|
|
70
|
+
scale_factor: int = 4,
|
|
71
|
+
enhance_details: bool = True,
|
|
72
|
+
webhook_url: Optional[str] = None,
|
|
73
|
+
timeout_seconds: float = 90.0,
|
|
74
|
+
poll_interval: float = 2.0,
|
|
75
|
+
on_progress: Optional[Callable[[StudioJob], None]] = None,
|
|
76
|
+
) -> UpscaleResult:
|
|
77
|
+
job = self.upscale(
|
|
78
|
+
image_url,
|
|
79
|
+
scale_factor=scale_factor,
|
|
80
|
+
enhance_details=enhance_details,
|
|
81
|
+
webhook_url=webhook_url,
|
|
82
|
+
)
|
|
83
|
+
completed = self._jobs.wait_for(
|
|
84
|
+
job.job_id,
|
|
85
|
+
timeout_seconds=timeout_seconds,
|
|
86
|
+
poll_interval=poll_interval,
|
|
87
|
+
on_progress=on_progress,
|
|
88
|
+
)
|
|
89
|
+
return UpscaleResult.model_validate(completed.result or {})
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class AsyncEnhanceResource:
|
|
93
|
+
"""Asynchronous exterior curb appeal and 4K upscaling operations."""
|
|
94
|
+
|
|
95
|
+
def __init__(self, http: AsyncHttpClient, jobs: AsyncStudioJobsResource) -> None:
|
|
96
|
+
self._http = http
|
|
97
|
+
self._jobs = jobs
|
|
98
|
+
|
|
99
|
+
async def exterior(
|
|
100
|
+
self,
|
|
101
|
+
photo_url: str,
|
|
102
|
+
enhancements: Optional[List[str]] = None,
|
|
103
|
+
webhook_url: Optional[str] = None,
|
|
104
|
+
) -> StudioJob:
|
|
105
|
+
payload: Dict[str, Any] = {"photo_url": photo_url}
|
|
106
|
+
if enhancements:
|
|
107
|
+
payload["enhancements"] = enhancements
|
|
108
|
+
if webhook_url:
|
|
109
|
+
payload["webhook_url"] = webhook_url
|
|
110
|
+
data = await self._http.post("/v1/studio/enhance/exterior", json=payload)
|
|
111
|
+
return StudioJob.model_validate(data)
|
|
112
|
+
|
|
113
|
+
async def exterior_and_wait(
|
|
114
|
+
self,
|
|
115
|
+
photo_url: str,
|
|
116
|
+
enhancements: Optional[List[str]] = None,
|
|
117
|
+
webhook_url: Optional[str] = None,
|
|
118
|
+
timeout_seconds: float = 90.0,
|
|
119
|
+
poll_interval: float = 2.0,
|
|
120
|
+
on_progress: Optional[Callable[[StudioJob], None]] = None,
|
|
121
|
+
) -> ExteriorEnhanceResult:
|
|
122
|
+
job = await self.exterior(photo_url, enhancements=enhancements, webhook_url=webhook_url)
|
|
123
|
+
completed = await self._jobs.wait_for(
|
|
124
|
+
job.job_id,
|
|
125
|
+
timeout_seconds=timeout_seconds,
|
|
126
|
+
poll_interval=poll_interval,
|
|
127
|
+
on_progress=on_progress,
|
|
128
|
+
)
|
|
129
|
+
return ExteriorEnhanceResult.model_validate(completed.result or {})
|
|
130
|
+
|
|
131
|
+
async def upscale(
|
|
132
|
+
self,
|
|
133
|
+
image_url: str,
|
|
134
|
+
scale_factor: int = 4,
|
|
135
|
+
enhance_details: bool = True,
|
|
136
|
+
webhook_url: Optional[str] = None,
|
|
137
|
+
) -> StudioJob:
|
|
138
|
+
payload: Dict[str, Any] = {
|
|
139
|
+
"image_url": image_url,
|
|
140
|
+
"scale_factor": scale_factor,
|
|
141
|
+
"enhance_details": enhance_details,
|
|
142
|
+
}
|
|
143
|
+
if webhook_url:
|
|
144
|
+
payload["webhook_url"] = webhook_url
|
|
145
|
+
data = await self._http.post("/v1/studio/enhance/upscale", json=payload)
|
|
146
|
+
return StudioJob.model_validate(data)
|
|
147
|
+
|
|
148
|
+
async def upscale_and_wait(
|
|
149
|
+
self,
|
|
150
|
+
image_url: str,
|
|
151
|
+
scale_factor: int = 4,
|
|
152
|
+
enhance_details: bool = True,
|
|
153
|
+
webhook_url: Optional[str] = None,
|
|
154
|
+
timeout_seconds: float = 90.0,
|
|
155
|
+
poll_interval: float = 2.0,
|
|
156
|
+
on_progress: Optional[Callable[[StudioJob], None]] = None,
|
|
157
|
+
) -> UpscaleResult:
|
|
158
|
+
job = await self.upscale(
|
|
159
|
+
image_url,
|
|
160
|
+
scale_factor=scale_factor,
|
|
161
|
+
enhance_details=enhance_details,
|
|
162
|
+
webhook_url=webhook_url,
|
|
163
|
+
)
|
|
164
|
+
completed = await self._jobs.wait_for(
|
|
165
|
+
job.job_id,
|
|
166
|
+
timeout_seconds=timeout_seconds,
|
|
167
|
+
poll_interval=poll_interval,
|
|
168
|
+
on_progress=on_progress,
|
|
169
|
+
)
|
|
170
|
+
return UpscaleResult.model_validate(completed.result or {})
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from pymlsapi.async_http import AsyncHttpClient
|
|
6
|
+
from pymlsapi.http import HttpClient
|
|
7
|
+
from pymlsapi.models.studio import FloorPlanAnalysisResponse, Render3dResult, StudioJob
|
|
8
|
+
from pymlsapi.resources.studio.jobs import AsyncStudioJobsResource, StudioJobsResource
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FloorPlanResource:
|
|
12
|
+
"""Synchronous 2D floor plan blueprint analysis and 3D isometric dollhouse rendering."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, http: HttpClient, jobs: StudioJobsResource) -> None:
|
|
15
|
+
self._http = http
|
|
16
|
+
self._jobs = jobs
|
|
17
|
+
|
|
18
|
+
def analyze(
|
|
19
|
+
self,
|
|
20
|
+
floorplan_image_url: str,
|
|
21
|
+
style: Optional[str] = "modern",
|
|
22
|
+
mls_id: Optional[str] = None,
|
|
23
|
+
generate_3d_render: bool = False,
|
|
24
|
+
) -> FloorPlanAnalysisResponse:
|
|
25
|
+
"""Analyze 2D floorplan geometry, detect rooms, and generate design prompts."""
|
|
26
|
+
payload: Dict[str, Any] = {
|
|
27
|
+
"floorplan_image_url": floorplan_image_url,
|
|
28
|
+
"style": style,
|
|
29
|
+
"generate_3d_render": generate_3d_render,
|
|
30
|
+
}
|
|
31
|
+
if mls_id:
|
|
32
|
+
payload["mls_id"] = mls_id
|
|
33
|
+
data = self._http.post("/v1/studio/floorplan/analyze", json=payload)
|
|
34
|
+
return FloorPlanAnalysisResponse.model_validate(data)
|
|
35
|
+
|
|
36
|
+
def render_3d(
|
|
37
|
+
self,
|
|
38
|
+
floorplan_image_url: str,
|
|
39
|
+
style: Optional[str] = "modern",
|
|
40
|
+
include_room_closeups: bool = True,
|
|
41
|
+
target_rooms: Optional[List[str]] = None,
|
|
42
|
+
custom_prompt: Optional[str] = None,
|
|
43
|
+
webhook_url: Optional[str] = None,
|
|
44
|
+
) -> StudioJob:
|
|
45
|
+
"""Trigger asynchronous 3D isometric dollhouse cutaway model rendering."""
|
|
46
|
+
payload: Dict[str, Any] = {
|
|
47
|
+
"floorplan_image_url": floorplan_image_url,
|
|
48
|
+
"style": style,
|
|
49
|
+
"include_room_closeups": include_room_closeups,
|
|
50
|
+
}
|
|
51
|
+
if target_rooms:
|
|
52
|
+
payload["target_rooms"] = target_rooms
|
|
53
|
+
if custom_prompt:
|
|
54
|
+
payload["custom_prompt"] = custom_prompt
|
|
55
|
+
if webhook_url:
|
|
56
|
+
payload["webhook_url"] = webhook_url
|
|
57
|
+
data = self._http.post("/v1/studio/floorplan/render-3d", json=payload)
|
|
58
|
+
return StudioJob.model_validate(data)
|
|
59
|
+
|
|
60
|
+
def render_3d_and_wait(
|
|
61
|
+
self,
|
|
62
|
+
floorplan_image_url: str,
|
|
63
|
+
style: Optional[str] = "modern",
|
|
64
|
+
include_room_closeups: bool = True,
|
|
65
|
+
target_rooms: Optional[List[str]] = None,
|
|
66
|
+
custom_prompt: Optional[str] = None,
|
|
67
|
+
webhook_url: Optional[str] = None,
|
|
68
|
+
timeout_seconds: float = 120.0,
|
|
69
|
+
poll_interval: float = 2.0,
|
|
70
|
+
on_progress: Optional[Callable[[StudioJob], None]] = None,
|
|
71
|
+
) -> Render3dResult:
|
|
72
|
+
"""Render 3D isometric dollhouse and auto-poll until complete."""
|
|
73
|
+
job = self.render_3d(
|
|
74
|
+
floorplan_image_url=floorplan_image_url,
|
|
75
|
+
style=style,
|
|
76
|
+
include_room_closeups=include_room_closeups,
|
|
77
|
+
target_rooms=target_rooms,
|
|
78
|
+
custom_prompt=custom_prompt,
|
|
79
|
+
webhook_url=webhook_url,
|
|
80
|
+
)
|
|
81
|
+
completed = self._jobs.wait_for(
|
|
82
|
+
job.job_id,
|
|
83
|
+
timeout_seconds=timeout_seconds,
|
|
84
|
+
poll_interval=poll_interval,
|
|
85
|
+
on_progress=on_progress,
|
|
86
|
+
)
|
|
87
|
+
return Render3dResult.model_validate(completed.result or {})
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class AsyncFloorPlanResource:
|
|
91
|
+
"""Asynchronous 2D floor plan blueprint analysis and 3D isometric dollhouse rendering."""
|
|
92
|
+
|
|
93
|
+
def __init__(self, http: AsyncHttpClient, jobs: AsyncStudioJobsResource) -> None:
|
|
94
|
+
self._http = http
|
|
95
|
+
self._jobs = jobs
|
|
96
|
+
|
|
97
|
+
async def analyze(
|
|
98
|
+
self,
|
|
99
|
+
floorplan_image_url: str,
|
|
100
|
+
style: Optional[str] = "modern",
|
|
101
|
+
mls_id: Optional[str] = None,
|
|
102
|
+
generate_3d_render: bool = False,
|
|
103
|
+
) -> FloorPlanAnalysisResponse:
|
|
104
|
+
"""Analyze 2D floorplan geometry, detect rooms, and generate design prompts."""
|
|
105
|
+
payload: Dict[str, Any] = {
|
|
106
|
+
"floorplan_image_url": floorplan_image_url,
|
|
107
|
+
"style": style,
|
|
108
|
+
"generate_3d_render": generate_3d_render,
|
|
109
|
+
}
|
|
110
|
+
if mls_id:
|
|
111
|
+
payload["mls_id"] = mls_id
|
|
112
|
+
data = await self._http.post("/v1/studio/floorplan/analyze", json=payload)
|
|
113
|
+
return FloorPlanAnalysisResponse.model_validate(data)
|
|
114
|
+
|
|
115
|
+
async def render_3d(
|
|
116
|
+
self,
|
|
117
|
+
floorplan_image_url: str,
|
|
118
|
+
style: Optional[str] = "modern",
|
|
119
|
+
include_room_closeups: bool = True,
|
|
120
|
+
target_rooms: Optional[List[str]] = None,
|
|
121
|
+
custom_prompt: Optional[str] = None,
|
|
122
|
+
webhook_url: Optional[str] = None,
|
|
123
|
+
) -> StudioJob:
|
|
124
|
+
"""Trigger asynchronous 3D isometric dollhouse cutaway model rendering."""
|
|
125
|
+
payload: Dict[str, Any] = {
|
|
126
|
+
"floorplan_image_url": floorplan_image_url,
|
|
127
|
+
"style": style,
|
|
128
|
+
"include_room_closeups": include_room_closeups,
|
|
129
|
+
}
|
|
130
|
+
if target_rooms:
|
|
131
|
+
payload["target_rooms"] = target_rooms
|
|
132
|
+
if custom_prompt:
|
|
133
|
+
payload["custom_prompt"] = custom_prompt
|
|
134
|
+
if webhook_url:
|
|
135
|
+
payload["webhook_url"] = webhook_url
|
|
136
|
+
data = await self._http.post("/v1/studio/floorplan/render-3d", json=payload)
|
|
137
|
+
return StudioJob.model_validate(data)
|
|
138
|
+
|
|
139
|
+
async def render_3d_and_wait(
|
|
140
|
+
self,
|
|
141
|
+
floorplan_image_url: str,
|
|
142
|
+
style: Optional[str] = "modern",
|
|
143
|
+
include_room_closeups: bool = True,
|
|
144
|
+
target_rooms: Optional[List[str]] = None,
|
|
145
|
+
custom_prompt: Optional[str] = None,
|
|
146
|
+
webhook_url: Optional[str] = None,
|
|
147
|
+
timeout_seconds: float = 120.0,
|
|
148
|
+
poll_interval: float = 2.0,
|
|
149
|
+
on_progress: Optional[Callable[[StudioJob], None]] = None,
|
|
150
|
+
) -> Render3dResult:
|
|
151
|
+
"""Render 3D isometric dollhouse and auto-poll until complete."""
|
|
152
|
+
job = await self.render_3d(
|
|
153
|
+
floorplan_image_url=floorplan_image_url,
|
|
154
|
+
style=style,
|
|
155
|
+
include_room_closeups=include_room_closeups,
|
|
156
|
+
target_rooms=target_rooms,
|
|
157
|
+
custom_prompt=custom_prompt,
|
|
158
|
+
webhook_url=webhook_url,
|
|
159
|
+
)
|
|
160
|
+
completed = await self._jobs.wait_for(
|
|
161
|
+
job.job_id,
|
|
162
|
+
timeout_seconds=timeout_seconds,
|
|
163
|
+
poll_interval=poll_interval,
|
|
164
|
+
on_progress=on_progress,
|
|
165
|
+
)
|
|
166
|
+
return Render3dResult.model_validate(completed.result or {})
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Optional
|
|
4
|
+
|
|
5
|
+
from pymlsapi.async_http import AsyncHttpClient
|
|
6
|
+
from pymlsapi.http import HttpClient
|
|
7
|
+
from pymlsapi.models.studio import StudioJob
|
|
8
|
+
from pymlsapi.poller import poll_job_async, poll_job_sync
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StudioJobsResource:
|
|
12
|
+
"""Synchronous tracking and polling for Studio AI background jobs."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, http: HttpClient) -> None:
|
|
15
|
+
self._http = http
|
|
16
|
+
|
|
17
|
+
def get(self, job_id: str) -> StudioJob:
|
|
18
|
+
"""Fetch current status and progress of a Studio job."""
|
|
19
|
+
data = self._http.get(f"/v1/studio/jobs/{job_id}")
|
|
20
|
+
return StudioJob.model_validate(data)
|
|
21
|
+
|
|
22
|
+
def wait_for(
|
|
23
|
+
self,
|
|
24
|
+
job_id: str,
|
|
25
|
+
timeout_seconds: float = 90.0,
|
|
26
|
+
poll_interval: float = 2.0,
|
|
27
|
+
on_progress: Optional[Callable[[StudioJob], None]] = None,
|
|
28
|
+
) -> StudioJob:
|
|
29
|
+
"""Poll job until completion, returning final StudioJob with result."""
|
|
30
|
+
|
|
31
|
+
def fetch_job() -> StudioJob:
|
|
32
|
+
return self.get(job_id)
|
|
33
|
+
|
|
34
|
+
def is_done(job: StudioJob) -> bool:
|
|
35
|
+
return job.status == "completed"
|
|
36
|
+
|
|
37
|
+
def is_failed(job: StudioJob) -> bool:
|
|
38
|
+
return job.status == "failed"
|
|
39
|
+
|
|
40
|
+
return poll_job_sync(
|
|
41
|
+
fetch_fn=fetch_job,
|
|
42
|
+
is_done_fn=is_done,
|
|
43
|
+
is_failed_fn=is_failed,
|
|
44
|
+
timeout_seconds=timeout_seconds,
|
|
45
|
+
initial_interval=poll_interval,
|
|
46
|
+
on_progress=on_progress,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AsyncStudioJobsResource:
|
|
51
|
+
"""Asynchronous tracking and polling for Studio AI background jobs."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, http: AsyncHttpClient) -> None:
|
|
54
|
+
self._http = http
|
|
55
|
+
|
|
56
|
+
async def get(self, job_id: str) -> StudioJob:
|
|
57
|
+
"""Fetch current status and progress of a Studio job."""
|
|
58
|
+
data = await self._http.get(f"/v1/studio/jobs/{job_id}")
|
|
59
|
+
return StudioJob.model_validate(data)
|
|
60
|
+
|
|
61
|
+
async def wait_for(
|
|
62
|
+
self,
|
|
63
|
+
job_id: str,
|
|
64
|
+
timeout_seconds: float = 90.0,
|
|
65
|
+
poll_interval: float = 2.0,
|
|
66
|
+
on_progress: Optional[Callable[[StudioJob], None]] = None,
|
|
67
|
+
) -> StudioJob:
|
|
68
|
+
"""Poll job until completion, returning final StudioJob with result."""
|
|
69
|
+
|
|
70
|
+
async def fetch_job() -> StudioJob:
|
|
71
|
+
return await self.get(job_id)
|
|
72
|
+
|
|
73
|
+
def is_done(job: StudioJob) -> bool:
|
|
74
|
+
return job.status == "completed"
|
|
75
|
+
|
|
76
|
+
def is_failed(job: StudioJob) -> bool:
|
|
77
|
+
return job.status == "failed"
|
|
78
|
+
|
|
79
|
+
return await poll_job_async(
|
|
80
|
+
fetch_fn=fetch_job,
|
|
81
|
+
is_done_fn=is_done,
|
|
82
|
+
is_failed_fn=is_failed,
|
|
83
|
+
timeout_seconds=timeout_seconds,
|
|
84
|
+
initial_interval=poll_interval,
|
|
85
|
+
on_progress=on_progress,
|
|
86
|
+
)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from pymlsapi.async_http import AsyncHttpClient
|
|
6
|
+
from pymlsapi.http import HttpClient
|
|
7
|
+
from pymlsapi.models.studio import ArchitecturalRenderResult, StudioJob
|
|
8
|
+
from pymlsapi.resources.studio.jobs import AsyncStudioJobsResource, StudioJobsResource
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RenderResource:
|
|
12
|
+
"""Synchronous architectural visualization rendering (CAD / Sketch / SketchUp to photorealism)."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, http: HttpClient, jobs: StudioJobsResource) -> None:
|
|
15
|
+
self._http = http
|
|
16
|
+
self._jobs = jobs
|
|
17
|
+
|
|
18
|
+
def architectural(
|
|
19
|
+
self,
|
|
20
|
+
source_image_url: str,
|
|
21
|
+
style: Optional[str] = "modern",
|
|
22
|
+
prompt: Optional[str] = None,
|
|
23
|
+
webhook_url: Optional[str] = None,
|
|
24
|
+
) -> StudioJob:
|
|
25
|
+
payload: Dict[str, Any] = {"source_image_url": source_image_url, "style": style}
|
|
26
|
+
if prompt:
|
|
27
|
+
payload["prompt"] = prompt
|
|
28
|
+
if webhook_url:
|
|
29
|
+
payload["webhook_url"] = webhook_url
|
|
30
|
+
data = self._http.post("/v1/studio/render/architectural", json=payload)
|
|
31
|
+
return StudioJob.model_validate(data)
|
|
32
|
+
|
|
33
|
+
def architectural_and_wait(
|
|
34
|
+
self,
|
|
35
|
+
source_image_url: str,
|
|
36
|
+
style: Optional[str] = "modern",
|
|
37
|
+
prompt: Optional[str] = None,
|
|
38
|
+
webhook_url: Optional[str] = None,
|
|
39
|
+
timeout_seconds: float = 90.0,
|
|
40
|
+
poll_interval: float = 2.0,
|
|
41
|
+
on_progress: Optional[Callable[[StudioJob], None]] = None,
|
|
42
|
+
) -> ArchitecturalRenderResult:
|
|
43
|
+
job = self.architectural(
|
|
44
|
+
source_image_url, style=style, prompt=prompt, webhook_url=webhook_url
|
|
45
|
+
)
|
|
46
|
+
completed = self._jobs.wait_for(
|
|
47
|
+
job.job_id,
|
|
48
|
+
timeout_seconds=timeout_seconds,
|
|
49
|
+
poll_interval=poll_interval,
|
|
50
|
+
on_progress=on_progress,
|
|
51
|
+
)
|
|
52
|
+
return ArchitecturalRenderResult.model_validate(completed.result or {})
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AsyncRenderResource:
|
|
56
|
+
"""Asynchronous architectural visualization rendering."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, http: AsyncHttpClient, jobs: AsyncStudioJobsResource) -> None:
|
|
59
|
+
self._http = http
|
|
60
|
+
self._jobs = jobs
|
|
61
|
+
|
|
62
|
+
async def architectural(
|
|
63
|
+
self,
|
|
64
|
+
source_image_url: str,
|
|
65
|
+
style: Optional[str] = "modern",
|
|
66
|
+
prompt: Optional[str] = None,
|
|
67
|
+
webhook_url: Optional[str] = None,
|
|
68
|
+
) -> StudioJob:
|
|
69
|
+
payload: Dict[str, Any] = {"source_image_url": source_image_url, "style": style}
|
|
70
|
+
if prompt:
|
|
71
|
+
payload["prompt"] = prompt
|
|
72
|
+
if webhook_url:
|
|
73
|
+
payload["webhook_url"] = webhook_url
|
|
74
|
+
data = await self._http.post("/v1/studio/render/architectural", json=payload)
|
|
75
|
+
return StudioJob.model_validate(data)
|
|
76
|
+
|
|
77
|
+
async def architectural_and_wait(
|
|
78
|
+
self,
|
|
79
|
+
source_image_url: str,
|
|
80
|
+
style: Optional[str] = "modern",
|
|
81
|
+
prompt: Optional[str] = None,
|
|
82
|
+
webhook_url: Optional[str] = None,
|
|
83
|
+
timeout_seconds: float = 90.0,
|
|
84
|
+
poll_interval: float = 2.0,
|
|
85
|
+
on_progress: Optional[Callable[[StudioJob], None]] = None,
|
|
86
|
+
) -> ArchitecturalRenderResult:
|
|
87
|
+
job = await self.architectural(
|
|
88
|
+
source_image_url, style=style, prompt=prompt, webhook_url=webhook_url
|
|
89
|
+
)
|
|
90
|
+
completed = await self._jobs.wait_for(
|
|
91
|
+
job.job_id,
|
|
92
|
+
timeout_seconds=timeout_seconds,
|
|
93
|
+
poll_interval=poll_interval,
|
|
94
|
+
on_progress=on_progress,
|
|
95
|
+
)
|
|
96
|
+
return ArchitecturalRenderResult.model_validate(completed.result or {})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from pymlsapi.async_http import AsyncHttpClient
|
|
6
|
+
from pymlsapi.http import HttpClient
|
|
7
|
+
from pymlsapi.models.studio import SocialPublishResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SocialResource:
|
|
11
|
+
"""Synchronous direct social network publishing operations."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, http: HttpClient) -> None:
|
|
14
|
+
self._http = http
|
|
15
|
+
|
|
16
|
+
def publish(
|
|
17
|
+
self,
|
|
18
|
+
asset_url: str,
|
|
19
|
+
destinations: List[str],
|
|
20
|
+
caption: Optional[str] = None,
|
|
21
|
+
schedule_time: Optional[str] = None,
|
|
22
|
+
) -> SocialPublishResult:
|
|
23
|
+
payload: Dict[str, Any] = {
|
|
24
|
+
"asset_url": asset_url,
|
|
25
|
+
"destinations": destinations,
|
|
26
|
+
}
|
|
27
|
+
if caption:
|
|
28
|
+
payload["caption"] = caption
|
|
29
|
+
if schedule_time:
|
|
30
|
+
payload["schedule_time"] = schedule_time
|
|
31
|
+
data = self._http.post("/v1/studio/social/publish", json=payload)
|
|
32
|
+
return SocialPublishResult.model_validate(data)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AsyncSocialResource:
|
|
36
|
+
"""Asynchronous direct social network publishing operations."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, http: AsyncHttpClient) -> None:
|
|
39
|
+
self._http = http
|
|
40
|
+
|
|
41
|
+
async def publish(
|
|
42
|
+
self,
|
|
43
|
+
asset_url: str,
|
|
44
|
+
destinations: List[str],
|
|
45
|
+
caption: Optional[str] = None,
|
|
46
|
+
schedule_time: Optional[str] = None,
|
|
47
|
+
) -> SocialPublishResult:
|
|
48
|
+
payload: Dict[str, Any] = {
|
|
49
|
+
"asset_url": asset_url,
|
|
50
|
+
"destinations": destinations,
|
|
51
|
+
}
|
|
52
|
+
if caption:
|
|
53
|
+
payload["caption"] = caption
|
|
54
|
+
if schedule_time:
|
|
55
|
+
payload["schedule_time"] = schedule_time
|
|
56
|
+
data = await self._http.post("/v1/studio/social/publish", json=payload)
|
|
57
|
+
return SocialPublishResult.model_validate(data)
|