206 lines
5.7 KiB
Markdown
206 lines
5.7 KiB
Markdown
# Quick tutorial
|
|
|
|
Summary: We're gonna build a todo app to demonstrate how the `m4g` command line tool works.<br/>
|
|
TODO: Supplement this section.
|
|
|
|
## Initialize the project
|
|
|
|
Run `m4g init todo-example` to initialize a new project.
|
|
|
|
Open the newly created project by running `code todo-example`, make sure you have `vscode` on you machine before running
|
|
this command. If you prefer using another editor, then manually open the generated folder.
|
|
|
|
## Setup the database
|
|
|
|
### Start the database
|
|
|
|
`m4g db run`
|
|
|
|
### Create the entities
|
|
|
|
`m4g db generate entity Tag -c "description:str:String(255)"`
|
|
|
|
`m4g db generate entity Todo -c "description:str:String(255)" -c "is_done:bool:Boolean()"`
|
|
|
|
### Define a many to many relation between Todo & Tag
|
|
|
|
`m4g db link many Todo --to-many Tag`
|
|
|
|
### Generate the migration
|
|
|
|
`m4g db generate migration initial_migration`
|
|
|
|
### Apply the migration
|
|
|
|
`m4g db migrate`
|
|
|
|
If you inspect the database in [PhpMyAdmin](http://localhost:5051), you should now see a populated schema.
|
|
|
|
To stop the database, you can run `m4g db stop`
|
|
|
|
## Setup the API
|
|
|
|
### Generate CRUD for Tag & Todo
|
|
|
|
`m4g api generate crud Tag`
|
|
|
|
`m4g api generate crud Todo`
|
|
|
|
### Modify the generated Todo request classes
|
|
|
|
Modify `CreateTodoRequest` in `api/requests/create_todo_request.py`, you might need to import `List` from `typing`
|
|
|
|
```python
|
|
# Before
|
|
class CreateTodoRequest(BaseModel):
|
|
description: str = None
|
|
is_done: bool = None
|
|
|
|
# After
|
|
class CreateTodoRequest(BaseModel):
|
|
description: str = None
|
|
is_done: bool = None
|
|
tag_ids: Optional[List[int]] = []
|
|
```
|
|
|
|
Modify `UpdateTodoRequest` in `api/requests/update_todo_request.py`, you might need to import `List` from `typing`
|
|
|
|
```python
|
|
# Before
|
|
class UpdateTodoRequest(BaseModel):
|
|
description: Optional[str] = None
|
|
is_done: Optional[bool] = None
|
|
tag_ids: Optional[List[int]] = []
|
|
|
|
# After
|
|
class UpdateTodoRequest(BaseModel):
|
|
description: Optional[str] = None
|
|
is_done: Optional[bool] = None
|
|
tag_ids: Optional[List[int]] = []
|
|
```
|
|
|
|
### Modify generated TodoService
|
|
|
|
Add the following imports in `api/services/todo_service.py`.
|
|
|
|
```python
|
|
from sqlalchemy.orm import selectinload
|
|
from db.entities.tag import Tag
|
|
```
|
|
|
|
Modify `TodoService.list`
|
|
|
|
```python
|
|
# Before
|
|
async with async_session() as session:
|
|
stmt = select(Todo)
|
|
results = (await session.scalars(stmt)).all()
|
|
return results
|
|
|
|
# After
|
|
async with async_session() as session:
|
|
stmt = select(Todo).options(selectinload(Todo.tags))
|
|
results = (await session.scalars(stmt)).all()
|
|
return results
|
|
```
|
|
|
|
Modify `TodoService.get_by_id`
|
|
|
|
```python
|
|
# Before
|
|
async def get_by_id(self, id: int) -> Optional[Todo]:
|
|
async with async_session() as session:
|
|
stmt = select(Todo).where(Todo.id == id)
|
|
result = (await session.scalars(stmt)).first()
|
|
return result
|
|
|
|
# After
|
|
async def get_by_id(self, id: int) -> Optional[Todo]:
|
|
async with async_session() as session:
|
|
stmt = select(Todo).where(Todo.id == id).options(selectinload(Todo.tags))
|
|
result = (await session.scalars(stmt)).first()
|
|
return result
|
|
```
|
|
|
|
Modify `TodoService.create`
|
|
|
|
```python
|
|
# Before
|
|
async def create(self, data: Dict[str, Any]) -> None:
|
|
async with async_session() as session:
|
|
entity = Todo(**data)
|
|
session.add(entity)
|
|
await session.commit()
|
|
|
|
# After
|
|
async def create(self, data: Dict[str, Any]) -> None:
|
|
tag_ids = []
|
|
|
|
if "tag_ids" in data.keys():
|
|
tag_ids = data["tag_ids"]
|
|
del data["tag_ids"]
|
|
|
|
async with async_session() as session:
|
|
entity = Todo(**data)
|
|
|
|
if len(tag_ids) > 0:
|
|
stmt = select(Tag).where(Tag.id.in_(tag_ids))
|
|
result = (await session.scalars(stmt)).all()
|
|
entity.tags = list(result)
|
|
|
|
session.add(entity)
|
|
await session.commit()
|
|
```
|
|
|
|
Modify `TodoService.update`
|
|
|
|
```python
|
|
# Before
|
|
async def update(self, id: int, data: Dict[str, Any]) -> bool:
|
|
tag_ids = []
|
|
|
|
if "tag_ids" in data.keys():
|
|
tag_ids = data["tag_ids"]
|
|
del data["tag_ids"]
|
|
|
|
async with async_session() as session:
|
|
stmt = select(Todo).where(Todo.id == id)
|
|
entity = (await session.scalars(stmt)).first()
|
|
|
|
if entity is None:
|
|
return False
|
|
else:
|
|
for key, value in data.items():
|
|
setattr(entity, key, value)
|
|
await session.commit()
|
|
return True
|
|
|
|
# After
|
|
async def update(self, id: int, data: Dict[str, Any]) -> bool:
|
|
tag_ids = []
|
|
|
|
if "tag_ids" in data.keys():
|
|
tag_ids = data["tag_ids"]
|
|
del data["tag_ids"]
|
|
|
|
async with async_session() as session:
|
|
stmt = select(Todo).where(Todo.id == id).options(selectinload(Todo.tags))
|
|
entity = (await session.scalars(stmt)).first()
|
|
|
|
if entity is None:
|
|
return False
|
|
else:
|
|
for key, value in data.items():
|
|
setattr(entity, key, value)
|
|
|
|
if len(tag_ids) > 0:
|
|
stmt = select(Tag).where(Tag.id.in_(tag_ids))
|
|
result = (await session.scalars(stmt)).all()
|
|
entity.tags = list(result)
|
|
else:
|
|
entity.tags = []
|
|
|
|
await session.commit()
|
|
return True
|
|
```
|