Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Database Configuration
SEMANTIC_TRANSLATOR_DB_DIALECT=postgresql
SEMANTIC_TRANSLATOR_DB_HOST="localhost"
SEMANTIC_TRANSLATOR_DB_PORT=5432
SEMANTIC_TRANSLATOR_DB_NAME=database_name
SEMANTIC_TRANSLATOR_DB_SCHEMA=semantic_translator
SEMANTIC_TRANSLATOR_DB_USER="database_user"
SEMANTIC_TRANSLATOR_DB_PASSWORD="database_password"
4 changes: 4 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from flask_cors import CORS
from werkzeug.middleware.proxy_fix import ProxyFix

from database import db
from execution_history.history_recorder import init_history_recorder
from old_request_handler.request_handler import request_handler as old_request_handler
from variability_solver.backends.minizinc.backend import GecodeBackend
from variability_solver.backends.z3.backend import Z3Backend
Expand All @@ -16,6 +18,8 @@
CORS(app)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)

db.open()
init_history_recorder(app)

MODEL_LANGUAGES = {CLIFFrontend()}
SOLVERS = {Z3Backend(), GecodeBackend()}
Expand Down
23 changes: 23 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import os

from dotenv import load_dotenv

load_dotenv()


def get_env(name: str) -> str:
value = os.getenv(name)
if value is None:
raise RuntimeError(f"Missing required environment variable: {name}")
return value


DB_DIALECT = get_env("SEMANTIC_TRANSLATOR_DB_DIALECT")
DB_HOST = get_env("SEMANTIC_TRANSLATOR_DB_HOST")
DB_PORT = get_env("SEMANTIC_TRANSLATOR_DB_PORT")
DB_NAME = get_env("SEMANTIC_TRANSLATOR_DB_NAME")
DB_SCHEMA = get_env("SEMANTIC_TRANSLATOR_DB_SCHEMA")
DB_USER = get_env("SEMANTIC_TRANSLATOR_DB_USER")
DB_PASSWORD = get_env("SEMANTIC_TRANSLATOR_DB_PASSWORD")

DB_URL = f"{DB_DIALECT}://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}?options=-csearch_path%3D{DB_SCHEMA}"
44 changes: 44 additions & 0 deletions database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from collections.abc import Iterator
from contextlib import contextmanager

from psycopg import Connection
from psycopg.rows import dict_row
from psycopg_pool import ConnectionPool

from config import DB_URL


class Database:
def __init__(
self,
conninfo: str,
*,
min_size: int = 2,
max_size: int = 10,
) -> None:
self._pool = ConnectionPool(
conninfo=conninfo,
min_size=min_size,
max_size=max_size,
open=False,
)

def open(self) -> None:
self._pool.open()

def close(self) -> None:
self._pool.close()

@contextmanager
def connection(self) -> Iterator[Connection]:
with self._pool.connection() as conn:
yield conn

@contextmanager
def cursor(self) -> Iterator:
with self.connection() as conn:
with conn.cursor(row_factory=dict_row) as cur:
yield cur


db = Database(conninfo=DB_URL)
11 changes: 11 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
services:
semantic_translator:
build:
context: .
image: semantic_translator
container_name: semantic_translator
ports:
- "5000:5000"
env_file:
- .env
restart: unless-stopped
Empty file added execution_history/__init__.py
Empty file.
14 changes: 14 additions & 0 deletions execution_history/history_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from dataclasses import dataclass
from datetime import datetime
from uuid import UUID


@dataclass
class HistoryItem:
id: UUID
occured_at: datetime
url: str
request_method: str
request_content: dict | None
response_status: int
response_content: dict | None
39 changes: 39 additions & 0 deletions execution_history/history_recorder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import uuid
from datetime import UTC, datetime

from flask import g, request

from database import db

from .history_item import HistoryItem
from .history_repository import HistoryRepository


def init_history_recorder(app):

@app.before_request
def record_request():
g.request_id = uuid.uuid4()
g.occurred_at = datetime.now(UTC)
g.request_path = request.path
g.request_method = request.method
g.request_content = request.get_json(silent=True) if request.is_json else None

@app.after_request
def record_response(response):
g.response_status = response.status_code
g.response_content = response.get_json(silent=True) if response.is_json else None

HistoryRepository(database=db).save(
HistoryItem(
id=g.request_id,
occured_at=g.occurred_at,
url=g.request_path,
request_method=g.request_method,
request_content=g.request_content,
response_status=g.response_status,
response_content=g.response_content,
)
)

return response
37 changes: 37 additions & 0 deletions execution_history/history_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from psycopg.types.json import Jsonb

from database import Database

from .history_item import HistoryItem


class HistoryRepository:
def __init__(self, database: Database) -> None:
self._database = database

def save(self, item: HistoryItem) -> None:
"""Save a history item to the database."""
with self._database.cursor() as cur:
cur.execute(
"""
INSERT INTO history (
id,
occured_at,
url,
request_method,
request_content,
response_status,
response_content
)
VALUES (%s, %s, %s, %s, %s, %s, %s)
""",
(
item.id,
item.occured_at,
item.url,
item.request_method,
Jsonb(item.request_content),
item.response_status,
Jsonb(item.response_content),
),
)
11 changes: 11 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ packaging==25.0
# wheel
pip-tools==7.6.0
# via -r requirements-dev.in
psycopg[binary]==3.3.4
# via -r requirements.txt
psycopg-binary==3.3.4
# via
# -r requirements.txt
# psycopg
psycopg-pool==3.3.1
# via -r requirements.txt
pydantic==1.10.2
# via -r requirements.txt
pyhumps==3.8.0
Expand All @@ -61,6 +69,8 @@ pyproject-hooks==1.2.0
# via
# build
# pip-tools
python-dotenv==1.2.2
# via -r requirements.txt
ruff==0.16.0
# via -r requirements-dev.in
swiplserver==1.0.2
Expand All @@ -70,6 +80,7 @@ textx==4.2.2
typing-extensions==4.14.0
# via
# -r requirements.txt
# psycopg-pool
# pydantic
werkzeug==3.1.3
# via
Expand Down
5 changes: 4 additions & 1 deletion requirements.in
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
Flask
flask-cors
gunicorn
pyhumps
minizinc
networkx
psycopg[binary]
psycopg-pool
pydantic
pyhumps
python-dotenv
swiplserver
textX
z3-solver
12 changes: 11 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,26 @@ networkx==3.4.2
# via -r requirements.in
packaging==25.0
# via gunicorn
psycopg[binary]==3.3.4
# via -r requirements.in
psycopg-binary==3.3.4
# via psycopg
psycopg-pool==3.3.1
# via -r requirements.in
pydantic==1.10.2
# via -r requirements.in
pyhumps==3.8.0
# via -r requirements.in
python-dotenv==1.2.2
# via -r requirements.in
swiplserver==1.0.2
# via -r requirements.in
textx==4.2.2
# via -r requirements.in
typing-extensions==4.14.0
# via pydantic
# via
# psycopg-pool
# pydantic
werkzeug==3.1.3
# via flask
z3-solver==4.15.1.0
Expand Down
Loading