Skip to content

Commit efbccd2

Browse files
authored
Merge pull request #28 from Wytamma/no-sql
v1.5.0
2 parents 05ad512 + 15116d0 commit efbccd2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1393
-300
lines changed

backend/beastiary/api/core.py

+18-22
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
from pathlib import Path
33
from typing import Optional, Tuple, Union, List
44
from beastiary import crud, schemas
5-
from beastiary.models.trace import Trace
5+
from beastiary.db.database import Database
66
from pydantic.utils import is_valid_field
7-
from sqlalchemy.orm.session import Session
8-
from beastiary.schemas.sample import SampleCreate
97
from beastiary.log import logger
108
import os, math, errno
119

@@ -39,45 +37,41 @@ def is_valid_log_file(headers_line: str, delimiter: Optional[str] = None) -> boo
3937
return False
4038

4139

42-
def add_trace(db: Session, trace_in: schemas.TraceCreate) -> Trace:
40+
def add_trace(db: dict, trace_in: schemas.TraceCreate) -> dict:
4341
if not trace_in.path.is_file():
4442
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), trace_in.path)
4543
last_byte, headers_line = get_headers(trace_in.path, delimiter=trace_in.delimiter)
4644
if not is_valid_log_file(headers_line, delimiter=trace_in.delimiter):
4745
raise ValueError(f"Invalid log file: {trace_in.path}")
4846
logger.debug(f"Creating trace: {trace_in}")
4947
trace = crud.trace.create(
50-
db=db, obj_in=trace_in, headers_line=headers_line, last_byte=last_byte
48+
db, obj_in=trace_in, last_byte=last_byte, headers_line=headers_line
5149
)
5250
logger.debug(f"Created trace: {trace}")
5351
return trace
5452

5553

56-
def read_lines(trace: Trace) -> Tuple[int, list]:
54+
def read_lines(trace: schemas.Trace) -> Tuple[int, list]:
5755
logger.debug(f"reading lines from: {trace}")
58-
if not trace.path:
56+
if not trace["path"]:
5957
raise ValueError("Path must be set.")
60-
with open(trace.path, "r") as f:
61-
f.seek(trace.last_byte, 0)
58+
with open(trace["path"], "r") as f:
59+
f.seek(trace["last_byte"], 0)
6260
lines = f.readlines()
6361
if lines:
6462
last_byte = f.tell()
6563
else:
66-
last_byte = trace.last_byte
64+
last_byte = trace["last_byte"]
6765
logger.debug(f"last_byte = {last_byte}")
6866
logger.debug(f"lines found = {len(lines)}")
6967
return last_byte, lines
7068

7169

7270
def lines_to_SampleCreate(
73-
headers: list, lines: list, delimiter: Optional[str] = None
74-
) -> List[SampleCreate]:
71+
headers: list, lines: list, trace_id: int, delimiter: Optional[str] = None
72+
) -> List[dict]:
7573
samples = []
7674
for line in lines:
77-
if not line:
78-
# blank lines are bad...
79-
# you'll have to have a smart way to handel this with the byte offset
80-
raise ValueError("Poorly formated line.")
8175
data = {}
8276
line = line.strip() # strip \n
8377
for header, value in zip(headers, line.split(delimiter)):
@@ -90,18 +84,20 @@ def lines_to_SampleCreate(
9084
sample_in = {}
9185
sample_in["data"] = data
9286
sample_in["state"] = data["state"]
93-
samples.append(schemas.sample.SampleCreate(**sample_in))
87+
sample_in["trace_id"] = trace_id
88+
samples.append(sample_in)
9489
return samples
9590

9691

97-
def check_for_new_samples(
98-
db: Session, trace: Trace, delimiter: Optional[str] = None
99-
) -> None:
92+
def check_for_new_samples(db: Database, trace: schemas.Trace) -> None:
10093
last_byte, lines = read_lines(trace)
10194
in_samples = lines_to_SampleCreate(
102-
trace.headers_line.split(delimiter), lines, delimiter=trace.delimiter
95+
trace["headers_line"].split(trace["delimiter"]),
96+
lines,
97+
trace_id=trace["id"],
98+
delimiter=trace["delimiter"],
10399
)
104100
if in_samples:
105-
crud.sample.create_multi_with_trace(db, objs_in=in_samples, trace_id=trace.id)
101+
crud.sample.create_multi(db, objs_in=in_samples)
106102
# update the trace byte
107103
crud.trace.update(db, db_obj=trace, obj_in={"last_byte": last_byte})
+12-17
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,41 @@
1-
from typing import Any, List, Tuple
2-
import math
3-
from beastiary.models.trace import Trace
4-
from fastapi import APIRouter, Depends, HTTPException, Path
5-
from sqlalchemy.orm import Session
1+
from typing import Any, List
2+
from fastapi import APIRouter, Depends, HTTPException, Path, Request
63

7-
from beastiary import crud, schemas
8-
from beastiary.api import deps
4+
from beastiary import crud
95
from beastiary.api.core import check_for_new_samples
106
from beastiary.log import logger
117

128
router = APIRouter()
139

1410

15-
@router.get("/", response_model=List[schemas.Sample])
11+
@router.get("/")
1612
def get_samples(
13+
request: Request,
1714
trace_id: int,
1815
skip: int = 0,
1916
limit: int = 100,
20-
db: Session = Depends(deps.get_db),
2117
) -> Any:
2218
"""
2319
Retrieve samples.
2420
"""
25-
trace = crud.trace.get(db, trace_id)
21+
trace = crud.trace.get(request.app.db, trace_id)
2622
if not trace:
2723
raise HTTPException(404, detail="Trace not found!")
2824

2925
samples = crud.sample.get_multi_by_trace(
30-
db, trace_id=trace.id, skip=skip, limit=limit
26+
request.app.db, trace_id=trace["id"], skip=skip, limit=limit
3127
)
32-
3328
logger.debug(f"{limit} samples requested - {len(samples)} found")
3429
if len(samples) < limit:
35-
36-
logger.debug(f"Checking for new samples in {trace.path}")
30+
logger.debug(f"Checking for new samples in {trace['path']}")
3731
try:
38-
check_for_new_samples(db, trace=trace, delimiter=trace.delimiter)
32+
check_for_new_samples(request.app.db, trace=trace)
3933
except Exception as e:
40-
raise HTTPException(500, detail=f"Could read samples in {trace.path}")
34+
logger.error(str(e))
35+
raise HTTPException(500, detail=f"Could read samples in {trace['path']}")
4136
# get samples
4237
samples = crud.sample.get_multi_by_trace(
43-
db, trace_id=trace.id, skip=skip, limit=limit
38+
request.app.db, trace_id=trace["id"], skip=skip, limit=limit
4439
)
4540
logger.debug(f"Returning {len(samples)} samples")
4641
return samples

backend/beastiary/api/endpoints/traces.py

+9-13
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,47 @@
11
from beastiary.api.core import add_trace
2-
from beastiary.models.trace import Trace
3-
from beastiary.schemas import sample
42
from typing import Any, List
5-
from fastapi import APIRouter, Depends, HTTPException
6-
from sqlalchemy.orm import Session
3+
from fastapi import APIRouter, Depends, HTTPException, Request
74

85
from beastiary import crud, schemas
9-
from beastiary.api import deps
106

117
router = APIRouter()
128

139

1410
@router.get("/", response_model=List[schemas.Trace])
1511
def get_traces(
16-
db: Session = Depends(deps.get_db),
12+
request: Request,
1713
skip: int = 0,
1814
limit: int = 100,
19-
) -> List[Trace]:
15+
) -> List[schemas.Trace]:
2016
"""
2117
Retrieve traces.
2218
"""
23-
traces = crud.trace.get_multi(db=db, skip=skip, limit=limit)
19+
traces = crud.trace.get_multi(db=request.app.db, skip=skip, limit=limit)
2420
return traces
2521

2622

2723
@router.get("/{trace_id}", response_model=schemas.Trace)
28-
def get_trace(trace_id: int, db: Session = Depends(deps.get_db)) -> Trace:
24+
def get_trace(request: Request, trace_id: int) -> dict:
2925
"""
3026
Retrieve traces.
3127
"""
32-
trace = crud.trace.get(db=db, id=trace_id)
28+
trace = crud.trace.get(db=request.app.db, id=trace_id)
3329
if not trace:
3430
raise HTTPException(404, "Trace not found!")
3531
return trace
3632

3733

3834
@router.post("/", response_model=schemas.Trace)
3935
def create_trace(
36+
request: Request,
4037
*,
41-
db: Session = Depends(deps.get_db),
4238
trace_in: schemas.TraceCreate,
43-
) -> Trace:
39+
) -> dict:
4440
"""
4541
Create new trace.
4642
"""
4743
try:
48-
trace = add_trace(db, trace_in)
44+
trace = add_trace(request.app.db, trace_in)
4945
except FileNotFoundError as e:
5046
raise HTTPException(404, detail="Could not find log file!")
5147
except ValueError as e:

backend/beastiary/cli.py

+9-10
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,14 @@
77

88
from pathlib import Path
99
from typing import List, Optional
10-
from fastapi.params import Security
1110

12-
from beastiary.api.core import add_trace
11+
from beastiary.api.core import add_trace, check_for_new_samples
1312
from beastiary.api import api
14-
from beastiary.db.init_db import init_db
15-
from beastiary.db.session import SessionLocal
13+
from beastiary.db import Database
1614
from beastiary import schemas
1715

18-
1916
app = typer.Typer()
2017

21-
db = SessionLocal()
22-
init_db(db)
23-
2418

2519
@app.command()
2620
def main(
@@ -43,6 +37,10 @@ def main(
4337
"""
4438
Realtime and remote trace inspection with BEASTIARY.
4539
"""
40+
db = Database()
41+
db.create_table("Trace")
42+
db.create_table("Sample")
43+
setattr(api, "db", db)
4644
if version:
4745
typer.echo(f"Beastiary {pkg_resources.get_distribution('beastiary').version}")
4846
return typer.Exit()
@@ -53,9 +51,10 @@ def main(
5351
for path in log_files:
5452
try:
5553
trace = add_trace(
56-
db, schemas.TraceCreate(path=str(path), delimiter=delimiter)
54+
api.db, schemas.TraceCreate(path=str(path), delimiter=delimiter)
5755
)
58-
typer.echo(f"✅ - {trace.path}")
56+
check_for_new_samples(api.db, trace=trace)
57+
typer.echo(f"✅ - {trace['path']}")
5958
except ValueError:
6059
typer.echo(f"❌ - {path}")
6160
typer.echo("")

backend/beastiary/crud/base.py

+17-41
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,43 @@
11
from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union
2+
from beastiary.db.database import Database
23

34
from fastapi.encoders import jsonable_encoder
45
from pydantic import BaseModel
5-
from sqlalchemy.orm import Session
66

7-
from beastiary.db.base_class import Base
8-
9-
ModelType = TypeVar("ModelType", bound=Base)
7+
InDBSchemaType = TypeVar("InDBSchemaType", bound=BaseModel)
108
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
119
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)
1210

1311

14-
class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
15-
def __init__(self, model: Type[ModelType]):
12+
class CRUDBase(Generic[InDBSchemaType, CreateSchemaType, UpdateSchemaType]):
13+
def __init__(self, model: Type[InDBSchemaType]):
1614
"""
1715
CRUD object with default methods to Create, Read, Update, Delete (CRUD).
1816
**Parameters**
19-
* `model`: A SQLAlchemy model class
17+
* `model`: A Pydantic model (schema) class for in database representation
2018
* `schema`: A Pydantic model (schema) class
2119
"""
2220
self.model = model
2321

24-
def get(self, db: Session, id: Any) -> Optional[ModelType]:
25-
return db.query(self.model).filter(self.model.id == id).first()
22+
def get(self, db: Database, id: Any) -> Optional[InDBSchemaType]:
23+
return db.query(self.model.__name__, id=id)
2624

2725
def get_multi(
28-
self, db: Session, *, skip: int = 0, limit: int = 100
29-
) -> List[ModelType]:
30-
return db.query(self.model).offset(skip).limit(limit).all()
26+
self, db: Database, *, skip: int = 0, limit: int = 100
27+
) -> List[InDBSchemaType]:
28+
return db.query(self.model.__name__, skip=skip, limit=limit)
3129

32-
def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType:
33-
obj_in_data = jsonable_encoder(obj_in)
34-
db_obj = self.model(**obj_in_data) # type: ignore
35-
db.add(db_obj)
36-
db.commit()
37-
db.refresh(db_obj)
30+
def create(self, db: Database, *, obj_in: CreateSchemaType) -> InDBSchemaType:
31+
db_obj = jsonable_encoder(obj_in)
32+
db.add(self.model.__name__, db_obj)
3833
return db_obj
3934

4035
def update(
4136
self,
42-
db: Session,
37+
db: Database,
4338
*,
44-
db_obj: ModelType,
39+
db_obj: dict,
4540
obj_in: Union[UpdateSchemaType, Dict[str, Any]],
46-
) -> ModelType:
47-
db.refresh(db_obj)
48-
obj_data = jsonable_encoder(db_obj)
49-
if isinstance(obj_in, dict):
50-
update_data = obj_in
51-
else:
52-
update_data = obj_in.dict(exclude_unset=True)
53-
for field in obj_data:
54-
if field in update_data:
55-
setattr(db_obj, field, update_data[field])
56-
db.add(db_obj)
57-
db.commit()
58-
db.refresh(db_obj)
41+
) -> InDBSchemaType:
42+
db_obj.update(obj_in)
5943
return db_obj
60-
61-
def remove(self, db: Session, *, id: int) -> ModelType:
62-
obj = db.query(self.model).get(id)
63-
if not obj:
64-
raise ValueError(f"{obj} not found!")
65-
db.delete(obj)
66-
db.commit()
67-
return obj

backend/beastiary/crud/crud_sample.py

+7-32
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,21 @@
11
from typing import List
2+
from beastiary.db.database import Database
23

34
from fastapi.encoders import jsonable_encoder
4-
from sqlalchemy.orm import Session
55

66
from beastiary.crud.base import CRUDBase
7-
from beastiary.models.sample import Sample
8-
from beastiary.schemas.sample import SampleCreate, SampleUpdate
7+
from beastiary.schemas.sample import SampleCreate, SampleUpdate, Sample
98

109

1110
class CRUDSample(CRUDBase[Sample, SampleCreate, SampleUpdate]):
12-
def create_with_trace(
13-
self, db: Session, *, obj_in: SampleCreate, trace_id: int
14-
) -> Sample:
15-
obj_in_data = jsonable_encoder(obj_in)
16-
db_obj = self.model(**obj_in_data, trace_id=trace_id)
17-
db.add(db_obj)
18-
db.commit()
19-
db.refresh(db_obj)
20-
return db_obj
21-
22-
def create_multi_with_trace(
23-
self, db: Session, *, objs_in: List[SampleCreate], trace_id: int
24-
) -> List[Sample]:
25-
objs_in_data = [jsonable_encoder(obj_in) for obj_in in objs_in]
26-
for obj_in in objs_in_data:
27-
obj_in.update({"trace_id": trace_id})
28-
db_objs = [self.model(**obj_in_data) for obj_in_data in objs_in_data]
29-
db.add_all(db_objs)
30-
db.commit()
31-
# db.refresh(db_objs)
32-
return db_objs
11+
def create_multi(self, db: Database, *, objs_in: List[dict]) -> List[Sample]:
12+
db.add_all(self.model.__name__, objs_in)
13+
return objs_in
3314

3415
def get_multi_by_trace(
35-
self, db: Session, *, trace_id: int, skip: int = 0, limit: int = 100
16+
self, db: Database, *, trace_id: int, skip: int = 0, limit: int = 100
3617
) -> List[Sample]:
37-
return (
38-
db.query(self.model)
39-
.filter(Sample.trace_id == trace_id)
40-
.offset(skip)
41-
.limit(limit)
42-
.all()
43-
)
18+
return db.query(self.model.__name__, skip=skip, limit=limit, trace_id=trace_id)
4419

4520

4621
sample = CRUDSample(Sample)

0 commit comments

Comments
 (0)