aspyx-service 0.10.3__py3-none-any.whl → 0.10.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.
Potentially problematic release.
This version of aspyx-service might be problematic. Click here for more details.
- aspyx_service/__init__.py +30 -5
- aspyx_service/authorization.py +126 -0
- aspyx_service/channels.py +236 -45
- aspyx_service/healthcheck.py +1 -1
- aspyx_service/registries.py +5 -5
- aspyx_service/restchannel.py +13 -20
- aspyx_service/server.py +209 -77
- aspyx_service/service.py +47 -12
- aspyx_service/session.py +136 -0
- {aspyx_service-0.10.3.dist-info → aspyx_service-0.10.5.dist-info}/METADATA +56 -12
- aspyx_service-0.10.5.dist-info/RECORD +13 -0
- aspyx_service/serialization.py +0 -137
- aspyx_service-0.10.3.dist-info/RECORD +0 -12
- {aspyx_service-0.10.3.dist-info → aspyx_service-0.10.5.dist-info}/WHEEL +0 -0
- {aspyx_service-0.10.3.dist-info → aspyx_service-0.10.5.dist-info}/licenses/LICENSE +0 -0
aspyx_service/session.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
session related module
|
|
3
|
+
"""
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
import contextvars
|
|
6
|
+
from typing import Type, Optional, Callable, Any, TypeVar
|
|
7
|
+
from datetime import datetime, timezone, timedelta
|
|
8
|
+
from cachetools import TTLCache
|
|
9
|
+
|
|
10
|
+
from aspyx.di import injectable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Session:
|
|
14
|
+
"""
|
|
15
|
+
Base class for objects covers data related to a server side session.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
class SessionContext:
|
|
23
|
+
# class properties
|
|
24
|
+
|
|
25
|
+
# current_session = ThreadLocal[Session]()
|
|
26
|
+
current_session = contextvars.ContextVar("session")
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def get(cls, type: Type[T]) -> T:
|
|
30
|
+
"""
|
|
31
|
+
return the current session associated with the context
|
|
32
|
+
Args:
|
|
33
|
+
type: the session type
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
the current session
|
|
37
|
+
"""
|
|
38
|
+
return cls.current_session.get()
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def set(cls, session: Session) -> None:
|
|
42
|
+
"""
|
|
43
|
+
set the current session in the context
|
|
44
|
+
Args:
|
|
45
|
+
session: the session
|
|
46
|
+
"""
|
|
47
|
+
cls.current_session.set(session)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def clear(cls) -> None:
|
|
51
|
+
"""
|
|
52
|
+
delete the current session
|
|
53
|
+
"""
|
|
54
|
+
cls.current_session.set(None) # clear()
|
|
55
|
+
|
|
56
|
+
@injectable()
|
|
57
|
+
class SessionManager(SessionContext):
|
|
58
|
+
"""
|
|
59
|
+
A SessionManager controls the lifecycle of sessions and is responsible to establish a session context local.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
# local classes
|
|
63
|
+
|
|
64
|
+
class Storage(ABC):
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def store(self, token: str, session: Session, ttl_seconds: int):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def read(self, token: str) -> Optional[Session]:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
class InMemoryStorage(Storage):
|
|
74
|
+
"""
|
|
75
|
+
InMemoryStorage is a simple in-memory storage for sessions.
|
|
76
|
+
It uses a TTLCache to store sessions with a time-to-live.
|
|
77
|
+
"""
|
|
78
|
+
# constructor
|
|
79
|
+
|
|
80
|
+
def __init__(self, max_size = 1000, ttl = 3600):
|
|
81
|
+
self.cache = TTLCache(maxsize=max_size, ttl=ttl)
|
|
82
|
+
|
|
83
|
+
# implement
|
|
84
|
+
|
|
85
|
+
def store(self, token: str, session: 'Session', ttl_seconds: int):
|
|
86
|
+
expiry_time = datetime.now(timezone.utc) + timedelta(seconds=ttl_seconds)
|
|
87
|
+
self.cache[token] = (session, expiry_time)
|
|
88
|
+
|
|
89
|
+
def read(self, token: str) -> Optional['Session']:
|
|
90
|
+
value = self.cache.get(token)
|
|
91
|
+
if value is None:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
session, expiry = value
|
|
95
|
+
if expiry < datetime.now(timezone.utc):
|
|
96
|
+
del self.cache[token]
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
return session
|
|
100
|
+
|
|
101
|
+
# constructor
|
|
102
|
+
|
|
103
|
+
def __init__(self, storage: 'SessionManager.Storage'):
|
|
104
|
+
self.storage = storage
|
|
105
|
+
self.session_factory : Optional[Callable[[Any], Session]] = None
|
|
106
|
+
|
|
107
|
+
# public
|
|
108
|
+
|
|
109
|
+
def set_factory(self, factory: Callable[..., Session]) -> None:
|
|
110
|
+
"""
|
|
111
|
+
set a factory function that will be used to create a concrete session
|
|
112
|
+
Args:
|
|
113
|
+
factory: the function
|
|
114
|
+
"""
|
|
115
|
+
self.session_factory = factory
|
|
116
|
+
|
|
117
|
+
def create_session(self, *args, **kwargs) -> Session:
|
|
118
|
+
"""
|
|
119
|
+
create a session given the arguments (usually a token, etc.)
|
|
120
|
+
Args:
|
|
121
|
+
args: rest args
|
|
122
|
+
kwargs: keyword args
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
the new session
|
|
126
|
+
"""
|
|
127
|
+
return self.session_factory(*args, **kwargs)
|
|
128
|
+
|
|
129
|
+
def store_session(self, token: str, session: Session, expiry: datetime):
|
|
130
|
+
now = datetime.now(timezone.utc)
|
|
131
|
+
ttl_seconds = max(int((expiry - now).total_seconds()), 0)
|
|
132
|
+
|
|
133
|
+
self.storage.store(token, session, ttl_seconds)
|
|
134
|
+
|
|
135
|
+
def read_session(self, token: str) -> Optional[Session]:
|
|
136
|
+
return self.storage.read(token)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx_service
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.5
|
|
4
4
|
Summary: Aspyx Service framework
|
|
5
5
|
Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -26,7 +26,7 @@ License: MIT License
|
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
License-File: LICENSE
|
|
28
28
|
Requires-Python: >=3.9
|
|
29
|
-
Requires-Dist: aspyx>=1.
|
|
29
|
+
Requires-Dist: aspyx>=1.6.0
|
|
30
30
|
Requires-Dist: fastapi~=0.115.13
|
|
31
31
|
Requires-Dist: httpx~=0.28.1
|
|
32
32
|
Requires-Dist: msgpack~=1.1.1
|
|
@@ -60,6 +60,8 @@ Description-Content-Type: text/markdown
|
|
|
60
60
|
- [Rest Calls](#rest-calls)
|
|
61
61
|
- [Intercepting calls](#intercepting-calls)
|
|
62
62
|
- [FastAPI server](#fastapi-server)
|
|
63
|
+
- [Session](#session)
|
|
64
|
+
- [Authorization](#authorization)
|
|
63
65
|
- [Implementing Channels](#implementing-channels)
|
|
64
66
|
- [Version History](#version-history)
|
|
65
67
|
|
|
@@ -107,18 +109,20 @@ class TestComponent(Component):
|
|
|
107
109
|
After booting the DI infrastructure with a main module we could already call a service:
|
|
108
110
|
|
|
109
111
|
**Example**:
|
|
112
|
+
|
|
110
113
|
```python
|
|
111
114
|
@module(imports=[ServiceModule])
|
|
112
115
|
class Module:
|
|
113
116
|
def __init__(self):
|
|
114
117
|
pass
|
|
115
|
-
|
|
118
|
+
|
|
116
119
|
@create()
|
|
117
120
|
def create_registry(self) -> ConsulComponentRegistry:
|
|
118
|
-
return ConsulComponentRegistry(Server.port, Consul(host="localhost", port=8500))
|
|
121
|
+
return ConsulComponentRegistry(Server.port, Consul(host="localhost", port=8500)) # a consul based registry!
|
|
122
|
+
|
|
119
123
|
|
|
120
124
|
environment = Environment(Module)
|
|
121
|
-
service_manager = environment.
|
|
125
|
+
service_manager = environment.read(ServiceManager)
|
|
122
126
|
|
|
123
127
|
service = service_manager.get_service(TestService)
|
|
124
128
|
|
|
@@ -166,10 +170,24 @@ environment = server.boot(Module)
|
|
|
166
170
|
Of course, service can also be called locally. In case of multiple possible channels, a keyword argument is used to
|
|
167
171
|
determine a specific channel. As a local channel has the name "local", the appropriate call is:
|
|
168
172
|
|
|
173
|
+
**Example**:
|
|
174
|
+
|
|
169
175
|
```python
|
|
170
176
|
service = service_manager.get_service(TestService, preferred_channel="local")
|
|
171
177
|
```
|
|
172
178
|
|
|
179
|
+
The default can be set globally with the method `set_preferred_channel(channel: str)`
|
|
180
|
+
|
|
181
|
+
Injecting services is also possible via the decorator `@inject_service(preferred_channel=""")`
|
|
182
|
+
|
|
183
|
+
**Example**:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
@inject_service()
|
|
187
|
+
def set_service(self, service: TestService)
|
|
188
|
+
self.service = service
|
|
189
|
+
```
|
|
190
|
+
|
|
173
191
|
## Features
|
|
174
192
|
|
|
175
193
|
The library offers:
|
|
@@ -185,6 +203,14 @@ The library offers:
|
|
|
185
203
|
As well as the DI and AOP core, all mechanisms are heavily optimized.
|
|
186
204
|
A simple benchmark resulted in message roundtrips in significanlty under a ms per call.
|
|
187
205
|
|
|
206
|
+
## Installation
|
|
207
|
+
|
|
208
|
+
Just install from PyPI with
|
|
209
|
+
|
|
210
|
+
`pip install aspyx-service`
|
|
211
|
+
|
|
212
|
+
The library is tested with all Python version >= 3.9
|
|
213
|
+
|
|
188
214
|
Let's see some details
|
|
189
215
|
|
|
190
216
|
## Service and Component declaration
|
|
@@ -352,12 +378,14 @@ Channels implement the possible transport layer protocols. In the sense of a dyn
|
|
|
352
378
|
Several channels are implemented:
|
|
353
379
|
|
|
354
380
|
- `dispatch-json`
|
|
355
|
-
channel that
|
|
381
|
+
channel that posts generic `Request` objects via a `invoke` POST-call
|
|
356
382
|
- `dispatch-msgpack`
|
|
357
|
-
channel that
|
|
383
|
+
channel that posts generic `Request` objects via a `invoke` POST-call after packing the json with msgpack
|
|
358
384
|
- `rest`
|
|
359
385
|
channel that executes regular rest-calls as defined by a couple of decorators.
|
|
360
386
|
|
|
387
|
+
The `dispatch`channels have the big advantage, that you don`t have to deal with additional http decorators!
|
|
388
|
+
|
|
361
389
|
All channels react on changed URLs as provided by the component registry.
|
|
362
390
|
|
|
363
391
|
A so called `URLSelector` is used internally to provide URLs for every single call. Two subclasses exist that offer a different logic
|
|
@@ -465,23 +493,39 @@ class ChannelAdvice:
|
|
|
465
493
|
|
|
466
494
|
## FastAPI server
|
|
467
495
|
|
|
468
|
-
|
|
496
|
+
The required - `FastAPI` - infrastructure to expose those services requires:
|
|
469
497
|
|
|
498
|
+
- a `FastAPI` instance
|
|
499
|
+
- an injectable `FastAPIServer`
|
|
500
|
+
- and a final `boot` call with the root module, which will return an `Environment`
|
|
470
501
|
|
|
471
502
|
```python
|
|
472
|
-
|
|
473
|
-
|
|
503
|
+
fast_api = FastAPI() # so you can run it with uvivorn from command-line
|
|
504
|
+
|
|
505
|
+
@module(imports=[ServiceModule])
|
|
506
|
+
class Module:
|
|
474
507
|
def __init__(self):
|
|
475
508
|
pass
|
|
509
|
+
|
|
510
|
+
@create()
|
|
511
|
+
def create_server(self, service_manager: ServiceManager, component_registry: ComponentRegistry) -> FastAPIServer:
|
|
512
|
+
return FastAPIServer(fastapi, service_manager, component_registry)
|
|
476
513
|
|
|
477
|
-
server = FastAPIServer(host="0.0.0.0", port=8000)
|
|
478
514
|
|
|
479
|
-
|
|
515
|
+
environment = FastAPIServer.boot(Moudle, host="0.0.0.0", port=8000)
|
|
480
516
|
```
|
|
481
517
|
|
|
482
518
|
This setup will also expose all service interfaces decorated with the corresponding http decorators!
|
|
483
519
|
No need to add any FastAPI decorators, since the mapping is already done internally!
|
|
484
520
|
|
|
521
|
+
## Session
|
|
522
|
+
|
|
523
|
+
TODO
|
|
524
|
+
|
|
525
|
+
## Authorization
|
|
526
|
+
|
|
527
|
+
TODO
|
|
528
|
+
|
|
485
529
|
## Implementing Channels
|
|
486
530
|
|
|
487
531
|
To implement a new channel, you only need to derive from one of the possible base classes ( `Channel` or `HTTPXChannel` that already has a `httpx` client)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
aspyx_service/__init__.py,sha256=OWJoScdDVK1NTs9cIgImShgEdJb8TZHLBQjS11rA0Yo,2564
|
|
2
|
+
aspyx_service/authorization.py,sha256=0B1xb0WrRaj2rcGTHVUhh6i8aA0sy7BmpYA18xI9LQA,3833
|
|
3
|
+
aspyx_service/channels.py,sha256=u1afqUfcmVgLxXDXC2BYHH-dMozLcmVROuPRpypSwr8,16397
|
|
4
|
+
aspyx_service/healthcheck.py,sha256=vjfY7s5kd5mRJynVpvAJ4BvVF7QY1xrvj94Y-m041LQ,5615
|
|
5
|
+
aspyx_service/registries.py,sha256=bnTjKb40fbZXA52E2lDSEzCWI5_NBKZzQjc8ffufB5g,8039
|
|
6
|
+
aspyx_service/restchannel.py,sha256=0Xb8grEE8Dyx3g3ENl78DDMKa2WGjIKIPgOrpw5p9ak,8470
|
|
7
|
+
aspyx_service/server.py,sha256=HLMsEpiXgpF7s1r1_1iRiufAvDfrOZGvljPjpf-7RCM,11096
|
|
8
|
+
aspyx_service/service.py,sha256=drETAZasbYJZisnmbhAqW0-mHghJ3IWyPaU-7etxvBI,27003
|
|
9
|
+
aspyx_service/session.py,sha256=HjGpnmwdislc8Ur6pQbSMi2K-lvTsb9_XyO80zupiF8,3713
|
|
10
|
+
aspyx_service-0.10.5.dist-info/METADATA,sha256=QHq4kp7Zn9roaenqpDa_t9KqZBlr0Ku4F7621L1ZM5k,17946
|
|
11
|
+
aspyx_service-0.10.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
12
|
+
aspyx_service-0.10.5.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
|
|
13
|
+
aspyx_service-0.10.5.dist-info/RECORD,,
|
aspyx_service/serialization.py
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
deserialization functions
|
|
3
|
-
"""
|
|
4
|
-
from dataclasses import is_dataclass, fields
|
|
5
|
-
from functools import lru_cache
|
|
6
|
-
from typing import get_origin, get_args, Union
|
|
7
|
-
|
|
8
|
-
from pydantic import BaseModel
|
|
9
|
-
|
|
10
|
-
class TypeDeserializer:
|
|
11
|
-
# constructor
|
|
12
|
-
|
|
13
|
-
def __init__(self, typ):
|
|
14
|
-
self.typ = typ
|
|
15
|
-
self.deserializer = self._build_deserializer(typ)
|
|
16
|
-
|
|
17
|
-
def __call__(self, value):
|
|
18
|
-
return self.deserializer(value)
|
|
19
|
-
|
|
20
|
-
# internal
|
|
21
|
-
|
|
22
|
-
def _build_deserializer(self, typ):
|
|
23
|
-
origin = get_origin(typ)
|
|
24
|
-
args = get_args(typ)
|
|
25
|
-
|
|
26
|
-
if origin is Union:
|
|
27
|
-
deserializers = [TypeDeserializer(arg) for arg in args if arg is not type(None)]
|
|
28
|
-
def deser_union(value):
|
|
29
|
-
if value is None:
|
|
30
|
-
return None
|
|
31
|
-
for d in deserializers:
|
|
32
|
-
try:
|
|
33
|
-
return d(value)
|
|
34
|
-
except Exception:
|
|
35
|
-
continue
|
|
36
|
-
return value
|
|
37
|
-
return deser_union
|
|
38
|
-
|
|
39
|
-
if isinstance(typ, type) and issubclass(typ, BaseModel):
|
|
40
|
-
return typ.parse_obj
|
|
41
|
-
|
|
42
|
-
if is_dataclass(typ):
|
|
43
|
-
field_deserializers = {f.name: TypeDeserializer(f.type) for f in fields(typ)}
|
|
44
|
-
def deser_dataclass(value):
|
|
45
|
-
if is_dataclass(value):
|
|
46
|
-
return value
|
|
47
|
-
|
|
48
|
-
return typ(**{
|
|
49
|
-
k: field_deserializers[k](v) for k, v in value.items()
|
|
50
|
-
})
|
|
51
|
-
return deser_dataclass
|
|
52
|
-
|
|
53
|
-
if origin is list:
|
|
54
|
-
item_deser = TypeDeserializer(args[0]) if args else lambda x: x
|
|
55
|
-
return lambda v: [item_deser(item) for item in v]
|
|
56
|
-
|
|
57
|
-
if origin is dict:
|
|
58
|
-
key_deser = TypeDeserializer(args[0]) if args else lambda x: x
|
|
59
|
-
val_deser = TypeDeserializer(args[1]) if len(args) > 1 else lambda x: x
|
|
60
|
-
return lambda v: {key_deser(k): val_deser(val) for k, val in v.items()}
|
|
61
|
-
|
|
62
|
-
# Fallback
|
|
63
|
-
return lambda v: v
|
|
64
|
-
|
|
65
|
-
class TypeSerializer:
|
|
66
|
-
def __init__(self, typ):
|
|
67
|
-
self.typ = typ
|
|
68
|
-
self.serializer = self._build_serializer(typ)
|
|
69
|
-
|
|
70
|
-
def __call__(self, value):
|
|
71
|
-
return self.serializer(value)
|
|
72
|
-
|
|
73
|
-
def _build_serializer(self, typ):
|
|
74
|
-
origin = get_origin(typ)
|
|
75
|
-
args = get_args(typ)
|
|
76
|
-
|
|
77
|
-
if origin is Union:
|
|
78
|
-
serializers = [TypeSerializer(arg) for arg in args if arg is not type(None)]
|
|
79
|
-
def ser_union(value):
|
|
80
|
-
if value is None:
|
|
81
|
-
return None
|
|
82
|
-
for s in serializers:
|
|
83
|
-
try:
|
|
84
|
-
return s(value)
|
|
85
|
-
except Exception:
|
|
86
|
-
continue
|
|
87
|
-
return value
|
|
88
|
-
return ser_union
|
|
89
|
-
|
|
90
|
-
if isinstance(typ, type) and issubclass(typ, BaseModel):
|
|
91
|
-
return lambda v: v.dict() if v is not None else None
|
|
92
|
-
|
|
93
|
-
if is_dataclass(typ):
|
|
94
|
-
field_serializers = {f.name: TypeSerializer(f.type) for f in fields(typ)}
|
|
95
|
-
def ser_dataclass(obj):
|
|
96
|
-
if obj is None:
|
|
97
|
-
return None
|
|
98
|
-
return {k: field_serializers[k](getattr(obj, k)) for k in field_serializers}
|
|
99
|
-
return ser_dataclass
|
|
100
|
-
|
|
101
|
-
if origin is list:
|
|
102
|
-
item_ser = TypeSerializer(args[0]) if args else lambda x: x
|
|
103
|
-
return lambda v: [item_ser(item) for item in v] if v is not None else None
|
|
104
|
-
|
|
105
|
-
if origin is dict:
|
|
106
|
-
key_ser = TypeSerializer(args[0]) if args else lambda x: x
|
|
107
|
-
val_ser = TypeSerializer(args[1]) if len(args) > 1 else lambda x: x
|
|
108
|
-
return lambda v: {key_ser(k): val_ser(val) for k, val in v.items()} if v is not None else None
|
|
109
|
-
|
|
110
|
-
# Fallback: primitive Typen oder unbekannt
|
|
111
|
-
return lambda v: v
|
|
112
|
-
|
|
113
|
-
@lru_cache(maxsize=512)
|
|
114
|
-
def get_deserializer(typ):
|
|
115
|
-
"""
|
|
116
|
-
return a function that is able to deserialize a value of the specified type
|
|
117
|
-
|
|
118
|
-
Args:
|
|
119
|
-
typ: the type
|
|
120
|
-
|
|
121
|
-
Returns:
|
|
122
|
-
|
|
123
|
-
"""
|
|
124
|
-
return TypeDeserializer(typ)
|
|
125
|
-
|
|
126
|
-
@lru_cache(maxsize=512)
|
|
127
|
-
def get_serializer(typ):
|
|
128
|
-
"""
|
|
129
|
-
return a function that is able to deserialize a value of the specified type
|
|
130
|
-
|
|
131
|
-
Args:
|
|
132
|
-
typ: the type
|
|
133
|
-
|
|
134
|
-
Returns:
|
|
135
|
-
|
|
136
|
-
"""
|
|
137
|
-
return TypeSerializer(typ)
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
aspyx_service/__init__.py,sha256=6t24VPrSCG83EAvYlqCKdEcEbyCY3vrSb5GoAx01Ymg,1662
|
|
2
|
-
aspyx_service/channels.py,sha256=p5JIyo7eWyBiR2xQrfsEvq2L89FzeFT1tqKYhvXQbXs,9035
|
|
3
|
-
aspyx_service/healthcheck.py,sha256=8ZPSkAx6ypoYaxDMkJT_MtL2pEN2LcUAishAWPCy-3I,5624
|
|
4
|
-
aspyx_service/registries.py,sha256=JSsD32F8VffZMHyEDuapEWtvmem5SK9kR6bgsFRLFZQ,8002
|
|
5
|
-
aspyx_service/restchannel.py,sha256=Q_7RURjZZW7N2LBLY9BL7qKyS_A62X7yFoGUoE-_0YY,9103
|
|
6
|
-
aspyx_service/serialization.py,sha256=GEgfg1cNSOJ_oe0gEm0ajzugLyUmPiEsp9Qz6Fu4vkA,4207
|
|
7
|
-
aspyx_service/server.py,sha256=PHKx3O90jnxm8dA4z331_51znhU-HlRB2Fa1AikmFXA,7052
|
|
8
|
-
aspyx_service/service.py,sha256=Odl6_nvOOJ2lFjqCLJD8KLPt-sCG55VtQQiKTyNEOPs,26062
|
|
9
|
-
aspyx_service-0.10.3.dist-info/METADATA,sha256=MrWNfy3T2m4G5MyKlV4Spr2yi6N_hE1VLRT3vNJ6Hm0,16955
|
|
10
|
-
aspyx_service-0.10.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
-
aspyx_service-0.10.3.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
|
|
12
|
-
aspyx_service-0.10.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|