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 partial class Generate : Command, ISubCommandOf<Db>
|
||||||
{
|
{
|
||||||
public Generate(IEnumerable<ISubCommandOf<Generate>> subCommands) :
|
public Generate(IEnumerable<ISubCommandOf<Generate>> commands) :
|
||||||
base("generate", "Generate a database item")
|
base("generate", "Generate a database item")
|
||||||
{
|
{
|
||||||
AddAlias("g");
|
AddAlias("g");
|
||||||
foreach (var subCommandOf in subCommands.Cast<Command>())
|
foreach (var command in commands.Cast<Command>())
|
||||||
AddCommand(subCommandOf);
|
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"
|
// Register "m4g db"
|
||||||
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.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.Migrate>();
|
||||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Db>, Commands.MycroForge.Db.Rollback>();
|
services.AddScoped<ISubCommandOf<Commands.MycroForge.Db>, Commands.MycroForge.Db.Rollback>();
|
||||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Db>, Commands.MycroForge.Db.Generate>();
|
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)
|
public async Task ExecuteAsync(ProjectContext context)
|
||||||
{
|
{
|
||||||
var config = await context.LoadConfig();
|
var config = await context.LoadConfig();
|
||||||
|
config.Api = new() { Port = 8000 };
|
||||||
|
await context.SaveConfig(config);
|
||||||
|
|
||||||
await context.Bash(
|
await context.Bash(
|
||||||
"source .venv/bin/activate",
|
"source .venv/bin/activate",
|
||||||
@ -49,12 +51,5 @@ public sealed class Api : IFeature
|
|||||||
var main = await context.ReadFile("main.py");
|
var main = await context.ReadFile("main.py");
|
||||||
main = string.Join('\n', MainTemplate) + main;
|
main = string.Join('\n', MainTemplate) + main;
|
||||||
await context.WriteFile("main.py", 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:",
|
"class DbSettings:",
|
||||||
"\tdef get_connectionstring() -> str:",
|
"\tdef get_connectionstring() -> str:",
|
||||||
"\t\t# Example",
|
"\t\tconnectionstring = \"mysql+asyncmy://root:password@localhost:%db_port%/%app_name%\"",
|
||||||
"\t\t# connectionstring = \"mysql+asyncmy://root:root@localhost:3306/your_database\"",
|
"\t\treturn connectionstring",
|
||||||
"\t\t# return connectionstring",
|
|
||||||
"\t\traise Exception(\"DbSettings.get_connectionstring was not implemented.\")",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
private static readonly string[] AsyncSession =
|
private static readonly string[] AsyncSession =
|
||||||
@ -35,20 +33,44 @@ public sealed class Db : IFeature
|
|||||||
"\tpass"
|
"\tpass"
|
||||||
];
|
];
|
||||||
|
|
||||||
private static readonly string[] User =
|
private static readonly string[] DockerCompose =
|
||||||
[
|
[
|
||||||
"from sqlalchemy import String",
|
"version: '3.8'",
|
||||||
"from sqlalchemy.orm import Mapped, mapped_column",
|
"# Access the database UI at http://localhost:${DB_PORT}.",
|
||||||
$"from {FeatureName}.entities.entity_base import EntityBase",
|
"# Login: username = root & password = password",
|
||||||
"",
|
"",
|
||||||
"class User(EntityBase):",
|
"services:",
|
||||||
"\t__tablename__ = \"users\"",
|
" %app_name%_mariadb:",
|
||||||
"\tid: Mapped[int] = mapped_column(primary_key=True)",
|
" image: 'mariadb:10.4'",
|
||||||
"\tfirstname: Mapped[str] = mapped_column(String(255))",
|
" container_name: '%app_name%_mariadb'",
|
||||||
"\tlastname: Mapped[str] = mapped_column(String(255))",
|
" 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:",
|
" %app_name%_phpmyadmin:",
|
||||||
"\t\treturn f\"User(id={self.id!r}, firstname={self.firstname!r}, lastname={self.lastname!r})\""
|
" 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
|
#endregion
|
||||||
@ -60,6 +82,10 @@ public sealed class Db : IFeature
|
|||||||
public async Task ExecuteAsync(ProjectContext context)
|
public async Task ExecuteAsync(ProjectContext context)
|
||||||
{
|
{
|
||||||
var config = await context.LoadConfig();
|
var config = await context.LoadConfig();
|
||||||
|
config.Db = new() { DbPort = 5050, PmaPort = 5051 };
|
||||||
|
await context.SaveConfig(config);
|
||||||
|
|
||||||
|
var appName = context.AppName;
|
||||||
|
|
||||||
await context.Bash(
|
await context.Bash(
|
||||||
"source .venv/bin/activate",
|
"source .venv/bin/activate",
|
||||||
@ -70,17 +96,21 @@ public sealed class Db : IFeature
|
|||||||
|
|
||||||
var env = await context.ReadFile($"{FeatureName}/env.py");
|
var env = await context.ReadFile($"{FeatureName}/env.py");
|
||||||
env = new DbEnvInitializer(env).Rewrite();
|
env = new DbEnvInitializer(env).Rewrite();
|
||||||
// env = new DbEnvUpdater(env, "user", "User").Rewrite();
|
|
||||||
await context.WriteFile($"{FeatureName}/env.py", env);
|
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}/engine/async_session.py", AsyncSession);
|
||||||
|
|
||||||
await context.CreateFile($"{FeatureName}/entities/entity_base.py", EntityBase);
|
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 partial class ProjectConfig
|
||||||
{
|
{
|
||||||
public ApiConfig Api { get; set; } = default!;
|
public ApiConfig Api { get; set; } = default!;
|
||||||
|
public DbConfig Db { get; set; } = default!;
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using Humanizer;
|
||||||
using MycroForge.CLI.Extensions;
|
using MycroForge.CLI.Extensions;
|
||||||
|
|
||||||
namespace MycroForge.CLI;
|
namespace MycroForge.CLI;
|
||||||
@ -7,6 +8,7 @@ namespace MycroForge.CLI;
|
|||||||
public class ProjectContext
|
public class ProjectContext
|
||||||
{
|
{
|
||||||
public string RootDirectory { get; private set; } = Environment.CurrentDirectory;
|
public string RootDirectory { get; private set; } = Environment.CurrentDirectory;
|
||||||
|
public string AppName => Path.GetFileNameWithoutExtension(RootDirectory).Underscore().ToLower();
|
||||||
private string ConfigPath => Path.Combine(RootDirectory, "m4g.json");
|
private string ConfigPath => Path.Combine(RootDirectory, "m4g.json");
|
||||||
|
|
||||||
|
|
||||||
|
10
README.md
10
README.md
@ -1,10 +1,10 @@
|
|||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
bash (/usr/bin/bash)
|
- Docker
|
||||||
|
- bash (/bin/bash)
|
||||||
Python 3.10.2 (/usr/bin/python3)
|
- Python 3.10.2 (/usr/bin/python3)
|
||||||
- python3-pip
|
- python3-pip
|
||||||
- python3-venv
|
- python3-venv
|
||||||
|
|
||||||
#### Note
|
#### Note
|
||||||
The MycroForge CLI assumes a linux compatible environment, so on Windows you'll have to use WSL.
|
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
|
# 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.
|
TODO: Supplement this section.
|
||||||
|
|
||||||
## Initialize the project
|
## Initialize the project
|
||||||
@ -12,71 +12,9 @@ this command. If you prefer using another editor, then manually open the generat
|
|||||||
|
|
||||||
## Setup the database
|
## Setup the database
|
||||||
|
|
||||||
Summary: This section explains how to interact with the database.
|
### Start the database
|
||||||
TODO: Supplement this section.
|
|
||||||
|
|
||||||
### Create a docker compose file for the database
|
`m4g db run`
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
### Create the entities
|
### 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`
|
`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
|
## 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`
|
`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
|
### 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`
|
Modify `TodoService.list`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Before
|
# Before
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
stmt = select(Todo)
|
stmt = select(Todo)
|
||||||
results = (await session.scalars(stmt)).all()
|
results = (await session.scalars(stmt)).all()
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# After
|
# After
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
stmt = select(Todo).options(selectinload(Todo.tags))
|
stmt = select(Todo).options(selectinload(Todo.tags))
|
||||||
results = (await session.scalars(stmt)).all()
|
results = (await session.scalars(stmt)).all()
|
||||||
return results
|
return results
|
||||||
```
|
```
|
||||||
|
|
||||||
Modify `TodoService.get_by_id`
|
Modify `TodoService.get_by_id`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Before
|
# Before
|
||||||
async def get_by_id(self, id: int) -> Optional[Todo]:
|
async def get_by_id(self, id: int) -> Optional[Todo]:
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
stmt = select(Todo).where(Todo.id == id)
|
stmt = select(Todo).where(Todo.id == id)
|
||||||
result = (await session.scalars(stmt)).first()
|
result = (await session.scalars(stmt)).first()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# After
|
# After
|
||||||
async def get_by_id(self, id: int) -> Optional[Todo]:
|
async def get_by_id(self, id: int) -> Optional[Todo]:
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
stmt = select(Todo).where(Todo.id == id).options(selectinload(Todo.tags))
|
stmt = select(Todo).where(Todo.id == id).options(selectinload(Todo.tags))
|
||||||
result = (await session.scalars(stmt)).first()
|
result = (await session.scalars(stmt)).first()
|
||||||
return result
|
return result
|
||||||
```
|
```
|
||||||
|
|
||||||
Modify `TodoService.create`
|
Modify `TodoService.create`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Before
|
# Before
|
||||||
async def create(self, data: Dict[str, Any]) -> None:
|
async def create(self, data: Dict[str, Any]) -> None:
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
entity = Todo(**data)
|
entity = Todo(**data)
|
||||||
session.add(entity)
|
session.add(entity)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
|
||||||
# After
|
# After
|
||||||
async def create(self, data: Dict[str, Any]) -> None:
|
async def create(self, data: Dict[str, Any]) -> None:
|
||||||
tag_ids = []
|
tag_ids = []
|
||||||
|
|
||||||
if "tag_ids" in data.keys():
|
if "tag_ids" in data.keys():
|
||||||
tag_ids = data["tag_ids"]
|
tag_ids = data["tag_ids"]
|
||||||
del data["tag_ids"]
|
del data["tag_ids"]
|
||||||
|
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
entity = Todo(**data)
|
entity = Todo(**data)
|
||||||
|
|
||||||
if len(tag_ids) > 0:
|
if len(tag_ids) > 0:
|
||||||
stmt = select(Tag).where(Tag.id.in_(tag_ids))
|
stmt = select(Tag).where(Tag.id.in_(tag_ids))
|
||||||
result = (await session.scalars(stmt)).all()
|
result = (await session.scalars(stmt)).all()
|
||||||
entity.tags = list(result)
|
entity.tags = list(result)
|
||||||
|
|
||||||
session.add(entity)
|
session.add(entity)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
```
|
```
|
||||||
|
|
||||||
Modify `TodoService.update`
|
Modify `TodoService.update`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Before
|
# Before
|
||||||
async def update(self, id: int, data: Dict[str, Any]) -> bool:
|
async def update(self, id: int, data: Dict[str, Any]) -> bool:
|
||||||
tag_ids = []
|
tag_ids = []
|
||||||
|
|
||||||
if "tag_ids" in data.keys():
|
if "tag_ids" in data.keys():
|
||||||
tag_ids = data["tag_ids"]
|
tag_ids = data["tag_ids"]
|
||||||
del data["tag_ids"]
|
del data["tag_ids"]
|
||||||
|
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
stmt = select(Todo).where(Todo.id == id)
|
stmt = select(Todo).where(Todo.id == id)
|
||||||
entity = (await session.scalars(stmt)).first()
|
entity = (await session.scalars(stmt)).first()
|
||||||
|
|
||||||
if entity is None:
|
if entity is None:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
setattr(entity, key, value)
|
setattr(entity, key, value)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
# After
|
# After
|
||||||
async def update(self, id: int, data: Dict[str, Any]) -> bool:
|
async def update(self, id: int, data: Dict[str, Any]) -> bool:
|
||||||
tag_ids = []
|
tag_ids = []
|
||||||
|
|
||||||
if "tag_ids" in data.keys():
|
if "tag_ids" in data.keys():
|
||||||
tag_ids = data["tag_ids"]
|
tag_ids = data["tag_ids"]
|
||||||
del data["tag_ids"]
|
del data["tag_ids"]
|
||||||
|
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
stmt = select(Todo).where(Todo.id == id).options(selectinload(Todo.tags))
|
stmt = select(Todo).where(Todo.id == id).options(selectinload(Todo.tags))
|
||||||
entity = (await session.scalars(stmt)).first()
|
entity = (await session.scalars(stmt)).first()
|
||||||
|
|
||||||
if entity is None:
|
if entity is None:
|
||||||
return False
|
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:
|
else:
|
||||||
entity.tags = []
|
for key, value in data.items():
|
||||||
|
setattr(entity, key, value)
|
||||||
await session.commit()
|
|
||||||
return True
|
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