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.
@@ -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)