Skip to content

Instantly share code, notes, and snippets.

@blink1073
Last active August 4, 2026 19:32
Show Gist options
  • Select an option

  • Save blink1073/0d8cd1af7934eb6e0902603cb350068f to your computer and use it in GitHub Desktop.

Select an option

Save blink1073/0d8cd1af7934eb6e0902603cb350068f to your computer and use it in GitHub Desktop.
Proposal: pymongo[settings] extra using pydantic-settings

Proposal: pymongo[settings] extra using pydantic-settings

Motivation

Users configuring MongoClient from environment variables or .env files currently do it by hand: os.environ.get(...), manual type coercion, no validation. This proposes an optional, typed, validated settings layer built on pydantic-settings, shipped as an opt-in extra so it never becomes a hard dependency for users who don't want pydantic.

Proposed API

New module: pymongo/pydantic_settings.py

(named to avoid colliding with the existing internal pymongo/synchronous/settings.py / pymongo/asynchronous/settings.py, which are unrelated ClientOptions/topology internals)

class MongoDBSettings(BaseSettings):
    model_config = SettingsConfigDict(
        env_prefix="MONGODB_", env_file=".env", extra="ignore"
    )

    uri: Optional[str] = None
    host: str = "localhost"
    port: int = 27017
    username: Optional[str] = None
    password: Optional[SecretStr] = None
    database: Optional[str] = None  # not a MongoClient kwarg; convenience for get_database()
    replica_set: Optional[str] = None
    tls: bool = False
    tls_ca_file: Optional[str] = None
    tls_certificate_key_file: Optional[str] = None
    auth_source: Optional[str] = None
    app_name: Optional[str] = None
    direct_connection: Optional[bool] = None

    def to_kwargs(self) -> dict[str, Any]:
        ...

Env vars: MONGODB_URI, MONGODB_HOST, MONGODB_PORT, etc. This matches the MONGODB_URI convention already used throughout this repo's own test suite and Evergreen scripts, and the convention used across MongoDB driver spec-test runners generally.

Merging rule

If MONGODB_URI (or uri=) is set, to_kwargs() passes it straight through as host, plus any individual curated fields that are explicitly set (non-default), passed alongside as their own kwargs. No parsing or shadow-detection logic lives in the settings extra - MongoClient's own connection-string handling already does the merge: explicit kwargs override matching URI-embedded options, and anything the URI doesn't specify (like tls) comes through from the individual field untouched.

If no MONGODB_URI is set, to_kwargs() builds the dict entirely from the individual curated fields.

database is never included in to_kwargs() - it's a separate convenience attribute for client.get_database(settings.database).

Client handoff

Decoupled from client construction. MongoDBSettings only produces to_kwargs(); the caller decides which client class to instantiate:

client = MongoClient(**settings.to_kwargs())
# or
client = AsyncMongoClient(**settings.to_kwargs())

One settings class serves both sync and async clients.

Import guard

pydantic_settings is imported inside a try/except, following the existing pattern in pymongo/compression_support.py for other optional extras (e.g. zstd, snappy). If the extra isn't installed, importing pymongo.pydantic_settings raises a clear ImportError pointing at pip install pymongo[settings]. pydantic-settings is not a hard dependency of core PyMongo.

Packaging

  • New requirements/settings.txt containing pydantic-settings>=2.0
  • New entry in pyproject.toml's [tool.hatch.metadata.hooks.requirements_txt.optional-dependencies]: settings = ["requirements/settings.txt"]

Testing

Unit tests only - no live MongoDB server required:

  • env var parsing and type coercion
  • .env file loading
  • to_kwargs() output for URI-only, individual-fields-only, and combined cases
  • field-to-kwarg name mapping (including SecretStr unwrapping)
  • clear ImportError message when the extra isn't installed

Non-goals

  • No full mirror of every MongoClient constructor kwarg - curated fields cover the common env-var-driven case (YAGNI)
  • No built-in client construction helper (e.g. no settings.create_client())
  • No pydantic v1 support - pydantic-settings v2 requires pydantic v2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment