initial commit

This commit is contained in:
boyska 2021-09-16 01:47:11 +02:00
commit 3fa81f0ae3
7 changed files with 129 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.mypy_cache/
__pycache__/

5
README.md Normal file
View file

@ -0,0 +1,5 @@
feature che vorremmo:
- interfaccia che mostra in grande dei numeri
- possibilità di controllo da tastiera, ma magari anche in altri modi
- possibilità di sapere lo stato corrente (HTTP)
- notifiche push (websocket)

15
pizzicore/Dockerfile Normal file
View file

@ -0,0 +1,15 @@
FROM python:3.7
ENV DEBIAN_FRONTEND=noninteractive
WORKDIR /src
RUN apt-get update
RUN python -m pip install wheel
COPY requirements.txt /
RUN python -m pip install -r /requirements.txt
COPY . /src/
CMD ["uvicorn", "pizzicore:app", "--reload", "--port", "8000", "--host", "0.0.0.0"]

6
pizzicore/README.md Normal file
View file

@ -0,0 +1,6 @@
Componente centrale del sistema pizzicaroli. tiene i conti.
espone HTTP e WebSocket.
Alcune API richiedono autenticazione.
Il docker-compose incluso è solo per comodità di sviluppo, non è strettamente necessario.

View file

@ -0,0 +1,10 @@
version: "3"
services:
server:
build: .
# XXX: prima o poi servirà una qualche /var/lib/ in cui tenere il contatore
volumes:
- .:/src/
ports:
- 8000:8000

89
pizzicore/pizzicore.py Normal file
View file

@ -0,0 +1,89 @@
import secrets
from collections import defaultdict
from asyncio.queues import Queue
from fastapi import FastAPI, WebSocket, HTTPException, Depends, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from pydantic import BaseModel
class Store:
def __init__(self, n: int):
self.values = {i: 0 for i in range(n)}
def get(self, key):
return self.values[key]
def incr(self, key):
return self.set(key, self.get(key) + 1)
def set(self, key, value):
self.values[key] = value
return value
class SignalStore(Store):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.registry = defaultdict(list)
def subscribe(self, key, q):
self.registry[key].append(q)
def _notify(self, key):
for queue in self.registry[key]:
queue.put_nowait(self.get(key))
def set(self, key, value):
super().set(key, value)
self._notify(key)
app = FastAPI()
counter_store = Store(n=1) # XXX: pesca da file di conf
security = HTTPBasic()
class Value(BaseModel):
counter: int
value: int
def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
# XXX: read user/pass from config
correct_username = secrets.compare_digest(credentials.username, "avanti")
correct_password = secrets.compare_digest(credentials.password, "prossimo")
if not (correct_username and correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Basic"},
)
return "admin"
@app.get("/counter/{cid}")
async def get_value(cid: int):
try:
val = counter_store.get(cid)
except KeyError:
raise HTTPException(status_code=404, detail="Item not found")
return Value(counter=cid, value=val)
@app.post("/counter/{cid}/increment")
async def increment(cid: int, role: str = Depends(get_current_username)):
if role != "admin":
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
val = counter_store.incr(cid)
return Value(counter=cid, value=val)
@app.websocket("/ws/counter/{cid}")
async def websocket_counter(websocket: WebSocket, cid: int):
await websocket.accept()
# XXX: subscribe to counter
while True:
# XXX: get notifications
val = 1
await websocket.send_text(str(val))

View file

@ -0,0 +1,2 @@
fastapi==0.62.0
uvicorn==0.13.1