bookalimo 0.1.5__py3-none-any.whl → 1.0.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.
Files changed (38) hide show
  1. bookalimo/__init__.py +17 -24
  2. bookalimo/_version.py +9 -0
  3. bookalimo/client.py +310 -0
  4. bookalimo/config.py +16 -0
  5. bookalimo/exceptions.py +115 -5
  6. bookalimo/integrations/__init__.py +1 -0
  7. bookalimo/integrations/google_places/__init__.py +31 -0
  8. bookalimo/integrations/google_places/client_async.py +258 -0
  9. bookalimo/integrations/google_places/client_sync.py +257 -0
  10. bookalimo/integrations/google_places/common.py +245 -0
  11. bookalimo/integrations/google_places/proto_adapter.py +224 -0
  12. bookalimo/{_logging.py → logging.py} +45 -42
  13. bookalimo/schemas/__init__.py +97 -0
  14. bookalimo/schemas/base.py +56 -0
  15. bookalimo/{models.py → schemas/booking.py} +88 -100
  16. bookalimo/schemas/places/__init__.py +37 -0
  17. bookalimo/schemas/places/common.py +198 -0
  18. bookalimo/schemas/places/google.py +596 -0
  19. bookalimo/schemas/places/place.py +337 -0
  20. bookalimo/services/__init__.py +11 -0
  21. bookalimo/services/pricing.py +191 -0
  22. bookalimo/services/reservations.py +227 -0
  23. bookalimo/transport/__init__.py +7 -0
  24. bookalimo/transport/auth.py +41 -0
  25. bookalimo/transport/base.py +44 -0
  26. bookalimo/transport/httpx_async.py +230 -0
  27. bookalimo/transport/httpx_sync.py +230 -0
  28. bookalimo/transport/retry.py +102 -0
  29. bookalimo/transport/utils.py +59 -0
  30. bookalimo-1.0.0.dist-info/METADATA +307 -0
  31. bookalimo-1.0.0.dist-info/RECORD +35 -0
  32. bookalimo/_client.py +0 -420
  33. bookalimo/wrapper.py +0 -444
  34. bookalimo-0.1.5.dist-info/METADATA +0 -392
  35. bookalimo-0.1.5.dist-info/RECORD +0 -12
  36. {bookalimo-0.1.5.dist-info → bookalimo-1.0.0.dist-info}/WHEEL +0 -0
  37. {bookalimo-0.1.5.dist-info → bookalimo-1.0.0.dist-info}/licenses/LICENSE +0 -0
  38. {bookalimo-0.1.5.dist-info → bookalimo-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,198 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Optional
5
+
6
+ from pydantic import (
7
+ AnyUrl,
8
+ BaseModel,
9
+ ConfigDict,
10
+ Field,
11
+ field_validator,
12
+ model_validator,
13
+ )
14
+ from typing_extensions import Self
15
+
16
+ BCP47 = re.compile(
17
+ r"^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
18
+ ) # pragmatic, e.g., "en", "en-US", "zh-Hant"
19
+
20
+ CLDR_REGION_2 = re.compile(r"^[A-Z]{2}$") # e.g., "US", "GB"
21
+
22
+ PLACE_ID = re.compile(
23
+ r"^[A-Za-z0-9_-]{10,}$"
24
+ ) # pragmatic: real IDs are long base64url-ish e.g., "ChIJN1t_tDeuEmsRUsoyG83frY4"
25
+
26
+ PLACE_RESOURCE = re.compile(
27
+ r"^places/[A-Za-z0-9_-]{10,}$"
28
+ ) # e.g., "places/ChIJN1t_tDeuEmsRUsoyG83frY4"
29
+
30
+ PLACE_TYPE = re.compile(r"^[a-z0-9_]+$") # e.g., "gas_station", "restaurant"
31
+
32
+ HEX_COLOR = re.compile(r"^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$") # #abc, #a1b2c3
33
+
34
+ PLUS_GLOBAL = re.compile(
35
+ r"^[23456789CFGHJMPQRVWX]{4,8}\+[23456789CFGHJMPQRVWX]{2,}$"
36
+ ) # Open Location Code (Plus Code), pragmatic
37
+
38
+ BASE64URL_36 = re.compile(
39
+ r"^[A-Za-z0-9_-]{1,36}$"
40
+ ) # up to 36 chars, URL-safe base64-ish
41
+
42
+
43
+ # ---------- “External” Google message wrappers ----------
44
+ class ExternalModel(BaseModel):
45
+ """Permissive wrapper for Google messages we don't model in detail."""
46
+
47
+ model_config = ConfigDict(extra="allow")
48
+
49
+
50
+ class OpeningHours(ExternalModel): ...
51
+
52
+
53
+ class PostalAddress(ExternalModel): ...
54
+
55
+
56
+ class TimeZone(ExternalModel): ...
57
+
58
+
59
+ class Timestamp(ExternalModel): ...
60
+
61
+
62
+ class Date(ExternalModel): ...
63
+
64
+
65
+ class ContentBlock(ExternalModel): ...
66
+
67
+
68
+ class Photo(ExternalModel): ...
69
+
70
+
71
+ class Review(ExternalModel): ...
72
+
73
+
74
+ class FuelOptions(ExternalModel): ...
75
+
76
+
77
+ class EVChargeOptions(ExternalModel): ...
78
+
79
+
80
+ class AddressDescriptor(ExternalModel): ...
81
+
82
+
83
+ class PriceRange(ExternalModel): ...
84
+
85
+
86
+ # ---------- Geometry ----------
87
+ class LatLng(BaseModel):
88
+ model_config = ConfigDict(extra="forbid")
89
+
90
+ latitude: float = Field(..., description="[-90, 90]")
91
+ longitude: float = Field(..., description="[-180, 180]")
92
+
93
+ @model_validator(mode="after")
94
+ def _check(self) -> Self:
95
+ if not (-90 <= self.latitude <= 90):
96
+ raise ValueError("latitude must be between -90 and 90")
97
+ if not (-180 <= self.longitude <= 180):
98
+ raise ValueError("longitude must be between -180 and 180")
99
+ return self
100
+
101
+
102
+ class Viewport(BaseModel):
103
+ model_config = ConfigDict(extra="forbid")
104
+
105
+ high: LatLng
106
+ low: LatLng
107
+
108
+ @model_validator(mode="after")
109
+ def _lat_order(self) -> Self:
110
+ if self.high.latitude < self.low.latitude:
111
+ raise ValueError("high.latitude must be >= low.latitude")
112
+ return self
113
+
114
+
115
+ # ---------- Simple shared types ----------
116
+ class PlusCode(BaseModel):
117
+ model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
118
+
119
+ global_code: Optional[str] = Field(default=None, description="e.g., '9FWM33GV+HQ'")
120
+ compound_code: Optional[str] = Field(default=None)
121
+
122
+ @field_validator("global_code")
123
+ @classmethod
124
+ def _check_global(cls, v: Optional[str]) -> Optional[str]:
125
+ if v is None:
126
+ return v
127
+ # Accept common formats; be permissive.
128
+ if not PLUS_GLOBAL.match(v):
129
+ # Let a variety of valid codes through without being too strict.
130
+ if "+" not in v or len(v) < 6:
131
+ raise ValueError(
132
+ "global_code must look like a valid plus code (e.g., '9FWM33GV+HQ')."
133
+ )
134
+ return v
135
+
136
+
137
+ class Attribution(BaseModel):
138
+ model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
139
+
140
+ provider: str
141
+ provider_uri: Optional[AnyUrl] = None
142
+
143
+
144
+ class SubDestination(BaseModel):
145
+ model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
146
+
147
+ name: str = Field(..., description="places/{place_id}")
148
+ id: str = Field(..., description="{place_id}")
149
+
150
+ @field_validator("name")
151
+ @classmethod
152
+ def _name(cls, v: str) -> str:
153
+ if not PLACE_RESOURCE.fullmatch(v):
154
+ raise ValueError("name must be in the form 'places/{place_id}'")
155
+ return v
156
+
157
+ @field_validator("id")
158
+ @classmethod
159
+ def _id(cls, v: str) -> str:
160
+ if not PLACE_ID.fullmatch(v):
161
+ raise ValueError("id must be a base64url-like token (>=10 chars)")
162
+ return v
163
+
164
+ @model_validator(mode="after")
165
+ def _match(self) -> Self:
166
+ if self.name.split("/", 1)[1] != self.id:
167
+ raise ValueError("id must match the trailing component of name")
168
+ return self
169
+
170
+
171
+ class AccessibilityOptions(BaseModel):
172
+ model_config = ConfigDict(extra="forbid")
173
+
174
+ wheelchair_accessible_parking: Optional[bool] = None
175
+ wheelchair_accessible_entrance: Optional[bool] = None
176
+ wheelchair_accessible_restroom: Optional[bool] = None
177
+ wheelchair_accessible_seating: Optional[bool] = None
178
+
179
+
180
+ class PaymentOptions(BaseModel):
181
+ model_config = ConfigDict(extra="forbid")
182
+
183
+ accepts_credit_cards: Optional[bool] = None
184
+ accepts_debit_cards: Optional[bool] = None
185
+ accepts_cash_only: Optional[bool] = None
186
+ accepts_nfc: Optional[bool] = None
187
+
188
+
189
+ class ParkingOptions(BaseModel):
190
+ model_config = ConfigDict(extra="forbid")
191
+
192
+ free_parking_lot: Optional[bool] = None
193
+ paid_parking_lot: Optional[bool] = None
194
+ free_street_parking: Optional[bool] = None
195
+ paid_street_parking: Optional[bool] = None
196
+ valet_parking: Optional[bool] = None
197
+ free_garage_parking: Optional[bool] = None
198
+ paid_garage_parking: Optional[bool] = None