Ading comments and cleaning up
This commit is contained in:
parent
5698b504e9
commit
aa1c2422ef
@ -1,4 +1,5 @@
|
|||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
using MycroForge.CLI.Commands;
|
||||||
using MycroForge.CLI.Extensions;
|
using MycroForge.CLI.Extensions;
|
||||||
using MycroForge.Core;
|
using MycroForge.Core;
|
||||||
|
|
||||||
@ -6,6 +7,8 @@ namespace MycroForge.CLI.CodeGen;
|
|||||||
|
|
||||||
public class CrudRouterGenerator
|
public class CrudRouterGenerator
|
||||||
{
|
{
|
||||||
|
#region Templates
|
||||||
|
|
||||||
private static readonly string[] Template =
|
private static readonly string[] Template =
|
||||||
[
|
[
|
||||||
"from typing import Annotated",
|
"from typing import Annotated",
|
||||||
@ -79,6 +82,8 @@ public class CrudRouterGenerator
|
|||||||
"\t\treturn JSONResponse(status_code=500, content=str(ex))",
|
"\t\treturn JSONResponse(status_code=500, content=str(ex))",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
private readonly ProjectContext _context;
|
private readonly ProjectContext _context;
|
||||||
|
|
||||||
public CrudRouterGenerator(ProjectContext context)
|
public CrudRouterGenerator(ProjectContext context)
|
||||||
@ -86,65 +91,49 @@ public class CrudRouterGenerator
|
|||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Generate(string path, string entity)
|
public async Task Generate(FullyQualifiedName fqn)
|
||||||
{
|
{
|
||||||
var entitySnakeCaseName = entity.Underscore().ToLower();
|
var serviceClassName = $"{fqn.PascalizedName}Service";
|
||||||
var entityClassName = entity.Pascalize();
|
var entityRoutePrefix = fqn.PascalizedName.Kebaberize().Pluralize().ToLower();
|
||||||
var serviceClassName = $"{entityClassName}Service";
|
|
||||||
var entityRoutePrefix = entity.Kebaberize().Pluralize().ToLower();
|
|
||||||
|
|
||||||
var servicesFolderPath = $"{Features.Api.FeatureName}/services/{path}";
|
var serviceFilePath = Path.Join(
|
||||||
var serviceFilePath = $"{servicesFolderPath}/{entitySnakeCaseName}_service.py";
|
Features.Api.FeatureName, "services", fqn.FolderPath, $"{fqn.SnakeCasedName}_service"
|
||||||
var serviceImportPath = serviceFilePath
|
);
|
||||||
.Replace('/', '.')
|
|
||||||
.Replace('\\', '.')
|
|
||||||
.Replace(".py", string.Empty)
|
|
||||||
.DeduplicateDots()
|
|
||||||
.Trim();
|
|
||||||
|
|
||||||
var routersFolderPath = $"{Features.Api.FeatureName}/routers/{path}";
|
var serviceImportPath = serviceFilePath.SlashesToDots();
|
||||||
var routerFilePath = $"{routersFolderPath}/{entitySnakeCaseName}.py";
|
var routerFolderPath = Path.Join(Features.Api.FeatureName, "routers", fqn.FolderPath);
|
||||||
var routerImportPath = routersFolderPath
|
var routerFilePath = Path.Join(routerFolderPath, $"{fqn.SnakeCasedName}");
|
||||||
.Replace('/', '.')
|
var routerImportPath = routerFolderPath.SlashesToDots();
|
||||||
.Replace('\\', '.')
|
var requestsFolderPath = Path.Join(Features.Api.FeatureName, "requests", fqn.FolderPath);
|
||||||
.Replace(".py", "")
|
|
||||||
.DeduplicateDots()
|
|
||||||
.Trim();
|
|
||||||
|
|
||||||
var requestsFolderPath = $"{Features.Api.FeatureName}/requests/{path}";
|
var createRequestImportPath = Path.Join(requestsFolderPath, $"Create{fqn.PascalizedName}Request")
|
||||||
|
.SlashesToDots()
|
||||||
var createRequestImportPath = $"{requestsFolderPath}/Create{entityClassName}Request"
|
|
||||||
.Replace('/', '.')
|
|
||||||
.Replace('\\', '.')
|
|
||||||
.DeduplicateDots()
|
|
||||||
.Underscore()
|
.Underscore()
|
||||||
.ToLower();
|
.ToLower();
|
||||||
var createRequestClassName = $"Create{entityClassName}Request";
|
var createRequestClassName = $"Create{fqn.PascalizedName}Request";
|
||||||
|
|
||||||
var updateRequestImportPath = $"{requestsFolderPath}/Update{entityClassName}Request"
|
var updateRequestImportPath = Path.Join(requestsFolderPath, $"Update{fqn.PascalizedName}Request")
|
||||||
.Replace('/', '.')
|
.SlashesToDots()
|
||||||
.Replace('\\', '.')
|
|
||||||
.DeduplicateDots()
|
|
||||||
.Underscore()
|
.Underscore()
|
||||||
.ToLower();
|
.ToLower();
|
||||||
var updateRequestClassName = $"Update{entityClassName}Request";
|
var updateRequestClassName = $"Update{fqn.PascalizedName}Request";
|
||||||
|
|
||||||
var router = string.Join("\n", Template)
|
var router = string.Join("\n", Template)
|
||||||
.Replace("%service_import_path%", serviceImportPath)
|
.Replace("%service_import_path%", serviceImportPath)
|
||||||
.Replace("%entity_class_name%", entityClassName)
|
.Replace("%entity_class_name%", fqn.PascalizedName)
|
||||||
.Replace("%service_class_name%", serviceClassName)
|
.Replace("%service_class_name%", serviceClassName)
|
||||||
.Replace("%create_entity_request_import_path%", createRequestImportPath)
|
.Replace("%create_entity_request_import_path%", createRequestImportPath)
|
||||||
.Replace("%create_entity_request_class_name%", createRequestClassName)
|
.Replace("%create_entity_request_class_name%", createRequestClassName)
|
||||||
.Replace("%update_entity_request_import_path%", updateRequestImportPath)
|
.Replace("%update_entity_request_import_path%", updateRequestImportPath)
|
||||||
.Replace("%update_entity_request_class_name%", updateRequestClassName);
|
.Replace("%update_entity_request_class_name%", updateRequestClassName);
|
||||||
|
|
||||||
await _context.CreateFile(routerFilePath, router);
|
await _context.CreateFile($"{routerFilePath}.py", router);
|
||||||
|
|
||||||
var main = await _context.ReadFile("main.py");
|
var main = await _context.ReadFile("main.py");
|
||||||
|
|
||||||
main = new MainModifier(main).Initialize()
|
main = new MainModifier(main).Initialize()
|
||||||
.Import(from: routerImportPath, import: entitySnakeCaseName)
|
.Import(from: routerImportPath, import: fqn.SnakeCasedName)
|
||||||
.IncludeRouter(prefix: entityRoutePrefix, router: entitySnakeCaseName)
|
.IncludeRouter(prefix: entityRoutePrefix, router: fqn.SnakeCasedName)
|
||||||
.Rewrite();
|
.Rewrite();
|
||||||
|
|
||||||
await _context.WriteFile("main.py", main);
|
await _context.WriteFile("main.py", main);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using Humanizer;
|
using MycroForge.CLI.Commands;
|
||||||
using MycroForge.CLI.Extensions;
|
|
||||||
using MycroForge.Core;
|
using MycroForge.Core;
|
||||||
|
|
||||||
namespace MycroForge.CLI.CodeGen;
|
namespace MycroForge.CLI.CodeGen;
|
||||||
@ -65,26 +64,15 @@ public class CrudServiceGenerator
|
|||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Generate(string path, string entity)
|
public async Task Generate(FullyQualifiedName fqn)
|
||||||
{
|
{
|
||||||
var entitySnakeCaseName = entity.Underscore().ToLower();
|
var entityImportPath = fqn.GetImportPath(root: [Features.Db.FeatureName, "entities"]);
|
||||||
var entityClassName = entity.Pascalize();
|
|
||||||
|
|
||||||
var entitiesFolderPath = $"{Features.Db.FeatureName}/entities/{path}";
|
var serviceFilePath = Path.Join(Features.Api.FeatureName, "services", $"{fqn.FilePath}_service.py");
|
||||||
var entityFilePath = $"{entitiesFolderPath}/{entitySnakeCaseName}.py";
|
|
||||||
var entityImportPath = entityFilePath
|
|
||||||
.Replace('/', '.')
|
|
||||||
.Replace('\\', '.')
|
|
||||||
.Replace(".py", string.Empty)
|
|
||||||
.DeduplicateDots()
|
|
||||||
.Trim();
|
|
||||||
|
|
||||||
var servicesFolderPath = $"{Features.Api.FeatureName}/services/{path}";
|
|
||||||
var serviceFilePath = $"{servicesFolderPath}/{entity.Underscore().ToLower()}_service.py";
|
|
||||||
|
|
||||||
var service = string.Join("\n", Template)
|
var service = string.Join("\n", Template)
|
||||||
.Replace("%entity_import_path%", entityImportPath)
|
.Replace("%entity_import_path%", entityImportPath)
|
||||||
.Replace("%entity_class_name%", entityClassName)
|
.Replace("%entity_class_name%", fqn.PascalizedName)
|
||||||
;
|
;
|
||||||
|
|
||||||
await _context.CreateFile(serviceFilePath, service);
|
await _context.CreateFile(serviceFilePath, service);
|
||||||
|
@ -148,7 +148,7 @@ public partial class EntityLinker
|
|||||||
var path = $"{Features.Db.FeatureName}/entities";
|
var path = $"{Features.Db.FeatureName}/entities";
|
||||||
|
|
||||||
if (fqn.HasPath)
|
if (fqn.HasPath)
|
||||||
path = Path.Combine(path, fqn.Path);
|
path = Path.Combine(path, fqn.FolderPath);
|
||||||
|
|
||||||
path = Path.Combine(path, $"{fqn.SnakeCasedName}.py");
|
path = Path.Combine(path, $"{fqn.SnakeCasedName}.py");
|
||||||
var entity = new EntityModel(fqn.PascalizedName, path, await _context.ReadFile(path));
|
var entity = new EntityModel(fqn.PascalizedName, path, await _context.ReadFile(path));
|
||||||
|
@ -38,12 +38,14 @@ public class MainModifier
|
|||||||
|
|
||||||
public MainModifier IncludeRouter(string prefix, string router)
|
public MainModifier IncludeRouter(string prefix, string router)
|
||||||
{
|
{
|
||||||
_routerIncludeBuffer.Add($"\napp.include_router(prefix=\"/{prefix}\", router={router}.router)");
|
_routerIncludeBuffer.Add($"app.include_router(prefix=\"/{prefix}\", router={router}.router)");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Rewrite()
|
public string Rewrite()
|
||||||
{
|
{
|
||||||
|
// Make sure to insert the includes before the imports, if done the other way around,
|
||||||
|
// the insertions of the includes will change the indexes of the imports.
|
||||||
InsertIncludes();
|
InsertIncludes();
|
||||||
|
|
||||||
InsertImports();
|
InsertImports();
|
||||||
@ -69,15 +71,17 @@ public class MainModifier
|
|||||||
{
|
{
|
||||||
if (_routerIncludeBuffer.Count == 0) return;
|
if (_routerIncludeBuffer.Count == 0) return;
|
||||||
|
|
||||||
|
// Prepend an empty string to the router include buffer,
|
||||||
|
// this will ensure that the new entries are all on separate lines.
|
||||||
|
var content = _routerIncludeBuffer.Prepend(string.Empty).ToArray();
|
||||||
|
|
||||||
if (_lastRouterInclude is not null)
|
if (_lastRouterInclude is not null)
|
||||||
{
|
{
|
||||||
_source.InsertMultiLine(
|
_source.InsertMultiLine(_lastRouterInclude.EndIndex, content);
|
||||||
_lastRouterInclude.EndIndex, _routerIncludeBuffer.ToArray()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_source.InsertMultiLineAtEnd(_routerIncludeBuffer.ToArray());
|
_source.InsertMultiLineAtEnd(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
using MycroForge.CLI.Commands;
|
||||||
using MycroForge.Core;
|
using MycroForge.Core;
|
||||||
|
|
||||||
namespace MycroForge.CLI.CodeGen;
|
namespace MycroForge.CLI.CodeGen;
|
||||||
@ -40,29 +41,30 @@ public class RequestClassGenerator
|
|||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Generate(string path, string entity, Type type)
|
public async Task Generate(FullyQualifiedName fqn, Type type)
|
||||||
{
|
{
|
||||||
var entitySnakeCaseName = entity.Underscore().ToLower();
|
var entityFilePath = Path.Join(Features.Db.FeatureName, "entities", $"{fqn.FilePath}.py");
|
||||||
var entityClassName = entity.Pascalize();
|
|
||||||
var entitiesFolderPath = $"{Features.Db.FeatureName}/entities/{path}";
|
|
||||||
var entityFilePath = $"{entitiesFolderPath}/{entitySnakeCaseName}.py";
|
|
||||||
var entitySource = await _context.ReadFile(entityFilePath);
|
var entitySource = await _context.ReadFile(entityFilePath);
|
||||||
|
|
||||||
var fieldInfo = ReadFields(entitySource);
|
var fieldInfo = ReadFields(entitySource);
|
||||||
var fields = string.Join('\n', fieldInfo.Select(x => ToFieldString(x, type)));
|
var fields = string.Join('\n', fieldInfo.Select(x => ToFieldString(x, type)));
|
||||||
|
|
||||||
var requestsFolderPath = $"{Features.Api.FeatureName}/requests/{path}";
|
var requestFilePath = Path.Join(
|
||||||
var updateRequestFilePath =
|
Features.Api.FeatureName,
|
||||||
$"{requestsFolderPath}/{type.ToString().ToLower()}_{entitySnakeCaseName}_request.py";
|
"requests",
|
||||||
|
fqn.FolderPath,
|
||||||
|
// requestsFolderPath,
|
||||||
|
$"{type.ToString().ToLower()}_{fqn.SnakeCasedName}_request.py"
|
||||||
|
);
|
||||||
|
|
||||||
var service = string.Join("\n", Template)
|
var service = string.Join("\n", Template)
|
||||||
.Replace("%imports%", GetImportString(entitySource, fieldInfo, type))
|
.Replace("%imports%", GetImportString(entitySource, fieldInfo, type))
|
||||||
.Replace("%request_type%", type.ToString().Pascalize())
|
.Replace("%request_type%", type.ToString().Pascalize())
|
||||||
.Replace("%entity_class_name%", entityClassName)
|
.Replace("%entity_class_name%", fqn.PascalizedName)
|
||||||
|
// .Replace("%entity_class_name%", entityClassName)
|
||||||
.Replace("%fields%", fields)
|
.Replace("%fields%", fields)
|
||||||
;
|
;
|
||||||
|
|
||||||
await _context.CreateFile(updateRequestFilePath, service);
|
await _context.CreateFile(requestFilePath, service);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ToFieldString(Field field, Type type)
|
private string ToFieldString(Field field, Type type)
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
using MycroForge.CLI.Extensions;
|
||||||
|
|
||||||
namespace MycroForge.CLI.Commands;
|
namespace MycroForge.CLI.Commands;
|
||||||
|
|
||||||
public class FullyQualifiedName
|
public class FullyQualifiedName
|
||||||
{
|
{
|
||||||
public string Path { get; }
|
public string FolderPath { get; }
|
||||||
public string PascalizedName { get; }
|
public string PascalizedName { get; }
|
||||||
public string SnakeCasedName { get; }
|
public string SnakeCasedName { get; }
|
||||||
|
|
||||||
public bool HasPath => Path.Length > 0;
|
public string FilePath =>
|
||||||
|
string.IsNullOrEmpty(FolderPath.Trim())
|
||||||
|
? SnakeCasedName
|
||||||
|
: Path.Join(FolderPath, SnakeCasedName);
|
||||||
|
|
||||||
|
public bool HasPath => FolderPath.Length > 0;
|
||||||
|
|
||||||
|
|
||||||
public FullyQualifiedName(string name)
|
public FullyQualifiedName(string name)
|
||||||
@ -21,8 +27,22 @@ public class FullyQualifiedName
|
|||||||
name = fullName[1];
|
name = fullName[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
Path = path;
|
FolderPath = path;
|
||||||
PascalizedName = name.Pascalize();
|
PascalizedName = name.Pascalize();
|
||||||
SnakeCasedName = name.Underscore().ToLower();
|
SnakeCasedName = SnakeCase(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetImportPath(params string[] root)
|
||||||
|
{
|
||||||
|
if (root.Length == 0)
|
||||||
|
return string.Join('.', FilePath).SlashesToDots();
|
||||||
|
|
||||||
|
var importRoot = string.Join('.', root);
|
||||||
|
|
||||||
|
return string.Join('.', SnakeCase(importRoot), FilePath).SlashesToDots();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SnakeCase(string value) => value.Underscore().ToLower();
|
||||||
|
|
||||||
|
// private static string SlashesToDots(string value) => value.Replace('\\', '.').Replace('/', '.');
|
||||||
}
|
}
|
@ -21,6 +21,11 @@ public partial class MycroForge
|
|||||||
description: "The database UI port"
|
description: "The database UI port"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private static readonly Option<ProjectConfig.DbConfig.DbuPlatformOptions> DbuPlatformOption = new(
|
||||||
|
aliases: ["--database-ui-platform", "--dbu-platform"],
|
||||||
|
description: "The docker platform for the PhpMyAdmin image"
|
||||||
|
);
|
||||||
|
|
||||||
private readonly ProjectContext _context;
|
private readonly ProjectContext _context;
|
||||||
private readonly OptionsContainer _optionsContainer;
|
private readonly OptionsContainer _optionsContainer;
|
||||||
private readonly List<IFeature> _features;
|
private readonly List<IFeature> _features;
|
||||||
@ -34,12 +39,22 @@ public partial class MycroForge
|
|||||||
|
|
||||||
AddOption(DbhPortOption);
|
AddOption(DbhPortOption);
|
||||||
AddOption(DbuPortOption);
|
AddOption(DbuPortOption);
|
||||||
this.SetHandler(ExecuteAsync, DbhPortOption, DbuPortOption);
|
AddOption(DbuPlatformOption);
|
||||||
|
this.SetHandler(ExecuteAsync, DbhPortOption, DbuPortOption, DbuPlatformOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteAsync(int dbhPort, int dbuPort)
|
private async Task ExecuteAsync(
|
||||||
|
int dbhPort,
|
||||||
|
int dbuPort,
|
||||||
|
ProjectConfig.DbConfig.DbuPlatformOptions dbuPlatform
|
||||||
|
)
|
||||||
{
|
{
|
||||||
_optionsContainer.Set(new Features.Db.Options { DbhPort = dbhPort, DbuPort = dbuPort });
|
_optionsContainer.Set(new Features.Db.Options
|
||||||
|
{
|
||||||
|
DbhPort = dbhPort,
|
||||||
|
DbuPort = dbuPort,
|
||||||
|
DbuPlatform = dbuPlatform
|
||||||
|
});
|
||||||
var feature = _features.First(f => f.Name == Features.Db.FeatureName);
|
var feature = _features.First(f => f.Name == Features.Db.FeatureName);
|
||||||
await feature.ExecuteAsync(_context);
|
await feature.ExecuteAsync(_context);
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,10 @@ public partial class MycroForge
|
|||||||
private async Task ExecuteAsync(string entity)
|
private async Task ExecuteAsync(string entity)
|
||||||
{
|
{
|
||||||
var fqn = new FullyQualifiedName(entity);
|
var fqn = new FullyQualifiedName(entity);
|
||||||
|
await new CrudServiceGenerator(_context).Generate(fqn);
|
||||||
await new CrudServiceGenerator(_context).Generate(fqn.Path, fqn.PascalizedName);
|
await new RequestClassGenerator(_context).Generate(fqn, RequestClassGenerator.Type.Create);
|
||||||
await new RequestClassGenerator(_context).Generate(fqn.Path, fqn.PascalizedName, RequestClassGenerator.Type.Create);
|
await new RequestClassGenerator(_context).Generate(fqn, RequestClassGenerator.Type.Update);
|
||||||
await new RequestClassGenerator(_context).Generate(fqn.Path, fqn.PascalizedName, RequestClassGenerator.Type.Update);
|
await new CrudRouterGenerator(_context).Generate(fqn);
|
||||||
await new CrudRouterGenerator(_context).Generate(fqn.Path, fqn.PascalizedName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ public partial class MycroForge
|
|||||||
_context.AssertDirectoryExists(folderPath);
|
_context.AssertDirectoryExists(folderPath);
|
||||||
|
|
||||||
if (fqn.HasPath)
|
if (fqn.HasPath)
|
||||||
folderPath = Path.Combine(folderPath, fqn.Path);
|
folderPath = Path.Combine(folderPath, fqn.FolderPath);
|
||||||
|
|
||||||
var fileName = $"{fqn.SnakeCasedName}.py";
|
var fileName = $"{fqn.SnakeCasedName}.py";
|
||||||
var filePath = Path.Combine(folderPath, fileName);
|
var filePath = Path.Combine(folderPath, fileName);
|
||||||
|
@ -75,7 +75,7 @@ public partial class MycroForge
|
|||||||
_context.AssertDirectoryExists(Features.Db.FeatureName);
|
_context.AssertDirectoryExists(Features.Db.FeatureName);
|
||||||
|
|
||||||
if (fqn.HasPath)
|
if (fqn.HasPath)
|
||||||
folderPath = Path.Combine(folderPath, fqn.Path);
|
folderPath = Path.Combine(folderPath, fqn.FolderPath);
|
||||||
|
|
||||||
var _columns = GetColumnDefinitions(columns.ToArray());
|
var _columns = GetColumnDefinitions(columns.ToArray());
|
||||||
var typeImports = string.Join(", ", _columns.Select(c => c.OrmType.Split('(').First()).Distinct());
|
var typeImports = string.Join(", ", _columns.Select(c => c.OrmType.Split('(').First()).Distinct());
|
||||||
|
@ -23,7 +23,8 @@ public partial class MycroForge
|
|||||||
{
|
{
|
||||||
var config = await _context.LoadConfig();
|
var config = await _context.LoadConfig();
|
||||||
var env = $"DBH_PORT={config.Db.DbhPort} DBU_PORT={config.Db.DbuPort}";
|
var env = $"DBH_PORT={config.Db.DbhPort} DBU_PORT={config.Db.DbuPort}";
|
||||||
await _context.Bash($"{env} docker compose -f {Features.Db.FeatureName}.docker-compose.yml up -d");
|
var command = $"{env} docker compose -f {Features.Db.FeatureName}.docker-compose.yml up -d";
|
||||||
|
await _context.Bash(command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ public partial class MycroForge
|
|||||||
private async Task ExecuteAsync()
|
private async Task ExecuteAsync()
|
||||||
{
|
{
|
||||||
await _context.Bash(
|
await _context.Bash(
|
||||||
|
// Set the log level to ERROR to prevent warnings concerning environment variables not being set.
|
||||||
$"docker --log-level ERROR compose -f {Features.Db.FeatureName}.docker-compose.yml down"
|
$"docker --log-level ERROR compose -f {Features.Db.FeatureName}.docker-compose.yml down"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ public partial class MycroForge
|
|||||||
var folderPath = string.Empty;
|
var folderPath = string.Empty;
|
||||||
|
|
||||||
if (fqn.HasPath)
|
if (fqn.HasPath)
|
||||||
folderPath = Path.Combine(folderPath, fqn.Path);
|
folderPath = Path.Combine(folderPath, fqn.FolderPath);
|
||||||
|
|
||||||
var filePath = Path.Combine(folderPath, $"{fqn.SnakeCasedName}.py");
|
var filePath = Path.Combine(folderPath, $"{fqn.SnakeCasedName}.py");
|
||||||
var template = withSession ? WithSessionTemplate : DefaultTemplate;
|
var template = withSession ? WithSessionTemplate : DefaultTemplate;
|
||||||
|
@ -15,6 +15,7 @@ public partial class MycroForge
|
|||||||
ApiPort = ctx.ParseResult.GetValueForOption(ApiPortOption),
|
ApiPort = ctx.ParseResult.GetValueForOption(ApiPortOption),
|
||||||
DbhPort = ctx.ParseResult.GetValueForOption(DbhPortOption),
|
DbhPort = ctx.ParseResult.GetValueForOption(DbhPortOption),
|
||||||
DbuPort = ctx.ParseResult.GetValueForOption(DbuPortOption),
|
DbuPort = ctx.ParseResult.GetValueForOption(DbuPortOption),
|
||||||
|
DbuPlatform = ctx.ParseResult.GetValueForOption(DbuPlatformOption),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
namespace MycroForge.CLI.Commands;
|
using MycroForge.Core;
|
||||||
|
|
||||||
|
namespace MycroForge.CLI.Commands;
|
||||||
|
|
||||||
public partial class MycroForge
|
public partial class MycroForge
|
||||||
{
|
{
|
||||||
@ -11,6 +13,7 @@ public partial class MycroForge
|
|||||||
public int? ApiPort { get; set; }
|
public int? ApiPort { get; set; }
|
||||||
public int? DbhPort { get; set; }
|
public int? DbhPort { get; set; }
|
||||||
public int? DbuPort { get; set; }
|
public int? DbuPort { get; set; }
|
||||||
|
public ProjectConfig.DbConfig.DbuPlatformOptions DbuPlatform { get; set; }
|
||||||
|
|
||||||
public Features.Api.Options ApiOptions => new()
|
public Features.Api.Options ApiOptions => new()
|
||||||
{
|
{
|
||||||
@ -20,7 +23,8 @@ public partial class MycroForge
|
|||||||
public Features.Db.Options DbOptions => new()
|
public Features.Db.Options DbOptions => new()
|
||||||
{
|
{
|
||||||
DbhPort = DbhPort <= 0 ? 5050 : DbhPort,
|
DbhPort = DbhPort <= 0 ? 5050 : DbhPort,
|
||||||
DbuPort = DbuPort <= 0 ? 5051 : DbhPort
|
DbuPort = DbuPort <= 0 ? 5051 : DbhPort,
|
||||||
|
DbuPlatform = DbuPlatform
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,11 @@ public partial class MycroForge
|
|||||||
description: "The database UI port"
|
description: "The database UI port"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private static readonly Option<ProjectConfig.DbConfig.DbuPlatformOptions> DbuPlatformOption = new(
|
||||||
|
aliases: ["--database-ui-platform", "--dbu-platform"],
|
||||||
|
description: "The docker platform for the PhpMyAdmin image"
|
||||||
|
);
|
||||||
|
|
||||||
private readonly ProjectContext _context;
|
private readonly ProjectContext _context;
|
||||||
private readonly List<IFeature> _features;
|
private readonly List<IFeature> _features;
|
||||||
private readonly OptionsContainer _optionsContainer;
|
private readonly OptionsContainer _optionsContainer;
|
||||||
@ -55,6 +60,7 @@ public partial class MycroForge
|
|||||||
AddOption(ApiPortOption);
|
AddOption(ApiPortOption);
|
||||||
AddOption(DbhPortOption);
|
AddOption(DbhPortOption);
|
||||||
AddOption(DbuPortOption);
|
AddOption(DbuPortOption);
|
||||||
|
AddOption(DbuPlatformOption);
|
||||||
|
|
||||||
this.SetHandler(ExecuteAsync, new Binder());
|
this.SetHandler(ExecuteAsync, new Binder());
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,8 @@
|
|||||||
|
|
||||||
public static class StringExtensions
|
public static class StringExtensions
|
||||||
{
|
{
|
||||||
public static string DeduplicateDots(this string path)
|
public static string SlashesToDots(this string path) =>
|
||||||
{
|
path.Replace('/', '.')
|
||||||
while (path.Contains(".."))
|
.Replace('\\', '.')
|
||||||
path = path.Replace("..", ".");
|
.Trim();
|
||||||
|
|
||||||
return path.Trim('.');
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,10 +1,15 @@
|
|||||||
namespace MycroForge.CLI.Features;
|
using MycroForge.Core;
|
||||||
|
|
||||||
|
namespace MycroForge.CLI.Features;
|
||||||
|
|
||||||
public sealed partial class Db
|
public sealed partial class Db
|
||||||
{
|
{
|
||||||
public class Options
|
public class Options
|
||||||
{
|
{
|
||||||
public int? DbhPort { get; set; }
|
public int? DbhPort { get; set; }
|
||||||
|
|
||||||
public int? DbuPort { get; set; }
|
public int? DbuPort { get; set; }
|
||||||
|
|
||||||
|
public ProjectConfig.DbConfig.DbuPlatformOptions DbuPlatform { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using MycroForge.CLI.CodeGen;
|
using MycroForge.CLI.CodeGen;
|
||||||
using MycroForge.CLI.Commands;
|
using MycroForge.CLI.Commands;
|
||||||
using MycroForge.Core;
|
using MycroForge.Core;
|
||||||
|
using MycroForge.Core.Extensions;
|
||||||
|
|
||||||
namespace MycroForge.CLI.Features;
|
namespace MycroForge.CLI.Features;
|
||||||
|
|
||||||
@ -37,7 +38,6 @@ public sealed partial class Db : IFeature
|
|||||||
|
|
||||||
private static readonly string[] DockerCompose =
|
private static readonly string[] DockerCompose =
|
||||||
[
|
[
|
||||||
"version: '3.8'",
|
|
||||||
"# Access the database UI at http://localhost:${DBU_PORT}.",
|
"# Access the database UI at http://localhost:${DBU_PORT}.",
|
||||||
"# Login: username = root & password = password",
|
"# Login: username = root & password = password",
|
||||||
"",
|
"",
|
||||||
@ -59,6 +59,7 @@ public sealed partial class Db : IFeature
|
|||||||
"",
|
"",
|
||||||
" %app_name%_phpmyadmin:",
|
" %app_name%_phpmyadmin:",
|
||||||
" image: phpmyadmin/phpmyadmin",
|
" image: phpmyadmin/phpmyadmin",
|
||||||
|
" platform: %dbu_platform%",
|
||||||
" container_name: %app_name%_phpmyadmin",
|
" container_name: %app_name%_phpmyadmin",
|
||||||
" ports:",
|
" ports:",
|
||||||
" - '${DBU_PORT}:80'",
|
" - '${DBU_PORT}:80'",
|
||||||
@ -95,7 +96,8 @@ public sealed partial class Db : IFeature
|
|||||||
config.Db = new()
|
config.Db = new()
|
||||||
{
|
{
|
||||||
DbhPort = options.DbhPort ?? 5050,
|
DbhPort = options.DbhPort ?? 5050,
|
||||||
DbuPort = options.DbuPort ?? 5051
|
DbuPort = options.DbuPort ?? 5051,
|
||||||
|
DbuPlatform = options.DbuPlatform
|
||||||
};
|
};
|
||||||
await context.SaveConfig(config);
|
await context.SaveConfig(config);
|
||||||
|
|
||||||
@ -123,7 +125,10 @@ public sealed partial class Db : IFeature
|
|||||||
|
|
||||||
await context.CreateFile($"{FeatureName}/entities/entity_base.py", EntityBase);
|
await context.CreateFile($"{FeatureName}/entities/entity_base.py", EntityBase);
|
||||||
|
|
||||||
var dockerCompose = string.Join('\n', DockerCompose).Replace("%app_name%", appName);
|
var dockerCompose = string.Join('\n', DockerCompose)
|
||||||
|
.Replace("%app_name%", appName)
|
||||||
|
.Replace("%dbu_platform%", options.DbuPlatform.ToDockerPlatformString())
|
||||||
|
;
|
||||||
|
|
||||||
await context.CreateFile($"{FeatureName}.docker-compose.yml", dockerCompose);
|
await context.CreateFile($"{FeatureName}.docker-compose.yml", dockerCompose);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/bash
|
#!/usr/bin/bash
|
||||||
|
|
||||||
dotnet pack -v d
|
dotnet pack -v d
|
||||||
|
|
||||||
|
6
MycroForge.Core/Attributes/DockerPlatformAttribute.cs
Normal file
6
MycroForge.Core/Attributes/DockerPlatformAttribute.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace MycroForge.Core.Attributes;
|
||||||
|
|
||||||
|
public class DockerPlatformAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string Platform { get; set; } = string.Empty;
|
||||||
|
}
|
19
MycroForge.Core/Extensions/EnumExtensions.cs
Normal file
19
MycroForge.Core/Extensions/EnumExtensions.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using MycroForge.Core.Attributes;
|
||||||
|
|
||||||
|
namespace MycroForge.Core.Extensions;
|
||||||
|
|
||||||
|
public static class EnumExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A generic extension method that aids in reflecting
|
||||||
|
/// and retrieving any attribute that is applied to an `Enum`.
|
||||||
|
/// </summary>
|
||||||
|
public static string ToDockerPlatformString(this ProjectConfig.DbConfig.DbuPlatformOptions value)
|
||||||
|
{
|
||||||
|
return value.GetType()
|
||||||
|
.GetMember(value.ToString())
|
||||||
|
.FirstOrDefault()!
|
||||||
|
.GetCustomAttribute<DockerPlatformAttribute>()!.Platform;
|
||||||
|
}
|
||||||
|
}
|
27
MycroForge.Core/ProjectConfig.DbConfig.DbuPlatformOptions.cs
Normal file
27
MycroForge.Core/ProjectConfig.DbConfig.DbuPlatformOptions.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using MycroForge.Core.Attributes;
|
||||||
|
|
||||||
|
namespace MycroForge.Core;
|
||||||
|
|
||||||
|
public partial class ProjectConfig
|
||||||
|
{
|
||||||
|
public partial class DbConfig
|
||||||
|
{
|
||||||
|
public enum DbuPlatformOptions
|
||||||
|
{
|
||||||
|
[DockerPlatform(Platform = "linux/amd64")]
|
||||||
|
linux_amd64,
|
||||||
|
|
||||||
|
[DockerPlatform(Platform = "linux/arm32/v5")]
|
||||||
|
linux_arm32v5,
|
||||||
|
|
||||||
|
[DockerPlatform(Platform = "linux/arm32/v6")]
|
||||||
|
linux_arm32v6,
|
||||||
|
|
||||||
|
[DockerPlatform(Platform = "linux/arm32/v7")]
|
||||||
|
linux_arm32v7,
|
||||||
|
|
||||||
|
[DockerPlatform(Platform = "linux/arm64/v8")]
|
||||||
|
linux_arm64v8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,16 @@
|
|||||||
namespace MycroForge.Core;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace MycroForge.Core;
|
||||||
|
|
||||||
public partial class ProjectConfig
|
public partial class ProjectConfig
|
||||||
{
|
{
|
||||||
public class DbConfig
|
public partial class DbConfig
|
||||||
{
|
{
|
||||||
public int DbhPort { get; set; }
|
public int DbhPort { get; set; }
|
||||||
|
|
||||||
public int DbuPort { get; set; }
|
public int DbuPort { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public DbuPlatformOptions DbuPlatform { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -27,3 +27,12 @@ Run the install script in the same directory as the downloaded zip. See the exam
|
|||||||
```bash
|
```bash
|
||||||
dotnet nuget add source --name devdisciples --username username --password password https://git.devdisciples.com/api/packages/devdisciples/nuget/index.json --store-password-in-clear-text
|
dotnet nuget add source --name devdisciples --username username --password password https://git.devdisciples.com/api/packages/devdisciples/nuget/index.json --store-password-in-clear-text
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
|
||||||
|
- Fix `-c` option for `m4g db generate entity`
|
||||||
|
- Mention `--dbu-platform` option for `m4g init`
|
||||||
|
- Research if System.CommandLine middleware can be used to safeguard commands like `m4g add` or `m4g api`.
|
||||||
|
- Fix up exception handling
|
||||||
|
- Clean up README files
|
||||||
|
-
|
@ -14,5 +14,6 @@ Usage:
|
|||||||
Options:
|
Options:
|
||||||
--database-host-port, --dbh-port <database-host-port> The database host port
|
--database-host-port, --dbh-port <database-host-port> The database host port
|
||||||
--database-ui-port, --dbu-port <database-ui-port> The database UI port
|
--database-ui-port, --dbu-port <database-ui-port> The database UI port
|
||||||
|
--database-ui-platform, --dbu-platform <linux_amd64|linux_arm32v5|linux_arm32v6|linux_arm32v7|linux_arm64v8> The docker platform for the PhpMyAdmin image
|
||||||
-?, -h, --help Show help and usage information
|
-?, -h, --help Show help and usage information
|
||||||
```
|
```
|
@ -19,5 +19,6 @@ Options:
|
|||||||
--api-port <api-port> The API port
|
--api-port <api-port> The API port
|
||||||
--database-host-port, --dbh-port <database-host-port> The database host port
|
--database-host-port, --dbh-port <database-host-port> The database host port
|
||||||
--database-ui-port, --dbu-port <database-ui-port> The database UI port
|
--database-ui-port, --dbu-port <database-ui-port> The database UI port
|
||||||
|
--database-ui-platform, --dbu-platform <linux_amd64|linux_arm32v5|linux_arm32v6|linux_arm32v7|linux_arm64v8> The docker platform for the PhpMyAdmin image
|
||||||
-?, -h, --help Show help and usage information
|
-?, -h, --help Show help and usage information
|
||||||
```
|
```
|
||||||
|
@ -13,10 +13,7 @@ To use MycroForge, ensure you have the following dependencies installed:
|
|||||||
- **Python 3.10**
|
- **Python 3.10**
|
||||||
- **Docker**
|
- **Docker**
|
||||||
- **.NET 8**
|
- **.NET 8**
|
||||||
|
- **XCode Command Line Tools (MacOS only)**
|
||||||
### Windows
|
|
||||||
|
|
||||||
For Windows users, MycroForge is designed to run in a POSIX compliant environment. It has been tested on Windows using WSL2 with Ubuntu 22.04.03. Therefore, it is recommended to run MycroForge within the same or a similar WSL2 distribution for optimal performance.
|
|
||||||
|
|
||||||
### Adding the Package Registry
|
### Adding the Package Registry
|
||||||
|
|
||||||
@ -37,3 +34,35 @@ dotnet tool install -g MycroForge.CLI
|
|||||||
```
|
```
|
||||||
dotnet tool install -g MycroForge.CLI
|
dotnet tool install -g MycroForge.CLI
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
MycroForge is designed to run in a POSIX compliant environment. It has been tested on Windows using WSL2 with Ubuntu 22.04.03. So it is recommended to run MycroForge within the same or a similar WSL2 distribution for optimal performance.
|
||||||
|
|
||||||
|
### MacOS
|
||||||
|
|
||||||
|
#### Post install steps
|
||||||
|
After installing MycroForge, the dotnet CLI will show a message with some instructions to make the `m4g` command available in `zsh`.
|
||||||
|
It should look similar to the example below.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
Tools directory '/Users/username/.dotnet/tools' is not currently on the PATH environment variable.
|
||||||
|
If you are using zsh, you can add it to your profile by running the following command:
|
||||||
|
|
||||||
|
cat << \EOF >> ~/.zprofile
|
||||||
|
# Add .NET Core SDK tools
|
||||||
|
export PATH="$PATH:/Users/username/.dotnet/tools"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
And run zsh -l to make it available for current session.
|
||||||
|
|
||||||
|
You can only add it to the current session by running the following command:
|
||||||
|
|
||||||
|
export PATH="$PATH:/Users/username/.dotnet/tools"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Known issues
|
||||||
|
|
||||||
|
##### FastAPI swagger blank screen
|
||||||
|
|
||||||
|
If you see a blank screen when opening the FastAPI Swagger documentation, then make sure you've activated the Safari developer tools.
|
@ -5,7 +5,7 @@ sidebar_position: 1
|
|||||||
|
|
||||||
# Intro
|
# Intro
|
||||||
|
|
||||||
Welcome to **MycroForge** – an opinionated CLI tool designed to streamline the development of FastAPI and SQLAlchemy-based backends. With MycroForge, you can effortlessly create and manage your backend projects through a powerful command line interface.
|
Welcome to **MycroForge** – an opinionated CLI tool designed to streamline the development of FastAPI and SQLAlchemy-based backends. With MycroForge, you can effortlessly create backend projects through a convenient command line interface.
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
@ -14,9 +14,3 @@ Welcome to **MycroForge** – an opinionated CLI tool designed to streamline the
|
|||||||
- **Migrations:** Handle database migrations seamlessly, allowing for smooth transitions and updates.
|
- **Migrations:** Handle database migrations seamlessly, allowing for smooth transitions and updates.
|
||||||
- **Routers:** Generate and manage routers to keep your application modular and organized.
|
- **Routers:** Generate and manage routers to keep your application modular and organized.
|
||||||
- **CRUD Functionality:** Automatically generate basic CRUD (Create, Read, Update, Delete) operations to accelerate your development process.
|
- **CRUD Functionality:** Automatically generate basic CRUD (Create, Read, Update, Delete) operations to accelerate your development process.
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
The primary goal of MycroForge is to reduce the boilerplate code and provide a unified interface for common development tasks. By leveraging this tool, developers can focus more on writing business logic rather than repetitive setup and configuration.
|
|
||||||
|
|
||||||
Get started with MycroForge and enhance your backend development efficiency today!
|
|
||||||
|
@ -28,7 +28,9 @@ locally.
|
|||||||
|
|
||||||
The first step is to start the database, you can do this by running the following command in a terminal.
|
The first step is to start the database, you can do this by running the following command in a terminal.
|
||||||
|
|
||||||
`m4g db run`
|
```bash
|
||||||
|
m4g db run
|
||||||
|
```
|
||||||
|
|
||||||
This command starts the services defined in the `db.docker-compose.yml` file.
|
This command starts the services defined in the `db.docker-compose.yml` file.
|
||||||
You can verify that the services are up by running `docker container ls`. If everything went well, then the previous
|
You can verify that the services are up by running `docker container ls`. If everything went well, then the previous
|
||||||
@ -47,9 +49,13 @@ When you're done developing, you can shut down the local database by running `m4
|
|||||||
Now that the database is running, we can start to create our entities. Run the commands below to create the `Todo` &
|
Now that the database is running, we can start to create our entities. Run the commands below to create the `Todo` &
|
||||||
`Tag` entities.
|
`Tag` entities.
|
||||||
|
|
||||||
`m4g db generate entity Tag --column "description:str:String(255)"`
|
```bash
|
||||||
|
m4g db generate entity Tag --column "description:str:String(255)"
|
||||||
|
```
|
||||||
|
|
||||||
`m4g db generate entity Todo --column "description:str:String(255)" -c "is_done:bool:Boolean()"`
|
```bash
|
||||||
|
m4g db generate entity Todo --column "description:str:String(255)" -c "is_done:bool:Boolean()"
|
||||||
|
```
|
||||||
|
|
||||||
After running these commands, you should find the generated entities in the `db/entities` folder of your project.
|
After running these commands, you should find the generated entities in the `db/entities` folder of your project.
|
||||||
You should also see that the `main.py` & `db/env.py` files have been modified to include the newly generated entity.
|
You should also see that the `main.py` & `db/env.py` files have been modified to include the newly generated entity.
|
||||||
@ -65,7 +71,9 @@ Creating a one-to-many relation would also make sense, but for the purpose of de
|
|||||||
the many-to-many relation, because this one is the most complex, since it requires an additional mapping to be included
|
the many-to-many relation, because this one is the most complex, since it requires an additional mapping to be included
|
||||||
in the database schema.
|
in the database schema.
|
||||||
|
|
||||||
`m4g db link many Todo --to-many Tag`
|
```bash
|
||||||
|
m4g db link many Todo --to-many Tag
|
||||||
|
```
|
||||||
|
|
||||||
After running this command you should see that both the `Todo` and `Tag` entities now have a new field referencing the
|
After running this command you should see that both the `Todo` and `Tag` entities now have a new field referencing the
|
||||||
a `List` containing instances of the other entity.
|
a `List` containing instances of the other entity.
|
||||||
@ -79,7 +87,9 @@ examine the command. The same is true for all the other commands as well.
|
|||||||
Now that we've generated our entities, it's time to generate a migration that will apply these changes in the database.
|
Now that we've generated our entities, it's time to generate a migration that will apply these changes in the database.
|
||||||
Generate the initial migration by running the following command.
|
Generate the initial migration by running the following command.
|
||||||
|
|
||||||
`m4g db generate migration initial_migration`
|
```bash
|
||||||
|
m4g db generate migration initial_migration
|
||||||
|
```
|
||||||
|
|
||||||
After running this command, you should see the new migration in the `db/version` directory.
|
After running this command, you should see the new migration in the `db/version` directory.
|
||||||
|
|
||||||
@ -88,7 +98,9 @@ After running this command, you should see the new migration in the `db/version`
|
|||||||
The last step for the database setup is to actually apply the new migration to the database. This can be done by running
|
The last step for the database setup is to actually apply the new migration to the database. This can be done by running
|
||||||
the following command.
|
the following command.
|
||||||
|
|
||||||
`m4g db migrate`
|
```bash
|
||||||
|
m4g db migrate
|
||||||
|
```
|
||||||
|
|
||||||
After running this command, you should now see a populated schema when visiting [PhpMyAdmin](http://localhost:5051).
|
After running this command, you should now see a populated schema when visiting [PhpMyAdmin](http://localhost:5051).
|
||||||
If for whatever reason you want to undo the last migration, you can simply run `m4g db rollback`.
|
If for whatever reason you want to undo the last migration, you can simply run `m4g db rollback`.
|
||||||
@ -102,9 +114,13 @@ Writing this code can be boring, since it's pretty much boilerplate with some cu
|
|||||||
Fortunately, MycroForge can generate a good chunk of this boring code on your behalf. Run the following commands to
|
Fortunately, MycroForge can generate a good chunk of this boring code on your behalf. Run the following commands to
|
||||||
generate CRUD functionality for the `Todo` & `Tag` classes.
|
generate CRUD functionality for the `Todo` & `Tag` classes.
|
||||||
|
|
||||||
`m4g api generate crud Tag`
|
```bash
|
||||||
|
m4g api generate crud Tag
|
||||||
|
```
|
||||||
|
|
||||||
`m4g api generate crud Todo`
|
```bash
|
||||||
|
m4g api generate crud Todo
|
||||||
|
```
|
||||||
|
|
||||||
After running this command you should see that the `api/requests`,`api/routers` & `api/services` now contain the
|
After running this command you should see that the `api/requests`,`api/routers` & `api/services` now contain the
|
||||||
relevant classes need to support the generated CRUD functionality. This could should be relatively straightforward, so
|
relevant classes need to support the generated CRUD functionality. This could should be relatively straightforward, so
|
||||||
@ -118,15 +134,22 @@ yet. We need to be able to specify which `Tags` to add to a `Todo` when creating
|
|||||||
To do this, we will allow for a `tag_ids` field in both the `CreateTodoRequest` & the `UpdateTodoRequest`.
|
To do this, we will allow for a `tag_ids` field in both the `CreateTodoRequest` & the `UpdateTodoRequest`.
|
||||||
This field will contain the ids of the `Tags` that are associated with a `Todo`.
|
This field will contain the ids of the `Tags` that are associated with a `Todo`.
|
||||||
|
|
||||||
Modify `CreateTodoRequest` in `api/requests/create_todo_request.py`, you might need to import `List` from `typing`.
|
Modify `CreateTodoRequest` in `api/requests/create_todo_request.py`.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Before
|
# Before
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class CreateTodoRequest(BaseModel):
|
class CreateTodoRequest(BaseModel):
|
||||||
description: str = None
|
description: str = None
|
||||||
is_done: bool = None
|
is_done: bool = None
|
||||||
|
|
||||||
# After
|
# After
|
||||||
|
from typing import List, Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class CreateTodoRequest(BaseModel):
|
class CreateTodoRequest(BaseModel):
|
||||||
description: str = None
|
description: str = None
|
||||||
is_done: bool = None
|
is_done: bool = None
|
||||||
@ -137,12 +160,19 @@ Modify `UpdateTodoRequest` in `api/requests/update_todo_request.py`, you might n
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
# Before
|
# Before
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class UpdateTodoRequest(BaseModel):
|
class UpdateTodoRequest(BaseModel):
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
is_done: Optional[bool] = None
|
is_done: Optional[bool] = None
|
||||||
tag_ids: Optional[List[int]] = []
|
|
||||||
|
|
||||||
# After
|
# After
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
class UpdateTodoRequest(BaseModel):
|
class UpdateTodoRequest(BaseModel):
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
is_done: Optional[bool] = None
|
is_done: Optional[bool] = None
|
||||||
@ -280,4 +310,9 @@ Modify `TodoService.update`
|
|||||||
|
|
||||||
## Test the API!
|
## Test the API!
|
||||||
|
|
||||||
|
Run the following command.
|
||||||
|
```bash
|
||||||
|
m4g api run
|
||||||
|
```
|
||||||
|
|
||||||
Go to http://localhost:5000/docs and test your Todo API!
|
Go to http://localhost:5000/docs and test your Todo API!
|
Loading…
Reference in New Issue
Block a user