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 .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)]
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user