django-admin-grpc 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ """
2
+ django-grpc-admin
3
+
4
+ A reusable Django package for creating admin interfaces backed by gRPC services.
5
+ """
6
+
7
+ from django_admin_grpc.adapters import BaseGrpcServiceAdapter
8
+ from django_admin_grpc.exceptions import (
9
+ GrpcAdminError,
10
+ GrpcDeadlineExceededError,
11
+ GrpcInvalidArgumentError,
12
+ GrpcNotFoundError,
13
+ GrpcPermissionDeniedError,
14
+ GrpcUnavailableError,
15
+ )
16
+ from django_admin_grpc.filters import (
17
+ GrpcBooleanFieldListFilter,
18
+ GrpcChoicesFieldListFilter,
19
+ GrpcSimpleListFilter,
20
+ GrpcTextInputFilter,
21
+ create_grpc_filter_spec,
22
+ )
23
+ from django_admin_grpc.mappers import BaseGrpcMapper
24
+ from django_admin_grpc.paginator import GrpcPaginator, PagedResult
25
+ from django_admin_grpc.registry import AdapterRegistry, adapter_registry
26
+ from django_admin_grpc.resources import (
27
+ BaseFieldConfig,
28
+ BaseGrpcResource,
29
+ BooleanFieldConfig,
30
+ CharFieldConfig,
31
+ ChoicesFieldConfig,
32
+ DateFieldConfig,
33
+ DateTimeFieldConfig,
34
+ FKFieldConfig,
35
+ FloatFieldConfig,
36
+ IntegerFieldConfig,
37
+ TextFieldConfig,
38
+ )
39
+
40
+ __version__ = "0.1.0"
41
+
42
+ __all__ = [
43
+ "BaseFieldConfig",
44
+ "BaseGrpcResource",
45
+ "BaseGrpcServiceAdapter",
46
+ "BaseGrpcMapper",
47
+ "BooleanFieldConfig",
48
+ "CharFieldConfig",
49
+ "ChoicesFieldConfig",
50
+ "DateFieldConfig",
51
+ "DateTimeFieldConfig",
52
+ "FKFieldConfig",
53
+ "FloatFieldConfig",
54
+ "IntegerFieldConfig",
55
+ "TextFieldConfig",
56
+ "GrpcAdminError",
57
+ "GrpcNotFoundError",
58
+ "GrpcPermissionDeniedError",
59
+ "GrpcInvalidArgumentError",
60
+ "GrpcUnavailableError",
61
+ "GrpcDeadlineExceededError",
62
+ "GrpcPaginator",
63
+ "PagedResult",
64
+ "GrpcBooleanFieldListFilter",
65
+ "GrpcChoicesFieldListFilter",
66
+ "GrpcSimpleListFilter",
67
+ "GrpcTextInputFilter",
68
+ "create_grpc_filter_spec",
69
+ "AdapterRegistry",
70
+ "adapter_registry",
71
+ ]
@@ -0,0 +1,153 @@
1
+ """
2
+ Base gRPC service adapter interface.
3
+
4
+ Concrete adapters subclass ``BaseGrpcServiceAdapter`` and implement ``list()``,
5
+ ``get()`` and optionally ``create()``, ``update()``, ``delete()``.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from abc import ABC, abstractmethod
11
+ from collections.abc import Callable
12
+ from typing import TYPE_CHECKING, Any, cast
13
+
14
+ import grpc
15
+
16
+ from django_admin_grpc.exceptions import map_grpc_error
17
+ from django_admin_grpc.paginator import PagedResult
18
+
19
+ if TYPE_CHECKING:
20
+ from django_admin_grpc.resources import BaseGrpcResource
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class BaseGrpcServiceAdapter(ABC):
26
+ """
27
+ Abstract interface between Django admin and a remote gRPC service.
28
+
29
+ Attributes:
30
+ service_name: Human-readable name used by the registry.
31
+ """
32
+
33
+ service_name: str = ""
34
+
35
+ @abstractmethod
36
+ def list(
37
+ self,
38
+ resource_class: type[BaseGrpcResource],
39
+ page: int = 1,
40
+ page_size: int = 25,
41
+ filters: dict[str, Any] | None = None,
42
+ ) -> PagedResult:
43
+ """
44
+ Fetch a page of entities.
45
+
46
+ Args:
47
+ resource_class: The resource class to instantiate for each row.
48
+ page: 1-indexed page number.
49
+ page_size: Items per page.
50
+ filters: Optional query/filter dictionary.
51
+
52
+ Returns:
53
+ A ``PagedResult`` containing items and pagination metadata.
54
+ """
55
+ ...
56
+
57
+ @abstractmethod
58
+ def get(
59
+ self,
60
+ resource_class: type[BaseGrpcResource],
61
+ pk: str,
62
+ ) -> BaseGrpcResource | None:
63
+ """
64
+ Fetch a single entity by primary key.
65
+
66
+ Args:
67
+ resource_class: The resource class to instantiate.
68
+ pk: Primary key value.
69
+
70
+ Returns:
71
+ A resource instance, or ``None`` if not found.
72
+ """
73
+ ...
74
+
75
+ def create(
76
+ self,
77
+ resource_class: type[BaseGrpcResource],
78
+ data: dict[str, Any],
79
+ ) -> BaseGrpcResource:
80
+ """Create a new entity via gRPC."""
81
+ raise NotImplementedError(
82
+ f"{self.__class__.__name__} does not support create"
83
+ )
84
+
85
+ def update(
86
+ self,
87
+ resource_class: type[BaseGrpcResource],
88
+ pk: str,
89
+ data: dict[str, Any],
90
+ ) -> BaseGrpcResource:
91
+ """Update an existing entity via gRPC."""
92
+ raise NotImplementedError(
93
+ f"{self.__class__.__name__} does not support update"
94
+ )
95
+
96
+ def delete(
97
+ self,
98
+ resource_class: type[BaseGrpcResource],
99
+ pk: str,
100
+ ) -> bool:
101
+ """Delete an entity via gRPC."""
102
+ raise NotImplementedError(
103
+ f"{self.__class__.__name__} does not support delete"
104
+ )
105
+
106
+ @property
107
+ def supports_create(self) -> bool:
108
+ return type(self).create is not BaseGrpcServiceAdapter.create
109
+
110
+ @property
111
+ def supports_update(self) -> bool:
112
+ return type(self).update is not BaseGrpcServiceAdapter.update
113
+
114
+ @property
115
+ def supports_delete(self) -> bool:
116
+ return type(self).delete is not BaseGrpcServiceAdapter.delete
117
+
118
+ def close(self) -> None:
119
+ """Release any held connections. Override if needed."""
120
+ return None
121
+
122
+ def _wrap_channel(self, channel: grpc.Channel) -> grpc.Channel:
123
+ """
124
+ Wrap a raw gRPC channel with the trace interceptor.
125
+
126
+ Must be called **inside** the ``if self._channel is None:`` guard in
127
+ concrete adapters so the channel is not double-wrapped.
128
+ """
129
+ from django_admin_grpc.interceptors import TraceClientInterceptor
130
+
131
+ provider = self._trace_context_provider()
132
+ return grpc.intercept_channel(
133
+ channel, TraceClientInterceptor(trace_context_provider=provider)
134
+ )
135
+
136
+ def _trace_context_provider(self) -> Callable[[], dict[str, str]]:
137
+ """Return the configured trace-context callable, or a no-op."""
138
+ from django_admin_grpc.settings import get_setting
139
+
140
+ provider = get_setting("GRPC_ADMIN_TRACE_CONTEXT_PROVIDER")
141
+ if provider is None:
142
+ return lambda: {}
143
+ if callable(provider):
144
+ return cast(Callable[[], dict[str, str]], provider)
145
+ # Django-style dotted path
146
+ from django.utils.module_loading import import_string
147
+
148
+ return cast(Callable[[], dict[str, str]], import_string(provider))
149
+
150
+ @staticmethod
151
+ def _map_rpc_error(exc: grpc.RpcError) -> Exception:
152
+ """Map a gRPC error to a typed exception. Callers should ``raise`` the result."""
153
+ return map_grpc_error(exc)