fustor-receiver-http 0.8.1__tar.gz

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,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: fustor-receiver-http
3
+ Version: 0.8.1
4
+ Summary: Fustor HTTP Receiver - Transport layer for Fusion to receive events from Agents
5
+ Author-email: Huajin Wang <wanghuajin999@163.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/excelwang/fustor/tree/master/extensions/receiver-http
8
+ Project-URL: Bug Tracker, https://github.com/excelwang/fustor/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: fustor-core
14
+ Requires-Dist: fastapi>=0.109.0
15
+ Requires-Dist: pydantic>=2.0.0
16
+
17
+ # fustor-receiver-http
18
+
19
+ HTTP Receiver for Fustor Fusion - implements the transport layer for receiving events from Agents.
20
+
21
+ ## Overview
22
+
23
+ This package provides an HTTP-based implementation of the `Receiver` transport abstraction. It creates FastAPI routers that handle session management and event ingestion.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install fustor-receiver-http
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ```python
34
+ from fustor_receiver_http import HTTPReceiver, SessionInfo
35
+
36
+ # Create receiver
37
+ receiver = HTTPReceiver(
38
+ receiver_id="main-receiver",
39
+ config={"session_timeout_seconds": 30}
40
+ )
41
+
42
+ # Register API keys for pipes
43
+ receiver.register_api_key("fk_abc123", "pipe-1")
44
+
45
+ # Register callbacks
46
+ async def on_session_created(session_id, task_id, pipe_id, client_info):
47
+ # Handle session creation
48
+ return SessionInfo(
49
+ session_id=session_id,
50
+ task_id=task_id,
51
+ pipe_id=pipe_id,
52
+ role="leader",
53
+ created_at=time.time(),
54
+ last_heartbeat=time.time()
55
+ )
56
+
57
+ async def on_event_received(session_id, events, source_type, is_end):
58
+ # Process events
59
+ return True
60
+
61
+ receiver.register_callbacks(
62
+ on_session_created=on_session_created,
63
+ on_event_received=on_event_received,
64
+ )
65
+
66
+ # Mount routers in FastAPI app
67
+ app.include_router(receiver.get_session_router(), prefix="/api/v1/pipe/session")
68
+ app.include_router(receiver.get_ingestion_router(), prefix="/api/v1/pipe/ingest")
69
+ ```
70
+
71
+ ## API Endpoints
72
+
73
+ ### Session Router
74
+
75
+ - `POST /` - Create a new session
76
+ - `POST /{session_id}/heartbeat` - Send heartbeat
77
+ - `DELETE /{session_id}` - Terminate session
78
+
79
+ ### Ingestion Router
80
+
81
+ - `POST /{session_id}/events` - Ingest event batch
82
+
83
+ ## Entry Points
84
+
85
+ This package registers itself as:
86
+ - `fustor.receivers:http` - Receiver registry
@@ -0,0 +1,70 @@
1
+ # fustor-receiver-http
2
+
3
+ HTTP Receiver for Fustor Fusion - implements the transport layer for receiving events from Agents.
4
+
5
+ ## Overview
6
+
7
+ This package provides an HTTP-based implementation of the `Receiver` transport abstraction. It creates FastAPI routers that handle session management and event ingestion.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install fustor-receiver-http
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```python
18
+ from fustor_receiver_http import HTTPReceiver, SessionInfo
19
+
20
+ # Create receiver
21
+ receiver = HTTPReceiver(
22
+ receiver_id="main-receiver",
23
+ config={"session_timeout_seconds": 30}
24
+ )
25
+
26
+ # Register API keys for pipes
27
+ receiver.register_api_key("fk_abc123", "pipe-1")
28
+
29
+ # Register callbacks
30
+ async def on_session_created(session_id, task_id, pipe_id, client_info):
31
+ # Handle session creation
32
+ return SessionInfo(
33
+ session_id=session_id,
34
+ task_id=task_id,
35
+ pipe_id=pipe_id,
36
+ role="leader",
37
+ created_at=time.time(),
38
+ last_heartbeat=time.time()
39
+ )
40
+
41
+ async def on_event_received(session_id, events, source_type, is_end):
42
+ # Process events
43
+ return True
44
+
45
+ receiver.register_callbacks(
46
+ on_session_created=on_session_created,
47
+ on_event_received=on_event_received,
48
+ )
49
+
50
+ # Mount routers in FastAPI app
51
+ app.include_router(receiver.get_session_router(), prefix="/api/v1/pipe/session")
52
+ app.include_router(receiver.get_ingestion_router(), prefix="/api/v1/pipe/ingest")
53
+ ```
54
+
55
+ ## API Endpoints
56
+
57
+ ### Session Router
58
+
59
+ - `POST /` - Create a new session
60
+ - `POST /{session_id}/heartbeat` - Send heartbeat
61
+ - `DELETE /{session_id}` - Terminate session
62
+
63
+ ### Ingestion Router
64
+
65
+ - `POST /{session_id}/events` - Ingest event batch
66
+
67
+ ## Entry Points
68
+
69
+ This package registers itself as:
70
+ - `fustor.receivers:http` - Receiver registry
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "setuptools-scm>=8.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fustor-receiver-http"
7
+ dynamic = ["version"]
8
+ description = "Fustor HTTP Receiver - Transport layer for Fusion to receive events from Agents"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3",
14
+ "Operating System :: OS Independent",
15
+ ]
16
+ dependencies = [
17
+ "fustor-core",
18
+ "fastapi>=0.109.0",
19
+ "pydantic>=2.0.0",
20
+ ]
21
+
22
+ [[project.authors]]
23
+ name = "Huajin Wang"
24
+ email = "wanghuajin999@163.com"
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/excelwang/fustor/tree/master/extensions/receiver-http"
28
+ "Bug Tracker" = "https://github.com/excelwang/fustor/issues"
29
+
30
+ # Entry point for receiver discovery
31
+ [project.entry-points."fustor.receivers"]
32
+ http = "fustor_receiver_http:HTTPReceiver"
33
+
34
+ [tool.setuptools_scm]
35
+ root = "../.."
36
+ version_scheme = "post-release"
37
+ local_scheme = "dirty-tag"
38
+
39
+ [tool.setuptools.packages.find]
40
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,375 @@
1
+ """
2
+ Fustor HTTP Receiver - Transport layer for Fusion to receive events from Agents.
3
+
4
+ This package implements the HTTP transport protocol for receiving events
5
+ on the Fusion side. It provides FastAPI routers that can be mounted into
6
+ the Fusion application.
7
+ """
8
+ import logging
9
+ from typing import Any, Dict, List, Optional, Callable, Awaitable
10
+ from dataclasses import dataclass
11
+ import uuid
12
+
13
+ from fastapi import APIRouter, Depends, HTTPException, status, Request
14
+ from pydantic import BaseModel
15
+
16
+ from fustor_core.transport import Receiver
17
+ from fustor_core.event import EventBase, EventType, MessageSource
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ # --- Pydantic Models for API ---
23
+
24
+ class CreateSessionRequest(BaseModel):
25
+ """Request payload for creating a new session."""
26
+ task_id: str
27
+ client_info: Optional[Dict[str, Any]] = None
28
+ session_timeout_seconds: Optional[int] = None
29
+
30
+
31
+ class CreateSessionResponse(BaseModel):
32
+ """Response for session creation."""
33
+ session_id: str
34
+ role: str # 'leader' or 'follower'
35
+ session_timeout_seconds: int
36
+ message: str
37
+
38
+
39
+ class EventBatch(BaseModel):
40
+ """Batch of events to ingest."""
41
+ events: List[EventBase]
42
+ source_type: str = "message" # 'message', 'snapshot', 'audit', 'scan_complete'
43
+ is_end: bool = False
44
+ metadata: Optional[Dict[str, Any]] = None # Extra info e.g., scan_path
45
+
46
+
47
+ class HeartbeatResponse(BaseModel):
48
+ status: str
49
+ role: Optional[str] = None
50
+ message: Optional[str] = None
51
+ can_realtime: Optional[bool] = None
52
+
53
+
54
+ # --- Session Handler Protocol ---
55
+
56
+ @dataclass
57
+ class SessionInfo:
58
+ """Information about an active session."""
59
+ session_id: str
60
+ task_id: str
61
+ view_id: str
62
+ role: str # 'leader' or 'follower'
63
+ created_at: float
64
+ last_heartbeat: float
65
+ can_realtime: bool = False
66
+
67
+ @property
68
+ def pipe_id(self) -> str:
69
+ """Deprecated alias for view_id."""
70
+ import warnings
71
+ warnings.warn("pipe_id is deprecated, use view_id instead", DeprecationWarning, stacklevel=2)
72
+ return self.view_id
73
+
74
+
75
+ # Type aliases for callbacks
76
+ SessionCreatedCallback = Callable[[str, str, str, Dict[str, Any], int], Awaitable[SessionInfo]]
77
+ EventReceivedCallback = Callable[[str, List[EventBase], str, bool], Awaitable[bool]]
78
+ HeartbeatCallback = Callable[[str, bool], Awaitable[Dict[str, Any]]]
79
+ SessionClosedCallback = Callable[[str], Awaitable[None]]
80
+
81
+
82
+ class HTTPReceiver(Receiver):
83
+ """
84
+ HTTP-based Receiver implementation for Fustor Fusion.
85
+
86
+ This receiver creates FastAPI routers that handle:
87
+ - Session creation and management
88
+ - Event batch ingestion
89
+ - Heartbeat processing
90
+
91
+ The receiver delegates actual processing to registered callbacks.
92
+ """
93
+
94
+ def __init__(
95
+ self,
96
+ receiver_id: str,
97
+ bind_host: str = "0.0.0.0",
98
+ port: int = 8101,
99
+ credentials: Optional[Dict[str, Any]] = None,
100
+ config: Optional[Dict[str, Any]] = None
101
+ ):
102
+ super().__init__(receiver_id, bind_host, port, credentials or {}, config)
103
+
104
+ # Callbacks for event processing
105
+ self._on_session_created: Optional[SessionCreatedCallback] = None
106
+ self._on_event_received: Optional[EventReceivedCallback] = None
107
+ self._on_heartbeat: Optional[HeartbeatCallback] = None
108
+ self._on_session_closed: Optional[SessionClosedCallback] = None
109
+ self._on_scan_complete: Optional[Callable[[str, str], Awaitable[None]]] = None # session_id, path
110
+
111
+ # API key to pipe mapping
112
+ self._api_key_to_pipe: Dict[str, str] = {}
113
+ self._api_key_cache: Dict[str, str] = {}
114
+
115
+
116
+ # Session timeout configuration
117
+ self.session_timeout_seconds = config.get("session_timeout_seconds", 30) if config else 30
118
+
119
+
120
+ # Create routers
121
+ self._session_router = self._create_session_router()
122
+ self._ingestion_router = self._create_ingestion_router()
123
+
124
+ def register_callbacks(
125
+ self,
126
+ on_session_created: Optional[SessionCreatedCallback] = None,
127
+ on_event_received: Optional[EventReceivedCallback] = None,
128
+ on_heartbeat: Optional[HeartbeatCallback] = None,
129
+ on_session_closed: Optional[SessionClosedCallback] = None,
130
+ on_scan_complete: Optional[Callable[[str, str], Awaitable[None]]] = None,
131
+ ):
132
+ """Register callbacks for event processing."""
133
+ if on_session_created:
134
+ self._on_session_created = on_session_created
135
+ if on_event_received:
136
+ self._on_event_received = on_event_received
137
+ if on_heartbeat:
138
+ self._on_heartbeat = on_heartbeat
139
+ if on_session_closed:
140
+ self._on_session_closed = on_session_closed
141
+ if on_scan_complete:
142
+ self._on_scan_complete = on_scan_complete
143
+
144
+ def register_api_key(self, api_key: str, pipe_id: str):
145
+ """Register an API key for a pipe."""
146
+ self._api_key_to_pipe[api_key] = pipe_id
147
+ self._api_key_cache.clear() # Invalidate cache
148
+ self.logger.debug(f"Registered API key for pipe {pipe_id}")
149
+
150
+ async def validate_credential(self, credential: Dict[str, Any]) -> Optional[str]:
151
+ """
152
+ Validate incoming credential.
153
+
154
+ Args:
155
+ credential: The credential to validate (expects {"api_key": "..."})
156
+
157
+ Returns:
158
+ Associated pipe_id if valid, None if invalid
159
+ """
160
+ api_key = credential.get("api_key") or credential.get("key")
161
+ if not api_key:
162
+ return None
163
+
164
+ # Check cache first
165
+ if api_key in self._api_key_cache:
166
+ return self._api_key_cache[api_key]
167
+
168
+ # Check mapping
169
+ if api_key in self._api_key_to_pipe:
170
+ pipe_id = self._api_key_to_pipe[api_key]
171
+ self._api_key_cache[api_key] = pipe_id
172
+ return pipe_id
173
+
174
+ return None
175
+
176
+ async def start(self) -> None:
177
+ """Start the receiver (routers are mounted externally)."""
178
+ self.logger.info(f"HTTP Receiver {self.id} ready on {self.get_address()}")
179
+
180
+ async def stop(self) -> None:
181
+ """Stop the receiver gracefully."""
182
+ self.logger.info(f"HTTP Receiver {self.id} stopping")
183
+
184
+ def get_session_router(self) -> APIRouter:
185
+ """Get the session management router."""
186
+ return self._session_router
187
+
188
+ def get_ingestion_router(self) -> APIRouter:
189
+ """Get the event ingestion router."""
190
+ return self._ingestion_router
191
+
192
+ def _create_session_router(self) -> APIRouter:
193
+ """Create the session management router."""
194
+ router = APIRouter(tags=["Session"])
195
+ receiver = self # Capture self for closures
196
+
197
+ @router.post("/", response_model=CreateSessionResponse)
198
+ async def create_session(
199
+ payload: CreateSessionRequest,
200
+ request: Request,
201
+ ):
202
+ """Create a new session for event ingestion."""
203
+ # Extract API key from header
204
+ api_key = request.headers.get("X-API-Key")
205
+ if not api_key:
206
+ raise HTTPException(
207
+ status_code=status.HTTP_401_UNAUTHORIZED,
208
+ detail="API key required"
209
+ )
210
+
211
+ pipe_id = await receiver.validate_credential({"api_key": api_key})
212
+ if not pipe_id:
213
+ raise HTTPException(
214
+ status_code=status.HTTP_401_UNAUTHORIZED,
215
+ detail="Invalid API key"
216
+ )
217
+
218
+ session_id = str(uuid.uuid4())
219
+
220
+ # Use client-requested timeout if provided, otherwise fallback to receiver config
221
+ session_timeout_seconds = payload.session_timeout_seconds or receiver.session_timeout_seconds
222
+
223
+
224
+ if receiver._on_session_created:
225
+ try:
226
+ session_info = await receiver._on_session_created(
227
+ session_id,
228
+ payload.task_id,
229
+ pipe_id,
230
+ payload.client_info or {},
231
+ session_timeout_seconds
232
+ )
233
+ return CreateSessionResponse(
234
+ session_id=session_info.session_id,
235
+ role=session_info.role,
236
+ session_timeout_seconds=session_timeout_seconds,
237
+ message="Session created successfully"
238
+ )
239
+ except Exception as e:
240
+ receiver.logger.error(f"Failed to create session: {e}")
241
+ raise HTTPException(
242
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
243
+ detail=str(e)
244
+ )
245
+ else:
246
+ raise HTTPException(
247
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
248
+ detail="Session handler not configured"
249
+ )
250
+
251
+ @router.post("/{session_id}/heartbeat", response_model=HeartbeatResponse)
252
+ async def heartbeat(session_id: str, request: Request):
253
+ """Send a heartbeat to maintain session."""
254
+ # Extract can_realtime from payload (if any)
255
+ try:
256
+ payload = await request.json()
257
+ can_realtime = payload.get("can_realtime", False)
258
+ except Exception:
259
+ can_realtime = False
260
+
261
+ logger.info(f"Received heartbeat for session {session_id}, can_realtime={can_realtime}")
262
+
263
+ if receiver._on_heartbeat:
264
+ try:
265
+ result = await receiver._on_heartbeat(session_id, can_realtime)
266
+ if result and result.get("status") == "error":
267
+ raise HTTPException(
268
+ status_code=419,
269
+ detail=result.get("message", "Session obsoleted")
270
+ )
271
+ return HeartbeatResponse(
272
+ status=result.get("status", "ok"),
273
+ role=result.get("role"),
274
+ message=result.get("message")
275
+ )
276
+ except HTTPException:
277
+ raise
278
+ except Exception as e:
279
+ receiver.logger.warning(f"Heartbeat failed for {session_id}: {e}")
280
+ return HeartbeatResponse(status="error", message=str(e))
281
+
282
+ return HeartbeatResponse(status="ok")
283
+
284
+ @router.delete("/{session_id}")
285
+ async def terminate_session(session_id: str, request: Request):
286
+ """Terminate a session."""
287
+ if receiver._on_session_closed:
288
+ await receiver._on_session_closed(session_id)
289
+ return {"status": "terminated", "session_id": session_id}
290
+
291
+ return router
292
+
293
+ def _create_ingestion_router(self) -> APIRouter:
294
+ """Create the event ingestion router."""
295
+ router = APIRouter(tags=["Ingestion"])
296
+ receiver = self
297
+
298
+ @router.post("/{session_id}/events")
299
+ async def ingest_events(
300
+ session_id: str,
301
+ batch: EventBatch,
302
+ request: Request,
303
+ ):
304
+ """Ingest a batch of events."""
305
+
306
+ # Handle scan_complete notification
307
+ if batch.source_type == "scan_complete" and batch.metadata:
308
+ scan_path = batch.metadata.get("scan_path")
309
+ if scan_path and receiver._on_scan_complete:
310
+ try:
311
+ await receiver._on_scan_complete(session_id, scan_path)
312
+ return {"status": "ok", "phase": "scan_complete"}
313
+ except Exception as e:
314
+ receiver.logger.error(f"Scan complete handling failed: {e}")
315
+ return {"status": "ok", "phase": "scan_complete"}
316
+
317
+ if receiver._on_event_received:
318
+ try:
319
+ success = await receiver._on_event_received(
320
+ session_id, batch.events, batch.source_type, batch.is_end
321
+ )
322
+ if success:
323
+ return {"status": "ok", "count": len(batch.events)}
324
+ else:
325
+ raise HTTPException(
326
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
327
+ detail="Failed to process events"
328
+ )
329
+ except HTTPException:
330
+ raise
331
+ except Exception as e:
332
+ receiver.logger.error(f"Event ingestion failed: {e}")
333
+ raise HTTPException(
334
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
335
+ detail=str(e)
336
+ )
337
+ else:
338
+ raise HTTPException(
339
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
340
+ detail="Event handler not configured"
341
+ )
342
+
343
+ return router
344
+
345
+
346
+ # Factory function for creating receiver with standard configuration
347
+ def create_http_receiver(
348
+ receiver_id: str = "default",
349
+ config: Optional[Dict[str, Any]] = None
350
+ ) -> HTTPReceiver:
351
+ """
352
+ Create an HTTP receiver with standard configuration.
353
+
354
+ Args:
355
+ receiver_id: Unique identifier for this receiver
356
+ config: Optional configuration dict
357
+
358
+ Returns:
359
+ Configured HTTPReceiver instance
360
+ """
361
+ return HTTPReceiver(
362
+ receiver_id=receiver_id,
363
+ config=config or {}
364
+ )
365
+
366
+
367
+ __all__ = [
368
+ "HTTPReceiver",
369
+ "SessionInfo",
370
+ "CreateSessionRequest",
371
+ "CreateSessionResponse",
372
+ "EventBatch",
373
+ "HeartbeatResponse",
374
+ "create_http_receiver",
375
+ ]
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: fustor-receiver-http
3
+ Version: 0.8.1
4
+ Summary: Fustor HTTP Receiver - Transport layer for Fusion to receive events from Agents
5
+ Author-email: Huajin Wang <wanghuajin999@163.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/excelwang/fustor/tree/master/extensions/receiver-http
8
+ Project-URL: Bug Tracker, https://github.com/excelwang/fustor/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: fustor-core
14
+ Requires-Dist: fastapi>=0.109.0
15
+ Requires-Dist: pydantic>=2.0.0
16
+
17
+ # fustor-receiver-http
18
+
19
+ HTTP Receiver for Fustor Fusion - implements the transport layer for receiving events from Agents.
20
+
21
+ ## Overview
22
+
23
+ This package provides an HTTP-based implementation of the `Receiver` transport abstraction. It creates FastAPI routers that handle session management and event ingestion.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install fustor-receiver-http
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ```python
34
+ from fustor_receiver_http import HTTPReceiver, SessionInfo
35
+
36
+ # Create receiver
37
+ receiver = HTTPReceiver(
38
+ receiver_id="main-receiver",
39
+ config={"session_timeout_seconds": 30}
40
+ )
41
+
42
+ # Register API keys for pipes
43
+ receiver.register_api_key("fk_abc123", "pipe-1")
44
+
45
+ # Register callbacks
46
+ async def on_session_created(session_id, task_id, pipe_id, client_info):
47
+ # Handle session creation
48
+ return SessionInfo(
49
+ session_id=session_id,
50
+ task_id=task_id,
51
+ pipe_id=pipe_id,
52
+ role="leader",
53
+ created_at=time.time(),
54
+ last_heartbeat=time.time()
55
+ )
56
+
57
+ async def on_event_received(session_id, events, source_type, is_end):
58
+ # Process events
59
+ return True
60
+
61
+ receiver.register_callbacks(
62
+ on_session_created=on_session_created,
63
+ on_event_received=on_event_received,
64
+ )
65
+
66
+ # Mount routers in FastAPI app
67
+ app.include_router(receiver.get_session_router(), prefix="/api/v1/pipe/session")
68
+ app.include_router(receiver.get_ingestion_router(), prefix="/api/v1/pipe/ingest")
69
+ ```
70
+
71
+ ## API Endpoints
72
+
73
+ ### Session Router
74
+
75
+ - `POST /` - Create a new session
76
+ - `POST /{session_id}/heartbeat` - Send heartbeat
77
+ - `DELETE /{session_id}` - Terminate session
78
+
79
+ ### Ingestion Router
80
+
81
+ - `POST /{session_id}/events` - Ingest event batch
82
+
83
+ ## Entry Points
84
+
85
+ This package registers itself as:
86
+ - `fustor.receivers:http` - Receiver registry
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/fustor_receiver_http/__init__.py
4
+ src/fustor_receiver_http.egg-info/PKG-INFO
5
+ src/fustor_receiver_http.egg-info/SOURCES.txt
6
+ src/fustor_receiver_http.egg-info/dependency_links.txt
7
+ src/fustor_receiver_http.egg-info/entry_points.txt
8
+ src/fustor_receiver_http.egg-info/requires.txt
9
+ src/fustor_receiver_http.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [fustor.receivers]
2
+ http = fustor_receiver_http:HTTPReceiver
@@ -0,0 +1,3 @@
1
+ fustor-core
2
+ fastapi>=0.109.0
3
+ pydantic>=2.0.0