Improved db feature and adding documentation
This commit is contained in:
parent
67b7dba341
commit
220a818970
@ -9,14 +9,13 @@ public partial class MycroForge
|
||||
{
|
||||
public partial class Generate : Command, ISubCommandOf<Db>
|
||||
{
|
||||
public Generate(IEnumerable<ISubCommandOf<Generate>> subCommands) :
|
||||
public Generate(IEnumerable<ISubCommandOf<Generate>> commands) :
|
||||
base("generate", "Generate a database item")
|
||||
{
|
||||
AddAlias("g");
|
||||
foreach (var subCommandOf in subCommands.Cast<Command>())
|
||||
AddCommand(subCommandOf);
|
||||
foreach (var command in commands.Cast<Command>())
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
29
MycroForge.CLI/Commands/MycroForge.Db.Run.cs
Normal file
29
MycroForge.CLI/Commands/MycroForge.Db.Run.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.CLI.Commands.Interfaces;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Db
|
||||
{
|
||||
public partial class Run : Command, ISubCommandOf<Db>
|
||||
{
|
||||
private readonly ProjectContext _context;
|
||||
|
||||
public Run(ProjectContext context) :
|
||||
base("run", $"Runs {Features.Db.FeatureName}.docker-compose.yml")
|
||||
{
|
||||
_context = context;
|
||||
this.SetHandler(ExecuteAsync);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync()
|
||||
{
|
||||
var config = await _context.LoadConfig();
|
||||
var env = $"DB_PORT={config.Db.DbPort} PMA_PORT={config.Db.PmaPort}";
|
||||
await _context.Bash($"{env} docker compose -f {Features.Db.FeatureName}.docker-compose.yml up -d");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
MycroForge.CLI/Commands/MycroForge.Db.Stop.cs
Normal file
29
MycroForge.CLI/Commands/MycroForge.Db.Stop.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.CLI.Commands.Interfaces;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Db
|
||||
{
|
||||
public partial class Stop : Command, ISubCommandOf<Db>
|
||||
{
|
||||
private readonly ProjectContext _context;
|
||||
|
||||
public Stop(ProjectContext context) :
|
||||
base("stop", $"Stops {Features.Db.FeatureName}.docker-compose.yml")
|
||||
{
|
||||
_context = context;
|
||||
this.SetHandler(ExecuteAsync);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync()
|
||||
{
|
||||
var config = await _context.LoadConfig();
|
||||
var env = $"DB_PORT={config.Db.DbPort} PMA_PORT={config.Db.PmaPort}";
|
||||
await _context.Bash($"{env} docker compose -f {Features.Db.FeatureName}.docker-compose.yml down");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -35,6 +35,8 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
// Register "m4g db"
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Db>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Db>, Commands.MycroForge.Db.Run>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Db>, Commands.MycroForge.Db.Stop>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Db>, Commands.MycroForge.Db.Migrate>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Db>, Commands.MycroForge.Db.Rollback>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Db>, Commands.MycroForge.Db.Generate>();
|
||||
|
@ -37,6 +37,8 @@ public sealed class Api : IFeature
|
||||
public async Task ExecuteAsync(ProjectContext context)
|
||||
{
|
||||
var config = await context.LoadConfig();
|
||||
config.Api = new() { Port = 8000 };
|
||||
await context.SaveConfig(config);
|
||||
|
||||
await context.Bash(
|
||||
"source .venv/bin/activate",
|
||||
@ -49,12 +51,5 @@ public sealed class Api : IFeature
|
||||
var main = await context.ReadFile("main.py");
|
||||
main = string.Join('\n', MainTemplate) + main;
|
||||
await context.WriteFile("main.py", main);
|
||||
|
||||
config.Api = new()
|
||||
{
|
||||
Port = 8000
|
||||
};
|
||||
|
||||
await context.SaveConfig(config);
|
||||
}
|
||||
}
|
@ -10,10 +10,8 @@ public sealed class Db : IFeature
|
||||
[
|
||||
"class DbSettings:",
|
||||
"\tdef get_connectionstring() -> str:",
|
||||
"\t\t# Example",
|
||||
"\t\t# connectionstring = \"mysql+asyncmy://root:root@localhost:3306/your_database\"",
|
||||
"\t\t# return connectionstring",
|
||||
"\t\traise Exception(\"DbSettings.get_connectionstring was not implemented.\")",
|
||||
"\t\tconnectionstring = \"mysql+asyncmy://root:password@localhost:%db_port%/%app_name%\"",
|
||||
"\t\treturn connectionstring",
|
||||
];
|
||||
|
||||
private static readonly string[] AsyncSession =
|
||||
@ -35,20 +33,44 @@ public sealed class Db : IFeature
|
||||
"\tpass"
|
||||
];
|
||||
|
||||
private static readonly string[] User =
|
||||
private static readonly string[] DockerCompose =
|
||||
[
|
||||
"from sqlalchemy import String",
|
||||
"from sqlalchemy.orm import Mapped, mapped_column",
|
||||
$"from {FeatureName}.entities.entity_base import EntityBase",
|
||||
"version: '3.8'",
|
||||
"# Access the database UI at http://localhost:${DB_PORT}.",
|
||||
"# Login: username = root & password = password",
|
||||
"",
|
||||
"class User(EntityBase):",
|
||||
"\t__tablename__ = \"users\"",
|
||||
"\tid: Mapped[int] = mapped_column(primary_key=True)",
|
||||
"\tfirstname: Mapped[str] = mapped_column(String(255))",
|
||||
"\tlastname: Mapped[str] = mapped_column(String(255))",
|
||||
"services:",
|
||||
" %app_name%_mariadb:",
|
||||
" image: 'mariadb:10.4'",
|
||||
" container_name: '%app_name%_mariadb'",
|
||||
" networks:",
|
||||
" - default",
|
||||
" ports:",
|
||||
" - '${DB_PORT}:3306'",
|
||||
" environment:",
|
||||
" MYSQL_ROOT_PASSWORD: 'password'",
|
||||
" MYSQL_USER: 'root'",
|
||||
" MYSQL_PASSWORD: 'password'",
|
||||
" MYSQL_DATABASE: '%app_name%'",
|
||||
" volumes:",
|
||||
" - '%app_name%_mariadb:/var/lib/mysql'",
|
||||
"",
|
||||
"\tdef __repr__(self) -> str:",
|
||||
"\t\treturn f\"User(id={self.id!r}, firstname={self.firstname!r}, lastname={self.lastname!r})\""
|
||||
" %app_name%_phpmyadmin:",
|
||||
" image: phpmyadmin/phpmyadmin",
|
||||
" container_name: %app_name%_phpmyadmin",
|
||||
" ports:",
|
||||
" - '${PMA_PORT}:80'",
|
||||
" networks:",
|
||||
" - default",
|
||||
" environment:",
|
||||
" PMA_HOST: %app_name%_mariadb",
|
||||
" PMA_PORT: 3306",
|
||||
" PMA_ARBITRARY: 1",
|
||||
" links:",
|
||||
" - %app_name%_mariadb",
|
||||
"",
|
||||
"volumes:",
|
||||
" %app_name%_mariadb:\n",
|
||||
];
|
||||
|
||||
#endregion
|
||||
@ -60,6 +82,10 @@ public sealed class Db : IFeature
|
||||
public async Task ExecuteAsync(ProjectContext context)
|
||||
{
|
||||
var config = await context.LoadConfig();
|
||||
config.Db = new() { DbPort = 5050, PmaPort = 5051 };
|
||||
await context.SaveConfig(config);
|
||||
|
||||
var appName = context.AppName;
|
||||
|
||||
await context.Bash(
|
||||
"source .venv/bin/activate",
|
||||
@ -70,17 +96,21 @@ public sealed class Db : IFeature
|
||||
|
||||
var env = await context.ReadFile($"{FeatureName}/env.py");
|
||||
env = new DbEnvInitializer(env).Rewrite();
|
||||
// env = new DbEnvUpdater(env, "user", "User").Rewrite();
|
||||
await context.WriteFile($"{FeatureName}/env.py", env);
|
||||
|
||||
await context.CreateFile($"{FeatureName}/settings.py", Settings);
|
||||
var settings = string.Join('\n', Settings)
|
||||
.Replace("%app_name%", appName)
|
||||
.Replace("%db_port%", config.Db.DbPort.ToString())
|
||||
;
|
||||
|
||||
await context.CreateFile($"{FeatureName}/settings.py", settings);
|
||||
|
||||
await context.CreateFile($"{FeatureName}/engine/async_session.py", AsyncSession);
|
||||
|
||||
await context.CreateFile($"{FeatureName}/entities/entity_base.py", EntityBase);
|
||||
|
||||
// await context.CreateFile($"{FeatureName}/entities/user.py", User);
|
||||
var dockerCompose = string.Join('\n', DockerCompose).Replace("%app_name%", appName);
|
||||
|
||||
await context.SaveConfig(config);
|
||||
await context.CreateFile($"{FeatureName}.docker-compose.yml", dockerCompose);
|
||||
}
|
||||
}
|
10
MycroForge.CLI/ProjectConfig.DbConfig.cs
Normal file
10
MycroForge.CLI/ProjectConfig.DbConfig.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace MycroForge.CLI;
|
||||
|
||||
public partial class ProjectConfig
|
||||
{
|
||||
public class DbConfig
|
||||
{
|
||||
public int DbPort { get; set; }
|
||||
public int PmaPort { get; set; }
|
||||
}
|
||||
}
|
@ -3,4 +3,5 @@
|
||||
public partial class ProjectConfig
|
||||
{
|
||||
public ApiConfig Api { get; set; } = default!;
|
||||
public DbConfig Db { get; set; } = default!;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using Humanizer;
|
||||
using MycroForge.CLI.Extensions;
|
||||
|
||||
namespace MycroForge.CLI;
|
||||
@ -7,6 +8,7 @@ namespace MycroForge.CLI;
|
||||
public class ProjectContext
|
||||
{
|
||||
public string RootDirectory { get; private set; } = Environment.CurrentDirectory;
|
||||
public string AppName => Path.GetFileNameWithoutExtension(RootDirectory).Underscore().ToLower();
|
||||
private string ConfigPath => Path.Combine(RootDirectory, "m4g.json");
|
||||
|
||||
|
||||
|
10
README.md
10
README.md
@ -1,10 +1,10 @@
|
||||
### Dependencies
|
||||
|
||||
bash (/usr/bin/bash)
|
||||
|
||||
Python 3.10.2 (/usr/bin/python3)
|
||||
- python3-pip
|
||||
- python3-venv
|
||||
- Docker
|
||||
- bash (/bin/bash)
|
||||
- Python 3.10.2 (/usr/bin/python3)
|
||||
- python3-pip
|
||||
- python3-venv
|
||||
|
||||
#### Note
|
||||
The MycroForge CLI assumes a linux compatible environment, so on Windows you'll have to use WSL.
|
||||
|
59
docs/README.md
Normal file
59
docs/README.md
Normal file
@ -0,0 +1,59 @@
|
||||
# General introduction
|
||||
|
||||
## What is MycroForge?
|
||||
|
||||
MycroForge is an opinionated CLI tool that is meant to facilitate the development of FastAPI & SQLAlchemy based backends.
|
||||
It provides a command line interface that allows users to generate a skeleton project and other common items like
|
||||
database entities, migrations, routers and even basic CRUD functionality. The main purpose of this tool is to generate
|
||||
boilerplate code and to provide a unified interface for performing recurrent development activities.
|
||||
|
||||
|
||||
## Generating a project
|
||||
|
||||
To generate a project you can run the following command.
|
||||
|
||||
`m4g init <name>`
|
||||
|
||||
```
|
||||
Description:
|
||||
Initialize a new project
|
||||
|
||||
Usage:
|
||||
m4g init <name> [options]
|
||||
|
||||
Arguments:
|
||||
<name> The name of your project
|
||||
|
||||
Options:
|
||||
--without <api|db|git> Features to exclude
|
||||
-?, -h, --help Show help and usage information
|
||||
|
||||
```
|
||||
|
||||
Running this command will generate
|
||||
|
||||
```
|
||||
📦<project_name>
|
||||
┣ 📂.git
|
||||
┣ 📂.venv
|
||||
┣ 📂api
|
||||
┃ ┗ 📂routers
|
||||
┃ ┃ ┗ 📜hello.py
|
||||
┣ 📂db
|
||||
┃ ┣ 📂engine
|
||||
┃ ┃ ┗ 📜async_session.py
|
||||
┃ ┣ 📂entities
|
||||
┃ ┃ ┗ 📜entity_base.py
|
||||
┃ ┣ 📂versions
|
||||
┃ ┣ 📜README
|
||||
┃ ┣ 📜env.py
|
||||
┃ ┣ 📜script.py.mako
|
||||
┃ ┗ 📜settings.py
|
||||
┣ 📜.gitignore
|
||||
┣ 📜alembic.ini
|
||||
┣ 📜db.docker-compose.yml
|
||||
┣ 📜m4g.json
|
||||
┣ 📜main.py
|
||||
┗ 📜requirements.txt
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Quick tutorial
|
||||
|
||||
Summary: We're gonna build a todo app to demonstrate how the `m4g` command works.<br/>
|
||||
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
|
||||
@ -12,71 +12,9 @@ this command. If you prefer using another editor, then manually open the generat
|
||||
|
||||
## Setup the database
|
||||
|
||||
Summary: This section explains how to interact with the database.
|
||||
TODO: Supplement this section.
|
||||
### Start the database
|
||||
|
||||
### Create a docker compose file for the database
|
||||
|
||||
Create a file called `docker-compose.yml` in the root of your project with the following content.
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# MariaDB for the database
|
||||
todo_db:
|
||||
image: 'mariadb:10.4'
|
||||
container_name: 'todo_db'
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- default
|
||||
ports:
|
||||
- '3306:3306'
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: 'root'
|
||||
MYSQL_USER: 'root'
|
||||
MYSQL_PASSWORD: 'root'
|
||||
MYSQL_DATABASE: 'todo'
|
||||
volumes:
|
||||
- 'todo_db:/var/lib/mysql'
|
||||
|
||||
# PhpMyAdmin for the database UI
|
||||
phpmyadmin:
|
||||
image: phpmyadmin/phpmyadmin
|
||||
container_name: phpmyadmin
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8888:80
|
||||
networks:
|
||||
- default
|
||||
environment:
|
||||
PMA_HOST: todo_db
|
||||
PMA_PORT: 3306
|
||||
PMA_ARBITRARY: 1
|
||||
links:
|
||||
- todo_db
|
||||
|
||||
volumes:
|
||||
todo_db:
|
||||
```
|
||||
|
||||
Run `docker compose up -d` to start the database containers.<br/>
|
||||
You should now be able to log into the database server at http://localhost:8888, with the following credentials.<br/>
|
||||
**username**: root, **password**: root
|
||||
|
||||
### Update the database connectionstring
|
||||
|
||||
Replace the contents of `db/settings.py` with the following.
|
||||
|
||||
```python
|
||||
class DbSettings:
|
||||
def get_connectionstring() -> str:
|
||||
connectionstring = "mysql+asyncmy://root:root@localhost:3306/todo"
|
||||
return connectionstring
|
||||
```
|
||||
|
||||
The connectionstring has been hardcoded for demo purposes.
|
||||
In a production app, you would use some kind of secret manager to retrieve it.
|
||||
`m4g db run`
|
||||
|
||||
### Create the entities
|
||||
|
||||
@ -96,7 +34,9 @@ In a production app, you would use some kind of secret manager to retrieve it.
|
||||
|
||||
`m4g db migrate`
|
||||
|
||||
If you inspect the database in [PhpMyAdmin](http://localhost:8888), you should now see a populated schema.
|
||||
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
|
||||
|
||||
@ -106,126 +46,160 @@ If you inspect the database in [PhpMyAdmin](http://localhost:8888), you should n
|
||||
|
||||
`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 import in `api/services/todo_service.py`.
|
||||
Add the following imports in `api/services/todo_service.py`.
|
||||
|
||||
`from sqlalchemy.orm import selectinload`
|
||||
```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
|
||||
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
|
||||
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
|
||||
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
|
||||
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()
|
||||
|
||||
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()
|
||||
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
|
||||
|
||||
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)
|
||||
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:
|
||||
entity.tags = []
|
||||
|
||||
await session.commit()
|
||||
return True
|
||||
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
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user