diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..b75c334 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,44 @@ +name: CI +on: + push: + branches: + - "*" + pull_request: + branches: + - "*" + workflow_dispatch: + +jobs: + test: + strategy: + fail-fast: true + matrix: + python-version: ['3.8', '3.10'] + os: ['20.04'] + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@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 errors + 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}}) \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index bf978ef..c336274 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "extensions.showBuiltin": true, "redhat.telemetry.enabled": false, - "workbench.colorTheme": "Gruvbox Dark Hard", + "workbench.colorTheme": "★★★★★ NORD DARK ★★★★★", "editor.fontSize": 18, "git.autofetch": true, "python.insidersChannel": "off", diff --git a/beerlog.db b/beerlog.db new file mode 100644 index 0000000..f507a91 Binary files /dev/null and b/beerlog.db differ diff --git a/beerlog/api.py b/beerlog/api.py new file mode 100644 index 0000000..33b9a87 --- /dev/null +++ b/beerlog/api.py @@ -0,0 +1,27 @@ +from typing import Optional, List +from fastapi import FastAPI, Response, status +from beerlog.core import get_beers_from_database +from beerlog.serializers import BeerIn, BeerOut +from beerlog.models import Beer +from beerlog.database import get_session + +api = FastAPI(title="beerlog") + + +@api.get("/beers", response_model=List[BeerOut]) +async def list_beers(style: Optional[str] = None): + """Lists beers from the database""" + beers = get_beers_from_database(style) + return beers + + +@api.post("/beers", response_model=BeerOut) +async def add_beer(beer_in: BeerIn, response: Response): + beer = Beer(**beer_in.dict()) + with get_session() as session: + session.add(beer) + session.commit() + session.refresh(beer) + + response.status_code = status.HTTP_201_CREATED + return beer diff --git a/beerlog/cli.py b/beerlog/cli.py index 03c2367..7cdde99 100644 --- a/beerlog/cli.py +++ b/beerlog/cli.py @@ -1,5 +1,52 @@ -from .config import settings +import typer +from typing import Optional +from rich.console import Console # NEW +from rich.table import Table # NEW +from rich import print # NEW +from beerlog.core import add_beer_to_database, get_beers_from_database -def main(): - print("Hello from", settings.NAME) + +main = typer.Typer(help="Beer Management Application") +console = Console() + + +@main.command() +def add( + name: str, + style: str, + flavor: int = typer.Option(...), + image: int = typer.Option(...), + cost: int = typer.Option(...), +): + """Adds a new beer to the database""" + if add_beer_to_database(name, style, flavor, image, cost): + print(":beer_mug: Beer added!!!") # NEW + else: + print(":no_entry: - Cannot add beer.") # NEW + + +@main.command("list") +def list_beers(style: Optional[str] = None): + """Lists beers from the database""" + beers = get_beers_from_database(style) + table = Table( + title="Beerlog Database" if not style else f"Beerlog {style}" + ) + headers = [ + "id", + "name", + "style", + "flavor", + "image", + "cost", + "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) diff --git a/beerlog/core.py b/beerlog/core.py index e69de29..39969f9 100644 --- a/beerlog/core.py +++ b/beerlog/core.py @@ -0,0 +1,35 @@ +from typing import Optional, 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, + ) + beer = Beer(name="Lagunitas", style="IPA", flavor=10, image=10, cost=5) + session.add(beer) + session.commit() + for beer in session.exec(select(Beer).where(Beer.style == "IPA")): + print(beer.name, beer.style, beer.rate) + return True + + +def get_beers_from_database(style: Optional[str] = None) -> List[Beer]: + with get_session() as session: + sql = select(Beer) + if style: + sql = sql.where(Beer.style == style) + return list(session.exec(sql)) diff --git a/beerlog/database.py b/beerlog/database.py index e69de29..2273797 100644 --- a/beerlog/database.py +++ b/beerlog/database.py @@ -0,0 +1,18 @@ +import warnings +from sqlalchemy.exc import SAWarning +from sqlmodel.sql.expression import Select, SelectOfScalar +from sqlmodel import create_engine, Session + +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, echo=False) +models.SQLModel.metadata.create_all(engine) + + +def get_session(): + return Session(engine) diff --git a/beerlog/models.py b/beerlog/models.py index e69de29..154563b 100644 --- a/beerlog/models.py +++ b/beerlog/models.py @@ -0,0 +1,27 @@ +from sqlmodel import SQLModel, Field +from datetime import datetime +from typing import Optional +from pydantic import validator +from statistics import mean + + +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("image", "flavor", "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 calculate_rate(cls, v, values): + rate = mean([values["flavor"], values["image"], values["cost"]]) + return int(rate) diff --git a/beerlog/serializers.py b/beerlog/serializers.py new file mode 100644 index 0000000..3a56628 --- /dev/null +++ b/beerlog/serializers.py @@ -0,0 +1,31 @@ +from pydantic import BaseModel, validator +from datetime import datetime +from fastapi import HTTPException, status + + +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("image", "flavor", "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 diff --git a/beerlog/settings.toml b/beerlog/settings.toml index 34d5fcb..d95698c 100644 --- a/beerlog/settings.toml +++ b/beerlog/settings.toml @@ -1 +1,4 @@ name = "beerlog" + +[database] +url = "sqlite:///beerlog.db" \ No newline at end of file diff --git a/conftest.py b/conftest.py index e69de29..745474e 100644 --- a/conftest.py +++ b/conftest.py @@ -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 \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 185de7b..c15bcf6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -23,6 +23,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "asgiref" +version = "3.5.0" +description = "ASGI specs, helper code, and adapters" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] + [[package]] name = "asttokens" version = "2.0.5" @@ -37,6 +48,28 @@ six = "*" [package.extras] test = ["astroid", "pytest"] +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + [[package]] name = "backcall" version = "0.2.0" @@ -67,6 +100,25 @@ d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "certifi" +version = "2021.10.8" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + [[package]] name = "click" version = "8.1.2" @@ -173,6 +225,14 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" [package.extras] docs = ["sphinx"] +[[package]] +name = "h11" +version = "0.13.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "idna" version = "3.3" @@ -181,6 +241,14 @@ category = "main" optional = false python-versions = ">=3.5" +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "ipython" version = "8.2.0" @@ -258,6 +326,17 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + [[package]] name = "parso" version = "0.8.3" @@ -309,6 +388,18 @@ python-versions = ">=3.7" docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "prompt-toolkit" version = "3.0.29" @@ -339,6 +430,14 @@ python-versions = "*" [package.extras] tests = ["pytest"] +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + [[package]] name = "pycodestyle" version = "2.8.0" @@ -372,11 +471,61 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.11.2" +version = "2.12.0" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" + +[[package]] +name = "pyparsing" +version = "3.0.8" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "dev" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["railroad-diagrams", "jinja2"] + +[[package]] +name = "pytest" +version = "7.1.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +tomli = ">=1.0.0" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "requests" +version = "2.27.1" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "rich" @@ -540,6 +689,35 @@ category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "urllib3" +version = "1.26.9" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "uvicorn" +version = "0.17.6" +description = "The lightning-fast ASGI server." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +asgiref = ">=3.4.0" +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] + [[package]] name = "wcwidth" version = "0.2.5" @@ -551,7 +729,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "1e063e12af3889e809b53f9a6c5d6a76b6f76639335e397e000877ad7931ebc1" +content-hash = "2a4e0a23a207eca63dab3626d724d027ff67e5d16ae03edb87cffa33219d95f6" [metadata.files] anyio = [ @@ -562,10 +740,22 @@ appnope = [ {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, ] +asgiref = [ + {file = "asgiref-3.5.0-py3-none-any.whl", hash = "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9"}, + {file = "asgiref-3.5.0.tar.gz", hash = "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0"}, +] asttokens = [ {file = "asttokens-2.0.5-py2.py3-none-any.whl", hash = "sha256:0844691e88552595a6f4a4281a9f7f79b8dd45ca4ccea82e5e05b4bbdb76705c"}, {file = "asttokens-2.0.5.tar.gz", hash = "sha256:9a54c114f02c7a9480d56550932546a3f1fe71d8a02f1bc7ccd0ee3ee35cf4d5"}, ] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, @@ -595,6 +785,14 @@ black = [ {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, ] +certifi = [ + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, +] click = [ {file = "click-8.1.2-py3-none-any.whl", hash = "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e"}, {file = "click-8.1.2.tar.gz", hash = "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"}, @@ -684,10 +882,18 @@ greenlet = [ {file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"}, {file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"}, ] +h11 = [ + {file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"}, + {file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"}, +] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] ipython = [ {file = "ipython-8.2.0-py3-none-any.whl", hash = "sha256:1b672bfd7a48d87ab203d9af8727a3b0174a4566b4091e9447c22fb63ea32857"}, {file = "ipython-8.2.0.tar.gz", hash = "sha256:70e5eb132cac594a34b5f799bd252589009905f05104728aea6a403ec2519dc1"}, @@ -708,6 +914,10 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, @@ -728,6 +938,10 @@ platformdirs = [ {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, ] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] prompt-toolkit = [ {file = "prompt_toolkit-3.0.29-py3-none-any.whl", hash = "sha256:62291dad495e665fca0bda814e342c69952086afb0f4094d0893d357e5c78752"}, {file = "prompt_toolkit-3.0.29.tar.gz", hash = "sha256:bd640f60e8cecd74f0dc249713d433ace2ddc62b65ee07f96d358e0b152b6ea7"}, @@ -740,6 +954,10 @@ pure-eval = [ {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, ] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] pycodestyle = [ {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, @@ -786,8 +1004,20 @@ pyflakes = [ {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] pygments = [ - {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, - {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, + {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, + {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, +] +pyparsing = [ + {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, + {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, +] +pytest = [ + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, +] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] rich = [ {file = "rich-12.2.0-py3-none-any.whl", hash = "sha256:c50f3d253bc6a9bb9c79d61a26d510d74abdf1b16881260fab5edfc3edfb082f"}, @@ -871,6 +1101,14 @@ typing-extensions = [ {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, ] +urllib3 = [ + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, +] +uvicorn = [ + {file = "uvicorn-0.17.6-py3-none-any.whl", hash = "sha256:19e2a0e96c9ac5581c01eb1a79a7d2f72bb479691acd2b8921fce48ed5b961a6"}, + {file = "uvicorn-0.17.6.tar.gz", hash = "sha256:5180f9d059611747d841a4a4c4ab675edf54c8489e97f96d0583ee90ac3bfc23"}, +] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, diff --git a/pyproject.toml b/pyproject.toml index ad3f120..a5f61d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,9 +14,12 @@ rich = "^12.2.0" dynaconf = "^3.1.8" black = "^22.3.0" flake8 = "^4.0.1" +uvicorn = "^0.17.6" [tool.poetry.dev-dependencies] ipython = "^8.2.0" +pytest = "^7.1.2" +requests = "^2.27.1" [tool.poetry.scripts] beerlog = "beerlog.__main__:main" diff --git a/testing.db b/testing.db new file mode 100644 index 0000000..be2d477 Binary files /dev/null and b/testing.db differ diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 0000000..101b6b8 --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,11 @@ +from beerlog.core import get_beers_from_database, add_beer_to_database + + +def test_add_beer_to_database(): + # ARRANGE + assert add_beer_to_database("Blue Moon", "Witbier", 10, 3, 6) + # ACT + results = get_beers_from_database() + # ASSERT + assert len(results) > 0 + diff --git a/tests/test_functional_api.py b/tests/test_functional_api.py new file mode 100644 index 0000000..7603a67 --- /dev/null +++ b/tests/test_functional_api.py @@ -0,0 +1,30 @@ +from fastapi.testclient import TestClient + +from beerlog.api import api + + +client = TestClient(api) + + +def test_create_beer_via_api(): + response = client.post( + "/beers", + json={ + "name": "Skol", + "style": "KornPA", + "flavor": 1, + "image": 1, + "cost": 2 + }, + ) + assert response.status_code == 201 + result = response.json() + assert result["name"] == "Skol" + assert result["id"] == 1 + + +def test_list_beers(): + response = client.get("/beers") + assert response.status_code == 200 + result = response.json() + assert len(result) == 0 \ No newline at end of file diff --git a/tests/test_functional_cli.py b/tests/test_functional_cli.py new file mode 100644 index 0000000..d836cce --- /dev/null +++ b/tests/test_functional_cli.py @@ -0,0 +1,12 @@ +from typer.testing import CliRunner + +from beerlog.cli import main + +runner = CliRunner() + + +def test_add_beer(): + result = runner.invoke( + main, ["add", "Skol", "KornPA", "--flavor=1", "--image=2", "--cost=3"]) + assert result.exit_code == 0 + assert "Beer added" in result.stdout \ No newline at end of file