You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PyNest has no first-party configuration abstraction. Developers currently read environment variables directly via os.getenv() scattered across services, or use python-dotenv manually with no validation. There is no typed, injectable configuration layer.
This feature request proposes a ConfigModule and ConfigService modeled after NestJS's @nestjs/config package, built on top of Pydantic BaseSettings.
Motivation
Config values are read in random places, making them untestable and hard to mock
No validation of required environment variables at startup (fail fast)
No namespaced config for feature modules
No support for different .env files per environment (dev, staging, prod)
Proposed API
ConfigModule.for_root()
```python @module(
imports=[
ConfigModule.for_root(
env_file=".env",
is_global=True, # makes ConfigService available everywhere
validate=AppConfig, # Pydantic BaseSettings schema
ignore_env_file=False,
)
],
...
)
class AppModule:
pass
```
Validation schema via Pydantic BaseSettings
```python
from pydantic_settings import BaseSettings
class AppConfig(BaseSettings):
database_url: str
jwt_secret: str
redis_host: str = "localhost"
redis_port: int = 6379
debug: bool = False
class Config:
env_file = ".env"
```
If database_url or jwt_secret are missing, PyNest raises a ConfigValidationException at startup — before any provider is instantiated.
Overview
PyNest has no first-party configuration abstraction. Developers currently read environment variables directly via
os.getenv()scattered across services, or usepython-dotenvmanually with no validation. There is no typed, injectable configuration layer.This feature request proposes a
ConfigModuleandConfigServicemodeled after NestJS's@nestjs/configpackage, built on top of PydanticBaseSettings.Motivation
.envfiles per environment (dev,staging,prod)Proposed API
ConfigModule.for_root()```python
@module(
imports=[
ConfigModule.for_root(
env_file=".env",
is_global=True, # makes ConfigService available everywhere
validate=AppConfig, # Pydantic BaseSettings schema
ignore_env_file=False,
)
],
...
)
class AppModule:
pass
```
Validation schema via Pydantic
BaseSettings```python
from pydantic_settings import BaseSettings
class AppConfig(BaseSettings):
database_url: str
jwt_secret: str
redis_host: str = "localhost"
redis_port: int = 6379
debug: bool = False
```
If
database_urlorjwt_secretare missing, PyNest raises aConfigValidationExceptionat startup — before any provider is instantiated.ConfigService— injectable anywhere```python
@Injectable
class UserService:
def init(self, config: ConfigService):
self.db_url = config.get("database_url") # str | None
self.db_url = config.get_or_throw("database_url") # str | raises
self.port = config.get("redis_port", default=6379)
```
Typed access with
config.get_typed()```python
Returns a fully validated Pydantic model instance
app_config: AppConfig = config.get_typed(AppConfig)
print(app_config.jwt_secret)
```
ConfigModule.for_feature()— namespaced module config```python
@module(
imports=[
ConfigModule.for_feature("database", DatabaseConfig)
],
providers=[DatabaseService],
)
class DatabaseModule:
pass
In DatabaseService:
@Injectable
class DatabaseService:
def init(self, config: ConfigService):
db_config = config.get_typed("database") # returns DatabaseConfig
```
Environment File Precedence
.env.{NODE_ENV}.local(e.g..env.development.local).env.local.env.{NODE_ENV}(e.g..env.development).envControlled by
env_fileandenv_file_encodingoptions onfor_root().expandVariablesSupport```
.env
BASE_URL=https://api.example.com
AUTH_URL=${BASE_URL}/auth
```
Set
expand_variables=Trueinfor_root()to resolve${VAR}references.Acceptance Criteria
ConfigModulewithfor_root(env_file, validate, is_global, ignore_env_file, expand_variables)factory methodConfigModule.for_feature(namespace, schema)for module-scoped configConfigServiceinjectable withget(),get_or_throw(),get_typed()methodsBaseSettingsintegration for schema validation at startupConfigValidationExceptionraised at boot if validation fails (fail fast)is_global=TruemakesConfigServiceinjectable without re-importingConfigModuleexpand_variables=True)python-dotenvoptional dependencyImplementation Notes
pydantic-settingsshould be added as an optional dependency (pip install pynest-api[config])ConfigModule.for_root()returns aDynamicModule— this is also a prerequisite for the Dynamic Modules pattern (a separate future feature)ConfigServiceshould be registered as a singleton scoped to the module graphOnModuleInithook (once Feature Add official docs #2 / Lifecycle Hooks lands)Related