Skip to content

Commit

Permalink
feat: POST bills
Browse files Browse the repository at this point in the history
  • Loading branch information
gablooge committed Apr 9, 2024
1 parent a533995 commit 836dc21
Show file tree
Hide file tree
Showing 11 changed files with 514 additions and 26 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ It likely means your `.env` is out of date. Perform a comparison between your `.
Before committing, run lint.sh:

```sh
# this will not checking the safety and include bandit checks
poetry run scripts/lint.sh

# include safety checks
poetry run scripts/lint.sh --include-safety
# exclude the bandit checks
poetry run scripts/lint.sh --skip-bandit
```

### Formatting code
Expand Down
118 changes: 118 additions & 0 deletions openapi.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,64 @@
components:
schemas:
Bill:
properties:
created:
description: Created at
format: date-time
title: Created
type: string
id:
description: Bill ID
maxLength: 32
title: Id
type: string
sub_bills:
items:
$ref: '#/components/schemas/SubBillSpec'
title: Sub Bills
type: array
total:
description: Total
title: Total
type: number
updated:
description: Updated at
format: date-time
title: Updated
type: string
required:
- total
- sub_bills
- created
- updated
- id
title: Bill
type: object
BillSpec:
properties:
sub_bills:
items:
$ref: '#/components/schemas/SubBillSpec'
title: Sub Bills
type: array
total:
description: Total
title: Total
type: number
required:
- total
- sub_bills
title: BillSpec
type: object
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
title: Detail
type: array
title: HTTPValidationError
type: object
Health:
properties:
db:
Expand All @@ -8,11 +67,70 @@ components:
type: boolean
title: Health
type: object
SubBillSpec:
properties:
amount:
title: Amount
type: number
reference:
anyOf:
- type: string
- type: 'null'
title: Reference
required:
- amount
title: SubBillSpec
type: object
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
title: Location
type: array
msg:
title: Message
type: string
type:
title: Error Type
type: string
required:
- loc
- msg
- type
title: ValidationError
type: object
info:
title: Uselevers Backend
version: 1.0.0-dev
openapi: 3.1.0
paths:
/api/v1/bills:
post:
description: Create new bill.
operationId: create_bills
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/BillSpec'
required: true
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/Bill'
description: Successful Response
'422':
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
description: Validation Error
summary: Create Bills
/health:
get:
description: Perform internal health checks
Expand Down
8 changes: 7 additions & 1 deletion scripts/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ ruff .
ruff format . --check
flake8 .
black --check --diff .
bandit -c .bandit.yml -r *

# Check if --skip-bandit argument is provided
if [[ "$1" == "--skip-bandit" ]]; then
echo "Skipping bandit check"
else
bandit -c .bandit.yml -r *
fi

if [[ -n "$1" && "$1" == "--include-safety" ]]; then
echo "Running safety check..."
Expand Down
32 changes: 32 additions & 0 deletions uselevers/bills/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from fastapi import HTTPException, status

from uselevers.core.exceptions import HTTPExceptionExtra, HTTPSimpleError


class ReferenceValueConflict(HTTPException, HTTPExceptionExtra):
def __init__(self) -> None:
super().__init__(
status_code=self.status_code,
detail=HTTPSimpleError(
message="Bill reference conflict",
errors={
"reference": [
"Bill reference value already exists. "
"Please select another value."
]
},
).model_dump(),
)

model = HTTPSimpleError
status_code = status.HTTP_409_CONFLICT
examples = {
"detail": {
"message": "Bill reference conflict",
"errors": {
"reference": [
"Bill reference value already exists. Please select another value."
]
},
}
}
18 changes: 18 additions & 0 deletions uselevers/bills/queries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from sqlalchemy.orm import Session

from . import models, schemas


def save_billspec(db: Session, bill_data: schemas.BillSpec) -> models.Bill:
# Create a new instance of models.Bill directly
bill = models.Bill(total=bill_data.total)
# Process sub_bills
for sub_bill_data in bill_data.sub_bills:
sub_bill = models.SubBill(
amount=sub_bill_data.amount, reference=sub_bill_data.reference
)
bill.sub_bills.append(sub_bill)

db.add(bill)
db.flush()
return bill
31 changes: 30 additions & 1 deletion uselevers/bills/schemas.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
from typing import Annotated

from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, validator

from uselevers.core.models import id_len
from uselevers.core.schemas import MixinCreatedUpdated


def check_positive_value(value: float) -> float:
if isinstance(value, float) and value < 0:
raise ValueError(
"Value must be a positive number or greater than or equal to 0.0"
)
return value


class SubBillSpec(BaseModel):
amount: float
reference: str | None = None

@validator("amount")
def check_amount(cls, value: float) -> float:
return check_positive_value(value)


class BillSpec(BaseModel):
total: Annotated[float, Field(description="Total")]
sub_bills: list[SubBillSpec]

@validator("total")
def check_total(cls, value: float) -> float:
# TODO: validate total should be equal with the total of sub_bill amounts
return check_positive_value(value)

@validator("sub_bills")
def check_sub_bills(cls, value: list[SubBillSpec]) -> list[SubBillSpec]:
if not value:
raise ValueError("At least one sub bill is required")
return value


class Bill(MixinCreatedUpdated, BillSpec, BaseModel):
Expand Down
50 changes: 29 additions & 21 deletions uselevers/bills/tests/test_models_bills.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
from typing import Annotated
from typing import Any

import pytest
from fastapi import Depends
from fastapi.encoders import jsonable_encoder
from pydantic import ValidationError
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

from uselevers.bills.models import Bill
from uselevers.bills.schemas import BillSpec
from uselevers.core import deps
from uselevers.bills.queries import save_billspec
from uselevers.bills.schemas import BillSpec, SubBillSpec
from uselevers.tests.conftest import SessionTest, alembic_engine, db # noqa: F401,F811


Expand All @@ -22,19 +21,19 @@
],
)
def test_crud_bills(
db: Annotated[Session, Depends(deps.get_db)], # noqa: F811
db: Session, # noqa: F811
total: float,
) -> None:
bill_create = BillSpec(
total=total,
sub_bills=[
SubBillSpec(amount=1, reference="INV-1"),
],
)
with db.begin():
# test create
assert db.query(Bill).count() == 0
instance = Bill(**jsonable_encoder(bill_create))
db.add(instance)
# write the object to the database
db.flush()
instance = save_billspec(db, bill_create)
assert db.query(Bill).count() == 1

# test read
Expand Down Expand Up @@ -67,19 +66,28 @@ def test_crud_bills(
assert db.query(Bill).count() == 0


@pytest.mark.parametrize(
"total",
[
(-1),
],
)
def test_create_bill_with_invalid_total(
db: Annotated[Session, Depends(deps.get_db)], # noqa: F811
db: Session, # noqa: F811
total: Any,
) -> None:
bill_create = BillSpec(
total=-1,
)
with db.begin():
# test create
assert db.query(Bill).count() == 0
with pytest.raises(IntegrityError):
instance = Bill(**jsonable_encoder(bill_create))
db.add(instance)
db.flush()
with pytest.raises(ValidationError):
bill_create = BillSpec(
total=total,
sub_bills=[
SubBillSpec(amount=1, reference="ref-1"),
],
)
with db.begin():
# test create
assert db.query(Bill).count() == 0
with pytest.raises(IntegrityError):
save_billspec(db, bill_create)

with db.begin():
assert db.query(Bill).count() == 0
Loading

0 comments on commit 836dc21

Please sign in to comment.