Compare commits

..

2 Commits

Author SHA1 Message Date
Michael Woods
2051cda1b4 Revert "Suggestions from grok about database.py. Updated bulletins to use new database logic."
This reverts commit 60165d658c.
2025-12-25 15:32:21 -05:00
Michael Woods
00cf6ab674 Removed a transaction.commit() that was unnecessary. 2025-12-25 15:31:28 -05:00
5 changed files with 188 additions and 113 deletions

7
examples/misc/script.py Normal file
View 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
View 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
View 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))

View File

@@ -1,65 +1,33 @@
from fastapi import Depends from .config import Settings
from typing import Annotated, Generator
from os.path import isfile from os.path import isfile
import ZEO import ZEO
import ZODB import ZODB
from ZODB.Connection import Connection from fastapi import Depends
import transaction from typing import Annotated, ContextManager
from .config import Settings # assuming Settings has zeo_file: str
settings = Settings() settings = Settings()
# Global shared DB instance (created once) def get_zeo_address(zeo_address_file: str) -> tuple[str,int]:
_db: ZODB.DB | None = None
def _get_zeo_address(zeo_address_file: str) -> tuple[str, int]:
if not isfile(zeo_address_file): 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(":") contents = open(zeo_address_file, 'r').read().strip().split(":")
if len(contents) != 2: 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] host = contents[0]
try: try:
port = int(contents[1]) port = int(contents[1])
except ValueError: 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
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: def get_db() -> ZODB.DB:
"""Dependency for the shared DB instance (e.g., for class methods needing DB).""" return ZEO.DB(get_zeo_address(settings.zeo_file))
if _db is None:
raise RuntimeError("Database not initialized call init_db() on startup")
return _db
def get_connection() -> Generator[Connection, None, None]: def get_transaction() -> ContextManager:
"""Per-request dependency: yields an open Connection, closes on exit.""" return ZEO.DB(get_zeo_address(settings.zeo_file)).transaction()
db = get_db()
conn = db.open()
try:
yield conn
finally:
conn.close()
# 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)] DbDependency = Annotated[ZODB.DB, Depends(get_db)]
ConnectionDependency = Annotated[Connection, Depends(get_connection)] TransactionDependency = Annotated[ContextManager, Depends(get_transaction)]

View File

@@ -7,7 +7,7 @@ import transaction
from persistent.list import PersistentList from persistent.list import PersistentList
from ZODB.Connection import Connection 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 ..dependencies import get_current_http_user
from ..auth import HttpUser from ..auth import HttpUser
from ..server import templates from ..server import templates
@@ -48,15 +48,15 @@ async def list_bulletins(connection: Connection, limit: int = 50, since: Optiona
@router.get("/bulletins") @router.get("/bulletins")
async def api_list_bulletins( async def api_list_bulletins(
db: DbDependency,
limit: Optional[int] = Query(50, le=100), limit: Optional[int] = Query(50, le=100),
since: Optional[datetime] = None, since: Optional[datetime] = None,
current_user: HttpUser = Depends(get_current_http_user)
): ):
with db.transaction() as conn: return await list_bulletins(limit=limit, since=since)
return await list_bulletins(conn, limit=limit, since=since)
async def get_one_bulletin(connection: Connection, bid: int) -> dict: async def get_one_bulletin(bid: int) -> dict:
root = connection.root() with get_transaction() as conn:
root = conn.root()
bulletins_list: List[Bulletin] = root.get("bulletins", []) bulletins_list: List[Bulletin] = root.get("bulletins", [])
for b in bulletins_list: for b in bulletins_list:
@@ -73,11 +73,10 @@ async def get_one_bulletin(connection: Connection, bid: int) -> dict:
@router.get("/bulletins/{bid}") @router.get("/bulletins/{bid}")
async def api_get_bulletin( async def api_get_bulletin(
db: DbDependency,
bid: int, bid: int,
current_user: HttpUser = Depends(get_current_http_user)
): ):
with db.transaction() as conn: return await get_one_bulletin(bid)
return await get_one_bulletin(conn, bid)
class CreateBulletinRequest(BaseModel): class CreateBulletinRequest(BaseModel):
subject: constr(min_length=1, max_length=100) = Field(..., description="Bulletin subject/title") 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) @router.post("/bulletins", status_code=status.HTTP_201_CREATED)
async def create_bulletin( async def create_bulletin(
db: DbDependency,
payload: CreateBulletinRequest, payload: CreateBulletinRequest,
current_user: HttpUser = Depends(get_current_http_user) 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() root = conn.root()
if 'bulletins' not in root: if 'bulletins' not in root:
@@ -118,13 +117,11 @@ async def create_bulletin(
@html_router.get("/bulletins", response_class=HTMLResponse) @html_router.get("/bulletins", response_class=HTMLResponse)
async def bulletin_list_page( async def bulletin_list_page(
db: DbDependency,
request: Request, request: Request,
limit: Optional[int] = Query(50, le=100), limit: Optional[int] = Query(50, le=100),
current_user: HttpUser = Depends(get_current_http_user) current_user: HttpUser = Depends(get_current_http_user)
): ):
with db.transaction() as conn: api_resp = await list_bulletins(limit=limit, since=None)
api_resp = await list_bulletins(conn, limit=limit, since=None)
bulletins = api_resp["bulletins"] bulletins = api_resp["bulletins"]
return templates.TemplateResponse( return templates.TemplateResponse(
@@ -135,6 +132,7 @@ async def bulletin_list_page(
@html_router.get("/bulletins/new", response_class=HTMLResponse) @html_router.get("/bulletins/new", response_class=HTMLResponse)
async def bulletin_new_form( async def bulletin_new_form(
request: Request, request: Request,
current_user: HttpUser = Depends(get_current_http_user) # require login, consistent with site
): ):
return templates.TemplateResponse( return templates.TemplateResponse(
"bulletin_new.html", "bulletin_new.html",
@@ -143,7 +141,6 @@ async def bulletin_new_form(
@html_router.post("/bulletins/new") @html_router.post("/bulletins/new")
async def bulletin_new_submit( async def bulletin_new_submit(
db: DbDependency,
request: Request, request: Request,
subject: str = Form(...), subject: str = Form(...),
body: str = Form(...), body: str = Form(...),
@@ -155,7 +152,8 @@ async def bulletin_new_submit(
{"request": request, "error": "Subject and body are required."}, {"request": request, "error": "Subject and body are required."},
status_code=400 status_code=400
) )
with db.transaction() as conn: from packetserver.runners.http_server import get_db_connection
conn = get_db_connection()
root = conn.root() root = conn.root()
if 'bulletins' not in root: if 'bulletins' not in root:
@@ -169,17 +167,17 @@ async def bulletin_new_submit(
new_id = new_bulletin.write_new(root) new_id = new_bulletin.write_new(root)
transaction.commit()
return RedirectResponse(url=f"/bulletins/{new_id}", status_code=303) return RedirectResponse(url=f"/bulletins/{new_id}", status_code=303)
@html_router.get("/bulletins/{bid}", response_class=HTMLResponse) @html_router.get("/bulletins/{bid}", response_class=HTMLResponse)
async def bulletin_detail_page( async def bulletin_detail_page(
db: DbDependency,
request: Request, request: Request,
bid: int = Path(...), bid: int = Path(...),
current_user: HttpUser = Depends(get_current_http_user) current_user: HttpUser = Depends(get_current_http_user)
): ):
with db.transaction() as conn: bulletin = await get_one_bulletin(bid=bid)
bulletin = await get_one_bulletin(conn, bid=bid)
return templates.TemplateResponse( return templates.TemplateResponse(
"bulletin_detail.html", "bulletin_detail.html",