karrio-server-graph 2025.5__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.
Files changed (39) hide show
  1. karrio/server/graph/__init__.py +1 -0
  2. karrio/server/graph/admin.py +3 -0
  3. karrio/server/graph/apps.py +5 -0
  4. karrio/server/graph/forms.py +57 -0
  5. karrio/server/graph/management/__init__.py +0 -0
  6. karrio/server/graph/management/commands/__init__.py +0 -0
  7. karrio/server/graph/management/commands/export_schema.py +9 -0
  8. karrio/server/graph/migrations/0001_initial.py +37 -0
  9. karrio/server/graph/migrations/0002_auto_20210512_1353.py +22 -0
  10. karrio/server/graph/migrations/__init__.py +0 -0
  11. karrio/server/graph/models.py +44 -0
  12. karrio/server/graph/schema.py +44 -0
  13. karrio/server/graph/schemas/__init__.py +2 -0
  14. karrio/server/graph/schemas/base/__init__.py +385 -0
  15. karrio/server/graph/schemas/base/inputs.py +612 -0
  16. karrio/server/graph/schemas/base/mutations.py +1033 -0
  17. karrio/server/graph/schemas/base/types.py +1406 -0
  18. karrio/server/graph/serializers.py +388 -0
  19. karrio/server/graph/templates/graphql/graphiql.html +142 -0
  20. karrio/server/graph/templates/karrio/email_change_email.html +13 -0
  21. karrio/server/graph/templates/karrio/email_change_email.txt +13 -0
  22. karrio/server/graph/templates/karrio/password_reset_email.html +14 -0
  23. karrio/server/graph/tests/__init__.py +9 -0
  24. karrio/server/graph/tests/base.py +153 -0
  25. karrio/server/graph/tests/test_carrier_connections.py +239 -0
  26. karrio/server/graph/tests/test_metafield.py +404 -0
  27. karrio/server/graph/tests/test_partial_shipments.py +603 -0
  28. karrio/server/graph/tests/test_rate_sheets.py +354 -0
  29. karrio/server/graph/tests/test_registration.py +209 -0
  30. karrio/server/graph/tests/test_templates.py +677 -0
  31. karrio/server/graph/tests/test_user_info.py +71 -0
  32. karrio/server/graph/urls.py +10 -0
  33. karrio/server/graph/utils.py +308 -0
  34. karrio/server/graph/views.py +91 -0
  35. karrio/server/settings/graph.py +7 -0
  36. karrio_server_graph-2025.5.dist-info/METADATA +29 -0
  37. karrio_server_graph-2025.5.dist-info/RECORD +39 -0
  38. karrio_server_graph-2025.5.dist-info/WHEEL +5 -0
  39. karrio_server_graph-2025.5.dist-info/top_level.txt +2 -0
@@ -0,0 +1,71 @@
1
+ from unittest.mock import ANY
2
+ from karrio.server.graph.tests.base import GraphTestCase
3
+
4
+
5
+ class TestUserUpdate(GraphTestCase):
6
+ def test_update_user_info(self):
7
+ response = self.query(
8
+ """
9
+ mutation update_user($data: UpdateUserInput!) {
10
+ update_user(input: $data) {
11
+ user {
12
+ email
13
+ full_name
14
+ is_staff
15
+ last_login
16
+ date_joined
17
+ }
18
+ }
19
+ }
20
+ """,
21
+ operation_name="update_user",
22
+ variables=USER_UPDATE_DATA,
23
+ )
24
+ response_data = response.data
25
+
26
+ self.assertResponseNoErrors(response)
27
+ self.assertDictEqual(response_data, USER_UPDATE_RESPONSE)
28
+
29
+
30
+ class TestTokenMutation(GraphTestCase):
31
+ def test_update_token(self):
32
+ current_token = self.token.key
33
+ response = self.query(
34
+ """
35
+ mutation mutate_token($data: TokenMutationInput!) {
36
+ mutate_token(input: $data) {
37
+ token {
38
+ key
39
+ created
40
+ }
41
+ }
42
+ }
43
+ """,
44
+ operation_name="mutate_token",
45
+ variables={
46
+ "data": {"refresh": True, "password": "test", "key": current_token}
47
+ },
48
+ )
49
+ response_data = response.data
50
+
51
+ self.assertResponseNoErrors(response)
52
+ self.assertFalse(
53
+ response_data["data"]["mutate_token"]["token"]["key"] == current_token
54
+ )
55
+
56
+
57
+ USER_UPDATE_DATA = {"data": {"full_name": "Marco"}}
58
+
59
+ USER_UPDATE_RESPONSE = {
60
+ "data": {
61
+ "update_user": {
62
+ "user": {
63
+ "date_joined": ANY,
64
+ "email": "admin@example.com",
65
+ "full_name": "Marco",
66
+ "is_staff": True,
67
+ "last_login": ANY,
68
+ }
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,10 @@
1
+ """
2
+ karrio server graph module urls
3
+ """
4
+ from django.urls import path, include
5
+
6
+
7
+ app_name = "karrio.server.graph"
8
+ urlpatterns = [
9
+ path("", include("karrio.server.graph.views")),
10
+ ]
@@ -0,0 +1,308 @@
1
+ from karrio.server.core.utils import *
2
+ import typing
3
+ import base64
4
+ import functools
5
+ import strawberry
6
+ import dataclasses
7
+ from rest_framework import exceptions
8
+ from django.utils.translation import gettext_lazy as _
9
+
10
+ import karrio.lib as lib
11
+ import karrio.server.core.utils as utils
12
+ import karrio.server.core.models as core
13
+ import karrio.server.orders.models as orders
14
+ import karrio.server.manager.models as manager
15
+ import karrio.server.providers.models as providers
16
+ import karrio.server.core.permissions as permissions
17
+ import karrio.server.core.serializers as serializers
18
+ from karrio.server.core.logging import logger
19
+
20
+ Cursor = str
21
+ T = typing.TypeVar("T")
22
+ GenericType = typing.TypeVar("GenericType")
23
+
24
+ error_logger = utils.error_wrapper
25
+
26
+ JSON: typing.Any = strawberry.scalar(
27
+ typing.NewType("JSON", object),
28
+ description="The `JSON` scalar type represents JSON values as specified by ECMA-404",
29
+ )
30
+ MANUAL_SHIPMENT_STATUSES = [
31
+ (_.name, _.name)
32
+ for _ in [
33
+ serializers.ShipmentStatus.in_transit,
34
+ serializers.ShipmentStatus.needs_attention,
35
+ serializers.ShipmentStatus.delivery_failed,
36
+ serializers.ShipmentStatus.delivered,
37
+ ]
38
+ ]
39
+
40
+ CurrencyCodeEnum: typing.Any = strawberry.enum( # type: ignore
41
+ lib.Enum("CurrencyCodeEnum", serializers.CURRENCIES)
42
+ )
43
+ CountryCodeEnum: typing.Any = strawberry.enum( # type: ignore
44
+ lib.Enum("CountryCodeEnum", serializers.COUNTRIES)
45
+ )
46
+ DimensionUnitEnum: typing.Any = strawberry.enum( # type: ignore
47
+ lib.Enum("DimensionUnitEnum", serializers.DIMENSION_UNIT)
48
+ )
49
+ WeightUnitEnum: typing.Any = strawberry.enum( # type: ignore
50
+ lib.Enum("WeightUnitEnum", serializers.WEIGHT_UNIT)
51
+ )
52
+ CustomsContentTypeEnum: typing.Any = strawberry.enum( # type: ignore
53
+ lib.Enum("CustomsContentTypeEnum", serializers.CUSTOMS_CONTENT_TYPE)
54
+ )
55
+ IncotermCodeEnum: typing.Any = strawberry.enum( # type: ignore
56
+ lib.Enum("IncotermCodeEnum", serializers.INCOTERMS)
57
+ )
58
+ PaidByEnum: typing.Any = strawberry.enum( # type: ignore
59
+ lib.Enum("PaidByEnum", serializers.PAYMENT_TYPES)
60
+ )
61
+ LabelTypeEnum: typing.Any = strawberry.enum( # type: ignore
62
+ lib.Enum("LabelTypeEnum", serializers.LABEL_TYPES)
63
+ )
64
+ LabelTemplateTypeEnum: typing.Any = strawberry.enum( # type: ignore
65
+ lib.Enum("LabelTemplateTypeEnum", serializers.LABEL_TEMPLATE_TYPES)
66
+ )
67
+ ShipmentStatusEnum: typing.Any = strawberry.enum( # type: ignore
68
+ lib.Enum("ShipmentStatusEnum", serializers.SHIPMENT_STATUS)
69
+ )
70
+ ManualShipmentStatusEnum: typing.Any = strawberry.enum( # type: ignore
71
+ lib.Enum("ManualShipmentStatusEnum", MANUAL_SHIPMENT_STATUSES)
72
+ )
73
+ TrackerStatusEnum: typing.Any = strawberry.enum( # type: ignore
74
+ lib.Enum("TrackerStatusEnum", serializers.TRACKER_STATUS)
75
+ )
76
+ CarrierNameEnum: typing.Any = strawberry.enum( # type: ignore
77
+ lib.Enum("CarrierNameEnum", serializers.CARRIERS)
78
+ )
79
+ MetafieldTypeEnum: typing.Any = strawberry.enum( # type: ignore
80
+ lib.Enum("MetafieldTypeEnum", core.METAFIELD_TYPE)
81
+ )
82
+
83
+
84
+ class MetadataObjectType(lib.Enum):
85
+ carrier = providers.Carrier
86
+ commodity = manager.Commodity
87
+ shipment = manager.Shipment
88
+ tracker = manager.Tracking
89
+ order = orders.Order
90
+
91
+
92
+ MetadataObjectTypeEnum: typing.Any = strawberry.enum( # type: ignore
93
+ lib.StrEnum(
94
+ "MetadataObjectTypeEnum", [(c.name, c.name) for c in list(MetadataObjectType)]
95
+ )
96
+ )
97
+
98
+
99
+ def authentication_required(func):
100
+ @functools.wraps(func)
101
+ def wrapper(info, **kwargs):
102
+ user = getattr(info.context.request, 'user', None)
103
+
104
+ if user is None or user.is_anonymous:
105
+ raise exceptions.AuthenticationFailed(
106
+ _("You are not authenticated"), code="authentication_required"
107
+ )
108
+
109
+ if not user.is_verified():
110
+ raise exceptions.AuthenticationFailed(
111
+ _("Authentication Token not verified"), code="two_factor_required"
112
+ )
113
+
114
+ return func(info, **kwargs)
115
+
116
+ return wrapper
117
+
118
+
119
+ def password_required(func):
120
+ @functools.wraps(func)
121
+ def wrapper(info, **kwargs):
122
+ password = kwargs.get("password")
123
+
124
+ if not info.context.request.user.check_password(password):
125
+ raise exceptions.ValidationError({"password": "Invalid password"})
126
+
127
+ return func(info, **kwargs)
128
+
129
+ return wrapper
130
+
131
+
132
+ def authorization_required(keys: typing.List[str] = None):
133
+ def decorator(func):
134
+ @functools.wraps(func)
135
+ def wrapper(info, **kwargs):
136
+ permissions.check_permissions(
137
+ context=info.context.request,
138
+ keys=keys or [],
139
+ )
140
+
141
+ return func(info, **kwargs)
142
+
143
+ return wrapper
144
+
145
+ return decorator
146
+
147
+
148
+ @strawberry.type
149
+ class ErrorType:
150
+ field: str
151
+ messages: typing.List[str]
152
+
153
+ @staticmethod
154
+ def from_errors(errors):
155
+ return []
156
+
157
+
158
+ @strawberry.input
159
+ class BaseInput:
160
+ def pagination(self) -> typing.Dict[str, typing.Any]:
161
+ return {
162
+ k: v
163
+ for k, v in dataclass_to_dict(self).items()
164
+ if k in ["offset", "before", "after", "first", "last"]
165
+ }
166
+
167
+ def to_dict(self) -> typing.Dict[str, typing.Any]:
168
+ return dataclass_to_dict(self)
169
+
170
+
171
+ @strawberry.type
172
+ class BaseMutation:
173
+ errors: typing.Optional[typing.List[ErrorType]] = None
174
+
175
+
176
+ @strawberry.type
177
+ class UsageStatType:
178
+ date: typing.Optional[str] = None
179
+ label: typing.Optional[str] = None
180
+ count: typing.Optional[float] = None
181
+ amount: typing.Optional[float] = None
182
+ currency: typing.Optional[str] = None
183
+
184
+ @staticmethod
185
+ def parse(value: dict, label: str = None) -> "UsageStatType":
186
+ return UsageStatType(**{
187
+ **(dict(label=label) if label else {}),
188
+ **{k: v for k, v in value.items() if k in UsageStatType.__annotations__},
189
+ "amount": lib.to_decimal(value.get("amount")),
190
+ })
191
+
192
+
193
+ @strawberry.input
194
+ class UsageFilter(BaseInput):
195
+ date_after: typing.Optional[str] = strawberry.UNSET
196
+ date_before: typing.Optional[str] = strawberry.UNSET
197
+ omit: typing.Optional[typing.List[str]] = strawberry.UNSET
198
+ surcharge_id: typing.Optional[str] = strawberry.UNSET
199
+
200
+
201
+ @dataclasses.dataclass
202
+ @strawberry.type
203
+ class Connection(typing.Generic[GenericType]):
204
+ """Represents a paginated relationship between two entities
205
+ This pattern is used when the relationship itself has attributes.
206
+ In a Facebook-based domain example, a friendship between two people
207
+ would be a connection that might have a `friendshipStartTime`
208
+ """
209
+
210
+ page_info: "PageInfo"
211
+ edges: typing.List["Edge[GenericType]"]
212
+
213
+
214
+ @dataclasses.dataclass
215
+ @strawberry.type
216
+ class PageInfo:
217
+ """Pagination context to navigate objects with cursor-based pagination
218
+ Instead of classic offset pagination via `page` and `limit` parameters,
219
+ here we have a cursor of the last object and we fetch items starting from that one
220
+ Read more at:
221
+ - https://graphql.org/learn/pagination/#pagination-and-edges
222
+ - https://relay.dev/graphql/connections.htm
223
+ """
224
+
225
+ count: int
226
+ has_next_page: bool
227
+ has_previous_page: bool
228
+ start_cursor: typing.Optional[str]
229
+ end_cursor: typing.Optional[str]
230
+
231
+
232
+ @dataclasses.dataclass
233
+ @strawberry.type
234
+ class Edge(typing.Generic[GenericType]):
235
+ """An edge may contain additional information of the relationship. This is the trivial case"""
236
+
237
+ node: GenericType
238
+ cursor: str
239
+
240
+
241
+ @strawberry.input
242
+ class Paginated(BaseInput):
243
+ offset: typing.Optional[int] = strawberry.UNSET
244
+ first: typing.Optional[int] = strawberry.UNSET
245
+
246
+
247
+ def build_entity_cursor(entity: T):
248
+ """Adapt this method to build an *opaque* ID from an instance"""
249
+ entityid = f"{getattr(entity, 'id', id(entity))}".encode("utf-8")
250
+ return base64.b64encode(entityid).decode()
251
+
252
+
253
+ def paginated_connection(
254
+ queryset,
255
+ first: int = 25,
256
+ offset: int = 0,
257
+ ) -> Connection[T]:
258
+ """A non-trivial implementation should efficiently fetch only
259
+ the necessary books after the offset.
260
+ For simplicity, here we build the list and then slice it accordingly
261
+ """
262
+
263
+ # Fetch the requested results plus one, just to calculate `has_next_page`
264
+ # fmt: off
265
+ results = queryset[offset:offset+first+1]
266
+ # fmt: on
267
+
268
+ edges: typing.List[typing.Any] = [
269
+ Edge(node=typing.cast(T, entity), cursor=build_entity_cursor(entity))
270
+ for entity in results
271
+ ]
272
+ return Connection(
273
+ page_info=PageInfo(
274
+ count=queryset.count(),
275
+ has_previous_page=False,
276
+ has_next_page=len(results) > first,
277
+ start_cursor=edges[0].cursor if edges else None,
278
+ end_cursor=edges[-2].cursor if len(edges) > 1 else None,
279
+ ),
280
+ edges=(edges[:-1] if len(edges) > first else edges),
281
+ )
282
+
283
+
284
+ def is_unset(v: typing.Any) -> bool:
285
+ return isinstance(v, type(strawberry.UNSET)) or v == strawberry.UNSET
286
+
287
+
288
+ def _dict_factory(items):
289
+ if isinstance(next(iter(items), None), tuple):
290
+ return dict(
291
+ [
292
+ (key, _dict_factory(value) if isinstance(value, list) else value)
293
+ for key, value in items
294
+ if not is_unset(value)
295
+ ]
296
+ )
297
+
298
+ return items
299
+
300
+
301
+ def dataclass_to_dict(data):
302
+ return lib.to_dict(
303
+ dataclasses.asdict(
304
+ data,
305
+ dict_factory=_dict_factory,
306
+ ),
307
+ clear_empty=False,
308
+ )
@@ -0,0 +1,91 @@
1
+ """
2
+ karrio server graph module urls
3
+ """
4
+
5
+ import pydoc
6
+ import typing
7
+ from django.urls import path
8
+ from django.conf import settings
9
+ from rest_framework import exceptions
10
+ from django.views.decorators.csrf import csrf_exempt
11
+ import graphql.error.graphql_error as graphql
12
+ import strawberry.django.views as views
13
+ import strawberry.types as types
14
+ import strawberry.http as http
15
+
16
+ import karrio.lib as lib
17
+ import karrio.server.conf as conf
18
+ import karrio.server.graph.schema as schema
19
+ from karrio.server.core.logging import logger
20
+ ACCESS_METHOD = getattr(
21
+ settings,
22
+ "SESSION_ACCESS_MIXIN",
23
+ "karrio.server.core.authentication.AccessMixin",
24
+ )
25
+ AccessMixin: typing.Any = pydoc.locate(ACCESS_METHOD)
26
+
27
+
28
+ class GraphQLView(AccessMixin, views.GraphQLView):
29
+ def dispatch(self, request, *args, **kwargs):
30
+ should_render_graphiql = lib.failsafe(
31
+ lambda: self.should_render_graphiql(request)
32
+ )
33
+
34
+ if should_render_graphiql:
35
+ context = dict(APP_NAME=conf.settings.APP_NAME)
36
+
37
+ return self._render_graphiql(request, context=context)
38
+
39
+ return super().dispatch(request, *args, **kwargs)
40
+
41
+ def process_result(
42
+ self, request, result: types.ExecutionResult
43
+ ) -> http.GraphQLHTTPResponse:
44
+ data: http.GraphQLHTTPResponse = {"data": result.data}
45
+
46
+ if result.errors:
47
+ data["errors"] = [self.format_graphql_error(err) for err in result.errors]
48
+ if result.extensions:
49
+ data["extensions"] = result.extensions
50
+
51
+ return data
52
+
53
+ def format_graphql_error(self, error: graphql.GraphQLError):
54
+ formatted_error: dict = (
55
+ graphql.format_error(error) # type: ignore
56
+ if isinstance(error, graphql.GraphQLError)
57
+ else {}
58
+ )
59
+
60
+ if isinstance(error.original_error, exceptions.APIException):
61
+ formatted_error["message"] = str(error.original_error.detail)
62
+ formatted_error["code"] = (
63
+ error.original_error.get_codes()
64
+ if hasattr(error.original_error, "get_codes")
65
+ else getattr(
66
+ error.original_error,
67
+ "code",
68
+ getattr(error.original_error, "default_code", None),
69
+ )
70
+ )
71
+ formatted_error["status_code"] = error.original_error.status_code
72
+
73
+ if isinstance(error.original_error, exceptions.ValidationError):
74
+ formatted_error["message"] = str(error.original_error.default_detail)
75
+ formatted_error["validation"] = error.original_error.detail
76
+
77
+ return formatted_error
78
+
79
+
80
+ urlpatterns = [
81
+ path(
82
+ "graphql/",
83
+ csrf_exempt(GraphQLView.as_view(schema=schema.schema)),
84
+ name="graphql",
85
+ ),
86
+ path(
87
+ "graphql",
88
+ csrf_exempt(GraphQLView.as_view(schema=schema.schema)),
89
+ name="graphql-api",
90
+ ),
91
+ ]
@@ -0,0 +1,7 @@
1
+ # type: ignore
2
+ from karrio.server.settings.base import *
3
+
4
+
5
+ INSTALLED_APPS += [
6
+ "strawberry.django",
7
+ ]
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: karrio_server_graph
3
+ Version: 2025.5
4
+ Summary: Multi-carrier shipping API Graph module
5
+ Author-email: karrio <hello@karrio.io>
6
+ License-Expression: LGPL-3.0
7
+ Project-URL: Homepage, https://github.com/karrioapi/karrio
8
+ Classifier: Programming Language :: Python :: 3
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: karrio_server_core
12
+ Requires-Dist: django-filter
13
+ Requires-Dist: strawberry-graphql
14
+
15
+ # karrio.server.graph
16
+
17
+ This package is a module of the [karrio](https://pypi.org/project/karrio.server) universal shipping API.
18
+
19
+ ## Requirements
20
+
21
+ `Python 3.11+`
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install karrio.server.graph
27
+ ```
28
+
29
+ Check the [karrio docs](https://docs.karrio.io) to get started.
@@ -0,0 +1,39 @@
1
+ karrio/server/graph/__init__.py,sha256=iOEMwnlORWezdO8-2vxBIPSR37D7JGjluZ8f55vzxls,81
2
+ karrio/server/graph/admin.py,sha256=suMo4x8I3JBxAFBVIdE-5qnqZ6JAZV0FESABHOSc-vg,63
3
+ karrio/server/graph/apps.py,sha256=grL5ySDnW2amzj0nTXFm-WCoTWViVYvKqGlsi4k5epI,99
4
+ karrio/server/graph/forms.py,sha256=7ow-WZ3OFUmnakj28nabWJflU1atAb86roJiok1NQ7A,1949
5
+ karrio/server/graph/models.py,sha256=CEnE4AsVyjBufyK6ebWmUH3s8DwA0HvZg0fUoZb5Pn4,1321
6
+ karrio/server/graph/schema.py,sha256=uENWASUR0r0RZGr8j5bp_xlBOskxqhW7Z-PYCkC-uzE,1226
7
+ karrio/server/graph/serializers.py,sha256=qhnr8sbv09uY1GBChR2fTZTrPrElu8YtIv86JfFL0oc,13744
8
+ karrio/server/graph/urls.py,sha256=HKo0gkx5TgoWDV0ap2QCtueNTmaAqvX6qVDe3c2UT1E,183
9
+ karrio/server/graph/utils.py,sha256=W68fmaEGp8giS3PFBPekjzd5aIPrbw7XOnKBYRBL0bs,9302
10
+ karrio/server/graph/views.py,sha256=H9GJAgd8y0yV9Z1qpxhBo5o7dfCZgegzebHi100M_E8,2882
11
+ karrio/server/graph/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ karrio/server/graph/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ karrio/server/graph/management/commands/export_schema.py,sha256=WMwiDISpTwtA6MRqSxKjX9LOa3caWEHA_OKRxPIgcsQ,294
14
+ karrio/server/graph/migrations/0001_initial.py,sha256=oeaS5JSkQP6w9ulYar3mdn695ybkog-1QoyKUIW1SCg,1609
15
+ karrio/server/graph/migrations/0002_auto_20210512_1353.py,sha256=TnUwR9EP0qp3gJ38f9w0PYawK2VheDtqXEgyRhYZS2M,538
16
+ karrio/server/graph/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ karrio/server/graph/schemas/__init__.py,sha256=Kn-I1j3HP3jwZccpz6FL9r1k6b3UEAMVh2kFPCKNS0w,80
18
+ karrio/server/graph/schemas/base/__init__.py,sha256=nnOfynQW842qOA0qD6bYz8GSHQCQeBW36vbq0sqRkJs,15121
19
+ karrio/server/graph/schemas/base/inputs.py,sha256=p6_u4sG1cbd18CpPjkyG3CD4cbvf4gBa3De4RDgCZ80,21419
20
+ karrio/server/graph/schemas/base/mutations.py,sha256=6ljP3_2UpUHpYIl06I7n-owQjUp2TT5jDgZ_fEzvB6o,35043
21
+ karrio/server/graph/schemas/base/types.py,sha256=FBayGYWlqcHK0tm43xFA6ZkUFlr1htM7-TutRKPmTkc,47613
22
+ karrio/server/graph/templates/graphql/graphiql.html,sha256=MQjQbBqoRE0QLsOUck8SaXo6B2oJO8dT6YZzUqbDan0,3786
23
+ karrio/server/graph/templates/karrio/email_change_email.html,sha256=gr55F97GYzY27TVKGl49037yd60eSYD0b0GXRlyoco4,552
24
+ karrio/server/graph/templates/karrio/email_change_email.txt,sha256=NXXuzLR63hn2F1fVAjzmuguptuuTvujwqI7YLSrQoio,431
25
+ karrio/server/graph/templates/karrio/password_reset_email.html,sha256=dttqYVL73cQNuTFsVdn2GV4Ckee8PTY8oEF53GbDRcg,553
26
+ karrio/server/graph/tests/__init__.py,sha256=dPzsYY5hoO5gmY6fhL8tiz7Bfst8RB3JzsBVHZazHRE,338
27
+ karrio/server/graph/tests/base.py,sha256=dzLiva3eTAsbBM5Ga8SI2fxSanBQgAqswS0YVdtTfEg,5122
28
+ karrio/server/graph/tests/test_carrier_connections.py,sha256=qZ1OL8CgZrHuluKJd7cjFXaRZ0VpogN5Srjk2EApLWU,6892
29
+ karrio/server/graph/tests/test_metafield.py,sha256=K7Oon0CLEm_MUMbmcu0t2iAZvFN8Wl7Kp4QAWeUXo_Y,12783
30
+ karrio/server/graph/tests/test_partial_shipments.py,sha256=dPIdIq4wiyovOaIIzbIX69eZnBqCA4ZvBSiGKYADM2g,19031
31
+ karrio/server/graph/tests/test_rate_sheets.py,sha256=cUzPV8dXQFPFh1r7W8bY6Lou3fjh8f9VGpyZrfbMXec,10300
32
+ karrio/server/graph/tests/test_registration.py,sha256=0vCTqlsLc0cl2m78umgfm7grnDgTI_NZJWNUrRUlQBY,7107
33
+ karrio/server/graph/tests/test_templates.py,sha256=WVU6vcfr6tEk917uSn1dECU8bkQtgD7FNuE-GJuFido,21626
34
+ karrio/server/graph/tests/test_user_info.py,sha256=K91BL7SgxLWflCZriSVI8Vt5M5RIqmSCHKrgN2w8GmM,1928
35
+ karrio/server/settings/graph.py,sha256=cz2yQHbp3xCfyFKuUkPEFfkI2fFVggExIY49wGz7mt0,106
36
+ karrio_server_graph-2025.5.dist-info/METADATA,sha256=iD1WSW3xUAPEL2uzrVdZqsMUUWhM93oRsW3dzSoNYL8,738
37
+ karrio_server_graph-2025.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
+ karrio_server_graph-2025.5.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
39
+ karrio_server_graph-2025.5.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ dist
2
+ karrio