initial commit
This commit is contained in:
commit
3fa81f0ae3
7 changed files with 129 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.mypy_cache/
|
||||||
|
__pycache__/
|
5
README.md
Normal file
5
README.md
Normal 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
15
pizzicore/Dockerfile
Normal 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
6
pizzicore/README.md
Normal 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.
|
10
pizzicore/docker-compose.yaml
Normal file
10
pizzicore/docker-compose.yaml
Normal 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
89
pizzicore/pizzicore.py
Normal 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))
|
2
pizzicore/requirements.txt
Normal file
2
pizzicore/requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
fastapi==0.62.0
|
||||||
|
uvicorn==0.13.1
|
Loading…
Reference in a new issue