|
23 | 23 | import threading
|
24 | 24 | import time
|
25 | 25 | import weakref
|
26 |
| -from typing import Any, NoReturn, Optional |
| 26 | +from typing import Any, Dict, NoReturn, Optional |
27 | 27 |
|
| 28 | +import bson |
28 | 29 | from bson import DEFAULT_CODEC_OPTIONS
|
29 | 30 | from bson.son import SON
|
30 | 31 | from pymongo import __version__, _csot, auth, helpers
|
@@ -231,6 +232,108 @@ def _set_keepalive_times(sock):
|
231 | 232 | )
|
232 | 233 |
|
233 | 234 |
|
| 235 | +def _is_lambda() -> bool: |
| 236 | + if os.getenv("AWS_LAMBDA_RUNTIME_API"): |
| 237 | + return True |
| 238 | + env = os.getenv("AWS_EXECUTION_ENV") |
| 239 | + if env: |
| 240 | + return env.startswith("AWS_Lambda_") |
| 241 | + return False |
| 242 | + |
| 243 | + |
| 244 | +def _is_azure_func() -> bool: |
| 245 | + return bool(os.getenv("FUNCTIONS_WORKER_RUNTIME")) |
| 246 | + |
| 247 | + |
| 248 | +def _is_gcp_func() -> bool: |
| 249 | + return bool(os.getenv("K_SERVICE") or os.getenv("FUNCTION_NAME")) |
| 250 | + |
| 251 | + |
| 252 | +def _is_vercel() -> bool: |
| 253 | + return bool(os.getenv("VERCEL")) |
| 254 | + |
| 255 | + |
| 256 | +def _getenv_int(key: str) -> Optional[int]: |
| 257 | + """Like os.getenv but returns an int, or None if the value is missing/malformed.""" |
| 258 | + val = os.getenv(key) |
| 259 | + if not val: |
| 260 | + return None |
| 261 | + try: |
| 262 | + return int(val) |
| 263 | + except ValueError: |
| 264 | + return None |
| 265 | + |
| 266 | + |
| 267 | +def _metadata_env() -> Dict[str, Any]: |
| 268 | + env: Dict[str, Any] = {} |
| 269 | + # Skip if multiple (or no) envs are matched. |
| 270 | + if (_is_lambda(), _is_azure_func(), _is_gcp_func(), _is_vercel()).count(True) != 1: |
| 271 | + return env |
| 272 | + if _is_lambda(): |
| 273 | + env["name"] = "aws.lambda" |
| 274 | + region = os.getenv("AWS_REGION") |
| 275 | + if region: |
| 276 | + env["region"] = region |
| 277 | + memory_mb = _getenv_int("AWS_LAMBDA_FUNCTION_MEMORY_SIZE") |
| 278 | + if memory_mb is not None: |
| 279 | + env["memory_mb"] = memory_mb |
| 280 | + elif _is_azure_func(): |
| 281 | + env["name"] = "azure.func" |
| 282 | + elif _is_gcp_func(): |
| 283 | + env["name"] = "gcp.func" |
| 284 | + region = os.getenv("FUNCTION_REGION") |
| 285 | + if region: |
| 286 | + env["region"] = region |
| 287 | + memory_mb = _getenv_int("FUNCTION_MEMORY_MB") |
| 288 | + if memory_mb is not None: |
| 289 | + env["memory_mb"] = memory_mb |
| 290 | + timeout_sec = _getenv_int("FUNCTION_TIMEOUT_SEC") |
| 291 | + if timeout_sec is not None: |
| 292 | + env["timeout_sec"] = timeout_sec |
| 293 | + elif _is_vercel(): |
| 294 | + env["name"] = "vercel" |
| 295 | + region = os.getenv("VERCEL_REGION") |
| 296 | + if region: |
| 297 | + env["region"] = region |
| 298 | + return env |
| 299 | + |
| 300 | + |
| 301 | +_MAX_METADATA_SIZE = 512 |
| 302 | + |
| 303 | + |
| 304 | +# See: https://github.com/mongodb/specifications/blob/5112bcc/source/mongodb-handshake/handshake.rst#limitations |
| 305 | +def _truncate_metadata(metadata): |
| 306 | + """Perform metadata truncation.""" |
| 307 | + if len(bson.encode(metadata)) <= _MAX_METADATA_SIZE: |
| 308 | + return |
| 309 | + # 1. Omit fields from env except env.name. |
| 310 | + env_name = metadata.get("env", {}).get("name") |
| 311 | + if env_name: |
| 312 | + metadata["env"] = {"name": env_name} |
| 313 | + if len(bson.encode(metadata)) <= _MAX_METADATA_SIZE: |
| 314 | + return |
| 315 | + # 2. Omit fields from os except os.type. |
| 316 | + os_type = metadata.get("os", {}).get("type") |
| 317 | + if os_type: |
| 318 | + metadata["os"] = {"type": os_type} |
| 319 | + if len(bson.encode(metadata)) <= _MAX_METADATA_SIZE: |
| 320 | + return |
| 321 | + # 3. Omit the env document entirely. |
| 322 | + metadata.pop("env", None) |
| 323 | + encoded_size = len(bson.encode(metadata)) |
| 324 | + if encoded_size <= _MAX_METADATA_SIZE: |
| 325 | + return |
| 326 | + # 4. Truncate platform. |
| 327 | + overflow = encoded_size - _MAX_METADATA_SIZE |
| 328 | + plat = metadata.get("platform", "") |
| 329 | + if plat: |
| 330 | + plat = plat[:-overflow] |
| 331 | + if plat: |
| 332 | + metadata["platform"] = plat |
| 333 | + else: |
| 334 | + metadata.pop("platform", None) |
| 335 | + |
| 336 | + |
234 | 337 | # If the first getaddrinfo call of this interpreter's life is on a thread,
|
235 | 338 | # while the main thread holds the import lock, getaddrinfo deadlocks trying
|
236 | 339 | # to import the IDNA codec. Import it here, where presumably we're on the
|
@@ -364,6 +467,12 @@ def __init__(
|
364 | 467 | if driver.platform:
|
365 | 468 | self.__metadata["platform"] = "%s|%s" % (_METADATA["platform"], driver.platform)
|
366 | 469 |
|
| 470 | + env = _metadata_env() |
| 471 | + if env: |
| 472 | + self.__metadata["env"] = env |
| 473 | + |
| 474 | + _truncate_metadata(self.__metadata) |
| 475 | + |
367 | 476 | @property
|
368 | 477 | def _credentials(self):
|
369 | 478 | """A :class:`~pymongo.auth.MongoCredentials` instance or None."""
|
|
0 commit comments