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 pymlsapi.models.common import InteriorStyle, JobStatus, RoomType, SocialPlatform
|
|
4
|
+
from pymlsapi.models.content import (
|
|
5
|
+
ContentGenerationResponse,
|
|
6
|
+
GeneratedContentBundle,
|
|
7
|
+
InstagramContent,
|
|
8
|
+
SocialPlatformBundle,
|
|
9
|
+
VideoScriptContent,
|
|
10
|
+
)
|
|
11
|
+
from pymlsapi.models.intelligence import (
|
|
12
|
+
FinancialFlag,
|
|
13
|
+
InvestorInsights,
|
|
14
|
+
LlmIntelligence,
|
|
15
|
+
PropertyIntelligence,
|
|
16
|
+
SystemCondition,
|
|
17
|
+
SystemsAndCapex,
|
|
18
|
+
)
|
|
19
|
+
from pymlsapi.models.listings import (
|
|
20
|
+
Address,
|
|
21
|
+
AgentInfo,
|
|
22
|
+
BaseListing,
|
|
23
|
+
Coordinates,
|
|
24
|
+
IngestJob,
|
|
25
|
+
ListingMetadata,
|
|
26
|
+
PropertyFeatures,
|
|
27
|
+
PropertySpecifications,
|
|
28
|
+
)
|
|
29
|
+
from pymlsapi.models.studio import (
|
|
30
|
+
AdCreativesResult,
|
|
31
|
+
ArchitecturalRenderResult,
|
|
32
|
+
CustomStudioResult,
|
|
33
|
+
DeclutterResult,
|
|
34
|
+
DeStageEmptyResult,
|
|
35
|
+
ExteriorEnhanceResult,
|
|
36
|
+
FloorPlanAnalysisResponse,
|
|
37
|
+
HouseTourResult,
|
|
38
|
+
Render3dResult,
|
|
39
|
+
ReplaceFurnitureResult,
|
|
40
|
+
ReplaceMaterialResult,
|
|
41
|
+
RestyleResult,
|
|
42
|
+
SocialPublishResult,
|
|
43
|
+
StagingResult,
|
|
44
|
+
StudioJob,
|
|
45
|
+
TwilightResult,
|
|
46
|
+
UploadResult,
|
|
47
|
+
UpscaleResult,
|
|
48
|
+
VideoEnhanceResult,
|
|
49
|
+
VideoTransitionResult,
|
|
50
|
+
VideoWalkthroughResult,
|
|
51
|
+
WallColorsResult,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
"AdCreativesResult",
|
|
56
|
+
"Address",
|
|
57
|
+
"AgentInfo",
|
|
58
|
+
"ArchitecturalRenderResult",
|
|
59
|
+
"BaseListing",
|
|
60
|
+
"ContentGenerationResponse",
|
|
61
|
+
"Coordinates",
|
|
62
|
+
"CustomStudioResult",
|
|
63
|
+
"DeStageEmptyResult",
|
|
64
|
+
"DeclutterResult",
|
|
65
|
+
"ExteriorEnhanceResult",
|
|
66
|
+
"FinancialFlag",
|
|
67
|
+
"FloorPlanAnalysisResponse",
|
|
68
|
+
"GeneratedContentBundle",
|
|
69
|
+
"HouseTourResult",
|
|
70
|
+
"IngestJob",
|
|
71
|
+
"InstagramContent",
|
|
72
|
+
"InteriorStyle",
|
|
73
|
+
"InvestorInsights",
|
|
74
|
+
"JobStatus",
|
|
75
|
+
"ListingMetadata",
|
|
76
|
+
"LlmIntelligence",
|
|
77
|
+
"PropertyFeatures",
|
|
78
|
+
"PropertyIntelligence",
|
|
79
|
+
"PropertySpecifications",
|
|
80
|
+
"Render3dResult",
|
|
81
|
+
"ReplaceFurnitureResult",
|
|
82
|
+
"ReplaceMaterialResult",
|
|
83
|
+
"RestyleResult",
|
|
84
|
+
"RoomType",
|
|
85
|
+
"SocialPlatform",
|
|
86
|
+
"SocialPlatformBundle",
|
|
87
|
+
"SocialPublishResult",
|
|
88
|
+
"StagingResult",
|
|
89
|
+
"StudioJob",
|
|
90
|
+
"SystemCondition",
|
|
91
|
+
"SystemsAndCapex",
|
|
92
|
+
"TwilightResult",
|
|
93
|
+
"UploadResult",
|
|
94
|
+
"UpscaleResult",
|
|
95
|
+
"VideoEnhanceResult",
|
|
96
|
+
"VideoScriptContent",
|
|
97
|
+
"VideoTransitionResult",
|
|
98
|
+
"VideoWalkthroughResult",
|
|
99
|
+
"WallColorsResult",
|
|
100
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class InteriorStyle(str, Enum):
|
|
7
|
+
MODERN = "modern"
|
|
8
|
+
LUXURY = "luxury"
|
|
9
|
+
SCANDINAVIAN = "scandinavian"
|
|
10
|
+
JAPANDI = "japandi"
|
|
11
|
+
INDUSTRIAL = "industrial"
|
|
12
|
+
BOHEMIAN = "bohemian"
|
|
13
|
+
MINIMALIST = "minimalist"
|
|
14
|
+
COASTAL = "coastal"
|
|
15
|
+
MID_CENTURY_MODERN = "mid_century_modern"
|
|
16
|
+
ART_DECO = "art_deco"
|
|
17
|
+
FARMHOUSE = "farmhouse"
|
|
18
|
+
MEDITERRANEAN = "mediterranean"
|
|
19
|
+
CONTEMPORARY = "contemporary"
|
|
20
|
+
RUSTIC = "rustic"
|
|
21
|
+
TRANSITIONAL = "transitional"
|
|
22
|
+
FRENCH_COUNTRY = "french_country"
|
|
23
|
+
HOLLYWOOD_REGENCY = "hollywood_regency"
|
|
24
|
+
ECLECTIC = "eclectic"
|
|
25
|
+
ZEN = "zen"
|
|
26
|
+
BAUHAUS = "bauhaus"
|
|
27
|
+
VICTORIAN = "victorian"
|
|
28
|
+
TROPICAL = "tropical"
|
|
29
|
+
MODERN_CRAFTSMAN = "modern_craftsman"
|
|
30
|
+
SOUTHWESTERN = "southwestern"
|
|
31
|
+
WABI_SABI = "wabi_sabi"
|
|
32
|
+
SHABBY_CHIC = "shabby_chic"
|
|
33
|
+
CHALET = "chalet"
|
|
34
|
+
URBAN_LOFT = "urban_loft"
|
|
35
|
+
CUSTOM = "custom"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class RoomType(str, Enum):
|
|
39
|
+
LIVING_ROOM = "living_room"
|
|
40
|
+
BEDROOM = "bedroom"
|
|
41
|
+
PRIMARY_BEDROOM = "primary_bedroom"
|
|
42
|
+
DINING_ROOM = "dining_room"
|
|
43
|
+
KITCHEN = "kitchen"
|
|
44
|
+
BATHROOM = "bathroom"
|
|
45
|
+
PATIO = "patio"
|
|
46
|
+
OUTDOOR_PATIO = "outdoor_patio"
|
|
47
|
+
HOME_OFFICE = "home_office"
|
|
48
|
+
ENTRYWAY = "entryway"
|
|
49
|
+
BASEMENT = "basement"
|
|
50
|
+
COMMERCIAL_LOBBY = "commercial_lobby"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SocialPlatform(str, Enum):
|
|
54
|
+
INSTAGRAM = "instagram"
|
|
55
|
+
FACEBOOK = "facebook"
|
|
56
|
+
LINKEDIN = "linkedin"
|
|
57
|
+
X_TWITTER = "x_twitter"
|
|
58
|
+
TIKTOK = "tiktok"
|
|
59
|
+
YOUTUBE = "youtube"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class JobStatus(str, Enum):
|
|
63
|
+
QUEUED = "queued"
|
|
64
|
+
PROCESSING = "processing"
|
|
65
|
+
COMPLETED = "completed"
|
|
66
|
+
FAILED = "failed"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class InstagramContent(BaseModel):
|
|
9
|
+
hook_above_fold: Optional[str] = None
|
|
10
|
+
caption: str
|
|
11
|
+
carousel_prompt: Optional[str] = None
|
|
12
|
+
call_to_action: Optional[str] = None
|
|
13
|
+
hashtags: List[str] = Field(default_factory=list)
|
|
14
|
+
character_count: Optional[int] = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FacebookContent(BaseModel):
|
|
18
|
+
headline: Optional[str] = None
|
|
19
|
+
post_copy: str
|
|
20
|
+
link_preview: Optional[Dict[str, str]] = None
|
|
21
|
+
hashtags: List[str] = Field(default_factory=list)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LinkedInContent(BaseModel):
|
|
25
|
+
post_copy: str
|
|
26
|
+
hashtags: List[str] = Field(default_factory=list)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class XTwitterContent(BaseModel):
|
|
30
|
+
single_tweet: Optional[str] = None
|
|
31
|
+
thread: List[str] = Field(default_factory=list)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TikTokContent(BaseModel):
|
|
35
|
+
caption: str
|
|
36
|
+
on_screen_hook_text: Optional[str] = None
|
|
37
|
+
call_to_action_cue: Optional[str] = None
|
|
38
|
+
sound_recommendation: Optional[str] = None
|
|
39
|
+
hashtags: List[str] = Field(default_factory=list)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class YouTubeContent(BaseModel):
|
|
43
|
+
video_title_options: List[str] = Field(default_factory=list)
|
|
44
|
+
description: Optional[str] = None
|
|
45
|
+
tags: List[str] = Field(default_factory=list)
|
|
46
|
+
shorts: Optional[Dict[str, str]] = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SocialPlatformBundle(BaseModel):
|
|
50
|
+
instagram: Optional[InstagramContent] = None
|
|
51
|
+
facebook: Optional[FacebookContent] = None
|
|
52
|
+
linkedin: Optional[LinkedInContent] = None
|
|
53
|
+
x_twitter: Optional[XTwitterContent] = None
|
|
54
|
+
tiktok: Optional[TikTokContent] = None
|
|
55
|
+
youtube: Optional[YouTubeContent] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class VideoScriptScene(BaseModel):
|
|
59
|
+
second_range: str
|
|
60
|
+
visual: str
|
|
61
|
+
voiceover: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class VideoScriptContent(BaseModel):
|
|
65
|
+
duration_seconds: int = 30
|
|
66
|
+
format: str = "vertical_9_16"
|
|
67
|
+
hook: str
|
|
68
|
+
scenes: List[VideoScriptScene] = Field(default_factory=list)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class EmailBlastContent(BaseModel):
|
|
72
|
+
subject_lines: List[str] = Field(default_factory=list)
|
|
73
|
+
preview_text: Optional[str] = None
|
|
74
|
+
body_html: Optional[str] = None
|
|
75
|
+
body_markdown: Optional[str] = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class GeneratedContentBundle(BaseModel):
|
|
79
|
+
social: Optional[SocialPlatformBundle] = None
|
|
80
|
+
email_blast: Optional[EmailBlastContent] = None
|
|
81
|
+
video_script: Optional[VideoScriptContent] = None
|
|
82
|
+
flyer_bullets: Optional[List[str]] = None
|
|
83
|
+
mls_remarks: Optional[str] = None
|
|
84
|
+
investor_pitch: Optional[Dict[str, Any]] = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ContentGenerationResponse(BaseModel):
|
|
88
|
+
mls_id: str
|
|
89
|
+
tone: str
|
|
90
|
+
target_audience: Optional[str] = None
|
|
91
|
+
generated_at: Optional[str] = None
|
|
92
|
+
content: GeneratedContentBundle = Field(default_factory=GeneratedContentBundle)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SystemCondition(BaseModel):
|
|
9
|
+
age_years: Optional[int] = None
|
|
10
|
+
material: Optional[str] = None
|
|
11
|
+
condition: Optional[str] = None
|
|
12
|
+
estimated_replacement_horizon: Optional[str] = None
|
|
13
|
+
confidence: Optional[float] = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StormProtection(BaseModel):
|
|
17
|
+
has_impact_windows: bool = False
|
|
18
|
+
has_impact_doors: bool = False
|
|
19
|
+
insurance_premium_impact: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SystemsAndCapex(BaseModel):
|
|
23
|
+
roof: Optional[SystemCondition] = None
|
|
24
|
+
hvac: Optional[SystemCondition] = None
|
|
25
|
+
water_heater: Optional[SystemCondition] = None
|
|
26
|
+
appliances: Optional[Dict[str, Any]] = None
|
|
27
|
+
storm_protection: Optional[StormProtection] = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RentRange(BaseModel):
|
|
31
|
+
low: float = 0.0
|
|
32
|
+
median: float = 0.0
|
|
33
|
+
high: float = 0.0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class InvestorInsights(BaseModel):
|
|
37
|
+
estimated_monthly_rent: RentRange = Field(default_factory=RentRange)
|
|
38
|
+
estimated_gross_yield_pct: float = 0.0
|
|
39
|
+
hoa_present: bool = False
|
|
40
|
+
rental_restrictions: Optional[str] = None
|
|
41
|
+
tenant_suitability: Optional[str] = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class FinancialFlag(BaseModel):
|
|
45
|
+
type: str
|
|
46
|
+
severity: str
|
|
47
|
+
summary: str
|
|
48
|
+
explanation: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class LlmIntelligence(BaseModel):
|
|
52
|
+
systems_and_capex: SystemsAndCapex = Field(default_factory=SystemsAndCapex)
|
|
53
|
+
investor_insights: InvestorInsights = Field(default_factory=InvestorInsights)
|
|
54
|
+
financial_flags: List[FinancialFlag] = Field(default_factory=list)
|
|
55
|
+
property_strengths: List[str] = Field(default_factory=list)
|
|
56
|
+
watch_items: List[str] = Field(default_factory=list)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class PropertyIntelligence(BaseModel):
|
|
60
|
+
mls_id: str
|
|
61
|
+
parcel_id: Optional[str] = None
|
|
62
|
+
summary: Dict[str, Any] = Field(default_factory=dict)
|
|
63
|
+
public_records: Dict[str, Any] = Field(default_factory=dict)
|
|
64
|
+
llm_derived_intelligence: Optional[LlmIntelligence] = None
|
|
65
|
+
generated_at: Optional[str] = None
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, List, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Address(BaseModel):
|
|
9
|
+
street: str
|
|
10
|
+
city: str
|
|
11
|
+
state: str
|
|
12
|
+
zip: str
|
|
13
|
+
county: Optional[str] = None
|
|
14
|
+
country: Optional[str] = None
|
|
15
|
+
formatted: str
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Coordinates(BaseModel):
|
|
19
|
+
latitude: float
|
|
20
|
+
longitude: float
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PropertySpecifications(BaseModel):
|
|
24
|
+
beds: int
|
|
25
|
+
baths_full: float
|
|
26
|
+
baths_half: float = 0.0
|
|
27
|
+
sqft: int
|
|
28
|
+
lot_sqft: Optional[int] = None
|
|
29
|
+
year_built: Optional[int] = None
|
|
30
|
+
stories: Optional[int] = None
|
|
31
|
+
property_type: str
|
|
32
|
+
parking_spaces: Optional[int] = None
|
|
33
|
+
garage: Optional[bool] = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PropertyFeatures(BaseModel):
|
|
37
|
+
heating: List[str] = Field(default_factory=list)
|
|
38
|
+
cooling: List[str] = Field(default_factory=list)
|
|
39
|
+
appliances: List[str] = Field(default_factory=list)
|
|
40
|
+
flooring: List[str] = Field(default_factory=list)
|
|
41
|
+
roof_type: Optional[str] = None
|
|
42
|
+
water_source: Optional[str] = None
|
|
43
|
+
sewer: Optional[str] = None
|
|
44
|
+
view: List[str] = Field(default_factory=list)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AgentInfo(BaseModel):
|
|
48
|
+
name: str
|
|
49
|
+
phone: Optional[str] = None
|
|
50
|
+
email: Optional[str] = None
|
|
51
|
+
license: Optional[str] = None
|
|
52
|
+
brokerage: Optional[str] = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ListingMetadata(BaseModel):
|
|
56
|
+
days_on_market: Optional[int] = None
|
|
57
|
+
listed_date: Optional[str] = None
|
|
58
|
+
mls_board: Optional[str] = None
|
|
59
|
+
updated_at: Optional[str] = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class BaseListing(BaseModel):
|
|
63
|
+
source: str = "cache"
|
|
64
|
+
mls_id: str
|
|
65
|
+
status: str
|
|
66
|
+
price: float
|
|
67
|
+
currency: str = "USD"
|
|
68
|
+
price_per_sqft: Optional[float] = None
|
|
69
|
+
address: Address
|
|
70
|
+
coordinates: Optional[Coordinates] = None
|
|
71
|
+
specifications: PropertySpecifications
|
|
72
|
+
features: PropertyFeatures = Field(default_factory=PropertyFeatures)
|
|
73
|
+
photos: List[str] = Field(default_factory=list)
|
|
74
|
+
photo_count: int = 0
|
|
75
|
+
agent: Optional[AgentInfo] = None
|
|
76
|
+
description: str = ""
|
|
77
|
+
meta: Optional[ListingMetadata] = None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class IngestJob(BaseModel):
|
|
81
|
+
job_id: Optional[str] = Field(None, alias="jobId")
|
|
82
|
+
mls_id: Optional[str] = Field(None, alias="mlsId")
|
|
83
|
+
status: str
|
|
84
|
+
step: Optional[str] = None
|
|
85
|
+
status_url: Optional[str] = Field(None, alias="statusUrl")
|
|
86
|
+
lookup_url: Optional[str] = Field(None, alias="lookupUrl")
|
|
87
|
+
progress_percentage: Optional[int] = 0
|
|
88
|
+
error: Optional[str] = None
|
|
89
|
+
result: Optional[Any] = None
|
|
90
|
+
|
|
91
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StudioJob(BaseModel):
|
|
9
|
+
job_id: str
|
|
10
|
+
status: str
|
|
11
|
+
progress_percentage: int = 0
|
|
12
|
+
current_step: Optional[str] = None
|
|
13
|
+
status_url: Optional[str] = None
|
|
14
|
+
result: Optional[Dict[str, Any]] = None
|
|
15
|
+
error: Optional[str] = None
|
|
16
|
+
created_at: Optional[str] = None
|
|
17
|
+
completed_at: Optional[str] = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class StagingResult(BaseModel):
|
|
21
|
+
staged_photo_url: str
|
|
22
|
+
before_after_comparison_url: Optional[str] = None
|
|
23
|
+
room_type: Optional[str] = None
|
|
24
|
+
style: Optional[str] = None
|
|
25
|
+
staging_manifest: List[str] = Field(default_factory=list)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DeclutterResult(BaseModel):
|
|
29
|
+
decluttered_photo_url: str
|
|
30
|
+
before_after_comparison_url: Optional[str] = None
|
|
31
|
+
items_removed: List[str] = Field(default_factory=list)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TwilightResult(BaseModel):
|
|
35
|
+
enhanced_photo_url: str
|
|
36
|
+
before_after_comparison_url: Optional[str] = None
|
|
37
|
+
mode: Optional[str] = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DeStageEmptyResult(BaseModel):
|
|
41
|
+
empty_photo_url: str
|
|
42
|
+
before_after_comparison_url: Optional[str] = None
|
|
43
|
+
flooring_restored: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RestyleResult(BaseModel):
|
|
47
|
+
restyled_photo_url: str
|
|
48
|
+
before_after_comparison_url: Optional[str] = None
|
|
49
|
+
target_style: Optional[str] = None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ReplaceFurnitureResult(BaseModel):
|
|
53
|
+
result_photo_url: str
|
|
54
|
+
target_furniture: Optional[str] = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ReplaceMaterialResult(BaseModel):
|
|
58
|
+
result_photo_url: str
|
|
59
|
+
surface_type: Optional[str] = None
|
|
60
|
+
material_applied: Optional[str] = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SwatchItem(BaseModel):
|
|
64
|
+
color_name: str
|
|
65
|
+
hex: str
|
|
66
|
+
image_url: str
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class WallColorsResult(BaseModel):
|
|
70
|
+
comparison_grid_3x3_url: str
|
|
71
|
+
swatch_results: List[SwatchItem] = Field(default_factory=list)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ExteriorEnhanceResult(BaseModel):
|
|
75
|
+
enhanced_photo_url: str
|
|
76
|
+
enhancements_applied: List[str] = Field(default_factory=list)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class UpscaleResult(BaseModel):
|
|
80
|
+
upscaled_image_url: str
|
|
81
|
+
target_resolution: Optional[str] = None
|
|
82
|
+
scale_factor: int = 4
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class RoomCloseup(BaseModel):
|
|
86
|
+
room_name: str
|
|
87
|
+
image_url: str
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Render3dResult(BaseModel):
|
|
91
|
+
isometric_3d_dollhouse_url: str
|
|
92
|
+
thumbnail_url: Optional[str] = None
|
|
93
|
+
room_renders: List[RoomCloseup] = Field(default_factory=list)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class SpatialSummary(BaseModel):
|
|
97
|
+
total_rooms_detected: int = 0
|
|
98
|
+
stories: int = 1
|
|
99
|
+
layout_type: Optional[str] = None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class FloorPlanAnalysisResponse(BaseModel):
|
|
103
|
+
style: Optional[str] = None
|
|
104
|
+
spatial_summary: SpatialSummary = Field(default_factory=SpatialSummary)
|
|
105
|
+
rooms: List[Dict[str, Any]] = Field(default_factory=list)
|
|
106
|
+
isometric_3d_prompt: Optional[str] = None
|
|
107
|
+
render_3d_url: Optional[str] = None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ArchitecturalRenderResult(BaseModel):
|
|
111
|
+
rendered_image_url: str
|
|
112
|
+
source_type: Optional[str] = None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class CreativePlacement(BaseModel):
|
|
116
|
+
placement: str
|
|
117
|
+
image_url: str
|
|
118
|
+
aspect_ratio: Optional[str] = None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class AdCreativesCompliance(BaseModel):
|
|
122
|
+
fair_housing_passed: bool = True
|
|
123
|
+
flags: List[str] = Field(default_factory=list)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class AdCreativesResult(BaseModel):
|
|
127
|
+
mls_id: Optional[str] = None
|
|
128
|
+
creatives: Dict[str, CreativePlacement] = Field(default_factory=dict)
|
|
129
|
+
compliance: AdCreativesCompliance = Field(default_factory=AdCreativesCompliance)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class SocialPublishResult(BaseModel):
|
|
133
|
+
status: str
|
|
134
|
+
destinations_dispatched: List[str] = Field(default_factory=list)
|
|
135
|
+
results: Dict[str, Any] = Field(default_factory=dict)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class MasteredVideo(BaseModel):
|
|
139
|
+
aspect_ratio: str
|
|
140
|
+
url: str
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class VideoEnhanceResult(BaseModel):
|
|
144
|
+
mastered_videos: List[MasteredVideo] = Field(default_factory=list)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class VideoWalkthroughResult(BaseModel):
|
|
148
|
+
video_url: str
|
|
149
|
+
duration_seconds: Optional[float] = None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class VideoTransitionResult(BaseModel):
|
|
153
|
+
video_url: str
|
|
154
|
+
transition_type: Optional[str] = None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class HouseTourResult(BaseModel):
|
|
158
|
+
video_tour_url: str
|
|
159
|
+
rooms_included: List[str] = Field(default_factory=list)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class CustomStudioResult(BaseModel):
|
|
163
|
+
output_url: str
|
|
164
|
+
prompt_used: str
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class UploadResult(BaseModel):
|
|
168
|
+
url: str
|
|
169
|
+
filename: Optional[str] = None
|
|
170
|
+
content_type: Optional[str] = None
|
|
171
|
+
bytes: Optional[int] = None
|
pymlsapi/poller.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any, Callable, Optional, TypeVar
|
|
6
|
+
|
|
7
|
+
from pymlsapi.errors import JobTimeoutError, StudioJobFailedError
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def poll_job_sync(
|
|
13
|
+
fetch_fn: Callable[[], Any],
|
|
14
|
+
is_done_fn: Callable[[Any], bool],
|
|
15
|
+
is_failed_fn: Callable[[Any], bool],
|
|
16
|
+
timeout_seconds: float = 90.0,
|
|
17
|
+
initial_interval: float = 1.5,
|
|
18
|
+
max_interval: float = 5.0,
|
|
19
|
+
backoff_multiplier: float = 1.25,
|
|
20
|
+
on_progress: Optional[Callable[[Any], None]] = None,
|
|
21
|
+
) -> Any:
|
|
22
|
+
"""Poll a job synchronously until it is finished, fails, or times out."""
|
|
23
|
+
start_time = time.monotonic()
|
|
24
|
+
current_interval = initial_interval
|
|
25
|
+
|
|
26
|
+
while True:
|
|
27
|
+
job = fetch_fn()
|
|
28
|
+
|
|
29
|
+
if on_progress:
|
|
30
|
+
try:
|
|
31
|
+
on_progress(job)
|
|
32
|
+
except Exception:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
if is_done_fn(job):
|
|
36
|
+
return job
|
|
37
|
+
|
|
38
|
+
if is_failed_fn(job):
|
|
39
|
+
job_id = getattr(job, "job_id", getattr(job, "id", "unknown"))
|
|
40
|
+
error_msg = getattr(job, "error", "Job processing failed on worker")
|
|
41
|
+
raise StudioJobFailedError(
|
|
42
|
+
job_id=str(job_id), message=str(error_msg), error_details=job
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
elapsed = time.monotonic() - start_time
|
|
46
|
+
if elapsed >= timeout_seconds:
|
|
47
|
+
job_id = getattr(job, "job_id", getattr(job, "id", "unknown"))
|
|
48
|
+
raise JobTimeoutError(job_id=str(job_id), timeout_seconds=timeout_seconds)
|
|
49
|
+
|
|
50
|
+
sleep_time = min(current_interval, timeout_seconds - elapsed)
|
|
51
|
+
if sleep_time > 0:
|
|
52
|
+
time.sleep(sleep_time)
|
|
53
|
+
|
|
54
|
+
current_interval = min(current_interval * backoff_multiplier, max_interval)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def poll_job_async(
|
|
58
|
+
fetch_fn: Callable[[], Any], # async function
|
|
59
|
+
is_done_fn: Callable[[Any], bool],
|
|
60
|
+
is_failed_fn: Callable[[Any], bool],
|
|
61
|
+
timeout_seconds: float = 90.0,
|
|
62
|
+
initial_interval: float = 1.5,
|
|
63
|
+
max_interval: float = 5.0,
|
|
64
|
+
backoff_multiplier: float = 1.25,
|
|
65
|
+
on_progress: Optional[Callable[[Any], None]] = None,
|
|
66
|
+
) -> Any:
|
|
67
|
+
"""Poll a job asynchronously until it is finished, fails, or times out."""
|
|
68
|
+
start_time = time.monotonic()
|
|
69
|
+
current_interval = initial_interval
|
|
70
|
+
|
|
71
|
+
while True:
|
|
72
|
+
job = await fetch_fn()
|
|
73
|
+
|
|
74
|
+
if on_progress:
|
|
75
|
+
try:
|
|
76
|
+
if asyncio.iscoroutinefunction(on_progress):
|
|
77
|
+
await on_progress(job)
|
|
78
|
+
else:
|
|
79
|
+
on_progress(job)
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
if is_done_fn(job):
|
|
84
|
+
return job
|
|
85
|
+
|
|
86
|
+
if is_failed_fn(job):
|
|
87
|
+
job_id = getattr(job, "job_id", getattr(job, "id", "unknown"))
|
|
88
|
+
error_msg = getattr(job, "error", "Job processing failed on worker")
|
|
89
|
+
raise StudioJobFailedError(
|
|
90
|
+
job_id=str(job_id), message=str(error_msg), error_details=job
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
elapsed = time.monotonic() - start_time
|
|
94
|
+
if elapsed >= timeout_seconds:
|
|
95
|
+
job_id = getattr(job, "job_id", getattr(job, "id", "unknown"))
|
|
96
|
+
raise JobTimeoutError(job_id=str(job_id), timeout_seconds=timeout_seconds)
|
|
97
|
+
|
|
98
|
+
sleep_time = min(current_interval, timeout_seconds - elapsed)
|
|
99
|
+
if sleep_time > 0:
|
|
100
|
+
await asyncio.sleep(sleep_time)
|
|
101
|
+
|
|
102
|
+
current_interval = min(current_interval * backoff_multiplier, max_interval)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pymlsapi.resources.account import AccountResource, AsyncAccountResource
|
|
4
|
+
from pymlsapi.resources.content import AsyncContentResource, ContentResource
|
|
5
|
+
from pymlsapi.resources.intelligence import AsyncIntelligenceResource, IntelligenceResource
|
|
6
|
+
from pymlsapi.resources.listings import AsyncListingsResource, ListingsResource
|
|
7
|
+
from pymlsapi.resources.studio import AsyncStudioResource, StudioResource
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AccountResource",
|
|
11
|
+
"AsyncAccountResource",
|
|
12
|
+
"AsyncContentResource",
|
|
13
|
+
"AsyncIntelligenceResource",
|
|
14
|
+
"AsyncListingsResource",
|
|
15
|
+
"AsyncStudioResource",
|
|
16
|
+
"ContentResource",
|
|
17
|
+
"IntelligenceResource",
|
|
18
|
+
"ListingsResource",
|
|
19
|
+
"StudioResource",
|
|
20
|
+
]
|