ephaptic 0.2.3__py3-none-any.whl → 0.2.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.
ephaptic/__init__.py CHANGED
@@ -3,6 +3,7 @@ from .ephaptic import (
3
3
  active_user,
4
4
  expose,
5
5
  identity_loader,
6
+ event,
6
7
  )
7
8
 
8
9
  from .client import (
ephaptic/cli/__main__.py CHANGED
@@ -83,6 +83,7 @@ def generate(
83
83
 
84
84
  schema_output = {
85
85
  "methods": {},
86
+ "events": {},
86
87
  "definitions": {},
87
88
  }
88
89
 
@@ -116,6 +117,15 @@ def generate(
116
117
 
117
118
  schema_output["methods"][name] = method_schema
118
119
 
120
+ for name, model in ephaptic._exposed_events.items():
121
+ typer.secho(f" - {name}")
122
+ adapter = TypeAdapter(model)
123
+
124
+ schema_output["events"][name] = create_schema(
125
+ adapter,
126
+ schema_output["definitions"],
127
+ )
128
+
119
129
  new = json.dumps(schema_output, indent=2)
120
130
 
121
131
  if output.exists():
ephaptic/ephaptic.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ from warnings import deprecated
2
3
  import msgpack
3
4
  import redis.asyncio as redis
4
5
  import pydantic
@@ -71,12 +72,23 @@ class ConnectionManager:
71
72
  manager = ConnectionManager()
72
73
 
73
74
  _EXPOSED_FUNCTIONS = {}
75
+ _EXPOSED_EVENTS = {}
74
76
  _IDENTITY_LOADER: Optional[Callable] = None
75
77
 
76
78
  class EphapticTarget:
77
79
  def __init__(self, user_ids: list[str]):
78
80
  self.user_ids = user_ids
79
81
 
82
+ async def emit(self, event_instance: pydantic.BaseModel):
83
+ event_name = event_instance.__class__.__name__
84
+ payload = event_instance.model_dump(mode='json')
85
+ await manager.broadcast(
86
+ self.user_ids,
87
+ event_name,
88
+ args=[],
89
+ kwargs=payload,
90
+ )
91
+
80
92
  def __getattr__(self, name: str):
81
93
  async def emitter(*args, **kwargs):
82
94
  await manager.broadcast(self.user_ids, name, list(args), dict(kwargs))
@@ -90,8 +102,13 @@ def identity_loader(func: Callable):
90
102
  _IDENTITY_LOADER = func
91
103
  return func
92
104
 
105
+ def event(model: typing.Type[pydantic.BaseModel]):
106
+ _EXPOSED_EVENTS[model.__name__] = model
107
+ return model
108
+
93
109
  class Ephaptic:
94
110
  _exposed_functions: Dict[str, Callable] = {}
111
+ _exposed_events: Dict[str, typing.Type[pydantic.BaseModel]]
95
112
  _identity_loader: Optional[Callable] = None
96
113
 
97
114
  def _async(self, func: Callable):
@@ -125,8 +142,9 @@ class Ephaptic:
125
142
  case _:
126
143
  raise TypeError(f"Unsupported app type: {module}")
127
144
 
128
- cls._exposed_functions = _EXPOSED_FUNCTIONS.copy()
129
- cls._identity_loader = _IDENTITY_LOADER
145
+ instance._exposed_functions = _EXPOSED_FUNCTIONS.copy()
146
+ instance._exposed_events = _EXPOSED_EVENTS.copy()
147
+ instance._identity_loader = _IDENTITY_LOADER
130
148
 
131
149
  return instance
132
150
 
@@ -135,6 +153,10 @@ class Ephaptic:
135
153
  self._exposed_functions[func.__name__] = func
136
154
  return func
137
155
 
156
+ def event(self, model: typing.Type[pydantic.BaseModel]):
157
+ self._exposed_events[model.__name__] = model
158
+ return model
159
+
138
160
  def identity_loader(self, func: Callable):
139
161
  self._identity_loader = func
140
162
  return func
@@ -147,6 +169,7 @@ class Ephaptic:
147
169
  return EphapticTarget(targets)
148
170
 
149
171
  def __getattr__(self, name: str):
172
+ @deprecated("Use `emit` and the new (typed) event system instead.")
150
173
  async def emitter(*args, **kwargs):
151
174
  transport: Transport = _active_transport_ctx.get()
152
175
  if not transport:
@@ -163,6 +186,24 @@ class Ephaptic:
163
186
 
164
187
  return emitter
165
188
 
189
+ async def emit(self, event_instance: pydantic.BaseModel):
190
+ event_name = event_instance.__class__.__name__
191
+ payload = event_instance.model_dump(mode='json')
192
+ transport: Transport = _active_transport_ctx.get()
193
+ if not transport:
194
+ raise RuntimeError(
195
+ f".emit({event_name}) called outside RPC context."
196
+ f"Use .to(...).emit({event_name}) to broadcast from background tasks, to specific user(s)."
197
+ )
198
+
199
+ # NOTE: There is slight duplication here and in the EphapticTarget. Perhaps make these functions internally route to EphapticTargets but pass the transport to use?
200
+
201
+ await transport.send(msgpack.dumps({
202
+ 'type': 'event',
203
+ 'name': event_name,
204
+ 'payload': {'args': [], 'kwargs': payload}
205
+ }))
206
+
166
207
  async def handle_transport(self, transport: Transport):
167
208
  current_uid = None
168
209
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ephaptic
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: The Python client/server package for ephaptic.
5
5
  Author-email: uukelele <robustrobot11@gmail.com>
6
6
  License: MIT License
@@ -92,16 +92,16 @@ What are you waiting for? **Let's go.**
92
92
  <details>
93
93
  <summary>Python</summary>
94
94
 
95
- #### Client:
95
+ <h4>Client:</h4>
96
96
 
97
97
  ```
98
- pip install ephaptic
98
+ $ pip install ephaptic
99
99
  ```
100
100
 
101
- #### Server:
101
+ <h4>Server:</h4>
102
102
 
103
103
  ```
104
- pip install ephaptic[server]
104
+ $ pip install ephaptic[server]
105
105
  ```
106
106
 
107
107
  ```python
@@ -133,7 +133,7 @@ async def add(num1: int, num2: int) -> int:
133
133
  return num1 + num2
134
134
  ```
135
135
 
136
- ###### If you're trying to expose functions statelessly, e.g. in a different file, feel free to instead import and use the `expose` function from the library instead of the instance. Please note that if you do this, you must define all exposed functions *before* creating the ephaptic instance - easily done by simply placing your import line above the ephaptic constructor. The same thing can be done with the global `identity_loader` decorator.
136
+ <h5>If you're trying to expose functions statelessly, e.g. in a different file, feel free to instead import and use the <code>expose</code> function from the library instead of the instance. Please note that if you do this, you must define all exposed functions <i>before</i> creating the ephaptic instance - easily done by simply placing your import line above the ephaptic constructor. The same thing can be done with the global <code>identity_loader</code> decorator.</h5>
137
137
 
138
138
  Yep, it's really that simple.
139
139
 
@@ -148,17 +148,32 @@ await ephaptic.to(user1, user2).notification("Hello, world!", priority="high")
148
148
  To create a schema of your RPC endpoints:
149
149
 
150
150
  ```
151
- $ ephaptic src.app:app -o schema.json
151
+ $ ephaptic src.app:app -o schema.json # --watch to run in background and auto-reload on file change.
152
152
  ```
153
153
 
154
154
  Pydantic is entirely supported. It's validated for arguments, it's auto-serialized when you return a pydantic model, and your models receive type definitions in the schema.
155
155
 
156
+ To receive authentication objects and handle them:
157
+
158
+ ```python
159
+ from ephaptic import identity_loader
160
+
161
+ @identity_loader
162
+ async def load_identity(auth): # You can use synchronous functions here too.
163
+ jwt = auth.get("token")
164
+ if not jwt: return None # unauthorized
165
+ ... # app logic to retrieve user ID
166
+ return user_id
167
+ ```
168
+
169
+ From here, you can use <code>ephaptic.active_user</code> within any exposed function, and it will give you the current active user ID / whatever else your identity loading function returns. (This is also how <code>ephaptic.to</code> works.)
170
+
156
171
  </details>
157
172
 
158
173
  <details>
159
- <summary>JavaScript/TypeScript — Browser (Svelt, React, Angular, Vite, etc.)</summary>
174
+ <summary>JavaScript/TypeScript — Browser (Svelte, React, Angular, Vite, etc.)</summary>
160
175
 
161
- #### To use with a framework / Vite:
176
+ <h4>To use with a framework / Vite:</h4>
162
177
 
163
178
  ```
164
179
  $ npm install @ephaptic/client
@@ -192,7 +207,7 @@ And you can load types, too.
192
207
 
193
208
  ```
194
209
  $ npm i --save-dev @ephaptic/type-gen
195
- $ npx @ephaptic/type-gen ./schema.json -o schema.d.ts
210
+ $ npx @ephaptic/type-gen ./schema.json -o schema.d.ts # --watch to auto-reload upon changes
196
211
  ```
197
212
 
198
213
  ```typescript
@@ -203,7 +218,7 @@ const client = connect(...) as unknown as EphapticService;
203
218
  ```
204
219
 
205
220
 
206
- #### Or, to use in your browser:
221
+ <h4>Or, to use in your browser:</h4>
207
222
 
208
223
  ```html
209
224
  <script type="module">
@@ -213,10 +228,10 @@ const client = connect(...);
213
228
  </script>
214
229
  ```
215
230
 
216
- <!-- TODO: Add extended documentation -->
217
-
218
231
  </details>
219
232
 
233
+ See more in the [docs](https://ephaptic.github.io/ephaptic/tutorial).
234
+
220
235
  ## [License](https://github.com/ephaptic/ephaptic/blob/main/LICENSE)
221
236
 
222
237
  ---
@@ -1,19 +1,19 @@
1
- ephaptic/__init__.py,sha256=3GmVqhbye3snH7KfnyqIt8t_w6ULnWOvDw07eB13iDA,126
2
- ephaptic/ephaptic.py,sha256=9PKqiUKWl5MWlBvqsVbpsEfBmJmZJFes0RbbeqFkX4I,10224
1
+ ephaptic/__init__.py,sha256=QIEfepOHMtM6Lrxg4AwZ28gEj0D5TY2UP8MGlrAb-DI,137
2
+ ephaptic/ephaptic.py,sha256=UtT3_KD5SQcVFZxW5I3rAuDkXaRkMhIYQDl8d1njwDQ,11904
3
3
  ephaptic/localproxy.py,sha256=fJaaskkiD6C2zaOod0F0HNWIbdKs_JMuHFwd0-sdLIM,19477
4
4
  ephaptic/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  ephaptic/adapters/fastapi_.py,sha256=yfSbJuA7Tgeh9EhZkfIve0Uj-cOZmTBljlBsCRKh2EE,1007
6
6
  ephaptic/adapters/quart_.py,sha256=MBo9g6h_zI63mL4aGdrvV5yEXsHaOd0Iv5J8SAPHBoA,537
7
7
  ephaptic/cli/__init__.py,sha256=p_mYumuQLr3HZa-6I4QKut6khZv3WQjEX-B-aa4cdEE,44
8
- ephaptic/cli/__main__.py,sha256=fbfEGUnufc7vO65ZHnCV7YSshlHyif1c3kzR7vSp9GM,4218
8
+ ephaptic/cli/__main__.py,sha256=79FQgQtsq09pWV-G-98oqGx7mYuVLvw42kUbNzEJvvA,4499
9
9
  ephaptic/client/__init__.py,sha256=NeaPIzTFeozP54wlDYHIg_adHP3Z3LWVujsRUlpn4_U,35
10
10
  ephaptic/client/client.py,sha256=YYAlzA40xBvWsiDu0Gsd1EBJaqivLR-bSszepWdNODs,4181
11
11
  ephaptic/transports/__init__.py,sha256=kSAlgvm8sV9nHHu61LTjjTpv4bweah90xvFrwQMDQtQ,169
12
12
  ephaptic/transports/fastapi_ws.py,sha256=X0PMRcwM-KDpKA-zXShGTFhD1kHMSqrx3PBBKZtQ1W0,258
13
13
  ephaptic/transports/websocket.py,sha256=jwgclSDSq0lQCvgwjwUXe9MzPk7NH0FdmsLhWxYBh-4,261
14
- ephaptic-0.2.3.dist-info/licenses/LICENSE,sha256=kMpJjLKMj6zsAhf4uHApO4q0r31Ye1VyfBOl9cFW13M,1065
15
- ephaptic-0.2.3.dist-info/METADATA,sha256=ZokQJXZp3r4icuY3F8IRpqALNpEZXrCOLkgv8l2lHvk,7783
16
- ephaptic-0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
- ephaptic-0.2.3.dist-info/entry_points.txt,sha256=lis9vIDIrMVJM43r9ooFkb07KhrX4946duvYru3VfT4,46
18
- ephaptic-0.2.3.dist-info/top_level.txt,sha256=nNhdhcz2o_IuwZ9I2uWQuLZrRmSW0dQVU3qwGrb35Io,9
19
- ephaptic-0.2.3.dist-info/RECORD,,
14
+ ephaptic-0.2.4.dist-info/licenses/LICENSE,sha256=kMpJjLKMj6zsAhf4uHApO4q0r31Ye1VyfBOl9cFW13M,1065
15
+ ephaptic-0.2.4.dist-info/METADATA,sha256=x3-a9_RDMcsnYPrQWYW0HaX3YwwZcEFuoqryONFYIUk,8533
16
+ ephaptic-0.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ ephaptic-0.2.4.dist-info/entry_points.txt,sha256=lis9vIDIrMVJM43r9ooFkb07KhrX4946duvYru3VfT4,46
18
+ ephaptic-0.2.4.dist-info/top_level.txt,sha256=nNhdhcz2o_IuwZ9I2uWQuLZrRmSW0dQVU3qwGrb35Io,9
19
+ ephaptic-0.2.4.dist-info/RECORD,,