Skip to content
Open
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
45 changes: 45 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Tests BeerLog
on:
push:
branches:
- "*"
pull_request:
branches:
- "*"
workflow_dispatch:

jobs:
test:
strategy:
fail-fast: true
matrix:
python-version: ['3.8', '3.10']
os: [ubuntu-latest]
runs-on: ${{matrix.os}}

steps:
- uses: actions/setup-python@v2
- uses: actions/checkout@v2
with:
python-version: ${{matrix.python-version}}
- name: Install Poetry
run: pip install --upgrade pip && pip install poetry

- name: Install Project
run: poetry install

- name: Look For Style Errors
run: poetry run flake8 beerlog

- name: Look for auto format erros
run: poetry run black -l 79 --check --diff beerlog tests

- name: Run tests
run: poetry run pytest -v --junitxml=test-result.xml

- name: Publish Junit results
uses: EnricoMi/publish-unit-test-result-action@v1
if: always()
with:
files: test-result.xml
check-name: Test Result (Python ${{matrix.python-version}})
Empty file added .idea/.gitignore
Empty file.
553 changes: 553 additions & 0 deletions .idea/dbnavigator.xml

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions .idea/encodings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/python-week-2022.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 68 additions & 0 deletions .idea/workspace.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added beerlog.db
Binary file not shown.
2 changes: 1 addition & 1 deletion beerlog/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .cli import main

if __name__ == "__main__":
if __name__ == "__main__": # dunder
main()
26 changes: 26 additions & 0 deletions beerlog/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import List

from fastapi import FastAPI

from beerlog.core import get_beers_from_database
from beerlog.database import get_session
from beerlog.models import Beer
from beerlog.serializers import BeerIn, BeerOut

api = FastAPI(title="Beerlog")


@api.get("/beers/", response_model=List[BeerOut])
async def list_beers():
beers = get_beers_from_database()
return beers


@api.post("/beers/", response_model=BeerOut)
async def add_beer(beer_in: BeerIn):
beer = Beer(**beer_in.dict())
with get_session() as session:
session.add(beer)
session.commit()
session.refresh(beer)
return beer
40 changes: 37 additions & 3 deletions beerlog/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
from .config import settings
from typing import Optional

import typer
from rich.console import Console
from rich.table import Table

def main():
print("Hello from", settings.NAME)
from beerlog.core import add_beer_to_database, get_beers_from_database

main = typer.Typer(help="Beer Management Application")

console = Console()


@main.command("add")
def add(
name: str,
style: str,
flavor: int = typer.Option(...),
image: int = typer.Option(...),
cost: int = typer.Option(...),
):
"""Adds a new beer to database."""
if add_beer_to_database(name, style, flavor, image, cost):
print("🍻 beer added to database.")


@main.command("list")
def list_beers(style: Optional[str] = None):
"""Lists beers in database."""
beers = get_beers_from_database()
table = Table(title="Beerlog")
headers = ["id", "name", "style", "rate", "date"]
for header in headers:
table.add_column(header, style="magenta")
for beer in beers:
beer.date = beer.date.strftime("%Y-%m-%d")
values = [str(getattr(beer, header)) for header in headers]
table.add_row(*values)
console.print(table)
2 changes: 1 addition & 1 deletion beerlog/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
from dynaconf import Dynaconf

from dynaconf import Dynaconf

settings = Dynaconf(
envvar_prefix="BEERLOG",
Expand Down
28 changes: 28 additions & 0 deletions beerlog/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from typing import List

from sqlmodel import select

from beerlog.database import get_session
from beerlog.models import Beer


def add_beer_to_database(
name: str,
style: str,
flavor: int,
image: int,
cost: int,
) -> bool:
with get_session() as session:
beer = Beer(
name=name, style=style, flavor=flavor, image=image, cost=cost
)
session.add(beer)
session.commit()
return True


def get_beers_from_database() -> List[Beer]:
with get_session() as session:
sql = select(Beer)
return list(session.exec(sql))
20 changes: 20 additions & 0 deletions beerlog/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import warnings

from sqlalchemy.exc import SAWarning
from sqlmodel import Session, create_engine
from sqlmodel.sql.expression import Select, SelectOfScalar

from beerlog import models
from beerlog.config import settings

warnings.filterwarnings("ignore", category=SAWarning)
SelectOfScalar.inherit_cache = True
Select.inherit_cache = True

engine = create_engine(settings.database.url)

models.SQLModel.metadata.create_all(engine)


def get_session():
return Session(engine)
28 changes: 28 additions & 0 deletions beerlog/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from datetime import datetime
from statistics import mean
from typing import Optional

from pydantic import validator
from sqlmodel import Field, SQLModel


class Beer(SQLModel, table=True):
id: Optional[int] = Field(primary_key=True, default=None, index=True)
name: str
style: str
flavor: int
image: int
cost: int
rate: int = 0
date: datetime = Field(default_factory=datetime.now)

@validator("flavor", "image", "cost")
def validate_ratings(cls, v, field):
if v < 1 or v > 10:
raise RuntimeError(f"{field.name} must be between 1 and 10.")
return v

@validator("rate", always=True)
def calcalute_rate(cls, v, values):
rate = mean([values["flavor"], values["image"], values["cost"]])
return int(rate)
32 changes: 32 additions & 0 deletions beerlog/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from datetime import datetime

from fastapi import HTTPException, status
from pydantic import BaseModel, validator


class BeerOut(BaseModel):
id: int
name: str
style: str
flavor: int
image: int
cost: int
rate: int
date: datetime


class BeerIn(BaseModel):
name: str
style: str
flavor: int
image: int
cost: int

@validator("flavor", "image", "cost")
def validate_ratings(cls, v, field):
if v < 1 or v > 10:
raise HTTPException(
detail=f"{field.name} must be between 1 and 10",
status_code=status.HTTP_400_BAD_REQUEST,
)
return v
4 changes: 4 additions & 0 deletions beerlog/settings.toml
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
name = "beerlog"

[database]
url = "sqlite:///beerlog.db"

14 changes: 14 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import pytest
from unittest.mock import patch
from sqlmodel import create_engine
from beerlog import models


@pytest.fixture(autouse=True, scope="function")
def each_test_uses_separate_database(request):
tmpdir = request.getfixturevalue("tmpdir")
test_db = tmpdir.join("beerlog.test.db")
engine = create_engine(f"sqlite:///{test_db}")
models.SQLModel.metadata.create_all(bind=engine)
with patch("beerlog.database.engine", engine):
yield
Loading