Resolution and injection details
dependency.descriptor(...)resolves when the attribute is read, not when the object is created. Before the dependency manager exists, current clients raise an attribute error; after that, a missing service is a dependency-resolution failure.dependency.instance(...)resolves immediately at the call site and should be treated separately from the complete feature behavior; resolution can also invoke a lazy creator.replace_none_kwargs(...)injects only when the named keyword is absent or explicitlyNone. Use it for dependency parameters with aNonedefault and pass those parameters by keyword or omit them.- If
Noneis a meaningful caller-provided value, do not usereplace_none_kwargsfor that parameter. - Runtime service factories can be lazy. Importing an interface or defining a descriptor does not prove the concrete service has been constructed.
- Do not retain a context-owned service past account, battle, app, or feature teardown. Re-resolve at the next lifecycle boundary instead of keeping stale globals.
Interfaces and implementations
An interface describes the contract used by callers. The concrete implementation can differ by context, mode, or client build. Find the registration site before assuming which implementation is active.
Illustrative. This authored fragment is not an extracted client caller.
IVerifiedService is undefined: no registration, refresh implementation,
signature, readiness condition or lifetime is supplied. Replace the placeholder interface and
operation with verified APIs, and call only at the actual owner’s ready lifecycle boundary.
from helpers import dependency
def refresh_feature():
service = dependency.instance(IVerifiedService)
service.refresh()
Each call resolves the current dependency.instance and then the interface name.
An undefined placeholder fails before lookup. Only after resolution returns does the function
read and call service.refresh with no arguments. It retains no cached service and
provides no configuration, readiness check, retry, fallback, subscription or cleanup.
In the current dependency implementation, an absent manager
raises DependencyError. The manager retrieves the registered item’s
value() inside a try block: a KeyError there, including one
from a lazy creator, becomes DependencyError; other creator failures can escape.
Lookup is not a passive availability test or an optional operation that always returns
None when unavailable.
Resolution can construct a lazy service, with the creator marked as invoked before it runs.
If it fails, later lookups need not retry and can return the still-empty None value.
A registered object or successful creator result can also be None: registration
validates the interface class, not whether the result implements refresh. Normal
lookup return therefore does not prove that the attribute lookup or zero-argument call succeeds.
The fragment catches no failures and supplies no rollback for lookup or refresh side effects.
An unexpected creator failure must remain diagnosable; catching every exception and returning
None hides it as apparent absence. Treat missing registration as optional only under
the verified feature’s own availability contract. A shipped adaptation needs its real interface,
version attribution and traceback-bearing diagnostics for
caught unexpected failures, while keeping optional reporting infrastructure optional.
refresh is called once only if the earlier steps succeed. Its result is discarded;
refresh_feature implicitly returns None on normal completion. There is
no awaiting, callback handling or receipt check: normal return establishes only that the
immediate call returned, not synchronized data, finished background work or an accepted action.
Those meanings require the real method contract. Later replacement cannot redirect the already
resolved local object; its owner still defines lifetime and teardown. This is source/control-flow
inspection, not execution of this fragment, a service factory or the game client.
Optional infrastructure provenance
An importable API may belong to optional mod infrastructure rather than the base client. Establish provenance before treating a package as a World of Tanks service, and keep the feature that uses it behind a narrow availability gate.
Readiness is system-specific
- A lobby controller may require account GUI readiness.
- An inventory-facing service may be meaningless before cache synchronization.
- A battle controller may not exist until the battle GUI is constructed.
- A mode service may be registered only while its feature package is active.
- A view-scoped service may disappear when its window closes.
Service availability and operation completion are different gates
A registered service, its availability flag, a completed login continuation and a current credential are separate facts. In the inspected IWebController implementation, AvailableState.isAvailable() is true independently of login state. Its loginAsync wrapper reports true after the inner login continuation; the outer WebController.login also reports true without forwarding the state's boolean result. That completion value alone does not establish authentication success.
Check the service's current login state and the returned data needed for the operation. Token retrieval uses a retained payload and expiry; a non-null payload is not by itself proof of a successful fresh refresh. Carry the original account/state generation across asynchronous work, and reject stale results independently of whether the service object still exists. Local freshness does not prove server acceptance.
These are version-sensitive Python contracts from web service states and the controller's forwarding methods in the latest public source. They establish caller-side state and completion semantics, not a live authentication failure or permission to reuse credentials across accounts.
Descriptor cautions
A descriptor on a module-global singleton can look convenient, but the first attribute access may occur during import or in the wrong context. Prefer a small accessor when availability is expected to change.
Configuration and lazy services
The current client builds one process-level dependency manager, then composes root client, helper, GUI, controller, and optional feature configurations into it. Adding a configuration function is eager composition. A runtime service is the lazy part: its creator runs on first lookup and a successful result is retained.
- A lazy creator is marked invoked before it runs. If it raises, later lookups do not retry it
automatically and can resolve to
None. - Creation order is assigned after successful lazy creation. Shutdown snapshots all registered item objects, including uncreated lazy entries; see the shutdown boundaries below.
- A descriptor resolves through the current manager on every access, but storing the returned object defeats replacement awareness.
- Container configuration is process-scoped. Account, avatar, feature, and view readiness are separate lifecycles layered on top.