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,245 @@
1
+ """
2
+ Common utilities and shared functionality for Google Places clients.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Literal, Sequence, Union
8
+
9
+ from ...logging import get_logger
10
+
11
+ logger = get_logger("places")
12
+
13
+
14
+ Fields = Union[
15
+ str,
16
+ Sequence[
17
+ Literal[
18
+ "*",
19
+ # Identity
20
+ "name",
21
+ # Labels & typing
22
+ "display_name",
23
+ "types",
24
+ "primary_type",
25
+ "primary_type_display_name",
26
+ # Phones & addresses
27
+ "national_phone_number",
28
+ "international_phone_number",
29
+ "formatted_address",
30
+ "short_formatted_address",
31
+ "postal_address",
32
+ "address_components",
33
+ "plus_code",
34
+ # Location & map
35
+ "location",
36
+ "viewport",
37
+ # Scores, links, media
38
+ "rating",
39
+ "google_maps_uri",
40
+ "website_uri",
41
+ "reviews",
42
+ "photos",
43
+ # Hours
44
+ "regular_opening_hours",
45
+ "current_opening_hours",
46
+ "current_secondary_opening_hours",
47
+ "regular_secondary_opening_hours",
48
+ "utc_offset_minutes",
49
+ "time_zone",
50
+ # Misc attributes
51
+ "adr_format_address",
52
+ "business_status",
53
+ "price_level",
54
+ "attributions",
55
+ "user_rating_count",
56
+ "icon_mask_base_uri",
57
+ "icon_background_color",
58
+ # Food/venue features
59
+ "takeout",
60
+ "delivery",
61
+ "dine_in",
62
+ "curbside_pickup",
63
+ "reservable",
64
+ "serves_breakfast",
65
+ "serves_lunch",
66
+ "serves_dinner",
67
+ "serves_beer",
68
+ "serves_wine",
69
+ "serves_brunch",
70
+ "serves_vegetarian_food",
71
+ "outdoor_seating",
72
+ "live_music",
73
+ "menu_for_children",
74
+ "serves_cocktails",
75
+ "serves_dessert",
76
+ "serves_coffee",
77
+ "good_for_children",
78
+ "allows_dogs",
79
+ "restroom",
80
+ "good_for_groups",
81
+ "good_for_watching_sports",
82
+ # Options & related places
83
+ "payment_options",
84
+ "parking_options",
85
+ "sub_destinations",
86
+ "accessibility_options",
87
+ # Fuel/EV & AI summaries
88
+ "fuel_options",
89
+ "ev_charge_options",
90
+ "generative_summary",
91
+ "review_summary",
92
+ "ev_charge_amenity_summary",
93
+ "neighborhood_summary",
94
+ # Context
95
+ "containing_places",
96
+ "pure_service_area_business",
97
+ "address_descriptor",
98
+ "price_range",
99
+ # Missing in your model but present in proto
100
+ "editorial_summary",
101
+ ]
102
+ ],
103
+ ]
104
+
105
+ PlaceListFields = Union[
106
+ str,
107
+ Sequence[
108
+ Literal[
109
+ "*",
110
+ # Identity
111
+ "places.name",
112
+ # Labels & typing
113
+ "places.display_name",
114
+ "places.types",
115
+ "places.primary_type",
116
+ "places.primary_type_display_name",
117
+ # Phones & addresses
118
+ "places.national_phone_number",
119
+ "places.international_phone_number",
120
+ "places.formatted_address",
121
+ "places.short_formatted_address",
122
+ "places.postal_address",
123
+ "places.address_components",
124
+ "places.plus_code",
125
+ # Location & map
126
+ "places.location",
127
+ "places.viewport",
128
+ # Scores, links, media
129
+ "places.rating",
130
+ "places.google_maps_uri",
131
+ "places.website_uri",
132
+ "places.reviews",
133
+ "places.photos",
134
+ # Hours
135
+ "places.regular_opening_hours",
136
+ "places.current_opening_hours",
137
+ "places.current_secondary_opening_hours",
138
+ "places.regular_secondary_opening_hours",
139
+ "places.utc_offset_minutes",
140
+ "places.time_zone",
141
+ # Misc attributes
142
+ "places.adr_format_address",
143
+ "places.business_status",
144
+ "places.price_level",
145
+ "places.attributions",
146
+ "places.user_rating_count",
147
+ "places.icon_mask_base_uri",
148
+ "places.icon_background_color",
149
+ # Food/venue features
150
+ "places.takeout",
151
+ "places.delivery",
152
+ "places.dine_in",
153
+ "places.curbside_pickup",
154
+ "places.reservable",
155
+ "places.serves_breakfast",
156
+ "places.serves_lunch",
157
+ "places.serves_dinner",
158
+ "places.serves_beer",
159
+ "places.serves_wine",
160
+ "places.serves_brunch",
161
+ "places.serves_vegetarian_food",
162
+ "places.outdoor_seating",
163
+ "places.live_music",
164
+ "places.menu_for_children",
165
+ "places.serves_cocktails",
166
+ "places.serves_dessert",
167
+ "places.serves_coffee",
168
+ "places.good_for_children",
169
+ "places.allows_dogs",
170
+ "places.restroom",
171
+ "places.good_for_groups",
172
+ "places.good_for_watching_sports",
173
+ # Options & related places
174
+ "places.payment_options",
175
+ "places.parking_options",
176
+ "places.sub_destinations",
177
+ "places.accessibility_options",
178
+ # Fuel/EV & AI summaries
179
+ "places.fuel_options",
180
+ "places.ev_charge_options",
181
+ "places.generative_summary",
182
+ "places.review_summary",
183
+ "places.ev_charge_amenity_summary",
184
+ "places.neighborhood_summary",
185
+ # Context
186
+ "places.containing_places",
187
+ "places.pure_service_area_business",
188
+ "places.address_descriptor",
189
+ "places.price_range",
190
+ # Missing in your model but present in proto
191
+ "places.editorial_summary",
192
+ ]
193
+ ],
194
+ ]
195
+
196
+ ADDRESS_TYPES = {
197
+ "street_address",
198
+ "route",
199
+ "intersection",
200
+ "premise",
201
+ "subpremise",
202
+ "plus_code",
203
+ "postal_code",
204
+ "locality",
205
+ "sublocality",
206
+ "neighborhood",
207
+ "administrative_area_level_1",
208
+ "administrative_area_level_2",
209
+ "country",
210
+ "floor",
211
+ "room",
212
+ }
213
+
214
+
215
+ def fmt_exc(e: BaseException) -> str:
216
+ """Format exception for logging without touching non-existent attributes."""
217
+ return f"{type(e).__name__}: {e}"
218
+
219
+
220
+ def mask_header(fields: Sequence[str] | str | None) -> tuple[tuple[str, str], ...]:
221
+ """
222
+ Build the X-Goog-FieldMask header. Pass a comma-separated string or a sequence.
223
+ If None, no header is added (e.g., autocomplete, get_photo_media).
224
+ """
225
+ if fields is None:
226
+ return ()
227
+ if isinstance(fields, str):
228
+ value = fields
229
+ else:
230
+ value = ",".join(fields)
231
+ return (("x-goog-fieldmask", value),)
232
+
233
+
234
+ # Default field mask for places queries
235
+ DEFAULT_PLACE_FIELDS: Fields = (
236
+ "display_name",
237
+ "formatted_address",
238
+ "location",
239
+ )
240
+
241
+ DEFAULT_PLACE_LIST_FIELDS: PlaceListFields = (
242
+ "places.display_name",
243
+ "places.formatted_address",
244
+ "places.location",
245
+ )
@@ -0,0 +1,224 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ from functools import lru_cache
5
+ from typing import (
6
+ Any,
7
+ Dict,
8
+ List,
9
+ Optional,
10
+ Tuple,
11
+ Type,
12
+ TypeVar,
13
+ cast,
14
+ get_args,
15
+ get_origin,
16
+ )
17
+
18
+ from google.protobuf import json_format as jf
19
+
20
+ # Protobuf imports
21
+ from google.protobuf.message import Message as GPBMessage
22
+ from pydantic import BaseModel, TypeAdapter
23
+ from pydantic_core import ValidationError
24
+
25
+ # Logging
26
+ from ...logging import get_logger
27
+
28
+ TModel = TypeVar("TModel", bound=BaseModel)
29
+
30
+ log = get_logger("proto_adapter")
31
+
32
+ # ---------------- Proto extraction (proto-plus or vanilla GPB) ----------------
33
+
34
+
35
+ def _extract_gpb_message(msg: Any) -> GPBMessage:
36
+ if isinstance(msg, GPBMessage):
37
+ return msg
38
+ for attr in ("_pb", "pb"):
39
+ if hasattr(msg, attr):
40
+ cand = getattr(msg, attr)
41
+ if isinstance(cand, GPBMessage):
42
+ return cand
43
+ for meth in ("to_protobuf", "to_pb"):
44
+ if hasattr(msg, meth):
45
+ cand = getattr(msg, meth)()
46
+ if isinstance(cand, GPBMessage):
47
+ return cand
48
+ raise TypeError(
49
+ "Unsupported protobuf message type. Provide a google.protobuf Message "
50
+ "or a proto-plus message exposing ._pb/.pb or .to_protobuf()."
51
+ )
52
+
53
+
54
+ # ---------------- Version-proof MessageToDict wrapper ----------------
55
+
56
+
57
+ @lru_cache(maxsize=1)
58
+ def _mtodict_signature() -> inspect.Signature:
59
+ return inspect.signature(jf.MessageToDict)
60
+
61
+
62
+ def _message_to_dict(
63
+ msg: Any,
64
+ *,
65
+ use_integers_for_enums: bool = True,
66
+ including_default_value_fields: bool = False,
67
+ preserving_proto_field_name: bool = True,
68
+ ) -> dict[str, Any]:
69
+ """
70
+ Wraps google.protobuf.json_format.MessageToDict with runtime arg mapping
71
+ so it works across protobuf versions.
72
+ """
73
+ gpb = _extract_gpb_message(msg)
74
+ sig = _mtodict_signature()
75
+ params = sig.parameters
76
+
77
+ kwargs: dict[str, Any] = {
78
+ "preserving_proto_field_name": preserving_proto_field_name,
79
+ "use_integers_for_enums": use_integers_for_enums,
80
+ }
81
+
82
+ # protobuf>=5 uses "always_print_fields_with_no_presence"
83
+ if "including_default_value_fields" in params:
84
+ kwargs["including_default_value_fields"] = including_default_value_fields
85
+ elif "always_print_fields_with_no_presence" in params:
86
+ # Map our public arg to the new name
87
+ kwargs["always_print_fields_with_no_presence"] = including_default_value_fields
88
+ # else: neither supported (very old?) -> omit, default behavior
89
+
90
+ try:
91
+ return jf.MessageToDict(gpb, **kwargs)
92
+ except Exception as e:
93
+ log.error(f"MessageToDict conversion failed: {e}")
94
+ raise
95
+
96
+
97
+ # ---------------- Optional base64 -> bytes coercion guided by model schema ----------------
98
+
99
+
100
+ def _unwrap_optional(tp: Any) -> Any:
101
+ if get_origin(tp) is Optional:
102
+ return get_args(tp)[0]
103
+ return tp
104
+
105
+
106
+ def _coerce_by_annotation(data: Any, annotation: Any) -> Any:
107
+ from base64 import urlsafe_b64decode
108
+
109
+ from pydantic import BaseModel as PydBaseModel
110
+
111
+ if annotation is None:
112
+ return data
113
+
114
+ if get_origin(annotation) is Optional:
115
+ annotation = _unwrap_optional(annotation)
116
+
117
+ if annotation is bytes and isinstance(data, str):
118
+ # tolerate missing padding
119
+ try:
120
+ return urlsafe_b64decode(data + "===")
121
+ except Exception:
122
+ return data
123
+
124
+ if get_origin(annotation) in (list, tuple, List, Tuple):
125
+ (item_type,) = get_args(annotation) or (Any,)
126
+ if isinstance(data, list):
127
+ return [_coerce_by_annotation(x, item_type) for x in data]
128
+ return data
129
+
130
+ if get_origin(annotation) in (dict, Dict):
131
+ args = get_args(annotation)
132
+ if len(args) == 2 and isinstance(data, dict):
133
+ _, v_type = args
134
+ return {k: _coerce_by_annotation(v, v_type) for k, v in data.items()}
135
+ return data
136
+
137
+ if (
138
+ isinstance(annotation, type)
139
+ and issubclass(annotation, PydBaseModel)
140
+ and isinstance(data, dict)
141
+ ):
142
+ out = dict(data)
143
+ for fname, f in annotation.model_fields.items():
144
+ keys = [fname]
145
+ if f.alias and f.alias != fname:
146
+ keys.insert(0, f.alias)
147
+ present = next((k for k in keys if k in out), None)
148
+ if present is not None:
149
+ out[present] = _coerce_by_annotation(out[present], f.annotation)
150
+ return out
151
+
152
+ return data
153
+
154
+
155
+ # ---------------- TypeAdapter cache (generic-erased to appease Pyright) ----------------
156
+
157
+
158
+ @lru_cache(maxsize=256)
159
+ def _adapter_for_cached(model_cls: type) -> TypeAdapter[BaseModel]:
160
+ # erase generic in the cache to avoid TypeVar identity issues in type checkers
161
+ return TypeAdapter(model_cls)
162
+
163
+
164
+ # ---------------- Public API ----------------
165
+
166
+
167
+ def validate_proto_to_model(
168
+ msg: Any,
169
+ model_type: Type[TModel],
170
+ *,
171
+ decode_bytes_by_schema: bool = True,
172
+ use_integers_for_enums: bool = True,
173
+ including_default_value_fields: bool = False,
174
+ preserving_proto_field_name: bool = True,
175
+ ) -> TModel:
176
+ """
177
+ Validate a protobuf message (proto-plus or GPB) into a Pydantic v2 model instance.
178
+
179
+ - Works across protobuf versions (handles arg rename to always_print_fields_with_no_presence).
180
+ - Fully runtime: you pass the concrete Pydantic model class.
181
+ """
182
+ # Convert protobuf to dictionary
183
+ try:
184
+ raw = _message_to_dict(
185
+ msg,
186
+ use_integers_for_enums=use_integers_for_enums,
187
+ including_default_value_fields=including_default_value_fields,
188
+ preserving_proto_field_name=preserving_proto_field_name,
189
+ )
190
+ except Exception as e:
191
+ log.error(f"Failed to convert protobuf to dict: {e}")
192
+ raise
193
+
194
+ # Apply type coercion if enabled
195
+ try:
196
+ data = _coerce_by_annotation(raw, model_type) if decode_bytes_by_schema else raw
197
+ except Exception as e:
198
+ log.error(f"Failed during type coercion: {e}")
199
+ raise
200
+
201
+ # Validate with Pydantic
202
+ try:
203
+ adapter = _adapter_for_cached(model_type)
204
+ result = cast(TModel, adapter.validate_python(data))
205
+ return result
206
+
207
+ except ValidationError as e:
208
+ log.error(
209
+ f"Pydantic validation failed for {model_type.__name__} ({e.error_count()} errors)"
210
+ )
211
+
212
+ # Log key validation errors with context
213
+ for error in e.errors():
214
+ if "loc" in error and error["loc"]:
215
+ location = ".".join(str(loc) for loc in error["loc"])
216
+ log.error(f"Validation error at {location}: {error['msg']}")
217
+ else:
218
+ log.error(f"Validation error: {error['msg']}")
219
+
220
+ raise
221
+
222
+ except Exception as e:
223
+ log.error(f"Unexpected error during validation: {e}")
224
+ raise
@@ -1,6 +1,15 @@
1
1
  """
2
- Logging configuration for bookalimo package.
3
- Public SDK-style logging with built-in redaction helpers.
2
+ Logging utilities for the Bookalimo SDK.
3
+
4
+ The SDK uses Python's standard logging module. To enable debug logging,
5
+ configure the 'bookalimo' logger or set the BOOKALIMO_LOG_LEVEL environment variable.
6
+
7
+ Example:
8
+ import logging
9
+ logging.getLogger('bookalimo').setLevel(logging.DEBUG)
10
+
11
+ # Or via environment variable
12
+ export BOOKALIMO_LOG_LEVEL=DEBUG
4
13
  """
5
14
 
6
15
  from __future__ import annotations
@@ -18,9 +27,41 @@ from typing_extensions import ParamSpec
18
27
  P = ParamSpec("P")
19
28
  R = TypeVar("R")
20
29
 
30
+
31
+ def _level_from_env() -> int | None:
32
+ """Get log level from BOOKALIMO_LOG_LEVEL environment variable."""
33
+ lvl = os.getenv("BOOKALIMO_LOG_LEVEL")
34
+ if not lvl:
35
+ return None
36
+ try:
37
+ return int(lvl)
38
+ except ValueError:
39
+ try:
40
+ return logging._nameToLevel.get(lvl.upper(), None)
41
+ except Exception:
42
+ return None
43
+
44
+
21
45
  logger = logging.getLogger("bookalimo")
22
- logger.addHandler(logging.NullHandler())
23
- logger.setLevel(logging.WARNING)
46
+
47
+ # Apply environment variable level if set, otherwise use WARNING as default
48
+ env_level = _level_from_env()
49
+ logger.setLevel(env_level if env_level is not None else logging.WARNING)
50
+
51
+ # If user set BOOKALIMO_LOG_LEVEL, they expect to see logs - add console handler
52
+ if env_level is not None:
53
+ # Only add console handler if one doesn't already exist
54
+ if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers):
55
+ console_handler = logging.StreamHandler()
56
+ console_handler.setLevel(env_level)
57
+ formatter = logging.Formatter(
58
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
59
+ )
60
+ console_handler.setFormatter(formatter)
61
+ logger.addHandler(console_handler)
62
+ else:
63
+ # If no env var is set, use NullHandler (library default behavior)
64
+ logger.addHandler(logging.NullHandler())
24
65
 
25
66
  REDACTED = "******"
26
67
 
@@ -138,44 +179,6 @@ def get_logger(name: str | None = None) -> logging.Logger:
138
179
  return logger
139
180
 
140
181
 
141
- def enable_debug_logging(level: int | None = None) -> None:
142
- level = level or _level_from_env() or logging.DEBUG
143
- logger.setLevel(level)
144
-
145
- has_real_handler = any(
146
- not isinstance(h, logging.NullHandler) for h in logger.handlers
147
- )
148
- if not has_real_handler:
149
- handler = logging.StreamHandler()
150
- formatter = logging.Formatter(
151
- "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
152
- )
153
- handler.setFormatter(formatter)
154
- logger.addHandler(handler)
155
-
156
- logger.info("bookalimo logging enabled at %s", logging.getLevelName(logger.level))
157
-
158
-
159
- def disable_debug_logging() -> None:
160
- logger.setLevel(logging.WARNING)
161
- for handler in logger.handlers[:]:
162
- if not isinstance(handler, logging.NullHandler):
163
- logger.removeHandler(handler)
164
-
165
-
166
- def _level_from_env() -> int | None:
167
- lvl = os.getenv("BOOKALIMO_LOG_LEVEL")
168
- if not lvl:
169
- return None
170
- try:
171
- return int(lvl)
172
- except ValueError:
173
- try:
174
- return logging._nameToLevel.get(lvl.upper(), None)
175
- except Exception:
176
- return None
177
-
178
-
179
182
  # ---- decorator for async methods --------------------------------------------
180
183
 
181
184
 
@@ -0,0 +1,97 @@
1
+ """Pydantic schemas for the Bookalimo SDK."""
2
+
3
+ from ..transport.auth import Credentials
4
+ from .booking import (
5
+ Address,
6
+ Airport,
7
+ BookRequest,
8
+ BookResponse,
9
+ CardHolderType,
10
+ City,
11
+ CreditCard,
12
+ DetailsRequest,
13
+ DetailsResponse,
14
+ EditableReservationRequest,
15
+ EditReservationResponse,
16
+ GetReservationRequest,
17
+ ListReservationsRequest,
18
+ ListReservationsResponse,
19
+ Location,
20
+ LocationType,
21
+ MeetGreetAdditional,
22
+ MeetGreetType,
23
+ Passenger,
24
+ Price,
25
+ PriceRequest,
26
+ PriceResponse,
27
+ RateType,
28
+ Reservation,
29
+ ReservationStatus,
30
+ Reward,
31
+ RewardType,
32
+ Stop,
33
+ )
34
+ from .places import (
35
+ AutocompletePlacesRequest,
36
+ AutocompletePlacesResponse,
37
+ Circle,
38
+ FormattableText,
39
+ GeocodingRequest,
40
+ GetPlaceRequest,
41
+ LocationBias,
42
+ LocationRestriction,
43
+ Place,
44
+ PlacePrediction,
45
+ PlaceType,
46
+ QueryPrediction,
47
+ StringRange,
48
+ StructuredFormat,
49
+ Suggestion,
50
+ )
51
+
52
+ __all__ = [
53
+ "RateType",
54
+ "LocationType",
55
+ "MeetGreetType",
56
+ "RewardType",
57
+ "ReservationStatus",
58
+ "CardHolderType",
59
+ "City",
60
+ "Address",
61
+ "Airport",
62
+ "Location",
63
+ "Stop",
64
+ "Passenger",
65
+ "Reward",
66
+ "CreditCard",
67
+ "MeetGreetAdditional",
68
+ "Price",
69
+ "Reservation",
70
+ "EditableReservationRequest",
71
+ "PriceRequest",
72
+ "PriceResponse",
73
+ "DetailsRequest",
74
+ "DetailsResponse",
75
+ "BookRequest",
76
+ "BookResponse",
77
+ "ListReservationsRequest",
78
+ "ListReservationsResponse",
79
+ "GetReservationRequest",
80
+ "EditReservationResponse",
81
+ "PlaceType",
82
+ "StringRange",
83
+ "FormattableText",
84
+ "StructuredFormat",
85
+ "Circle",
86
+ "LocationBias",
87
+ "LocationRestriction",
88
+ "Place",
89
+ "AutocompletePlacesResponse",
90
+ "PlacePrediction",
91
+ "QueryPrediction",
92
+ "Suggestion",
93
+ "GetPlaceRequest",
94
+ "AutocompletePlacesRequest",
95
+ "GeocodingRequest",
96
+ "Credentials",
97
+ ]