Skip to content

Commit

Permalink
Add Mutation and beta pagination (#2)
Browse files Browse the repository at this point in the history
Start documenting my functions with docstrings (numpy standard)

see CHANGELOG.md

Reviewed-on: https://codeberg.org/Krisque/jacobson/pulls/2
Co-authored-by: Christian G. Semke <[email protected]>
Co-committed-by: Christian G. Semke <[email protected]>
  • Loading branch information
chrisemke authored and Krisque committed Apr 17, 2024
1 parent b197c5a commit f00a7eb
Show file tree
Hide file tree
Showing 12 changed files with 693 additions and 497 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ repos:
--no-space,
]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.5
rev: v0.3.7
hooks:
- id: ruff
name: Run Ruff Linter & Formater
Expand All @@ -66,7 +66,7 @@ repos:
- id: poetry-export
args: [-f, requirements.txt, -o, requirements.txt]
- repo: https://github.com/commitizen-tools/commitizen
rev: v3.21.3
rev: v3.22.0
hooks:
- id: commitizen
- id: commitizen-branch
Expand Down
12 changes: 3 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.

## Unreleased

### BREAKING CHANGE

- The query has completely changed IO

### Feat

- **sqlmodel-mariadb**: add sqlmodel models, create async session, create tables on startup and add query function with beta filters
- **mutation**: add create_address mutation and beta pagination
- Initial commit

### Fix

- **brazil-model**: change Enum -> StrEnum and table columns order
- **types**: remove unused code
- some docker files
- continued fixing some wrong features
- **Hooks**: Validate merge branches

### Refactor

- **pydantic**: move pydantic models to sqlmodel models
- **edgedb-jacobson**: remove edgedb and legacy jacobson
- **graphql-query**: add pydantic and strawberry types to query
- start edgedb support
- create docker folders and minor fixes
21 changes: 17 additions & 4 deletions api/address/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,23 @@
from strawberry import auto, input
from strawberry.experimental.pydantic import input as pydantic_input

from database.models.brazil import AddressBase, City, State
from database.models.brazil import Address, AddressBase, City, State


@pydantic_input(model=State)
class StateInput:
name: auto
acronym: auto
# Needed until strawberry support auto | None type
# https://github.com/strawberry-graphql/strawberry/issues/3435
name: str | None


@pydantic_input(model=City)
class CityInput:
ibge: auto
name: auto
# Needed until strawberry support auto | None type
# https://github.com/strawberry-graphql/strawberry/issues/3435
name: str | None
ddd: auto


Expand All @@ -43,10 +47,19 @@ class CoordinatesInput:


@pydantic_input(model=AddressBase)
class AddressInput:
class AddressFilterInput:
zipcode: auto
city: CityInput | None = None
state: StateInput | None = None
neighborhood: auto
complement: auto
# coordinates: CoordinatesInput | None = None


@pydantic_input(model=Address)
class AddressInsertInput:
zipcode: auto
state: StateInput
city: CityInput
neighborhood: auto
complement: auto
38 changes: 34 additions & 4 deletions api/resolvers.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,48 @@
from api.address.inputs import AddressInput
"""
Jacobson is a self hosted zipcode API
Copyright (C) 2023-2024 Christian G. Semke.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

from pydantic import PositiveInt

from api.address.inputs import AddressFilterInput, AddressInsertInput
from database import functions
from database.models.brazil import Address


async def get_address(filter: AddressInput) -> list[Address | None]:
result = await functions.get_address_by_dc_join_state_join_city(filter)
async def get_address(
filter: AddressFilterInput,
page_size: PositiveInt,
page_number: PositiveInt,
) -> list[Address | None]:
result = await functions.get_address_by_dc_join_state_join_city(
filter, page_size, page_number
)
if not result:
try:
...
# result = async get from plugins
except Exception as e:
except Exception:
...
# there is no address found
else:
...
# async insert on database
return result


async def insert_address(address: AddressInsertInput) -> Address:
return await functions.insert_address_by_dc(address)
28 changes: 20 additions & 8 deletions api/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,39 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

from pydantic import PositiveInt
from strawberry import Schema, field, type
from strawberry.fastapi import GraphQLRouter

from api.address.inputs import AddressInput
from api.address.inputs import AddressFilterInput, AddressInsertInput
from api.address.types import AddressType
from api.resolvers import get_address
from api.resolvers import get_address, insert_address


@type
class Query:
@field
async def all_address(
self, filter: AddressInput
self,
filter: AddressFilterInput,
page_size: PositiveInt = 10,
page_number: PositiveInt = 1,
) -> list[AddressType | None]:
return list(map(AddressType.from_pydantic, await get_address(filter)))
return list(
map(
AddressType.from_pydantic,
await get_address(filter, page_size, page_number),
)
)


# @type
# class Mutation:
# create_address: Address = Address field(resolver=create_address)
@type
class Mutation:
@field
async def create_address(self, address: AddressInsertInput) -> AddressType:
return AddressType.from_pydantic(await insert_address(address))


schema = Schema(query=Query) # , mutation=Mutation)
schema = Schema(query=Query, mutation=Mutation) # , mutation=Mutation)

graphql_app = GraphQLRouter[object, object](schema)
120 changes: 110 additions & 10 deletions database/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,75 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

from fastapi import HTTPException
from pydantic import PositiveInt
from sqlalchemy.orm import joinedload
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession

from api.address.inputs import AddressInput
from api.address.inputs import AddressFilterInput
from api.address.types import AddressType
from database.engine import engine
from database.models.brazil import Address, City, State


async def page_to_offset(
page_size: PositiveInt, page_number: PositiveInt
) -> PositiveInt:
"""
Calculate the database offset based on page size and number.
Parameters
----------
page_size : PositiveInt
How many elements in each page
page_number : PositiveInt
Number of the page
Returns
-------
PositiveInt
The database offset
"""
if page_number <= 1:
return 0
return page_size * (page_number - 1)


async def get_address_by_dc_join_state_join_city(
filter: AddressInput,
filter: AddressFilterInput,
page_size: PositiveInt = 10,
page_number: PositiveInt = 1,
) -> list[Address | None]:
"""
Query addresses by the strawberry dataclass.
Parameters
----------
filter : AddressFilterInput
Strawberry input dataclass, everything can be None
(based on sqlmodel model)
page_size : PositiveInt, optional
How many elements in each page, by default 10
page_number : PositiveInt, optional
Number of the page, by default 1
Returns
-------
list[Address | None]
Return all addresses based on filter or None list
Todo: Fix Joins with async client
"""
query = (
select(Address)
.join(State)
.join(City)
.options(
joinedload(Address.state),
joinedload(Address.city),
joinedload('*'),
)
.limit(page_size)
.offset(await page_to_offset(page_size, page_number))
)

if filter.zipcode:
Expand All @@ -51,10 +100,61 @@ async def get_address_by_dc_join_state_join_city(
query = query.where(State.acronym == filter.state.acronym.value)

async with AsyncSession(engine) as session:
result = (await session.exec(query)).unique().all()
adresses_result = await session.exec(query)
addresses = adresses_result.unique().all()

return addresses


return result
async def insert_address_by_dc(address: AddressType) -> Address:
"""
Create address by the strawberry dataclass.
Parameters
----------
address : AddressType
Strawberry input dataclass, strict (based on sqlmodel model)
Returns
-------
Address
Single model instance, should be converted for strawberry
Raises
------
HTTPException
If city.ibge is not found on database: 404 error
"""
address_model = address.to_pydantic()

async with AsyncSession(engine) as session:
state_query = select(State).where(
State.acronym == address.state.acronym.value
)
state_result = await session.exec(state_query)
state = state_result.one()
address_model.state = state

city_query = select(City).where(City.ibge == address.city.ibge)
city_result = await session.exec(city_query)
city = city_result.one_or_none()
if not city:
raise HTTPException(status_code=404, detail='City not found')
address_model.city = city

session.add(address_model)
await session.commit()
await session.refresh(address_model)

query = (
select(Address)
.options(
joinedload('*'),
)
.where(Address.id == address_model.id)
)
adress_result = await session.exec(query)
address_model = adress_result.unique().one()

async def create_address():
...
return address_model
9 changes: 6 additions & 3 deletions database/models/brazil.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

from enum import Enum, StrEnum
from enum import StrEnum
from typing import NamedTuple

from pydantic import PositiveInt
Expand Down Expand Up @@ -82,6 +82,8 @@ class Coordinates(NamedTuple):
altitude: float


# Needed until strawberry support auto | None type
# https://github.com/strawberry-graphql/strawberry/issues/3435
class AddressBase(SQLModel):
id: int | None = Field(default=None, primary_key=True)
zipcode: PositiveInt | None = None
Expand All @@ -92,11 +94,12 @@ class AddressBase(SQLModel):

class Address(AddressBase, table=True):
zipcode: PositiveInt = Field(unique=True)
neighborhood: str
complement: str | None = None

state_id: PositiveInt = Field(foreign_key='state.id')
state: State = Relationship(back_populates='addresses')

city_id: PositiveInt = Field(foreign_key='city.id')
city: City = Relationship(back_populates='addresses')

neighborhood: str
complement: str | None = None
Loading

0 comments on commit f00a7eb

Please sign in to comment.