GraphQL API¶
general_manager.api.graphql.GraphQL ¶
Static helper that builds GraphQL types, queries, and mutations for managers.
get_schema classmethod ¶
get_schema()
Get the currently configured Graphene schema for the GraphQL registry.
Returns:
| Type | Description |
|---|---|
Schema | None | The active |
reset_registry classmethod ¶
reset_registry()
Reset all class-level registries to their initial empty state.
Useful in tests to isolate schema construction between test cases.
register_file_upload_mutation classmethod ¶
register_file_upload_mutation()
Register the generic begin-upload mutation when the feature is enabled.
get_registry_snapshot classmethod ¶
get_registry_snapshot()
Return a snapshot of the current registry state as a :class:GraphQLRegistry dataclass.
Useful for inspecting registered types and mutations in tests without depending on the mutable class variables directly.
create_graphql_mutation classmethod ¶
create_graphql_mutation(generalManagerClass)
Register GraphQL mutation classes for a GeneralManager and store them in the class mutation registry.
If the manager class has no Interface, this method returns without registering anything. Otherwise, it generates create/update/delete mutation classes when the manager's Interface advertises support for the corresponding operation: either by overriding the InterfaceBase method or by listing the operation in Interface.get_capabilities(). Each successfully generated mutation is stored on the class-level registry (_mutations) under the names create<ManagerName>, update<ManagerName>, and delete<ManagerName>. If a mutation factory returns None, that mutation kind is skipped and later supported kinds are still considered.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
generalManagerClass | type[GeneralManager] | The GeneralManager subclass whose Interface determines which mutations are created and registered. | required |
create_graphql_interface classmethod ¶
create_graphql_interface(generalManagerClass)
Create and register a Graphene ObjectType for a GeneralManager class and expose its queries and subscription.
If the manager class has no Interface, this method returns without registering anything. Otherwise it builds a Graphene type by mapping the manager's Interface attributes and GraphQLProperties to Graphene fields and resolvers, registers the resulting type and manager in the GraphQL registries, adds corresponding list/detail query fields, adds subscription fields, and adds a capabilities field when the manager exposes GraphQL permission capabilities.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
generalManagerClass | type[GeneralManager] | The manager class whose Interface and GraphQLProperties are used to generate Graphene fields and resolvers. | required |
register_current_user_capabilities classmethod ¶
register_current_user_capabilities()
Register the optional provider-backed me GraphQL field.
The field is added only when GRAPHQL_GLOBAL_CAPABILITIES_PROVIDER resolves to a provider class. Provider graphql_fields entries may be concrete Graphene fields or Python types that map through the base GraphQL scalar mapper. For each field, a provider method named resolve_<field> is called when present; otherwise the attribute is read from the current request user. Provider graphql_capabilities entries that are not GraphQLPermissionCapability instances are ignored.
register_search_query classmethod ¶
register_search_query()
Register the global search field. See graphql_search.register_search_query.
create_write_fields classmethod ¶
create_write_fields(interface_cls, *, require_fields=True)
Thin wrapper - see :func:general_manager.api.graphql_mutations.create_write_fields.
generate_create_mutation_class classmethod ¶
generate_create_mutation_class(
generalManagerClass, default_return_values
)
Thin wrapper - see :func:general_manager.api.graphql_mutations.generate_create_mutation_class.
generate_update_mutation_class classmethod ¶
generate_update_mutation_class(
generalManagerClass, default_return_values
)
Thin wrapper - see :func:general_manager.api.graphql_mutations.generate_update_mutation_class.
generate_delete_mutation_class classmethod ¶
generate_delete_mutation_class(
generalManagerClass, default_return_values
)
Thin wrapper - see :func:general_manager.api.graphql_mutations.generate_delete_mutation_class.
general_manager.api.graphql.MeasurementType ¶
Bases: ObjectType
GraphQL object wrapper exposing measurement magnitude and unit text.
value is emitted through Graphene Float and may lose Decimal precision. unit is the measurement's GeneralManager public unit string.
general_manager.api.graphql.MeasurementScalar ¶
Bases: Scalar
Serialize and parse Measurement values as "<value> <unit>" text.
Stable user imports are available from general_manager.api and general_manager.api.graphql. The published direct-call contract is exactly parse_value(value: str) -> Measurement even though Graphene may call scalar hooks dynamically at runtime. Non-string Graphene runtime calls are outside the documented user API.
serialize() accepts only Measurement instances and returns their public string representation; it raises InvalidMeasurementValueError for every other value. parse_value() is the public variable-input parser, accepts a string, and returns a Measurement. Type checkers should reject non-string direct calls. Runtime non-string calls are unsupported; their exact exception class is unspecified and Graphene reports them to clients as scalar input coercion failures on the supplied variable. Valid strings are delegated to Measurement.from_string(), so InvalidDimensionlessValueError and InvalidMeasurementStringError from the measurement module propagate unchanged for malformed text. parse_literal() accepts graphql.language.ast.StringValueNode only and returns Measurement | None; other literal nodes return None so Graphene can treat the literal as invalid.
general_manager.api.graphql.BigIntScalar ¶
Bases: Scalar
GraphQL scalar for integers outside the built-in GraphQL Int range.
Stable user imports are available from general_manager.api.graphql.
serialize() returns a string so clients do not lose precision beyond GraphQL's built-in Int range. parse_value() returns an int. Runtime values accepted by both methods are strings, bytes, bytearrays, integers, floats, or Decimal values; they are coerced with Python int(value) after rejecting booleans. Accepted string/bytes/bytearray grammar, whitespace, signs, base-10 parsing, NaN, and infinities therefore follow Python int(value) exactly. Prefixes such as "0x10" are not auto-detected because no explicit base is passed. Invalid numeric text and non-finite floats/decimals propagate Python ValueError or OverflowError rather than being normalized to InvalidBigIntScalarValueError. Fractional floats and Decimal values are accepted only for compatibility, not as integral-value validation; that truncation behavior is an intentional compatibility guarantee for current releases. For example, Decimal("1.9") and 1.9 both parse as 1. Booleans and objects outside the accepted coercible types raise InvalidBigIntScalarValueError. parse_literal() accepts graphql.language.ast.IntValueNode and StringValueNode only and returns int | None; invalid accepted literal values propagate Python coercion errors, while unsupported AST node types return None.
general_manager.api.graphql_errors.PublicGraphQLError ¶
Bases: GraphQLError
Deliberately public GraphQL failure with a stable client code.
PublicGraphQLError(message: str, *, code: str) constructs a GraphQL error for an intentionally public application failure. message is exposed unchanged to the client. The required keyword-only code is stored as the complete extensions mapping: {"code": code}. Construction returns a PublicGraphQLError instance, which is also a graphql.GraphQLError; it does not perform code-format validation or sanitize the supplied message. Callers are responsible for ensuring both values are safe and stable. Import the class from general_manager.api, not its implementation module.
The constructor has no GeneralManager-specific failure path. Normal Python and GraphQLError constructor errors propagate if callers pass incompatible values. When raised inside a GeneralManager-generated mutation or a mutation registered with @graph_ql_mutation, the same error instance, message, and extensions are preserved.
Compatibility: this stable export was added in GeneralManager 0.63.0. In the same release, plain ValueError stopped producing public BAD_USER_INPUT messages at GeneralManager mutation boundaries; use PublicGraphQLError for an application code or Django ValidationError for validation details.
Stable imports from general_manager.api include GraphQL, MeasurementType, MeasurementScalar, PublicGraphQLError, bulk_data_change_notifications, and the file-upload contracts documented below. Stable imports from general_manager.api.graphql are limited to the compatibility exports GraphQL, MeasurementType, MeasurementScalar, and BigIntScalar. The implementation keeps lower-level helpers, constants, pagination types, permission-plan adapters, and exception mappers in extracted submodules; those submodules are private implementation details and are not stable import paths. Some internal types may still appear in generated reference pages or type-checker-visible implementation annotations when they are part of generated GraphQL plumbing; that visibility does not make them public import targets.
Historical queries with @asOf¶
The Historical Context API reference contains the directive signature, Python context helpers, and stable historical error-code table; this section describes the generated GraphQL behavior in context.
GeneralManager registers the built-in query directive @asOf(date: DateTime!). Use either a literal:
query HistoricalProjects @asOf(date: "2022-01-01T00:00:00Z") {
projectList {
items {
id
name
}
}
}
or a variable:
query HistoricalProjects($date: DateTime!) @asOf(date: $date) {
projectList {
items {
id
name
}
}
}
{"date":"2022-01-01T00:00:00Z"}
The directive belongs to the selected query operation, not individual fields. When a document contains multiple operations, operationName therefore chooses which operation and snapshot execute. The date applies to every resolver, nested relation, calculation, and cache access in that operation. A resolver that reaches a request-backed or otherwise unsupported interface fails closed instead of mixing current data into the response.
@asOf is query-only and accepts exactly one non-null date. It cannot be placed on mutations or subscriptions. The synchronous GeneralManager GraphQL endpoint also rejects mutation root resolvers declared with async def before their body starts, because they cannot retain the endpoint's atomic mutation scope; use synchronous mutation resolvers there.
Each item in a batched GraphQL request has an independent historical context. Its date is restored after success or failure, so one batch item cannot leak a snapshot into the next. Historical failures use stable public extension codes:
- invalid or unresolved dates:
BAD_USER_INPUT; - conflicting snapshots:
HISTORICAL_CONTEXT_CONFLICT; - attempted historical mutations:
HISTORICAL_MUTATION_FORBIDDEN; - unsupported historical reads:
HISTORICAL_READ_NOT_SUPPORTED; and - invalid directive placement or shape:
GRAPHQL_VALIDATION_FAILED.
Bulk notification context¶
general_manager.api.notification_batching.bulk_data_change_notifications ¶
bulk_data_change_notifications()
Collect and flush data-change notifications for a bulk operation.
bulk_data_change_notifications() accepts no arguments and yields None to the with body. It returns a context manager, not a notification result. The context deduplicates targets and flushes one refresh event per affected GraphQL manager class or RemoteAPI resource after the body exits. Individual ordinary channel-layer send failures are logged and do not abort the remaining flushes; MemoryError and other non-ordinary flush failures propagate.
The context is not transaction-aware. Put it outside the true outermost transaction.atomic() block so refresh delivery follows the completed commit or rollback. Body exceptions are re-raised after the queued notifications are flushed; if flushing also raises, both failures are reported in a BaseExceptionGroup. The stable import is available from general_manager.api in 0.64.0 and later; the notification batching module is an implementation location rather than a separate package-level compatibility promise.
Relation annotation compatibility¶
Generated GraphQL relation fields resolve annotations to one registered GeneralManager class before generating the schema. The supported annotation shapes are:
- a manager class, a generated/existing Django model class with its manager back-reference, or a named postponed reference such as
"User"; Bucket[User],list[User],tuple[User, ...],set[User], and theirtypingspellings; andUser | None,Optional[User], and equivalent postponed string forms such as"Bucket[User]"or"typing.List[User]".
The resolved type is used for object fields, paginated relation-list fields, nested relation filters, mutation relation inputs, sort options, and subscription identifiers. If a postponed name is not registered, or a union contains more than one manager target, the annotation is not recognized as a manager relation and the generated schema does not provide manager-relation behavior for that field. The resolver returns the single manager target when recognized and otherwise no manager target; it does not raise a framework exception for an unresolved or ambiguous annotation.
The helper that performs this resolution is an implementation detail, not a stable import. Applications should declare manager annotations and consume the generated schema rather than importing the helper directly.
This compatibility behavior is covered by the 0.64.1 and 0.64.2 GraphQL relation fixes. The concept guide explains the model, the task guide shows declaration patterns, and the cookbook recipe shows directly usable queries.
Manager-typed calculation input filters¶
GraphQL.create_graphql_interface(manager: type[GeneralManager]) generates a direct nested relation filter for a calculation input declared with Input(RelatedManager), even though calculation get_attribute_types() rows do not include relation_kind or filter_lookup. A list query can therefore use:
query ProjectCommercials($projectId: ID!) {
projectCommercialList(filter: {project: {id: $projectId}}) {
items { project { id } targetDate }
}
}
The resolver returns the equivalent backend plan {"filter": {"project__id": 42}, "exclude": {}} when the variable is 42. Nested fields and lookups are prefixed in the same way. The generated input is subject to GRAPHQL_FILTER_RELATION_DEPTH; when the depth is exhausted or the input type is not a registered manager, the nested relation field is omitted. Explicit relation_kind and filter_lookup metadata remains authoritative, including custom prefixes. Invalid nested values continue through the existing GraphQL input and bucket error paths; no new framework exception is introduced.
The calculation concept guide explains the model, the task guide shows the declaration, and the cookbook recipe provides a directly usable request.
File uploads¶
Use the lazy stable imports from general_manager.api; modules under general_manager.uploads are implementation locations, not additional public import promises.
Configuration and inspection:
FileUploadPolicy(max_bytes=None, allowed_content_types=None, allowed_extensions=None, public=None, content_inspector=None)configures one manager file field. Invalid limits, allowlists, booleans, or inspectors raiseFileUploadConfigurationErrorduring construction.FileInspectionis the credential-free bounded value passed to aFileContentInspector.FileContentInspectorhas the callable signature(inspection: FileInspection) -> str | None; a returned string rejects the file with that reason andNoneaccepts it.FileUploadConfigurationErrorreports invalid policy/settings construction.
GraphQL contracts:
UploadTokenis the opaque generated mutation scalar.StoredFileandStoredImageare generated structured output types.StoredFileStatuscontainsAVAILABLE,PROCESSING, andFAILED.UploadTransportcontainsDIRECTandPROXY.
Custom storage extension contracts:
register_upload_adapter(storage_class: type[Storage], factory: UploadAdapterFactory) -> Noneregisters one global factory before upload use. Invalid arguments raiseTypeError; duplicate or ambiguous registration raisesValueError.UploadAdapterFactoryhas the callable signature(storage: Storage) -> UploadAdapter.UploadAdapteris the complete transfer/inspect/download protocol.UploadFinalizationAdapteris the exact post-commit and replacement cleanup protocol.ExactPublicDownloadAdapteris the optional unsigned immutable public URL protocol required for retained uploads in public mode.ProxyUploadSinkadds streamingsave_stagefor proxy adapters.UploadInstructions,ObjectVersion, andClaimedObjectare immutable boundary values. Instruction credentials and object-version identities have redacted reprs; treat aClaimedObjectkey as sensitive operational data and do not log it.
See the setup and custom-adapter guide for method semantics and contract testing, or use the end-to-end upload recipe. Persistence models, token/digest helpers, the registry instance, local capability codecs, and finalization functions are not public API.
The file-upload surface was introduced in GeneralManager 0.62.0. The current contract includes the same-release hardening for Unicode-safe paths, no-overwrite storage behavior, deterministic admission, single-use token preflight, post-commit finalization, secure downloads, and cleanup. Callers must not depend on the intermediate behavior of earlier commits in that release.
Expected failures derive from UploadError and carry stable code class attributes. Public exception classes are:
UploadAuthenticationError,UploadManagerInvalidError,UploadFieldInvalidError,UploadOperationInvalidError,UploadTargetUnavailableError;InvalidUploadFilenameError,InvalidUploadSizeError,InvalidUploadChecksumError,UploadQuotaExceededError,UploadRateLimitExceededError,UploadDatabaseMismatchError;UploadExpiredError,UploadTokenInvalidError,UploadIncompleteError,UploadAlreadyConsumedError,UploadTransferConflictError,UploadSupersededError,UploadBindingMismatchError;UploadSizeMismatchError,UploadChecksumMismatchError,InvalidFileTypeError,InvalidImageError;UploadBackendUnsupportedError,UploadStorageChangedError,UploadObjectMissingError,UploadFinalizationFailedError, andUploadStorageError.
Adapters may raise these framework exceptions. Arbitrary subclasses, messages, and exception chains are sanitized at GraphQL/HTTP boundaries; do not rely on custom error codes reaching clients.
Custom adapters should raise UploadObjectMissingError only when an exact inspect or cleanup operation can prove that its target is already absent. The cleanup worker treats that signal as idempotent success. Direct-upload preflight maps it to the generic client-safe UPLOAD_INCOMPLETE error; the exception message is never exposed to GraphQL clients.
For local replacement cleanup, gm-upload-old-claims/ is reserved internal storage. The built-in adapter's exact-delete contract assumes no application or external process mutates that namespace; GeneralManager's durable lease serializes framework workers. Storage integrations that cannot provide this exclusivity should not enable replacement deletion and should instead register an adapter with a backend-native atomic exact-delete operation.
general_manager.uploads.config.FileUploadPolicy dataclass ¶
Optional per-file-field policy values layered over global policy.
general_manager.uploads.config.FileInspection dataclass ¶
Bounded, credential-free content passed to a configured inspector.
general_manager.uploads.config.FileUploadConfigurationError ¶
Bases: ValueError
Raised when file upload configuration is invalid.
unknown_settings classmethod ¶
unknown_settings(names)
Build the error for unsupported configuration keys.
positive_integer classmethod ¶
positive_integer(name)
Build the error for a non-positive or non-integer limit.
maximum_integer classmethod ¶
maximum_integer(name, maximum)
Build the error for an integer above a backend protocol limit.
unsafe_path classmethod ¶
unsafe_path(name)
Build the error for a path that could escape its namespace.
invalid_database_alias classmethod ¶
invalid_database_alias()
Build the error for an unsupported database alias value.
non_empty_strings classmethod ¶
non_empty_strings(name)
Build the error for a malformed policy allowlist.
general_manager.uploads.public.register_upload_adapter ¶
register_upload_adapter(storage_class, factory)
Register one process-wide adapter factory for a Django storage class.
Registration is intended to happen during application startup, before any upload intents are created. The most-specific registered storage class wins; registering the same class twice is rejected by the registry.
general_manager.uploads.adapters.UploadAdapter ¶
Bases: Protocol
Bounded interface implemented by durable upload storage adapters.
supports_public_urls property ¶
supports_public_urls
Whether public URLs were explicitly enabled for this storage.
supports_direct classmethod ¶
supports_direct(storage)
Return whether the backend can safely accept direct uploads.
create_upload_instructions ¶
create_upload_instructions(
*,
stage_key,
upload_url,
content_type,
size,
checksum_sha256,
headers=None,
expires_in=900
)
Create client-safe transfer instructions for a private stage key.
inspect_staged ¶
inspect_staged(stage_key)
Inspect and return the immutable identity of staged bytes.
materialize ¶
materialize(stage_key, version, final_key, *, intent_id)
Conditionally materialize an exact version and return its actual key.
delete_stage ¶
delete_stage(stage_key, version=None)
Delete staged bytes, optionally constrained to an exact version.
private_download_url ¶
private_download_url(
key,
*,
expires_in,
version=None,
response_content_type=None,
response_content_disposition=None
)
Return a private download URL supported by the backend.
inspect_download ¶
inspect_download(key, version)
Return the exact retained object version available for download.
open_download ¶
open_download(key, version)
Open the same retained version after verifying its immutable identity.
general_manager.uploads.adapters.UploadFinalizationAdapter ¶
Bases: Protocol
Exact-version operations required by the post-commit saga.
inspect_materialized ¶
inspect_materialized(
final_key, source_version, *, intent_id
)
Return the exact intent-owned destination version.
delete_materialized ¶
delete_materialized(final_key, final_version, *, intent_id)
Delete only the exact destination version owned by intent_id.
delete_object ¶
delete_object(key, version)
Delete one exact non-staging object version or fail closed.
inspect_replaced_object ¶
inspect_replaced_object(key)
Inspect immutable identity for a potentially replaced object.
plan_replaced_object_claim ¶
plan_replaced_object_claim(key, version, *, cleanup_id)
Return a deterministic claim handle without accessing storage.
claim_replaced_object ¶
claim_replaced_object(key, claimed, *, cleanup_id)
Idempotently bind an old object to a persisted cleanup handle.
delete_claimed_object ¶
delete_claimed_object(claimed, *, cleanup_id)
Idempotently delete only an already-claimed object handle.
general_manager.uploads.adapters.ExactPublicDownloadAdapter ¶
Bases: Protocol
Optional adapter capability for immutable, non-credential public URLs.
public_download_url ¶
public_download_url(key, *, version)
Return a public URL bound to exactly version of key.
general_manager.uploads.adapters.ProxyUploadSink ¶
Bases: UploadAdapter, Protocol
Upload adapter that can accept a proxy stream through Django storage.
save_stage ¶
save_stage(
stage_key,
chunks,
*,
content_type,
checksum_sha256=None,
size=None
)
Stream and verify one proxy upload into staging.
general_manager.uploads.adapters.UploadInstructions dataclass ¶
Client-safe instructions for transferring one staged object.
general_manager.uploads.adapters.ClaimedObject dataclass ¶
Intent-owned exact object handle used by retry-safe cleanup.
general_manager.uploads.types.ObjectVersion dataclass ¶
Immutable identity and verified metadata for a staged object.
general_manager.uploads.types.UploadTransport ¶
Bases: StrEnum
Mechanism used to transfer staged bytes.
general_manager.uploads.types.StoredFileStatus ¶
Bases: StrEnum
Client-visible availability of a stored file.
general_manager.uploads.graphql_types.UploadToken ¶
Bases: Scalar
Opaque, non-empty upload token accepted by generated mutations.
general_manager.uploads.graphql_types.StoredFile ¶
Bases: ObjectType
Client-visible metadata for a stored file.
general_manager.uploads.graphql_types.StoredImage ¶
general_manager.uploads.errors.UploadError ¶
Bases: Exception
Base class for expected upload failures with stable client codes.
Every public upload exception listed above inherits UploadError(message: str | None = None), returns no value when raised, and exposes its stable code plus a safe default message. Passing a message changes the local exception text, but GraphQL and HTTP boundaries sanitize arbitrary messages and non-framework subclasses.
MeasurementScalar parses string inputs such as "12.5 m/s" into Measurement values and serializes stored measurements with the canonical Measurement string formatting; it does not preserve the caller's original text exactly. Invalid measurement text propagates the measurement module's validation errors during input coercion. BigIntScalar serializes large integers as strings so clients do not lose precision beyond GraphQL's built-in Int range; boolean values and non-coercible Python objects are rejected. Float and Decimal inputs are accepted for compatibility; current releases guarantee Python int(...) truncation toward zero for those inputs.
For pagination, treat the generated GraphQL pageInfo response field as the user-facing contract. totalCount is counted after permission filters, user filters, excludes, sorting, and grouping, but before page slicing. Supplying only one of page or pageSize defaults the missing value to page 1 or size 10 for slicing. Falsey explicit values such as page: 0 or pageSize: 0 follow the same fallbacks for slicing; currentPage is page || 1. The reported pageSize is the original argument value, not the effective slicing default, so it can be null when only page is supplied and 0 when pageSize: 0 is supplied. totalPages is 1 when the original pageSize is omitted or falsey. Negative page or pageSize values raise a GraphQL BAD_USER_INPUT error before slicing. Do not import generated/internal Python pagination classes directly.
general_manager.api.mutation.graph_ql_mutation ¶
graph_ql_mutation(_func=None, permission=None)
Register a synchronous function as a Graphene GraphQL mutation.
Supported forms are @graph_ql_mutation, @graph_ql_mutation(), @graph_ql_mutation(SomePermission), and @graph_ql_mutation(permission=SomePermission). Passing both a positional permission class and permission= is unsupported; the positional class is treated as the permission and replaces the keyword value.
Registration happens immediately when the decorator runs. A generated graphene.Mutation subclass is stored in GraphQL._mutations under the decorated function name converted by snake_to_camel: the first underscore-delimited segment is kept unchanged and later segments are title-cased. The original function object is returned so it remains directly callable.
The decorated function must provide type hints for all parameters except the parameter named info; info is skipped by name and may appear in any position. Parameters become GraphQL arguments. Optional[T] marks an argument as not required, default values are passed through as Graphene defaults, list[T] becomes a GraphQL list, GeneralManager parameters with no declared inputs or only one id input become ID arguments, and multi-input GeneralManager parameters become generated nested input objects. Runtime manager normalization preserves existing manager instances, returns None unchanged, constructs mapping inputs with manager_class(**value), and constructs non-mapping inputs with manager_class(value). For list[Manager] and List[Manager] arguments, each list item follows that same manager normalization before permission checks and resolver calls. Other supported Python annotations are mapped through GraphQL._map_field_to_graphene_base_type.
The return annotation creates output fields. A single type creates one output field named after that type with a lower-case first letter; a tuple return annotation creates one output field per tuple member. Duplicate output names are rejected. The generated mutation also includes a required success field. Resolver execution normalizes GeneralManager arguments, calls permission.check(normalized_kwargs, info.context.user) when a permission class is configured, calls the original function, and returns the generated mutation instance. Explicit GraphQLError instances are preserved. Validation and deliberately public errors retain their intended client behavior, while unexpected ordinary exceptions are sanitized and assigned correlation IDs by GraphQL._handle_graph_ql_error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
_func | FuncT | type[MutationPermission] | None | Decorated function for bare usage, or a positional | None |
permission | type[MutationPermission] | None | Optional permission class used to enforce access control on the normalized mutation arguments. | None |
Returns:
| Type | Description |
|---|---|
FuncT | Callable[[FuncT], FuncT] | The original function for bare usage, or a decorator that registers the |
FuncT | Callable[[FuncT], FuncT] | mutation and returns the original function. |
Raises:
| Type | Description |
|---|---|
MissingParameterTypeHintError | A non- |
MissingMutationReturnAnnotationError | The decorated function has no return annotation. |
InvalidMutationReturnTypeError | The return annotation is not a concrete type or supported type alias. |
DuplicateMutationOutputNameError | Two return values would expose the same output field name. |
graph_ql_mutation registers a synchronous function as a Graphene mutation at import time and returns the original function unchanged. Supported decorator forms are bare usage, call usage, positional MutationPermission, and permission=...; if both permission forms are supplied, the positional class is used. The generated mutation name is the function name converted by snake_to_camel: the first underscore-delimited segment is kept unchanged and later segments are title-cased. Duplicate generated names are first-writer-wins in GraphQL._mutations. Every non-info parameter needs a type annotation. Optional annotations make an argument nullable, defaults become Graphene defaults, list annotations become GraphQL lists, manager arguments with no declared inputs or only one id input become ID, and multi-input manager arguments become cached nested input objects keyed by manager module and qualified name. Runtime manager normalization preserves existing instances, leaves None unchanged, constructs mapping inputs with Manager(**value), and constructs non-mapping inputs with Manager(value). For list[Manager] and List[Manager] arguments, each list item follows that same normalization. Return annotations create output fields plus required success; tuple annotations create multiple output fields and duplicate field names are rejected. Type aliases expose the alias name while mapping through the target type. Runtime tuple results must contain exactly one value per annotated tuple output and are assigned to fields in annotation order. A count mismatch is sanitized as INTERNAL_SERVER_ERROR under the shared mutation boundary; internal mismatch details are not exposed. Resolver execution normalizes manager arguments before permission checks and before calling the original function.
At that boundary, explicit GraphQLError instances are preserved; ValidationError and PublicGraphQLError retain their intended public behavior; PermissionError receives the fixed safe denial; and every other ordinary Exception is sanitized and correlated for server logs.
general_manager.api.property.graph_ql_property ¶
graph_ql_property(func: T) -> GraphQLProperty
graph_ql_property(
*,
sortable: bool = False,
filterable: bool = False,
query_annotation: object | None = None,
cache: GraphQLPropertyCache = "run",
timeout: int | None = None,
warm_up: bool = False
) -> Callable[[T], GraphQLProperty]
graph_ql_property(
func=None,
*,
sortable=False,
filterable=False,
query_annotation=None,
cache="run",
timeout=None,
warm_up=False
)
Decorate a resolver as a GraphQL-exposed cached property.
The decorator supports both @graph_ql_property and @graph_ql_property(...) forms. The wrapped resolver is called with the manager instance and must declare a return annotation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func | Callable[..., object] | None | Resolver function when the decorator is used without parentheses. | None |
sortable | bool | Whether generated GraphQL fields may expose sorting for this property. | False |
filterable | bool | Whether generated GraphQL fields may expose filtering for this property. | False |
query_annotation | object | None | Opaque ORM annotation object or callable consumed by database bucket query construction. | None |
cache | GraphQLPropertyCache | Cache scope. | 'run' |
timeout | int | None | Timeout in seconds. Required with | None |
warm_up | bool | Whether proactive GraphQL warm-up may precompute this property. Only dependency and timeout caches support warm-up. Runtime truthiness is used; values are not coerced to | False |
Returns:
| Type | Description |
|---|---|
GraphQLProperty | Callable[[T], GraphQLProperty] | A |
GraphQLProperty | Callable[[T], GraphQLProperty] | that converts one resolver function into a |
Raises:
| Type | Description |
|---|---|
GraphQLPropertyReturnAnnotationError | If the resolver has no return annotation. |
GraphQLPropertyWarmUpConfigurationError | If |
GraphQLPropertyTimeoutConfigurationError | If timeout configuration is missing for |
ValueError | Propagated from the shared cache decorator for an unsupported runtime cache scope. |
TypeError | Raised by ordinary Python call mechanics for unsupported decorator forms, for example non-callable |
Validation of configured decorator options occurs when the returned decorator wraps a resolver function, not when graph_ql_property(...) is called without func. Passing func=None explicitly is the same as omitting func and returns a configured decorator.
general_manager.api.property.GraphQLPropertyReturnAnnotationError ¶
Bases: TypeError
Raised when a GraphQLProperty is defined without a return type annotation.
__init__ ¶
__init__()
Indicates a GraphQLProperty-decorated function is missing a return type annotation.
This exception is raised to signal that a property resolver intended for use with GraphQLProperty must have an explicit return type hint. The exception message is: "GraphQLProperty requires a return type hint for the property function."
general_manager.api.property.GraphQLPropertyTimeoutConfigurationError ¶
general_manager.api.property.GraphQLPropertyWarmUpConfigurationError ¶
Bases: ValueError
Raised when warm-up is configured for an unsupported cache scope.
general_manager.api.registry.GraphQLRegistry dataclass ¶
Snapshot of the GraphQL class's mutable registry state.
All fields mirror the ClassVars on the GraphQL class. An instance can be obtained at any time via GraphQL.get_registry_snapshot(), or a fresh empty instance can be used to reset state via GraphQL.reset_registry(). Snapshot dictionaries are shallow copies: mutating the dictionary on a snapshot does not change the GraphQL class registry, but the generated Graphene classes and field/resolver objects inside those dictionaries are shared.
GraphQLRegistry is the public snapshot shape returned by GraphQL.get_registry_snapshot(). Registry dictionaries in a snapshot are shallow copies, so adding or removing keys on the snapshot does not mutate the live GraphQL registries. The generated Graphene classes, fields, and resolver objects stored inside those dictionaries are shared with the live registry. Use GraphQL.reset_registry() in tests before rebuilding a schema; it clears generated query, mutation, subscription, search, capability, and manager registries. Generated registry entries are Graphene classes, fields, and callables, so treat them as opaque objects unless you are writing integration tests around schema assembly.
GraphQL.create_graphql_interface(MyManager) registers the manager's Graphene object type, list/detail queries, relation filters, pagination wrapper, and subscription fields. It returns without side effects when the manager has no Interface, and it adds a capabilities field only when the manager exposes GraphQL permission capabilities. GraphQL.create_graphql_mutation(MyManager) also returns without side effects when the manager has no Interface; otherwise it registers create/update/delete mutations only for operations supported by the manager interface through an overridden base method or an advertised capability. Mutation factory results of None are skipped.
Filter helper inputs are mapping-shaped lookup objects or JSON object strings; malformed JSON and decoded non-object JSON normalize to empty filters. Search filter helpers additionally accept the list-of-filter-object form used by search UIs, where each item contains field plus optional op, value, or values. When values is present and op is omitted, search filter parsing uses the __in lookup, and values takes precedence over value. JSON strings may also decode to that list form. The lookup key is field when op is blank and field__op otherwise; operator names are not validated during parsing. Malformed list entries are ignored.
Permission constraints used by generated resolvers are ordered alternatives. Each entry may contain a filter mapping, an exclude mapping, both, or neither. Search merges each alternative's filter mapping over the user filter mapping before the backend query; matching exclude mappings remain paired with that alternative for the per-instance authorization pass. Empty constraints such as {}, {"filter": {}}, and {"exclude": {}} therefore represent one unrestricted alternative relative to the user filters. If the read permission plan still requires instance checks, that unrestricted alternative must still pass can_read_instance().
GraphQL relation filters are flattened before backend/query execution. Direct relations are attribute metadata entries with relation_kind="direct" and flatten to the relation's filter_lookup prefix. Collection relations use relation_kind="collection" and expose nested any and none inputs: any becomes positive filters, none becomes excludes, and nested excludes under none invert back into positive filters. Generated relation filter input types are cached only in the caller-owned registry under a name containing the manager class and remaining relation depth; rebuild them with a fresh registry when metadata or depth changes. Equality-style id, id__exact, and list/tuple-shaped id__in filters are cast with the manager interface's id input field when one is available; other iterable shapes are returned unchanged. Subscription identifiers and signal payloads are object-valued manager identification mappings and are copied before channel dispatch.
The optional global me field is added only when GRAPHQL_GLOBAL_CAPABILITIES_PROVIDER points to a provider class. Provider graphql_fields entries may be Graphene fields or Python types; provider methods named resolve_<field> are used when present, otherwise the field is read from the current request user. Provider graphql_capabilities entries that are not GraphQLPermissionCapability instances are ignored.
GeneralManager-generated mutations and mutations created with @graph_ql_mutation convert exceptions through the shared safe error mapper. This boundary does not intercept arbitrary third-party GraphQL resolvers that bypass GeneralManager's generated and decorator mutation paths.
Existing GraphQLError instances are trusted and returned unchanged, preserving object identity, message, and the full existing extensions mapping. PublicGraphQLError is the stable application contract for an intentional public failure with a safe message and stable application code; import it from general_manager.api.
Django ValidationError remains public validation output. Unstructured errors preserve Django's rendered validation message with code BAD_USER_INPUT. When a ValidationError has message_dict, the mutation error uses the generic message Validation failed. and includes structured details in extensions.fieldErrors and extensions.nonFieldErrors. Generated mutations use schema-aware input field names, so a Python/Django field such as project_phase_type is reported as projectPhaseType, and relation raw-id implementation keys such as customer_id are reported as their exposed GraphQL input field, such as customer. Decorator-created mutations map structured validation field keys with snake_to_camel.
PermissionError returns the fixed message Permission denied. with code PERMISSION_DENIED, unless application code deliberately raises an explicit public error. Every other ordinary exception crossing these boundaries, including ValueError, returns exactly An internal server error occurred. with code INTERNAL_SERVER_ERROR and an opaque errorId. Server logs retain the original exception details and traceback with the matching error_id; failures while rendering the original exception are also kept out of the client response.
Migrate client-facing ValueError uses to PublicGraphQLError, or to Django ValidationError for validation (use structured validation errors for field details). Public messages must never contain secrets or other internal details.
See the safe mutation error recipe for a directly usable resolver and client-handling pattern.
general_manager.api.graphql_view.GeneralManagerGraphQLView ¶
Bases: GraphQLView
Graphene-Django view wrapper with GeneralManager metrics instrumentation.
The wrapper preserves Graphene-Django response behavior while adding optional resolver timing middleware and request/error metrics. Metrics failures are logged at debug level and do not alter the GraphQL response.
get_middleware ¶
get_middleware(request)
Return Graphene middleware with resolver timing added when enabled.
The base Graphene middleware value is returned unchanged when resolver timing metrics are disabled. If the base value is None and timing is enabled, a one-item list containing GraphQLResolverTimingMiddleware is returned. Otherwise the timing middleware is appended after existing middleware so normal project middleware runs first. Existing timing middleware instances are not duplicated, making repeated calls idempotent when the base middleware stack is stable. request is passed through to Graphene-Django without inspection by this wrapper.
get_response ¶
get_response(request, data, show_graphiql=False)
Execute one GraphQL request and return (encoded_response, status_code).
The method mirrors Graphene-Django response shaping: no execution result returns (None, 200), request-level GraphQL errors without a path return status 400, and other successful executions return status 200. Batched responses include id and status fields from Graphene's request metadata. request, data, query parsing, operationName/ operation-name handling, variable coercion, malformed query handling, GraphiQL behavior, concrete encoded response type, and concrete batch response item shape are delegated to Graphene-Django. This wrapper only guarantees the outer (encoded_response, status_code) return shape and the GeneralManager metrics/rollback side effects documented here.
Side effects
Ensures the calculation run context is active during execution, marks Django rollback when Graphene-Django sets MUTATION_ERRORS_FLAG on the request or when request-level GraphQL errors are present, and records request/error metrics when metrics are enabled. Metrics are recorded for GraphiQL requests when Graphene returns an execution result.
Partial GraphQL errors count as metrics status error; executions without errors count as success. Metrics backend failures, label normalization failures, and error-code extraction failures are swallowed and logged by _record_metrics. Exceptions from Graphene request parsing, execution, formatting, encoding, rollback handling, or the calculation run context propagate.
general_manager.api.graphql_warmup.GraphQLWarmUpManagerClass ¶
Bases: Protocol
Class-level manager surface required for all-entry GraphQL warm-up.
The object is expected to be an importable manager class with a bound all() method that yields manager instances. Local or nested classes can be evaluated but cannot produce reconstructable warm-up recipes.
general_manager.api.graphql_warmup.GraphQLWarmUpSummary dataclass ¶
Counts from one all-entry warm-up execution.
evaluated counts successful property evaluations, not manager instances. failed counts property evaluations that raised and were logged by the executor. recipes counts successfully built recipe payloads handed to the registry for persistence.
general_manager.api.graphql_warmup.warmable_graphql_properties ¶
warmable_graphql_properties(
manager_class, property_names=None
)
Return warm-up-eligible GraphQL properties for a manager class.
A property is warmable only when the manager has an Interface with get_graph_ql_properties(), the descriptor is a GraphQLProperty, the property opted in with warm_up=True, and its cache scope is "dependency" or "timeout". property_names=None allows every warmable property; otherwise the provided names are matched against the keys returned by get_graph_ql_properties() and used as a global allow-list. Unknown names, non-GraphQL descriptors, malformed mapping values, and non-warmable cache scopes are ignored. Duplicate names in property_names have no additional effect. Missing Interface or missing get_graph_ql_properties() returns an empty dictionary.
Raises:
| Type | Description |
|---|---|
TypeError | If |
Exception | Propagates errors raised by |
general_manager.api.graphql_warmup.warm_up_graphql_properties ¶
warm_up_graphql_properties(
manager_classes=None, property_names=None
)
Warm opted-in GraphQL properties by enumerating each manager's .all().
Returns zero counts when GRAPHQL_WARMUP_ENABLED is false. When manager_classes=None, managers come from the GraphQL manager registry in registry order; otherwise the supplied iterable is consumed in order and duplicate classes are processed repeatedly. property_names is applied to every manager. The executor batches instances by GRAPHQL_WARMUP_BATCH_SIZE, logs once per manager after GRAPHQL_WARMUP_WARNING_ITEMS_PER_MANAGER entries, isolates property evaluation failures by logging and incrementing failed, and continues with later properties/managers. Local or nested manager classes are evaluated and counted, but they cannot be imported by future workers, so their successful property reads do not create recipes.
Exceptions from manager enumeration, recipe construction, recipe registry writes, non-mapping instance identification, invalid manager surfaces, or settings access propagate.
general_manager.api.graphql_warmup.warm_up_graphql_recipe ¶
warm_up_graphql_recipe(cache_key)
Reconstruct and warm one recipe-backed GraphQL property.
Returns False when warm-up is disabled, the recipe is missing or version-incompatible, the per-recipe lock is already held, the manager lacks a usable GraphQL property, or reconstruction/evaluation/recipe persistence raises inside the guarded warm-up attempt. Those guarded failures are logged with the cache key. Dependency recipes re-run the descriptor path in a CalculationRunContext; timeout recipes refresh the cached value directly without evicting the previous value first. cache_key is validated before the enabled setting is checked, so invalid key types raise even when warm-up is disabled.
Raises:
| Type | Description |
|---|---|
TypeError | If |
Exception | Propagates lock acquisition and lock release failures. |
general_manager.api.graphql_warmup.refresh_due_graphql_warmup_recipes ¶
refresh_due_graphql_warmup_recipes(limit=None)
Refresh due timeout recipes and return the number refreshed.
Returns 0 when warm-up is disabled. limit=None refreshes every due key; 0 and negative limits refresh no keys; positive limits cap the sorted due key list from the registry. limit is validated before the enabled setting is checked, so invalid limits raise even when warm-up is disabled. The returned integer counts successful recipe refreshes only, not due keys found or attempted.
Raises:
| Type | Description |
|---|---|
TypeError | If |
Exception | Propagates registry due-key lookup, lock acquisition, and lock release failures. |
general_manager.api.graphql_warmup.enqueue_graphql_warmup ¶
enqueue_graphql_warmup(manager_classes=None)
Enqueue all-entry warm-up through the built-in task adapter.
Returns False when GRAPHQL_WARMUP_ENABLED is false. Otherwise delegates to dispatch_graphql_warmup(...) and returns whether the task adapter accepted the enqueue request; it does not mean warm-up work has completed. Task-adapter validation errors and iterator errors propagate. Enqueue failures handled by the adapter are returned as False.
general_manager.api.graphql_warmup.enqueue_graphql_recipe_warmup ¶
enqueue_graphql_recipe_warmup(cache_keys)
Enqueue recipe re-warm through the built-in task adapter.
Returns False when warm-up is disabled, re-warm after invalidation is disabled, or the deduplicated key list is empty. Duplicate keys are removed while preserving first-seen order before dispatch. A True result means the task adapter accepted the enqueue request, not that recipe work has completed. The settings gates are checked before cache_keys is consumed; when both gates are enabled, the iterable is consumed once, entries are validated as strings before dispatch, and adapter enqueue failures are returned as False.
Raises:
| Type | Description |
|---|---|
TypeError | If |
Exception | Propagates errors raised while iterating |
general_manager.api.graphql_warmup.graphql_warmup_enabled ¶
graphql_warmup_enabled()
Return whether framework-owned GraphQL warm-up behavior is enabled.
Missing GRAPHQL_WARMUP_ENABLED defaults to False. Otherwise the value is read through GeneralManager's settings resolver and converted with normal Python truthiness.
general_manager.api.graphql_warmup_registry.GraphQLWarmUpRecipe dataclass ¶
Information required to reconstruct one warmed GraphQL property entry.
cache_key is the warmed value's cache key. manager_path, property_name, and identification are reconstruction data consumed by the warm-up executor. Identification values only need to be serializable by the configured Django cache backend. timeout is a Django cache timeout in seconds and is meaningful only for cache="timeout". refresh_at should be timezone-aware for timeout recipes. The registry stores naive refresh_at values without validation; due checks compare them with normal Python datetime rules and may raise TypeError if callers mix naive and aware values. Timeout recipes with refresh_at=None are not considered due. search_date retains the manager snapshot that the executor must re-enter.
general_manager.api.graphql_warmup_registry.GraphQLWarmUpRecipeLock dataclass ¶
Token proving ownership of one recipe warm-up attempt.
key is the cache lock key and token is the value that must still be stored there before release deletes the lock.
general_manager.api.graphql_warmup_registry.GraphQLWarmUpRecipeLockTimeoutError ¶
Bases: TimeoutError
Raised when an index update lock cannot be acquired within the wait budget.
general_manager.api.graphql_warmup_registry.register_graphql_warmup_recipe ¶
register_graphql_warmup_recipe(
recipe, *, cache_backend=django_cache
)
Persist one warm-up recipe and update registry indexes.
Registering an existing cache_key overwrites the stored recipe. The key is always present in the main recipe index after registration. It is present in the timeout index only when recipe.cache == "timeout" and recipe.refresh_at is not None; otherwise any stale timeout-index reference for the same key is removed.
Raises:
| Type | Description |
|---|---|
GraphQLWarmUpRecipeLockTimeoutError | If an index update lock cannot be acquired. |
Exception | Propagates cache backend |
general_manager.api.graphql_warmup_registry.get_graphql_warmup_recipe ¶
get_graphql_warmup_recipe(
cache_key, *, cache_backend=django_cache
)
Return the recipe for cache_key, if it exists and matches this version.
Missing, malformed, and version-incompatible cache payloads return None without pruning index entries. For this reader, malformed means the cache payload is not a GraphQLWarmUpRecipe instance. Dataclass field values are trusted after construction; invalid field combinations may fail later when a caller uses the recipe.
general_manager.api.graphql_warmup_registry.get_graphql_warmup_recipes ¶
get_graphql_warmup_recipes(
cache_keys, *, cache_backend=django_cache
)
Return existing recipes for cache_keys keyed by cache key.
Duplicate requested keys are read at most once. Missing, non-recipe, and version-incompatible payloads are omitted.
general_manager.api.graphql_warmup_registry.graphql_warmup_recipe_keys ¶
graphql_warmup_recipe_keys(*, cache_backend=django_cache)
Return known recipe cache keys in deterministic order.
This reads the main index only. Stale entries whose recipe payload is missing or version-incompatible are not filtered here.
general_manager.api.graphql_warmup_registry.due_timeout_graphql_warmup_recipe_keys ¶
due_timeout_graphql_warmup_recipe_keys(
*, now=None, limit=None, cache_backend=django_cache
)
Return timeout recipe keys whose refresh time has arrived.
now defaults to the current UTC time. limit=None returns every due key; otherwise the non-negative limit is applied after sorting by (refresh_at, cache_key), so limit=0 returns an empty tuple. Entries in the timeout index are validated against their stored recipe: missing, non-recipe, version-incompatible, non-timeout, and refresh_at=None entries are removed from the timeout index and not returned. Other dataclass field values are trusted here. Naive datetimes are compared using normal Python datetime rules and may raise TypeError when compared with aware now values.
Raises:
| Type | Description |
|---|---|
GraphQLWarmUpRecipeLockTimeoutError | If pruning a stale timeout-index member cannot acquire the index lock. Index updates wait |
Exception | Propagates cache backend failures. |
general_manager.api.graphql_warmup_registry.delete_graphql_warmup_recipe ¶
delete_graphql_warmup_recipe(
cache_key, *, cache_backend=django_cache
)
Remove one recipe and all index references to it.
Missing recipes are ignored. Index references are removed idempotently.
Raises:
| Type | Description |
|---|---|
GraphQLWarmUpRecipeLockTimeoutError | If an index update lock cannot be acquired. Index updates wait |
Exception | Propagates cache backend failures. |
general_manager.api.graphql_warmup_registry.acquire_graphql_warmup_recipe_lock ¶
acquire_graphql_warmup_recipe_lock(
cache_key,
*,
timeout=DEFAULT_RECIPE_LOCK_TIMEOUT,
cache_backend=django_cache
)
Acquire a best-effort per-recipe execution lock.
timeout is the cache lock TTL in seconds, not an acquisition wait budget. The function performs one cache add(...) attempt and returns None on contention.
Raises:
| Type | Description |
|---|---|
Exception | Propagates cache backend |
general_manager.api.graphql_warmup_registry.release_graphql_warmup_recipe_lock ¶
release_graphql_warmup_recipe_lock(
lock, *, cache_backend=django_cache
)
Release a recipe lock without deleting another worker's newer lock.
Missing, expired, or token-mismatched locks are ignored. Redis-like backends with client.get_client() and make_key(...) use a compare-and-delete Lua script; other backends use a process-local mutex and token re-check before delete(...).
general_manager.api.graphql_warmup_tasks.configure_graphql_warmup_beat_schedule_from_settings ¶
configure_graphql_warmup_beat_schedule_from_settings(
django_settings=settings,
)
Register the periodic timeout refresh task in Celery Beat.
Settings may live either in GENERAL_MANAGER or as top-level Django settings; nested keys take precedence over top-level settings when both are present. Returns False when GRAPHQL_WARMUP_BEAT_ENABLED is false, Celery is not importable, or the module-level Celery current_app is None. When configured, the helper writes or replaces one beat schedule entry named GRAPHQL_WARMUP_BEAT_SCHEDULE_KEY. The entry has task path general_manager.api.graphql_warmup_tasks.refresh_due_graphql_warmup_recipes_task, a float seconds schedule, no args or kwargs, and options={"queue": GRAPHQL_WARMUP_QUEUE}. GRAPHQL_WARMUP_BEAT_INTERVAL_SECONDS is parsed as a positive integer and defaults to 60 when missing, non-positive, or invalid. Existing mapping beat schedule entries are copied and preserved; non-mapping schedule values are replaced by a fresh schedule dictionary. Boolean settings accept normal truthiness plus string false values "", "0", "false", "no", "off", "none", and "null", and string true values "1", "true", "yes", and "on"; unrecognized non-empty strings are treated as enabled.
Exceptions from Celery app configuration access or assignment propagate.
general_manager.api.graphql_warmup_tasks.warm_up_graphql_properties_task ¶
warm_up_graphql_properties_task(manager_paths=None)
Resolve optional manager paths and run all-entry GraphQL warm-up.
manager_paths=None warms every discoverable manager. Otherwise each dotted path is resolved before calling warm_up_graphql_properties(...). Import errors and executor errors propagate to the Celery worker/caller. The return value contains evaluated successful property reads, failed property reads that raised inside the executor, and recipes persisted warm-up recipes; no other keys are added by this adapter. An empty path list delegates an empty manager list and returns zero counts unless the executor is patched/customized.
Raises:
| Type | Description |
|---|---|
TypeError | If |
general_manager.api.graphql_warmup_tasks.warm_up_graphql_recipes_task ¶
warm_up_graphql_recipes_task(cache_keys)
Warm recipe-backed cache entries with per-key failure isolation.
Each cache key is attempted in list order. warm_up_graphql_recipe(...) returning True increments the returned count; False does not. Exceptions for one key are logged with the cache key and do not stop later keys. Empty input returns 0.
Raises:
| Type | Description |
|---|---|
TypeError | If |
general_manager.api.graphql_warmup_tasks.refresh_due_graphql_warmup_recipes_task ¶
refresh_due_graphql_warmup_recipes_task(limit=None)
Refresh due timeout recipes and return the number refreshed.
limit is forwarded unchanged to refresh_due_graphql_warmup_recipes(...). None means no cap, 0 and negative values refresh no recipes, and positive values cap the sorted due-key list in the registry. Exceptions from the executor propagate to the Celery worker/caller.
Raises:
| Type | Description |
|---|---|
TypeError | If |
general_manager.api.graphql_warmup_tasks.dispatch_graphql_warmup ¶
dispatch_graphql_warmup(manager_classes=None)
Dispatch all-entry warm-up to Celery when available.
Returns False when Celery is unavailable, when every supplied manager class lacks an importable worker path, when the supplied iterable is empty, or when enqueueing raises. When manager_classes is None, the task is enqueued with None so the worker warms all managers. Local/nested manager classes are skipped because workers cannot import them. Duplicate import paths are removed while preserving first-seen order.
Raises:
| Type | Description |
|---|---|
TypeError | If an item in |
Exception | Propagates errors raised while iterating |
general_manager.api.graphql_warmup_tasks.dispatch_graphql_recipe_warmup ¶
dispatch_graphql_recipe_warmup(cache_keys)
Dispatch recipe re-warm to Celery when available.
Duplicate cache keys are removed while preserving first-seen order. Returns False when Celery is unavailable, the deduplicated key list is empty, or enqueueing raises.
Raises:
| Type | Description |
|---|---|
TypeError | If |
Exception | Propagates errors raised while iterating |
general_manager.api.graphql.SubscriptionEvent dataclass ¶
Payload delivered to GraphQL subscription resolvers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item | object | None | Changed manager object or | required |
action | GraphQLSubscriptionAction | Open channel-layer action string. Generated detail subscriptions first yield | required |
general_manager.api.graphql_subscription_consumer.GraphQLSubscriptionContext dataclass ¶
Request context passed to GraphQL subscription resolvers.
Attributes:
| Name | Type | Description |
|---|---|---|
user | object | None | Authenticated user from the Channels scope, or |
headers | dict[str, str] | ASGI headers decoded as Latin-1 text. Duplicate header names keep the last value seen in the scope. |
scope | Mapping[str, object] | Original Channels scope for callers that need transport details. |
connection_params | Mapping[str, object] | Object payload from |
general_manager.api.graphql_subscription_consumer.GraphQLSubscriptionConsumer ¶
Bases: AsyncJsonWebsocketConsumer
Websocket consumer implementing the graphql-transport-ws protocol for GraphQL subscriptions.
The consumer streams results produced by the dynamically generated GeneralManager GraphQL schema. It accepts connection_init, ping, subscribe, and complete messages, sends protocol envelopes (connection_ack, pong, next, error, and complete), and closes malformed protocol messages without custom close reason text and with the close codes documented by the public GraphQL subscription guide: 4400 for invalid message shape or type, 4401 for subscribe before acknowledgement, 4403 for invalid subscribe id/payload shape, and 4429 for repeated connection_init.
connect async ¶
connect()
Initialize connection state and accept the WebSocket, preferring the "graphql-transport-ws" subprotocol when offered.
Sets up initial flags and containers used for subscription management (connection_acknowledged, connection_params, active_subscriptions). The connection is accepted with "graphql-transport-ws" when the client offers it, otherwise it is still accepted without a selected subprotocol.
disconnect async ¶
disconnect(code)
Perform cleanup on WebSocket disconnect by cancelling and awaiting active subscription tasks and clearing the subscription registry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code | int | WebSocket close code received from the connection. | required |
Notes
Awaiting cancelled tasks suppresses asyncio.CancelledError so task cancellation completes silently.
receive_json async ¶
receive_json(content, **_)
Route an incoming graphql-transport-ws protocol message to the corresponding handler based on its "type" field.
Valid message types: "connection_init", "ping", "subscribe", and "complete". Messages with an unrecognized or missing "type" cause the connection to be closed with code 4400. Non-object messages also close with 4400. JSON parse failures are handled by the Channels JSON consumer before this method receives content.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
content | object | The received JSON message. It must be an object with a string | required |
general_manager.api.remote_invalidation_client.RemoteInvalidationClient ¶
Listen for websocket invalidation events and clear remote-manager caches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
manager_classes | Sequence[type[GeneralManager]] | Non-empty sequence of | required |
connection_factory | ConnectionFactory | None | Optional callable receiving a websocket URL and returning, or resolving to, an object with async | None |
reconnect_delay | float | Delay in seconds between background listener reconnect attempts after a listener error. | 1.0 |
Raises:
| Type | Description |
|---|---|
RemoteInvalidationConfigurationError | If no managers are supplied, a manager is not backed by an enabled |
For each manager class, the client reads _interface first and then Interface to find the bound interface. Websocket URLs come from interface_cls.get_websocket_invalidation_url(), and websocket support is checked through interface_cls.websocket_invalidation_enabled. The client groups managers by websocket URL and deduplicates interface classes by class identity within each URL, preserving the first occurrence order from manager_classes. Decoded invalidation payloads are dispatched to each interface's synchronous handle_invalidation_event(...) method in that stored interface order. The payload is the decoded mapping as received from the websocket; event field semantics are owned by RemoteManagerInterface.handle_invalidation_event(...). listen_once() waits for the first completed receive task across the configured URLs, cancels pending receive tasks, and gathers those pending tasks with return_exceptions=True. When any completed task raises, it closes cached connections for every configured URL and re-raises that exception; if more than one task completed, asyncio.gather determines which exception is raised. Handler exceptions are not caught by the client. Handlers are called synchronously; awaitable handler returns are not awaited. The listen_once() return count intentionally uses normal truthiness, so a handler that returns None is not counted. run() is intended as a long-running coroutine. It starts persistent listener tasks and always calls close() in its finally block when the gather exits, including external cancellation. It can create websocket connections lazily, but it does not call optional connection connect() hooks itself. This is intentional: call :meth:connect before :meth:run when a connection object needs that hook. close() is safe to call repeatedly during normal shutdown: it marks the client closed, cancels any current listener tasks, and closes any cached connections. It does not directly cancel one-off listen_once() receive tasks that are already waiting; those tasks finish according to their connection's receive/close behavior.
listen_once async ¶
listen_once()
Receive and dispatch a single invalidation event.
Returns:
| Type | Description |
|---|---|
int | Number of configured interfaces whose |
int |
|
Raises:
| Type | Description |
|---|---|
RemoteInvalidationConfigurationError | If a received payload is not a JSON object or decoded mapping. |
JSONDecodeError | If a string/bytes websocket message is not valid UTF-8 JSON. Bytes are decoded with UTF-8 before JSON parsing. |
UnicodeDecodeError | If a bytes websocket message is not valid UTF-8. |
Exception | Propagates connection factory, receive, close, and interface dispatch errors. |
general_manager.api.remote_invalidation.remote_invalidation_group_name ¶
remote_invalidation_group_name(config)
Return the channel-layer group name for a RemoteAPI websocket resource.
The group name is gm.remote.<base-path>.<resource-name>, where slashes in config.base_path are stripped and converted to dots. The helper has no side effects and does not check whether websocket invalidation is enabled. Repeated internal slashes produce repeated dots, an empty base path produces an empty group segment, and Channels group-name length/character constraints are not enforced here. Attribute errors from malformed config objects propagate.
general_manager.api.remote_invalidation.emit_remote_invalidation ¶
emit_remote_invalidation(
sender,
*,
instance=None,
identification=None,
action,
**_
)
Emit or queue a websocket invalidation for a RemoteAPI-enabled manager.
Returns without sending when the manager has no RemoteAPI config, websocket invalidation is disabled, or Channels has no configured channel layer. Identification is taken from the explicit mapping first, then from instance.identification when an instance is provided; missing identification is sent as None. The action string is forwarded without validation. UUID, date, and datetime identification values are serialized to strings, and other non-JSON values fall back to str(value).
During a bulk notification batch, the receiver queues one resource-wide refresh payload and returns without resolving row identification or creating an immediate async bridge. Outside a batch, it sends one row-level payload through async_to_sync(channel_layer.group_send). Payload fields are type ("gm.remote.invalidation"), protocol_version, base_path, resource_name, action, identification, and event_id as a UUID4 string. Identification serialization is shallow; nested lists or mappings fall back to str(value). Missing instance.identification, pathological str(value) errors, and channel-layer send errors propagate.
general_manager.api.remote_invalidation.RemoteInvalidationConsumer ¶
Minimal ASGI websocket consumer for remote invalidation events.
__call__ async ¶
__call__(scope, receive, send)
Accept a remote invalidation websocket and forward channel-layer events.
The consumer closes with 4404 for unknown or disabled resources, 4406 for protocol-version mismatches, and 1011 when no channel layer is available. It accepts matching connections, subscribes to the resource group, sends JSON invalidation payloads, and discards the channel in a finally block. The generated route supplies scope["url_route"] ["kwargs"]["base_path"] and ["resource_name"]; the optional version query string parameter is compared with the resource protocol version. A missing version is accepted, multiple values use the first parsed value, and invalid UTF-8 query strings propagate decoding errors. Non-disconnect inbound websocket messages are ignored while the consumer keeps listening.
Outbound JSON messages include protocol_version, base_path, resource_name, action, identification, and event_id in a websocket.send frame with a JSON text payload. Extra channel-layer event fields are ignored; missing required event fields raise KeyError. Client disconnect exits the loop and cleans up without raising. Missing route kwargs, channel-layer, receive, send, and JSON errors propagate.
general_manager.api.remote_invalidation.ensure_remote_invalidation_route ¶
ensure_remote_invalidation_route(manager_classes)
Install RemoteAPI websocket invalidation routes into the configured ASGI app.
Returns without changes when ASGI_APPLICATION is missing, does not contain a dotted module attribute, Channels is unavailable, the ASGI module is unavailable during reentrant population, or no manager has websocket invalidation enabled. Existing generated routes for the same RemoteAPI resource are preserved. Import errors other than reentrant population and router construction errors propagate.
Generated paths are <base_path>/ws/<resource_name>/? with an optional trailing slash. The helper is idempotent for the same (base_path, resource_name) pair using the resolved config values without additional normalization; changed config for an existing key is preserved unchanged. Routes append after existing websocket patterns. Missing or non-mutable websocket_urlpatterns are replaced with a new list. The helper supports ASGI modules that expose either a Channels application_mapping router or a plain HTTP application attribute named by ASGI_APPLICATION.
general_manager.api.remote_invalidation.clear_remote_invalidation_routes ¶
clear_remote_invalidation_routes()
Remove generated RemoteAPI websocket invalidation routes from the ASGI app.
GraphQL or user-defined websocket routes are preserved. Generated routes are detected by the _general_manager_remote_ws marker or the _general_manager_remote_ws_key marker installed by ensure_remote_invalidation_route(). All generated RemoteAPI invalidation routes in the configured ASGI module are removed regardless of path or resource shape while preserving the order of remaining routes. Returns without changes when ASGI settings, Channels, the ASGI module, or mutable sequence websocket patterns are unavailable. Router reconstruction errors propagate.
general_manager.api.remote_api.RemoteAPIConfig dataclass ¶
Normalized opt-in REST exposure settings for one manager.
get_remote_api_config() creates this value from a manager's nested RemoteAPI declaration. base_path defaults to /gm and is normalized to lowercase slug path segments with one leading slash and no trailing slash; root, empty, double-slash, and non-slug paths are rejected. resource_name is required when enabled, surrounding slashes are stripped before slug validation, protocol_version defaults to "v1", at least one operation flag must be true, and websocket invalidation requires at least one mutation operation. identifier_type is extracted from the interface id input when available and item views coerce URL identifiers only when it is exactly int; every other type leaves identifiers as strings.
general_manager.api.remote_api.add_remote_api_urls ¶
add_remote_api_urls(manager_classes)
Append generated RemoteAPI URL patterns to the configured root URLconf.
Generated routes are added in query, item, then create order when the corresponding operation is enabled. Existing generated routes with the same route key are skipped, so repeated startup registration is idempotent. Duplicate exposures are rejected while the registry is built before any new route from that registry pass is appended. If settings.ROOT_URLCONF is not set, this helper returns without changes.
general_manager.api.remote_api.clear_remote_api_urls ¶
clear_remote_api_urls()
Remove only URL patterns marked as generated RemoteAPI routes.
RemoteAPIConfig is the normalized server-side REST exposure for managers that define RemoteAPI.enabled = True. base_path defaults to /gm, is normalized with one leading slash and no trailing slash, rejects root or empty paths, rejects empty // segments, and requires lowercase slug path segments. resource_name is required; surrounding slashes are stripped before validation, and the remaining value must be a lowercase slug. At least one of allow_filter, allow_detail, allow_create, allow_update, or allow_delete must be true, and websocket_invalidation=True requires one of the mutation operations. Duplicate (base_path, resource_name) exposures raise RemoteAPIConfigurationError. When the manager interface declares an id input typed as int, URL item identifiers are coerced with int(identifier). The coercion check is exactly identifier_type is int; compatible subclasses or other numeric types leave URL identifiers as strings.
add_remote_api_urls(manager_classes) imports settings.ROOT_URLCONF, builds the enabled RemoteAPI registry, and appends generated URL patterns in query, item, then create order. Repeated calls skip already marked generated routes for the same route key. It returns without changes when ROOT_URLCONF is unset. clear_remote_api_urls() removes only URL patterns marked as generated RemoteAPI routes and also returns without changes when ROOT_URLCONF is unset.
RemoteAPI views accept an optional X-General-Manager-Protocol-Version header; when present it must match the configured protocol version. Request bodies must be empty or JSON objects. POST <base>/<resource>/query accepts optional filters, excludes, ordering, page, and page_size. It starts with manager_cls.all(), applies bucket.filter(**filters) only when filters is truthy, applies bucket.exclude(**excludes) only when excludes is truthy, then applies ordering, computes total_count, and finally slices only for positive integer page and page_size; invalid pagination values are ignored. ordering may be one field name or an iterable of field names; entries prefixed with - sort descending and are applied in reverse order so multi-key ordering is stable with bucket chaining. GET, PATCH, and DELETE <base>/<resource>/<identifier> are controlled by the item operation flags; disabled operations and unsupported methods return HTTP 405 without constructing a manager. PATCH and POST <base>/<resource> require object payloads and call the manager's normal update() or create() methods, so permission, validation, and interface errors still apply.
Success envelopes include items, metadata.protocol_version, metadata.request_id, response header X-Request-ID, optional metadata extras such as query controls, and total_count only when supplied by the endpoint. Error responses are sanitized envelopes with error, error_code, metadata, X-Request-ID, and optional details. ObjectDoesNotExist maps to 404/not_found, PermissionError to 403/permission_denied, ValidationError to 400/validation_error, RuntimeError to 500/internal_error, and caught AttributeError, LookupError, RemoteAPIConfigurationError, TypeError, ValueError, and RemoteAPIRequestError subclasses map to 400/invalid_request.