feat(di): rewrite DI engine — proper injector integration, instance-based routing, provider normalization#125
Merged
Merged
Conversation
…ovider Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ormalize providers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…derDescriptors to injector bindings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…I, instance-based injection Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ss mutation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…no class mutation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…methods Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…engine end-to-end - PyNestFactory.create() now calls container.build() before creating PyNestApp - PyNestApp rewritten: no longer inherits PyNestApplicationContext, no select_context_module/register_routes methods; RoutesResolver called inline in __init__ - test_pynest_factory.py replaced with 6 focused TDD tests covering e2e routes, isolation, and DI correctness - test_pynest_application.py updated to match new PyNestApp API Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ort InjectionToken + Scope - Drop parse_dependencies, get_instance_variables, get_non_dependencies_params from utils.py - Remove dead imports from cli_decorators.py (only parse_params remains) - Delete class_based_view.py (replaced by instance-based routing) - Richer docstrings on all three exception classes in exceptions.py - Export InjectionToken and Scope from nest.core.__init__ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolved conflicts: - nest/core/pynest_application.py: kept new DI structure, added use_global_filters() + _register_global_handler() from main - nest/core/decorators/class_based_view.py: deleted (replaced by RoutesResolver); ported _wrap_route_with_filters() logic into route_resolver._wrap_with_filters() Also fixed nest/common/module.py forward-ref NameError by adding `from __future__ import annotations`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The new ModuleRef stores controllers as compiled.controllers (list), not module.controllers (dict). Fixes integration test boot failure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CLIAppFactory now calls container.build() then manually resolves each CLI controller's constructor deps from the injector, instead of passing the class as 'self' (which relied on the old class-mutation DI). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pynest generate module -n <name> now automatically adds the import and registers the new module in src/app_module.py, matching the behavior of 'generate resource' and NestJS CLI convention. Also fixes generate_empty_module_file to scaffold with proper `@Module(imports=[], controllers=[], providers=[])` instead of `@Module()`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hardcoded Path.cwd() / 'src' doubled the path when running from inside src/. Now uses the same find_target_folder() logic as generate resource, which walks up/down the directory tree to find src/ regardless of cwd. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two issues: 1. AppController scaffold was missing tag="app" — its routes appeared in Swagger's unnamed "default" bucket instead of an "app" section. Fixed by adding tag="app" to app_controller_file() template. 2. 'generate module' created a silent empty skeleton — no output, no hint, leaving the user wondering why nothing appeared in /docs. Now prints CREATE/UPDATE messages and a hint pointing to 'generate resource' for a full CRUD scaffold. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. db_request_handler: change 'return HTTPException(...)' to 'raise HTTPException(...)'. Returning an exception object lets it propagate silently as a truthy value, poisoning any multi-hop service call chain with confusing TypeErrors instead of HTTP 500s. Also removed session lifecycle from the decorator — session management belongs in each service method, not in a cross-cutting decorator. 2. OrmProvider.get_db(): the try/finally block was closing the session immediately after returning it, so the caller always received an already-closed session. Removed the finally; added get_session() context manager (rollback on exception, always close) as the canonical way to obtain a per-call session. 3. Service template: replaced 'self.session = self.config.get_db()' in __init__ (one shared session for the entire singleton lifetime) with 'with self.config.get_session() as session:' inside each method, giving every request its own isolated session and preventing concurrent-request data corruption. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously every provider from every module landed in one flat injector
pool — a service in module A could inject a service from module B even
when A never imported B and B never exported the service. Encapsulation
was a documentation-only contract, not a check.
Now PyNestContainer.build() runs a validation pass after cycle detection:
- Each module gets a 'visible' set:
own providers + own controllers
+ transitively-resolved exports of its imports (re-exports supported)
+ every provider from any @module(is_global=True)
- Every consumer's __init__ annotations are walked; class-typed deps that
are registered providers but not in the consumer's visible set raise
ProviderNotExportedException listing every violation with a concrete
fix ('add imports=[X] to A and exports=[Y] to X, or move Y into A').
Updated test_imported_module_providers_are_resolvable to declare the
export it was implicitly relying on. Added 8 new tests covering:
same-module deps, proper import+export, global modules, module re-exports,
missing import, missing export, controller violations, and unrelated
sibling modules.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This was referenced May 9, 2026
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
injectorlibrary instead of using it as syntactic sugar. Dependencies are now resolved at container build time and injected into actual instances — no moresetattr(class, dep, value)mutating class-level attributes.PyNestContaineris now a plain factory-created class (no_instance = None), making it safe to run multiple apps in the same process and fully testable.RoutesResolvergets a real controller instance from the container and registers bound methods as FastAPI endpoints, instead of class-level monkey-patching viaclass_based_view.ProviderDescriptor/normalize_provider()pipeline supportsuseClass,useValue,useFactory,useExisting, andInjectionTokenkeys — the full NestJS provider surface.DependencyGraphvalidates the provider graph with DFS before any instance is built.@Catch,@UseFilters,use_global_filters()from main; filter wrapping now lives inRoutesResolverrather than the deletedclass_based_view.py.Key files changed
nest/common/provider.pyInjectionToken,Scope,ProviderDescriptor,normalize_providernest/core/dependency_graph.pynest/core/injector_module.pyPyNestInjectorModule,build_injector()nest/core/pynest_container.pybuild()/get()APInest/common/route_resolver.pynest/core/decorators/injectable.pynest/core/decorators/controller.pynest/core/pynest_factory.pycontainer.build()nest/core/decorators/class_based_view.pyTest plan
uv run --group test pytest)🤖 Generated with Claude Code