Migrate ConnectorConfig to BaseSettings and add ConnectorSpecificConfig#75
Open
b-Tomas wants to merge 4 commits into
Open
Migrate ConnectorConfig to BaseSettings and add ConnectorSpecificConfig#75b-Tomas wants to merge 4 commits into
b-Tomas wants to merge 4 commits into
Conversation
Rename ConnectorConfig to ConnectorRootConfig, rebase from BaseModel to
pydantic-settings BaseSettings. Add ConnectorSpecificConfig base class
for per-connector settings with automatic INORBIT_{CONNECTOR_TYPE}_ env
prefix. Env vars now resolve at instantiation time instead of import
time, eliminating load_dotenv() workarounds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ConnectorRootConfig is now Generic[T] where T is bound to ConnectorSpecificConfig. Connectors parametrize it directly (ConnectorRootConfig[MyConfig](**yaml_data)) instead of writing a boilerplate subclass. Type checkers resolve connector_config to the concrete type. Existing subclasses continue to work. Also uses Self return type for to_singular_config to preserve both generic parameters and subclass types for type checkers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tox was only linting inorbit_connector/, missing test files entirely. Add tests/ to both flake8 and black commands, then fix all pre-existing violations: unused imports, long docstrings, blank line issues, and formatting inconsistencies. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR migrates the framework’s connector configuration model to pydantic-settings so INORBIT_* environment variables (and config/.env) are resolved at instantiation time rather than import time, and introduces a standardized ConnectorSpecificConfig base to reduce per-connector boilerplate.
Changes:
- Replaced
ConnectorConfig(BaseModel)withConnectorRootConfig(BaseSettings)and addedConnectorSpecificConfig(BaseSettings)for per-connector settings. - Made root config generic (
ConnectorRootConfig[T]) and updated connectors/examples/docs/tests to use the new API. - Added
pydantic-settingsdependency and expanded tox lint targets to includetests/.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
inorbit_connector/models.py |
Introduces ConnectorSpecificConfig/ConnectorRootConfig (BaseSettings + generic) and updates env resolution behavior. |
inorbit_connector/connector.py |
Updates connector base classes to accept ConnectorRootConfig instead of ConnectorConfig. |
pyproject.toml |
Adds pydantic-settings dependency; runs flake8/black over tests/ in tox. |
tests/conftest.py |
Adds autouse fixture to clear INORBIT_* env vars for test isolation. |
tests/test_models.py |
Updates config model tests and adds coverage for env + dotenv + generic parametrization. |
tests/test_connector.py |
Migrates tests to ConnectorRootConfig/ConnectorSpecificConfig. |
tests/test_connector_metrics.py |
Migrates metrics tests to new config types. |
tests/test_metrics_server.py |
Minor formatting-only change. |
tests/test_utils.py |
Removes stray whitespace line. |
tests/test_commands.py |
Minor docstring/text formatting updates. |
examples/simple-connector/connector.py |
Updates example to use ConnectorRootConfig[ExampleBotConfig] + ConnectorSpecificConfig. |
examples/simple-fleet-connector/connector.py |
Updates fleet example to use ConnectorRootConfig[ExampleBotConfig] + ConnectorSpecificConfig. |
examples/robot-connector/main.py |
Updates example entrypoint to construct ConnectorRootConfig[ExampleBotConfig]. |
examples/robot-connector/datatypes.py |
Replaces connector-specific root subclass with ConnectorSpecificConfig. |
examples/robot-connector/connector.py |
Updates example connector type hints to ConnectorRootConfig[ExampleBotConfig]. |
examples/fleet-connector/main.py |
Updates example entrypoint to construct ConnectorRootConfig[ExampleBotConfig]. |
examples/fleet-connector/datatypes.py |
Replaces connector-specific root subclass with ConnectorSpecificConfig. |
examples/fleet-connector/connector.py |
Updates example connector type hints to ConnectorRootConfig[ExampleBotConfig]. |
docs/contents/configuration.md |
Updates configuration docs to describe ConnectorRootConfig + ConnectorSpecificConfig usage and env behavior. |
docs/contents/specification/models.md |
Updates spec to document ConnectorRootConfig and adds ConnectorSpecificConfig section. |
docs/contents/specification/index.md |
Updates exported symbol table to reflect new config types. |
docs/contents/usage/single-robot.md |
Updates usage docs to reference ConnectorRootConfig in constructor signature. |
docs/contents/usage/fleet.md |
Updates usage docs to reference ConnectorRootConfig in constructor signature. |
docs/contents/usage/metrics.md |
Updates metrics docs to reference ConnectorRootConfig. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+363
to
+374
| if isinstance(data, dict) and isinstance(data.get("connector_config"), dict): | ||
| ann_type = cls.model_fields["connector_config"].annotation | ||
| if ( | ||
| isinstance(ann_type, type) | ||
| and issubclass(ann_type, ConnectorSpecificConfig) | ||
| and ann_type is not ConnectorSpecificConfig | ||
| ): | ||
| data = { | ||
| **data, | ||
| "connector_config": ann_type(**data["connector_config"]), | ||
| } | ||
| return data |
Comment on lines
20
to
22
| # Third-party | ||
| from pydantic import field_validator, BaseModel | ||
| from pydantic import field_validator | ||
|
|
Comment on lines
29
to
32
| CONFIG_FILE = Path(__file__).resolve().parent.parent / "example.yaml" # ../example.yaml | ||
| ROBOT_ID = "my-example-robot" | ||
| CONNECTOR_TYPE = "example_bot" | ||
|
|
Comment on lines
20
to
22
| # Third-party | ||
| from pydantic import field_validator, BaseModel | ||
| from pydantic import field_validator | ||
|
|
Comment on lines
29
to
33
| @@ -32,7 +32,7 @@ | |||
| CONNECTOR_TYPE = "example_bot" | |||
|
|
|||
| from inorbit_connector.models import ConnectorSpecificConfig | ||
|
|
||
|
|
||
| CONNECTOR_TYPE = "example_bot" |
| from inorbit_connector.models import ConnectorSpecificConfig | ||
|
|
||
|
|
||
| CONNECTOR_TYPE = "example_bot" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ConnectorConfigtoConnectorRootConfigand rebase it fromBaseModelto pydantic-settingsBaseSettingsConnectorSpecificConfig(BaseSettings)base class for per-connector settingsConnectorRootConfiggeneric overT: ConnectorSpecificConfig— connectors parametrize directly instead of writing a boilerplate subclassto_singular_configreturnsSelf, preserving both generic parameters and subclass types for type checkerspydantic-settingsas a framework dependencytests/to tox flake8/black targets and fix pre-existing lint violationsMotivation
ConnectorConfigusedos.getenv()as class-level field defaults forapi_keyandapi_url. These evaluated once at import time, beforeload_dotenv()or container-injected secrets were available. Connectors worked around this with fragileload_dotenv("config/.env")calls wedged before framework imports. This was brittle and undocumented.Additionally, every connector independently declared a
BaseSettingssubclass with the sameSettingsConfigDict(env_ignore_empty, case_sensitive, env_file, extra) and anenv_prefixderived fromCONNECTOR_TYPE-- multiple connectors repeating the same ~10 lines verbatim.On top of that, every connector needed a boilerplate
ConnectorRootConfigsubclass whose only purpose was to narrow theconnector_configfield type. These subclasses contained no logic.What this fixes
Environment variable resolution at instantiation time.
api_keyandapi_url(and all other fields) are now resolved fromINORBIT_*env vars when the config object is constructed, not when the module is imported.config/.envis read by pydantic-settings automatically -- noload_dotenv()hacks needed.Eliminated per-connector boilerplate. A new
ConnectorSpecificConfigbase class provides standard env loading with prefixINORBIT_{CONNECTOR_TYPE}_. A connector config collapses from ~15 lines ofSettingsConfigDictsetup to:No
SettingsConfigDict, noDEFAULT_ENV_FILE, nopydantic_settingsimports needed in each connector.Eliminated root config subclasses.
ConnectorRootConfigis nowGeneric[T]whereTis bound toConnectorSpecificConfig. Connectors parametrize it directly — no subclass needed:Subclassing is still supported for connectors that need root-level validators or additional fields.
Automatic nested env resolution. When
ConnectorRootConfigreceivesconnector_configas a dict (from YAML), a model validator explicitly instantiates the annotatedConnectorSpecificConfigsubclass via__init__(), ensuring env sources participate in field resolution. Pydantic's default dict-to-model coercion usesmodel_validate()which does not trigger BaseSettings env resolution -- only__init__()does.Precedence is preserved. Init kwargs (YAML values) > env vars > .env file > field defaults. This matches the previous behavior where YAML always won.
Breaking changes and migration guide
ConnectorConfigrenamed toConnectorRootConfigs/ConnectorConfig/ConnectorRootConfig/in imports and subclass declarationsconnector_configfield type changed fromBaseModeltoConnectorSpecificConfigConnectorSpecificConfiginstead ofBaseModel, and setCONNECTOR_TYPEas a class variableextra="forbid"changed toextra="ignore"ValidationError. Remove any code that relied on catching extra-field errorspydantic-settingsis now a transitive dependencypydantic-settingspins from connectorpyproject.tomlor align with>=2.14,<3.0os.getenv()defaults removed fromapi_key/api_urlConnectorRootConfiginstance now correctly reads env vars at instantiation timeMyConnectorConfig(ConnectorRootConfig))connector_config. UseConnectorRootConfig[MyConfig]directly.Before / after example
Before:
After: