Compare commits
2 Commits
60165d658c
...
2051cda1b4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2051cda1b4 | ||
|
|
00cf6ab674 |
7
examples/misc/script.py
Normal file
7
examples/misc/script.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from packetserver.server import Server
|
||||
from packetserver.common import PacketServerConnection
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
s = Server('localhost', 8000, 'KQ4PEC')
|
||||
s.start()
|
||||
cm = s.app._engine._active_handler._handlers[1]._connection_map
|
||||
51
examples/misc/test.py
Normal file
51
examples/misc/test.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from packetserver.common import DummyPacketServerConnection, Request, Response, Message
|
||||
from packetserver.server import TestServer
|
||||
from packetserver.server.objects import Object
|
||||
from packetserver.server.messages import Message as Mail
|
||||
from packetserver.server.messages import Attachment
|
||||
import time
|
||||
import logging
|
||||
import json
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
server_callsign = "KQ4PEC"
|
||||
client_callsign = 'KQ4PEC-7'
|
||||
#client_callsign = "TEST1"
|
||||
|
||||
ts = TestServer(server_callsign, zeo=True)
|
||||
ts.start()
|
||||
|
||||
time.sleep(1)
|
||||
print("creating connection")
|
||||
|
||||
conn = DummyPacketServerConnection(client_callsign, server_callsign, incoming=True)
|
||||
print(conn.remote_callsign)
|
||||
print(conn.call_to)
|
||||
print(conn.call_from)
|
||||
conn.connected()
|
||||
|
||||
req = Request.blank()
|
||||
|
||||
req.set_var('fetch_attachments', 1)
|
||||
req.path = "message"
|
||||
|
||||
#req.method=Request.Method.POST
|
||||
#attach = [Attachment("test.txt", "Hello sir, I hope that this message finds you well. The other day..")]
|
||||
#req.payload = Mail("Hi there from a test user!", "KQ4PEC", attachments=attach).to_dict()
|
||||
|
||||
|
||||
#req.payload = Object(name="test.txt", data="hello there").to_dict()
|
||||
|
||||
print("sending request")
|
||||
conn.data_received(0, bytearray(req.pack()))
|
||||
#ts.send_test_data(conn, bytearray(req.pack()))
|
||||
print("Waiting on response.")
|
||||
time.sleep(.5)
|
||||
ts.stop()
|
||||
msg = conn.sent_data.unpack()
|
||||
#print(f"msg: {msg}")
|
||||
response = Response(Message.partial_unpack(msg))
|
||||
#print(type(response.payload))
|
||||
#print(f"Response: {response}: {response.payload}")
|
||||
print(json.dumps(response.payload, indent=4))
|
||||
51
examples/misc/testdb.py
Normal file
51
examples/misc/testdb.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from packetserver.common import DummyPacketServerConnection, Request, Response, Message
|
||||
from packetserver.server import TestServer
|
||||
from packetserver.server.objects import Object
|
||||
from packetserver.server.messages import Message as Mail
|
||||
from packetserver.server.messages import Attachment
|
||||
import time
|
||||
import logging
|
||||
import json
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
server_callsign = "KQ4PEC"
|
||||
client_callsign = 'KQ4PEC-7'
|
||||
#client_callsign = "TEST1"
|
||||
|
||||
ts = TestServer(server_callsign, zeo=True)
|
||||
ts.start()
|
||||
|
||||
time.sleep(1)
|
||||
print("creating connection")
|
||||
|
||||
conn = DummyPacketServerConnection(client_callsign, server_callsign, incoming=True)
|
||||
print(conn.remote_callsign)
|
||||
print(conn.call_to)
|
||||
print(conn.call_from)
|
||||
conn.connected()
|
||||
|
||||
req = Request.blank()
|
||||
|
||||
req.set_var('fetch_attachments', 1)
|
||||
req.path = "message"
|
||||
|
||||
#req.method=Request.Method.POST
|
||||
#attach = [Attachment("test.txt", "Hello sir, I hope that this message finds you well. The other day..")]
|
||||
#req.payload = Mail("Hi there from a test user!", "KQ4PEC", attachments=attach).to_dict()
|
||||
|
||||
|
||||
#req.payload = Object(name="test.txt", data="hello there").to_dict()
|
||||
|
||||
print("sending request")
|
||||
conn.data_received(0, bytearray(req.pack()))
|
||||
#ts.send_test_data(conn, bytearray(req.pack()))
|
||||
print("Waiting on response.")
|
||||
time.sleep(.5)
|
||||
ts.stop()
|
||||
msg = conn.sent_data.unpack()
|
||||
#print(f"msg: {msg}")
|
||||
response = Response(Message.partial_unpack(msg))
|
||||
#print(type(response.payload))
|
||||
#print(f"Response: {response}: {response.payload}")
|
||||
print(json.dumps(response.payload, indent=4))
|
||||
@@ -1,65 +1,33 @@
|
||||
from fastapi import Depends
|
||||
from typing import Annotated, Generator
|
||||
from .config import Settings
|
||||
from os.path import isfile
|
||||
|
||||
import ZEO
|
||||
import ZODB
|
||||
from ZODB.Connection import Connection
|
||||
import transaction
|
||||
|
||||
from .config import Settings # assuming Settings has zeo_file: str
|
||||
from fastapi import Depends
|
||||
from typing import Annotated, ContextManager
|
||||
|
||||
settings = Settings()
|
||||
|
||||
# Global shared DB instance (created once)
|
||||
_db: ZODB.DB | None = None
|
||||
|
||||
def _get_zeo_address(zeo_address_file: str) -> tuple[str, int]:
|
||||
def get_zeo_address(zeo_address_file: str) -> tuple[str,int]:
|
||||
if not isfile(zeo_address_file):
|
||||
raise FileNotFoundError(f"ZEO address file not found: '{zeo_address_file}'")
|
||||
raise FileNotFoundError(f"ZEO address file is not a file: '{zeo_address_file}'")
|
||||
|
||||
contents = open(zeo_address_file, 'r').read().strip().split(":")
|
||||
|
||||
if len(contents) != 2:
|
||||
raise ValueError(f"Invalid ZEO address format in {zeo_address_file}")
|
||||
raise ValueError(f"Invalid ZEO address file: {zeo_address_file}")
|
||||
|
||||
host = contents[0]
|
||||
try:
|
||||
port = int(contents[1])
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid port in ZEO address file: {zeo_address_file}")
|
||||
|
||||
raise ValueError(f"Invalid ZEO address file: {zeo_address_file}")
|
||||
return host,port
|
||||
|
||||
def init_db() -> ZODB.DB:
|
||||
"""Call this on app startup to create the shared DB instance."""
|
||||
global _db
|
||||
if _db is not None:
|
||||
return _db
|
||||
|
||||
host, port = _get_zeo_address(settings.zeo_file)
|
||||
storage = ZEO.ClientStorage((host, port))
|
||||
_db = ZODB.DB(storage)
|
||||
return _db
|
||||
|
||||
def get_db() -> ZODB.DB:
|
||||
"""Dependency for the shared DB instance (e.g., for class methods needing DB)."""
|
||||
if _db is None:
|
||||
raise RuntimeError("Database not initialized – call init_db() on startup")
|
||||
return _db
|
||||
return ZEO.DB(get_zeo_address(settings.zeo_file))
|
||||
|
||||
def get_connection() -> Generator[Connection, None, None]:
|
||||
"""Per-request dependency: yields an open Connection, closes on exit."""
|
||||
db = get_db()
|
||||
conn = db.open()
|
||||
try:
|
||||
yield conn
|
||||
finally:
|
||||
conn.close()
|
||||
def get_transaction() -> ContextManager:
|
||||
return ZEO.DB(get_zeo_address(settings.zeo_file)).transaction()
|
||||
|
||||
# Optional: per-request transaction (if you want automatic commit/abort)
|
||||
def get_transaction_manager():
|
||||
return transaction.manager
|
||||
|
||||
# Annotated dependencies for routers
|
||||
DbDependency = Annotated[ZODB.DB, Depends(get_db)]
|
||||
ConnectionDependency = Annotated[Connection, Depends(get_connection)]
|
||||
TransactionDependency = Annotated[ContextManager, Depends(get_transaction)]
|
||||
|
||||
@@ -7,7 +7,7 @@ import transaction
|
||||
from persistent.list import PersistentList
|
||||
from ZODB.Connection import Connection
|
||||
|
||||
from packetserver.http.database import DbDependency, ConnectionDependency, get_db
|
||||
from ..database import DbDependency, TransactionDependency
|
||||
from ..dependencies import get_current_http_user
|
||||
from ..auth import HttpUser
|
||||
from ..server import templates
|
||||
@@ -48,15 +48,15 @@ async def list_bulletins(connection: Connection, limit: int = 50, since: Optiona
|
||||
|
||||
@router.get("/bulletins")
|
||||
async def api_list_bulletins(
|
||||
db: DbDependency,
|
||||
limit: Optional[int] = Query(50, le=100),
|
||||
since: Optional[datetime] = None,
|
||||
current_user: HttpUser = Depends(get_current_http_user)
|
||||
):
|
||||
with db.transaction() as conn:
|
||||
return await list_bulletins(conn, limit=limit, since=since)
|
||||
return await list_bulletins(limit=limit, since=since)
|
||||
|
||||
async def get_one_bulletin(connection: Connection, bid: int) -> dict:
|
||||
root = connection.root()
|
||||
async def get_one_bulletin(bid: int) -> dict:
|
||||
with get_transaction() as conn:
|
||||
root = conn.root()
|
||||
bulletins_list: List[Bulletin] = root.get("bulletins", [])
|
||||
|
||||
for b in bulletins_list:
|
||||
@@ -73,11 +73,10 @@ async def get_one_bulletin(connection: Connection, bid: int) -> dict:
|
||||
|
||||
@router.get("/bulletins/{bid}")
|
||||
async def api_get_bulletin(
|
||||
db: DbDependency,
|
||||
bid: int,
|
||||
current_user: HttpUser = Depends(get_current_http_user)
|
||||
):
|
||||
with db.transaction() as conn:
|
||||
return await get_one_bulletin(conn, bid)
|
||||
return await get_one_bulletin(bid)
|
||||
|
||||
class CreateBulletinRequest(BaseModel):
|
||||
subject: constr(min_length=1, max_length=100) = Field(..., description="Bulletin subject/title")
|
||||
@@ -85,11 +84,11 @@ class CreateBulletinRequest(BaseModel):
|
||||
|
||||
@router.post("/bulletins", status_code=status.HTTP_201_CREATED)
|
||||
async def create_bulletin(
|
||||
db: DbDependency,
|
||||
payload: CreateBulletinRequest,
|
||||
current_user: HttpUser = Depends(get_current_http_user)
|
||||
):
|
||||
with db.transaction() as conn:
|
||||
from packetserver.runners.http_server import get_db_connection
|
||||
conn = get_db_connection()
|
||||
root = conn.root()
|
||||
|
||||
if 'bulletins' not in root:
|
||||
@@ -118,13 +117,11 @@ async def create_bulletin(
|
||||
|
||||
@html_router.get("/bulletins", response_class=HTMLResponse)
|
||||
async def bulletin_list_page(
|
||||
db: DbDependency,
|
||||
request: Request,
|
||||
limit: Optional[int] = Query(50, le=100),
|
||||
current_user: HttpUser = Depends(get_current_http_user)
|
||||
):
|
||||
with db.transaction() as conn:
|
||||
api_resp = await list_bulletins(conn, limit=limit, since=None)
|
||||
api_resp = await list_bulletins(limit=limit, since=None)
|
||||
bulletins = api_resp["bulletins"]
|
||||
|
||||
return templates.TemplateResponse(
|
||||
@@ -135,6 +132,7 @@ async def bulletin_list_page(
|
||||
@html_router.get("/bulletins/new", response_class=HTMLResponse)
|
||||
async def bulletin_new_form(
|
||||
request: Request,
|
||||
current_user: HttpUser = Depends(get_current_http_user) # require login, consistent with site
|
||||
):
|
||||
return templates.TemplateResponse(
|
||||
"bulletin_new.html",
|
||||
@@ -143,7 +141,6 @@ async def bulletin_new_form(
|
||||
|
||||
@html_router.post("/bulletins/new")
|
||||
async def bulletin_new_submit(
|
||||
db: DbDependency,
|
||||
request: Request,
|
||||
subject: str = Form(...),
|
||||
body: str = Form(...),
|
||||
@@ -155,7 +152,8 @@ async def bulletin_new_submit(
|
||||
{"request": request, "error": "Subject and body are required."},
|
||||
status_code=400
|
||||
)
|
||||
with db.transaction() as conn:
|
||||
from packetserver.runners.http_server import get_db_connection
|
||||
conn = get_db_connection()
|
||||
root = conn.root()
|
||||
|
||||
if 'bulletins' not in root:
|
||||
@@ -169,17 +167,17 @@ async def bulletin_new_submit(
|
||||
|
||||
new_id = new_bulletin.write_new(root)
|
||||
|
||||
transaction.commit()
|
||||
|
||||
return RedirectResponse(url=f"/bulletins/{new_id}", status_code=303)
|
||||
|
||||
@html_router.get("/bulletins/{bid}", response_class=HTMLResponse)
|
||||
async def bulletin_detail_page(
|
||||
db: DbDependency,
|
||||
request: Request,
|
||||
bid: int = Path(...),
|
||||
current_user: HttpUser = Depends(get_current_http_user)
|
||||
):
|
||||
with db.transaction() as conn:
|
||||
bulletin = await get_one_bulletin(conn, bid=bid)
|
||||
bulletin = await get_one_bulletin(bid=bid)
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"bulletin_detail.html",
|
||||
|
||||
Reference in New Issue
Block a user