aspyx-service 0.10.2__py3-none-any.whl → 0.10.4__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 +28 -4
- aspyx_service/authorization.py +135 -0
- aspyx_service/channels.py +251 -57
- aspyx_service/healthcheck.py +1 -1
- aspyx_service/registries.py +5 -5
- aspyx_service/restchannel.py +17 -21
- aspyx_service/serialization.py +6 -3
- aspyx_service/server.py +139 -69
- aspyx_service/service.py +55 -19
- aspyx_service/session.py +97 -0
- {aspyx_service-0.10.2.dist-info → aspyx_service-0.10.4.dist-info}/METADATA +41 -10
- aspyx_service-0.10.4.dist-info/RECORD +14 -0
- aspyx_service-0.10.2.dist-info/RECORD +0 -12
- {aspyx_service-0.10.2.dist-info → aspyx_service-0.10.4.dist-info}/WHEEL +0 -0
- {aspyx_service-0.10.2.dist-info → aspyx_service-0.10.4.dist-info}/licenses/LICENSE +0 -0
aspyx_service/session.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
session related module
|
|
3
|
+
"""
|
|
4
|
+
import contextvars
|
|
5
|
+
from typing import Type, Optional, Callable, Any, TypeVar
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from cachetools import TTLCache
|
|
8
|
+
|
|
9
|
+
from aspyx.di import injectable
|
|
10
|
+
from aspyx.threading import ThreadLocal
|
|
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
|
+
@injectable()
|
|
23
|
+
class SessionManager:
|
|
24
|
+
"""
|
|
25
|
+
A SessionManager controls the lifecycle of sessions and is responsible to establish a session thread local.
|
|
26
|
+
"""
|
|
27
|
+
#current_session = ThreadLocal[Session]()
|
|
28
|
+
current_session = contextvars.ContextVar("session")
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def current(cls, type: Type[T]) -> T:
|
|
32
|
+
"""
|
|
33
|
+
return the current session associated with the thread
|
|
34
|
+
Args:
|
|
35
|
+
type: the session type
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
the current session
|
|
39
|
+
"""
|
|
40
|
+
return cls.current_session.get()
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def set_session(cls, session: Session) -> None:
|
|
44
|
+
"""
|
|
45
|
+
set the current session in the thread context
|
|
46
|
+
Args:
|
|
47
|
+
session: the session
|
|
48
|
+
"""
|
|
49
|
+
cls.current_session.set(session)
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def delete_session(cls) -> None:
|
|
53
|
+
"""
|
|
54
|
+
delete the current session
|
|
55
|
+
"""
|
|
56
|
+
cls.current_session.set(None)#clear()
|
|
57
|
+
|
|
58
|
+
# constructor
|
|
59
|
+
|
|
60
|
+
def __init__(self):
|
|
61
|
+
self.sessions = TTLCache(maxsize=1000, ttl=3600)
|
|
62
|
+
self.session_creator : Optional[Callable[[Any], Session]] = None
|
|
63
|
+
|
|
64
|
+
# public
|
|
65
|
+
|
|
66
|
+
def set_session_factory(self, callable: Callable[..., Session]) -> None:
|
|
67
|
+
"""
|
|
68
|
+
set a factory function that will be used to create a concrete session
|
|
69
|
+
Args:
|
|
70
|
+
callable: the function
|
|
71
|
+
"""
|
|
72
|
+
self.session_creator = callable
|
|
73
|
+
|
|
74
|
+
def create_session(self, *args, **kwargs) -> Session:
|
|
75
|
+
"""
|
|
76
|
+
create a session given the argument s(usually a token, etc.)
|
|
77
|
+
Args:
|
|
78
|
+
args: rest args
|
|
79
|
+
kwargs: keyword args
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
the new session
|
|
83
|
+
"""
|
|
84
|
+
return self.session_creator(*args, **kwargs)
|
|
85
|
+
|
|
86
|
+
def store_session(self, token: str, session: Session, expiry: datetime):
|
|
87
|
+
now = datetime.now(timezone.utc)
|
|
88
|
+
ttl_seconds = max(int((expiry - now).total_seconds()), 0)
|
|
89
|
+
self.sessions[token] = (session, ttl_seconds)
|
|
90
|
+
|
|
91
|
+
def get_session(self, token: str) -> Optional[Session]:
|
|
92
|
+
value = self.sessions.get(token)
|
|
93
|
+
if value is None:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
session, ttl = value
|
|
97
|
+
return session
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx_service
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.4
|
|
4
4
|
Summary: Aspyx Service framework
|
|
5
5
|
Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -26,7 +26,8 @@ License: MIT License
|
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
License-File: LICENSE
|
|
28
28
|
Requires-Python: >=3.9
|
|
29
|
-
Requires-Dist: aspyx>=1.5.
|
|
29
|
+
Requires-Dist: aspyx>=1.5.3
|
|
30
|
+
Requires-Dist: cachetools~=5.5.2
|
|
30
31
|
Requires-Dist: fastapi~=0.115.13
|
|
31
32
|
Requires-Dist: httpx~=0.28.1
|
|
32
33
|
Requires-Dist: msgpack~=1.1.1
|
|
@@ -60,6 +61,8 @@ Description-Content-Type: text/markdown
|
|
|
60
61
|
- [Rest Calls](#rest-calls)
|
|
61
62
|
- [Intercepting calls](#intercepting-calls)
|
|
62
63
|
- [FastAPI server](#fastapi-server)
|
|
64
|
+
- [Session](#session)
|
|
65
|
+
- [Authorization](#authorization)
|
|
63
66
|
- [Implementing Channels](#implementing-channels)
|
|
64
67
|
- [Version History](#version-history)
|
|
65
68
|
|
|
@@ -70,19 +73,23 @@ that lets you deploy, discover and call services with different remoting protoco
|
|
|
70
73
|
|
|
71
74
|
The basic design consists of four different concepts:
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
**Service**
|
|
77
|
+
|
|
74
78
|
defines a group of methods that can be called either locally or remotely.
|
|
75
79
|
These methods represent the functional interface exposed to clients — similar to an interface in traditional programming
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
**Component**
|
|
82
|
+
|
|
78
83
|
a component bundles one or more services and declares the channels (protocols) used to expose them.
|
|
79
84
|
Think of a component as a deployment unit or module.
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
**Component Registry**
|
|
87
|
+
|
|
82
88
|
acts as the central directory for managing available components.
|
|
83
89
|
It allows the framework to register, discover, and resolve components and their services.
|
|
84
90
|
|
|
85
|
-
|
|
91
|
+
**Channel**
|
|
92
|
+
|
|
86
93
|
is a pluggable transport layer that defines how service method invocations are transmitted and handled.
|
|
87
94
|
|
|
88
95
|
Let's look at the "interface" layer first.
|
|
@@ -111,7 +118,7 @@ class Module:
|
|
|
111
118
|
|
|
112
119
|
@create()
|
|
113
120
|
def create_registry(self) -> ConsulComponentRegistry:
|
|
114
|
-
return ConsulComponentRegistry(Server.port, "
|
|
121
|
+
return ConsulComponentRegistry(Server.port, Consul(host="localhost", port=8500)) # a consul based registry!
|
|
115
122
|
|
|
116
123
|
environment = Environment(Module)
|
|
117
124
|
service_manager = environment.get(ServiceManager)
|
|
@@ -162,10 +169,24 @@ environment = server.boot(Module)
|
|
|
162
169
|
Of course, service can also be called locally. In case of multiple possible channels, a keyword argument is used to
|
|
163
170
|
determine a specific channel. As a local channel has the name "local", the appropriate call is:
|
|
164
171
|
|
|
172
|
+
**Example**:
|
|
173
|
+
|
|
165
174
|
```python
|
|
166
175
|
service = service_manager.get_service(TestService, preferred_channel="local")
|
|
167
176
|
```
|
|
168
177
|
|
|
178
|
+
The default can be set globally with the method `set_preferred_channel(channel: str)`
|
|
179
|
+
|
|
180
|
+
Injecting services is also possible via the decorator `@inject_service(preferred_channel=""")`
|
|
181
|
+
|
|
182
|
+
**Example**:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
@inject_service()
|
|
186
|
+
def set_service(self, service: TestService)
|
|
187
|
+
self.service = service
|
|
188
|
+
```
|
|
189
|
+
|
|
169
190
|
## Features
|
|
170
191
|
|
|
171
192
|
The library offers:
|
|
@@ -348,12 +369,14 @@ Channels implement the possible transport layer protocols. In the sense of a dyn
|
|
|
348
369
|
Several channels are implemented:
|
|
349
370
|
|
|
350
371
|
- `dispatch-json`
|
|
351
|
-
channel that
|
|
372
|
+
channel that posts generic `Request` objects via a `invoke` POST-call
|
|
352
373
|
- `dispatch-msgpack`
|
|
353
|
-
channel that
|
|
374
|
+
channel that posts generic `Request` objects via a `invoke` POST-call after packing the json with msgpack
|
|
354
375
|
- `rest`
|
|
355
376
|
channel that executes regular rest-calls as defined by a couple of decorators.
|
|
356
377
|
|
|
378
|
+
The `dispatch`channels have the big advantage, that you don`t have to deal with additional http decorators!
|
|
379
|
+
|
|
357
380
|
All channels react on changed URLs as provided by the component registry.
|
|
358
381
|
|
|
359
382
|
A so called `URLSelector` is used internally to provide URLs for every single call. Two subclasses exist that offer a different logic
|
|
@@ -361,7 +384,7 @@ A so called `URLSelector` is used internally to provide URLs for every single ca
|
|
|
361
384
|
- `FirstURLSelector` always returns the first URL of the list of possible URLs
|
|
362
385
|
- `RoundRobinURLSelector` switches sequentially between all URLs.
|
|
363
386
|
|
|
364
|
-
To customize the behavior, an around advice can be implemented easily:
|
|
387
|
+
To customize the behavior, an `around` advice can be implemented easily:
|
|
365
388
|
|
|
366
389
|
**Example**:
|
|
367
390
|
|
|
@@ -478,6 +501,14 @@ class Module():
|
|
|
478
501
|
This setup will also expose all service interfaces decorated with the corresponding http decorators!
|
|
479
502
|
No need to add any FastAPI decorators, since the mapping is already done internally!
|
|
480
503
|
|
|
504
|
+
## Session
|
|
505
|
+
|
|
506
|
+
TODO
|
|
507
|
+
|
|
508
|
+
## Authorization
|
|
509
|
+
|
|
510
|
+
TODO
|
|
511
|
+
|
|
481
512
|
## Implementing Channels
|
|
482
513
|
|
|
483
514
|
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,14 @@
|
|
|
1
|
+
aspyx_service/__init__.py,sha256=h9zcGzYaVdU2_mXON-k-mgYErEJ7eIs-wjNDKtet1_s,2488
|
|
2
|
+
aspyx_service/authorization.py,sha256=vBM8uPsAZwMiTilqFZMJ101Qy37gL2Y9vdGTLp-ykFg,3983
|
|
3
|
+
aspyx_service/channels.py,sha256=3Fv6055n1hw8HQ6MKu2BROsq6gmPdKIhayUhQiTRLic,16461
|
|
4
|
+
aspyx_service/healthcheck.py,sha256=vjfY7s5kd5mRJynVpvAJ4BvVF7QY1xrvj94Y-m041LQ,5615
|
|
5
|
+
aspyx_service/registries.py,sha256=bnTjKb40fbZXA52E2lDSEzCWI5_NBKZzQjc8ffufB5g,8039
|
|
6
|
+
aspyx_service/restchannel.py,sha256=wutLGnxqMAS1oX7cc1pvN8qIIpjeBEvPz_hsKeWHVZs,8474
|
|
7
|
+
aspyx_service/serialization.py,sha256=OrwOAUsHQyGDyhYTkTc-0v8urYMbh0_3fgkpTvNOl0o,4214
|
|
8
|
+
aspyx_service/server.py,sha256=_LFRy1XIXTbu7CLoXsoPGQwKHkpSPDh82VV4ppahzr0,9057
|
|
9
|
+
aspyx_service/service.py,sha256=drETAZasbYJZisnmbhAqW0-mHghJ3IWyPaU-7etxvBI,27003
|
|
10
|
+
aspyx_service/session.py,sha256=ytWRTlnu1kDpTkLBCy_WF2i-mdffG-exIqsUQZ1Udo0,2592
|
|
11
|
+
aspyx_service-0.10.4.dist-info/METADATA,sha256=-WA5_ta5gP3AXy8EA0JhFzoFstaS_RgeGjyglOAYqXw,17499
|
|
12
|
+
aspyx_service-0.10.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
13
|
+
aspyx_service-0.10.4.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
|
|
14
|
+
aspyx_service-0.10.4.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
aspyx_service/__init__.py,sha256=6t24VPrSCG83EAvYlqCKdEcEbyCY3vrSb5GoAx01Ymg,1662
|
|
2
|
-
aspyx_service/channels.py,sha256=9EjnY6DV_toqeQNswYZgmAtirXM_TTLGErs6hb1U9yw,8934
|
|
3
|
-
aspyx_service/healthcheck.py,sha256=8ZPSkAx6ypoYaxDMkJT_MtL2pEN2LcUAishAWPCy-3I,5624
|
|
4
|
-
aspyx_service/registries.py,sha256=JSsD32F8VffZMHyEDuapEWtvmem5SK9kR6bgsFRLFZQ,8002
|
|
5
|
-
aspyx_service/restchannel.py,sha256=OVGyKNtORJEFnWsFB5MPkNldVFfJTGuUa4X658_x9Kg,9169
|
|
6
|
-
aspyx_service/serialization.py,sha256=lEr106yiZ0UzVxGTGdiorZSSSNI7vP4C7RyrBJku-U0,4133
|
|
7
|
-
aspyx_service/server.py,sha256=PHKx3O90jnxm8dA4z331_51znhU-HlRB2Fa1AikmFXA,7052
|
|
8
|
-
aspyx_service/service.py,sha256=-hhxRpEtMn-JP57bHNF521zbGMHUy5YaXkbXyiu7dW4,25927
|
|
9
|
-
aspyx_service-0.10.2.dist-info/METADATA,sha256=KEdh30D63sXs26FVOtCph_LF521bBJa7A6Qpc8d-9iQ,16966
|
|
10
|
-
aspyx_service-0.10.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
-
aspyx_service-0.10.2.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
|
|
12
|
-
aspyx_service-0.10.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|