Changed orm to db and added path resolution for entity generation

This commit is contained in:
mdnapo 2024-05-17 17:55:05 +02:00
parent 15bf94e195
commit d59dedc106
21 changed files with 232 additions and 155 deletions

View File

@ -2,14 +2,14 @@
namespace MycroForge.CLI.CodeGen; namespace MycroForge.CLI.CodeGen;
public class OrmEnvInitializer : PythonSourceModifier public class DbEnvInitializer : PythonSourceModifier
{ {
private PythonParser.Import_fromContext? _alembicImport; private PythonParser.Import_fromContext? _alembicImport;
private PythonParser.AssignmentContext? _targetMetaDataAssignment; private PythonParser.AssignmentContext? _targetMetaDataAssignment;
private PythonParser.AssignmentContext? _urlAssignmentContext; private PythonParser.AssignmentContext? _urlAssignmentContext;
private PythonParser.AssignmentContext? _connectableAssignmentContext; private PythonParser.AssignmentContext? _connectableAssignmentContext;
public OrmEnvInitializer(string source) : base(source) public DbEnvInitializer(string source) : base(source)
{ {
} }
@ -32,17 +32,17 @@ public class OrmEnvInitializer : PythonSourceModifier
Rewrite(_alembicImport, [ Rewrite(_alembicImport, [
GetOriginalText(_alembicImport), GetOriginalText(_alembicImport),
"from orm.settings import OrmSettings", "from db.settings import DbSettings",
"from orm.entities.entity_base import EntityBase" "from db.entities.entity_base import EntityBase"
]); ]);
Rewrite(_targetMetaDataAssignment, "target_metadata = EntityBase.metadata"); Rewrite(_targetMetaDataAssignment, "target_metadata = EntityBase.metadata");
Rewrite(_urlAssignmentContext, "url = OrmSettings.get_connectionstring()"); Rewrite(_urlAssignmentContext, "url = DbSettings.get_connectionstring()");
const string indent = " "; const string indent = " ";
Rewrite(_connectableAssignmentContext, [ Rewrite(_connectableAssignmentContext, [
"url = OrmSettings.get_connectionstring()", "url = DbSettings.get_connectionstring()",
$"{indent}context.config.set_main_option('sqlalchemy.url', url)", $"{indent}context.config.set_main_option('sqlalchemy.url', url)",
$"{indent}{GetOriginalText(_connectableAssignmentContext)}" $"{indent}{GetOriginalText(_connectableAssignmentContext)}"
]); ]);

View File

@ -2,15 +2,15 @@
namespace MycroForge.CLI.CodeGen; namespace MycroForge.CLI.CodeGen;
public class OrmEnvUpdater : PythonSourceModifier public class DbEnvUpdater : PythonSourceModifier
{ {
private readonly string _moduleName; private readonly string _importPath;
private readonly string _className; private readonly string _className;
private PythonParser.Import_fromContext? _lastImport; private PythonParser.Import_fromContext? _lastImport;
public OrmEnvUpdater(string source, string moduleName, string className) : base(source) public DbEnvUpdater(string source, string importPath, string className) : base(source)
{ {
_moduleName = moduleName; _importPath = importPath;
_className = className; _className = className;
} }
@ -22,11 +22,11 @@ public class OrmEnvUpdater : PythonSourceModifier
if (_lastImport is null) if (_lastImport is null)
throw new Exception("Could not find import insertion point."); throw new Exception("Could not find import insertion point.");
var text = GetOriginalText(_lastImport); var lastImportText = GetOriginalText(_lastImport);
Rewrite(_lastImport, [ Rewrite(_lastImport, [
text, lastImportText,
$"from orm.entities.{_moduleName} import {_className}" $"from db.entities.{_importPath} import {_className}"
]); ]);
return Rewriter.GetText(); return Rewriter.GetText();
@ -36,7 +36,7 @@ public class OrmEnvUpdater : PythonSourceModifier
{ {
var text = GetOriginalText(context); var text = GetOriginalText(context);
if (text.StartsWith("from orm.entities")) if (text.StartsWith("from db.entities"))
_lastImport = context; _lastImport = context;
return base.VisitImport_from(context); return base.VisitImport_from(context);

View File

@ -77,9 +77,9 @@ public partial class EntityLinker
// var right = await LoadEntity(_right); // var right = await LoadEntity(_right);
// //
// left.AppendImportIfNotExists( // left.AppendImportIfNotExists(
// $"orm.entities.{right.FieldName}", // $"db.entities.{right.FieldName}",
// $"{right.ClassName}", // $"{right.ClassName}",
// $"from orm.entities.{right.FieldName} import {right.ClassName}" // $"from db.entities.{right.FieldName} import {right.ClassName}"
// ); // );
// left.AppendImportIfNotExists( // left.AppendImportIfNotExists(
// "typing", // "typing",
@ -92,9 +92,9 @@ public partial class EntityLinker
// ]); // ]);
// //
// right.AppendImportIfNotExists( // right.AppendImportIfNotExists(
// $"orm.entities.{left.FieldName}", // $"db.entities.{left.FieldName}",
// $"{left.ClassName}", // $"{left.ClassName}",
// $"from orm.entities.{left.FieldName} import {left.ClassName}" // $"from db.entities.{left.FieldName} import {left.ClassName}"
// ); // );
// left.AppendImportIfNotExists( // left.AppendImportIfNotExists(
// "typing", // "typing",
@ -118,5 +118,5 @@ public partial class EntityLinker
return entity; return entity;
} }
private string GetEntityPath(string name) => $"orm/entities/{name.Underscore().ToLower()}.py"; private string GetEntityPath(string name) => $"{Features.Db.FeatureName}/entities/{name.Underscore().ToLower()}.py";
} }

View File

@ -40,14 +40,14 @@ public partial class MycroForge
private async Task ExecuteAsync(string name) private async Task ExecuteAsync(string name)
{ {
_context.AssertDirectoryExists("api/routers"); _context.AssertDirectoryExists($"{Features.Api.FeatureName}/routers");
var moduleName = name.Underscore(); var moduleName = name.Underscore();
await _context.CreateFile($"api/routers/{moduleName}.py", Template); await _context.CreateFile($"{Features.Api.FeatureName}/routers/{moduleName}.py", Template);
var main = await _context.ReadFile("main.py"); var main = await _context.ReadFile("main.py");
main += string.Join('\n', main += string.Join('\n',
$"\n\nfrom api.routers import {moduleName}", $"\n\nfrom {Features.Api.FeatureName}.routers import {moduleName}",
$"app.include_router(prefix=\"/{name.Kebaberize()}\", router={moduleName}.router)" $"app.include_router(prefix=\"/{name.Kebaberize()}\", router={moduleName}.router)"
); );
await _context.WriteFile("main.py", main); await _context.WriteFile("main.py", main);

View File

@ -0,0 +1,131 @@
using System.CommandLine;
using Humanizer;
using MycroForge.CLI.CodeGen;
using MycroForge.CLI.Commands.Interfaces;
namespace MycroForge.CLI.Commands;
public partial class MycroForge
{
public partial class Db
{
public partial class Generate
{
public class Entity : Command, ISubCommandOf<Generate>
{
private record ColumnDefinition(string Name, string NativeType, string OrmType);
private static readonly string[] Template =
[
"from sqlalchemy import %type_imports%",
"from sqlalchemy.orm import Mapped, mapped_column",
"from db.entities.entity_base import EntityBase",
"",
"class %class_name%(EntityBase):",
"\t__tablename__ = \"%table_name%\"",
"\tid: Mapped[int] = mapped_column(primary_key=True)",
"\t%column_definitions%",
"",
"\tdef __repr__(self) -> str:",
"\t\treturn f\"%class_name%(id={self.id!r})\""
];
private static readonly Argument<string> NameArgument =
new(name: "name", description: string.Join('\n', [
"The name of the database entity",
"",
"Supported formats:",
"\tEntity",
"\tpath/relative/to/entities:Entity",
]));
private static readonly Option<IEnumerable<string>> ColumnsOption =
new(aliases: ["--column", "-c"], description: string.Join('\n', [
"Specify the fields to add.",
"",
"Format:",
"\t<name>:<native_type>:<orm_type>",
"\t",
"\t<name> = Name of the column",
"\t<native_type> = The native Python type",
"\t<orm_type> = The SQLAlchemy type",
"",
"Example:",
"\tfirst_name:str:String(255)",
])) { AllowMultipleArgumentsPerToken = true };
private readonly ProjectContext _context;
public Entity(ProjectContext context) : base("entity", "Generate and database entity")
{
_context = context;
AddAlias("e");
AddArgument(NameArgument);
AddOption(ColumnsOption);
this.SetHandler(ExecuteAsync, NameArgument, ColumnsOption);
}
private async Task ExecuteAsync(string name, IEnumerable<string> columns)
{
_context.AssertDirectoryExists(Features.Db.FeatureName);
var path = string.Empty;
if (name.Split(':').Select(s => s.Trim()).ToArray() is { Length: 2 } fullName)
{
path = fullName[0];
name = fullName[1];
}
var _columns = GetColumnDefinitions(columns.ToArray());
var className = name.Underscore().Pascalize();
var typeImports = string.Join(", ", _columns.Select(c => c.OrmType.Split('(').First()).Distinct());
var columnDefinitions = string.Join("\n\t", _columns.Select(ColumnToString));
var code = string.Join('\n', Template);
code = code.Replace("%type_imports%", typeImports);
code = code.Replace("%class_name%", className);
code = code.Replace("%table_name%", name.Underscore().ToLower().Pluralize());
code = code.Replace("%column_definitions%", columnDefinitions);
var folderPath = Path.Join($"{Features.Db.FeatureName}/entities", path);
var fileName = $"{name.ToLower()}.py";
var filePath = Path.Join(folderPath, fileName);
await _context.CreateFile(filePath, code);
var importPathParts = new[] { path, fileName.Replace(".py", "") }
.Where(s => !string.IsNullOrEmpty(s));
var importPath = string.Join('.', importPathParts)
.Replace('/', '.')
.Replace('\\', '.')
.Underscore()
.ToLower();
var env = await _context.ReadFile($"{Features.Db.FeatureName}/env.py");
env = new DbEnvUpdater(env, importPath, className).Rewrite();
await _context.WriteFile($"{Features.Db.FeatureName}/env.py", env);
}
private List<ColumnDefinition> GetColumnDefinitions(string[] fields)
{
var definitions = new List<ColumnDefinition>();
foreach (var field in fields)
{
if (field.Split(':') is not { Length: 3 } definition)
throw new Exception($"Field definition {field} is invalid.");
definitions.Add(new ColumnDefinition(definition[0], definition[1], definition[2]));
}
return definitions;
}
private static string ColumnToString(ColumnDefinition definition)
{
return $"{definition.Name}: Mapped[{definition.NativeType}] = mapped_column({definition.OrmType})";
}
}
}
}
}

View File

@ -5,7 +5,7 @@ namespace MycroForge.CLI.Commands;
public partial class MycroForge public partial class MycroForge
{ {
public partial class Orm public partial class Db
{ {
public partial class Generate public partial class Generate
{ {
@ -26,7 +26,7 @@ public partial class MycroForge
private async Task ExecuteAsync(string name) private async Task ExecuteAsync(string name)
{ {
_context.AssertDirectoryExists("orm/versions"); _context.AssertDirectoryExists($"{Features.Db.FeatureName}/versions");
await _context.Bash( await _context.Bash(
"source .venv/bin/activate", "source .venv/bin/activate",

View File

@ -5,12 +5,12 @@ namespace MycroForge.CLI.Commands;
public partial class MycroForge public partial class MycroForge
{ {
public partial class Orm public partial class Db
{ {
public partial class Generate : Command, ISubCommandOf<Orm> public partial class Generate : Command, ISubCommandOf<Db>
{ {
public Generate(IEnumerable<ISubCommandOf<Generate>> subCommands) : public Generate(IEnumerable<ISubCommandOf<Generate>> subCommands) :
base("generate", "Generate an ORM item") base("generate", "Generate a database item")
{ {
AddAlias("g"); AddAlias("g");
foreach (var subCommandOf in subCommands.Cast<Command>()) foreach (var subCommandOf in subCommands.Cast<Command>())

View File

@ -6,7 +6,7 @@ namespace MycroForge.CLI.Commands;
public partial class MycroForge public partial class MycroForge
{ {
public partial class Orm public partial class Db
{ {
public partial class Link public partial class Link
{ {
@ -39,20 +39,20 @@ public partial class MycroForge
throw new Exception("Cannot set both --to-one and --to-many option."); throw new Exception("Cannot set both --to-one and --to-many option.");
if (toOneOption is not null) if (toOneOption is not null)
await ManyToOne(left, toOneOption); await ToOne(left, toOneOption);
else if (toManyOption is not null) else if (toManyOption is not null)
await ManyToMany(left, toManyOption); await ToMany(left, toManyOption);
else throw new Exception("Set --to-one or --to-many option."); else throw new Exception("Set --to-one or --to-many option.");
} }
private async Task ManyToOne(string left, string toOneOption) private async Task ToOne(string left, string toOneOption)
{ {
await new EntityLinker(_context, left, toOneOption).ManyToOne(); await new EntityLinker(_context, left, toOneOption).ManyToOne();
} }
private async Task ManyToMany(string left, string toManyOption) private async Task ToMany(string left, string toManyOption)
{ {
await new EntityLinker(_context, left, toManyOption).ManyToMany(); await new EntityLinker(_context, left, toManyOption).ManyToMany();
} }

View File

@ -6,7 +6,7 @@ namespace MycroForge.CLI.Commands;
public partial class MycroForge public partial class MycroForge
{ {
public partial class Orm public partial class Db
{ {
public partial class Link public partial class Link
{ {
@ -39,20 +39,20 @@ public partial class MycroForge
throw new Exception("Cannot set both --to-one and --to-many option."); throw new Exception("Cannot set both --to-one and --to-many option.");
if (toOneOption is not null) if (toOneOption is not null)
await OneToOne(left, toOneOption); await ToOne(left, toOneOption);
else if (toManyOption is not null) else if (toManyOption is not null)
await OneToMany(left, toManyOption); await ToMany(left, toManyOption);
else throw new Exception("Set --to-one or --to-many option."); else throw new Exception("Set --to-one or --to-many option.");
} }
private async Task OneToOne(string left, string toOneOption) private async Task ToOne(string left, string toOneOption)
{ {
await new EntityLinker(_context, left, toOneOption).OneToOne(); await new EntityLinker(_context, left, toOneOption).OneToOne();
} }
private async Task OneToMany(string left, string toManyOption) private async Task ToMany(string left, string toManyOption)
{ {
await new EntityLinker(_context, left, toManyOption).OneToMany(); await new EntityLinker(_context, left, toManyOption).OneToMany();
} }

View File

@ -5,9 +5,9 @@ namespace MycroForge.CLI.Commands;
public partial class MycroForge public partial class MycroForge
{ {
public partial class Orm public partial class Db
{ {
public partial class Link : Command, ISubCommandOf<Orm> public partial class Link : Command, ISubCommandOf<Db>
{ {
public Link(IEnumerable<ISubCommandOf<Link>> commands) : public Link(IEnumerable<ISubCommandOf<Link>> commands) :
base("link", "Define relationships between entities") base("link", "Define relationships between entities")

View File

@ -5,9 +5,9 @@ namespace MycroForge.CLI.Commands;
public partial class MycroForge public partial class MycroForge
{ {
public partial class Orm public partial class Db
{ {
public class Migrate : Command, ISubCommandOf<Orm> public class Migrate : Command, ISubCommandOf<Db>
{ {
private readonly ProjectContext _context; private readonly ProjectContext _context;

View File

@ -5,9 +5,9 @@ namespace MycroForge.CLI.Commands;
public partial class MycroForge public partial class MycroForge
{ {
public partial class Orm public partial class Db
{ {
public class Rollback : Command, ISubCommandOf<Orm> public class Rollback : Command, ISubCommandOf<Db>
{ {
private readonly ProjectContext _context; private readonly ProjectContext _context;

View File

@ -0,0 +1,17 @@
using System.CommandLine;
using MycroForge.CLI.Commands.Interfaces;
namespace MycroForge.CLI.Commands;
public partial class MycroForge
{
public partial class Db : Command, ISubCommandOf<MycroForge>
{
public Db(IEnumerable<ISubCommandOf<Db>> commands)
: base("db", "Database related commands")
{
foreach (var command in commands.Cast<Command>())
AddCommand(command);
}
}
}

View File

@ -30,9 +30,9 @@ public partial class MycroForge
private static readonly string[] WithSessionTemplate = private static readonly string[] WithSessionTemplate =
[ [
"from orm.engine.async_session import async_session", "from db.engine.async_session import async_session",
"from sqlalchemy import select", "from sqlalchemy import select",
"# from orm.entities.some_entity import SomeEntity", "# from db.entities.some_entity import SomeEntity",
"", "",
"class %class_name%Service:", "class %class_name%Service:",
"", "",

View File

@ -10,7 +10,7 @@ public partial class MycroForge
private readonly ProjectContext _context; private readonly ProjectContext _context;
public Hydrate(ProjectContext context) public Hydrate(ProjectContext context)
: base("hydrate", "Create a new venv and install dependencies listed in requirements.txt") : base("hydrate", "Initialize venv and install dependencies from requirements.txt")
{ {
_context = context; _context = context;
this.SetHandler(ExecuteAsync); this.SetHandler(ExecuteAsync);

View File

@ -59,7 +59,7 @@ public partial class MycroForge
[ [
Features.Git.FeatureName, Features.Git.FeatureName,
Features.Api.FeatureName, Features.Api.FeatureName,
Features.Orm.FeatureName Features.Db.FeatureName
]; ];
private static readonly Argument<string> NameArgument = private static readonly Argument<string> NameArgument =
@ -111,8 +111,17 @@ public partial class MycroForge
// Initialize default features // Initialize default features
foreach (var feature in _features.Where(f => DefaultFeatures.Contains(f.Name))) foreach (var feature in _features.Where(f => DefaultFeatures.Contains(f.Name)))
{
if (!withoutList.Contains(feature.Name)) if (!withoutList.Contains(feature.Name))
{
Console.WriteLine($"Initializing feature {feature.Name}");
await feature.ExecuteAsync(_context); await feature.ExecuteAsync(_context);
}
else
{
Console.WriteLine($"Skipping feature {feature.Name}");
}
}
Console.WriteLine($"Directory {projectRoot} was successfully initialized"); Console.WriteLine($"Directory {projectRoot} was successfully initialized");
} }

View File

@ -1,63 +0,0 @@
using System.CommandLine;
using Humanizer;
using MycroForge.CLI.CodeGen;
using MycroForge.CLI.Commands.Interfaces;
namespace MycroForge.CLI.Commands;
public partial class MycroForge
{
public partial class Orm
{
public partial class Generate
{
public class Entity : Command, ISubCommandOf<Generate>
{
private static readonly string[] Template =
[
"from sqlalchemy import String",
"from sqlalchemy.orm import Mapped, mapped_column",
"from orm.entities.entity_base import EntityBase",
"",
"class %class_name%(EntityBase):",
"\t__tablename__ = \"%table_name%\"",
"\tid: Mapped[int] = mapped_column(primary_key=True)",
"\tvalue: Mapped[str] = mapped_column(String(255))",
"",
"\tdef __repr__(self) -> str:",
"\t\treturn f\"%class_name%(id={self.id!r}, value={self.value!r})\""
];
private static readonly Argument<string> NameArgument =
new(name: "name", description: "The name of the orm entity");
private readonly ProjectContext _context;
public Entity(ProjectContext context) : base("entity", "Generate and orm entity")
{
_context = context;
AddAlias("e");
AddArgument(NameArgument);
this.SetHandler(ExecuteAsync, NameArgument);
}
private async Task ExecuteAsync(string name)
{
_context.AssertDirectoryExists("orm");
var className = name.Underscore().Pascalize();
var moduleName = name.Underscore();
var code = string.Join('\n', Template);
code = code.Replace("%class_name%", className);
code = code.Replace("%table_name%", name.Underscore().ToLower().Pluralize());
await _context.CreateFile($"orm/entities/{moduleName}.py", code);
var env = await _context.ReadFile("orm/env.py");
env = new OrmEnvUpdater(env, moduleName, className).Rewrite();
await _context.WriteFile("orm/env.py", env);
}
}
}
}
}

View File

@ -1,17 +0,0 @@
using System.CommandLine;
using MycroForge.CLI.Commands.Interfaces;
namespace MycroForge.CLI.Commands;
public partial class MycroForge
{
public partial class Orm : Command, ISubCommandOf<MycroForge>
{
public Orm(IEnumerable<ISubCommandOf<Orm>> subCommands)
: base("orm", "ORM related commands")
{
foreach (var subCommandOf in subCommands.Cast<Command>())
AddCommand(subCommandOf);
}
}
}

View File

@ -11,7 +11,7 @@ public static class ServiceCollectionExtensions
services.AddScoped<ProjectContext>(); services.AddScoped<ProjectContext>();
services.AddScoped<IFeature, Git>(); services.AddScoped<IFeature, Git>();
services.AddScoped<IFeature, Api>(); services.AddScoped<IFeature, Api>();
services.AddScoped<IFeature, Orm>(); services.AddScoped<IFeature, Db>();
return services; return services;
} }
@ -37,15 +37,15 @@ public static class ServiceCollectionExtensions
services.AddScoped<ISubCommandOf<Commands.MycroForge.Api.Generate>, Commands.MycroForge.Api.Generate.Router>(); services.AddScoped<ISubCommandOf<Commands.MycroForge.Api.Generate>, Commands.MycroForge.Api.Generate.Router>();
// Register "m4g orm" // Register "m4g orm"
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Orm>(); services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Db>();
services.AddScoped<ISubCommandOf<Commands.MycroForge.Orm>, Commands.MycroForge.Orm.Migrate>(); services.AddScoped<ISubCommandOf<Commands.MycroForge.Db>, Commands.MycroForge.Db.Migrate>();
services.AddScoped<ISubCommandOf<Commands.MycroForge.Orm>, Commands.MycroForge.Orm.Rollback>(); services.AddScoped<ISubCommandOf<Commands.MycroForge.Db>, Commands.MycroForge.Db.Rollback>();
services.AddScoped<ISubCommandOf<Commands.MycroForge.Orm>, Commands.MycroForge.Orm.Generate>(); services.AddScoped<ISubCommandOf<Commands.MycroForge.Db>, Commands.MycroForge.Db.Generate>();
services.AddScoped<ISubCommandOf<Commands.MycroForge.Orm.Generate>, Commands.MycroForge.Orm.Generate.Entity>(); services.AddScoped<ISubCommandOf<Commands.MycroForge.Db.Generate>, Commands.MycroForge.Db.Generate.Entity>();
services.AddScoped<ISubCommandOf<Commands.MycroForge.Orm.Generate>, Commands.MycroForge.Orm.Generate.Migration>(); services.AddScoped<ISubCommandOf<Commands.MycroForge.Db.Generate>, Commands.MycroForge.Db.Generate.Migration>();
services.AddScoped<ISubCommandOf<Commands.MycroForge.Orm>, Commands.MycroForge.Orm.Link>(); services.AddScoped<ISubCommandOf<Commands.MycroForge.Db>, Commands.MycroForge.Db.Link>();
services.AddScoped<ISubCommandOf<Commands.MycroForge.Orm.Link>, Commands.MycroForge.Orm.Link.One>(); services.AddScoped<ISubCommandOf<Commands.MycroForge.Db.Link>, Commands.MycroForge.Db.Link.One>();
services.AddScoped<ISubCommandOf<Commands.MycroForge.Orm.Link>, Commands.MycroForge.Orm.Link.Many>(); services.AddScoped<ISubCommandOf<Commands.MycroForge.Db.Link>, Commands.MycroForge.Db.Link.Many>();
// Register "m4g script" // Register "m4g script"
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Script>(); services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Script>();

View File

@ -22,7 +22,7 @@ public sealed class Api : IFeature
"from fastapi import FastAPI", "from fastapi import FastAPI",
"app = FastAPI()", "app = FastAPI()",
"", "",
"from api.routers import hello", $"from {FeatureName}.routers import hello",
"app.include_router(prefix=\"/hello\", router=hello.router)" "app.include_router(prefix=\"/hello\", router=hello.router)"
]; ];
@ -42,7 +42,7 @@ public sealed class Api : IFeature
"python3 -m pip freeze > requirements.txt" "python3 -m pip freeze > requirements.txt"
); );
await context.CreateFile("api/routers/hello.py", HelloRouter); await context.CreateFile($"{FeatureName}/routers/hello.py", HelloRouter);
var main = await context.ReadFile("main.py"); var main = await context.ReadFile("main.py");
main = string.Join('\n', Main) + main; main = string.Join('\n', Main) + main;

View File

@ -2,7 +2,7 @@
namespace MycroForge.CLI.Features; namespace MycroForge.CLI.Features;
public sealed class Orm : IFeature public sealed class Db : IFeature
{ {
#region Defaults #region Defaults
@ -10,7 +10,7 @@ public sealed class Orm : IFeature
[ [
"connectionstring = \"mysql+asyncmy://root:root@localhost:3306/example\"", "connectionstring = \"mysql+asyncmy://root:root@localhost:3306/example\"",
"", "",
"class OrmSettings:", "class DbSettings:",
"\tdef get_connectionstring() -> str:", "\tdef get_connectionstring() -> str:",
"\t\treturn connectionstring" "\t\treturn connectionstring"
]; ];
@ -18,9 +18,9 @@ public sealed class Orm : IFeature
private static readonly string[] AsyncSession = private static readonly string[] AsyncSession =
[ [
"from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine, AsyncSession", "from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine, AsyncSession",
"from orm.settings import OrmSettings", "from db.settings import DbSettings",
"", "",
"async_engine: AsyncEngine = create_async_engine(OrmSettings.get_connectionstring())", "async_engine: AsyncEngine = create_async_engine(DbSettings.get_connectionstring())",
"", "",
"def async_session() -> AsyncSession:", "def async_session() -> AsyncSession:",
"\treturn AsyncSession(async_engine, expire_on_commit=False)" "\treturn AsyncSession(async_engine, expire_on_commit=False)"
@ -38,7 +38,7 @@ public sealed class Orm : IFeature
[ [
"from sqlalchemy import String", "from sqlalchemy import String",
"from sqlalchemy.orm import Mapped, mapped_column", "from sqlalchemy.orm import Mapped, mapped_column",
"from orm.entities.entity_base import EntityBase", "from db.entities.entity_base import EntityBase",
"", "",
"class User(EntityBase):", "class User(EntityBase):",
"\t__tablename__ = \"users\"", "\t__tablename__ = \"users\"",
@ -52,7 +52,7 @@ public sealed class Orm : IFeature
#endregion #endregion
public const string FeatureName = "orm"; public const string FeatureName = "db";
public string Name => FeatureName; public string Name => FeatureName;
@ -64,21 +64,21 @@ public sealed class Orm : IFeature
"source .venv/bin/activate", "source .venv/bin/activate",
"python3 -m pip install asyncmy sqlalchemy alembic", "python3 -m pip install asyncmy sqlalchemy alembic",
"python3 -m pip freeze > requirements.txt", "python3 -m pip freeze > requirements.txt",
"alembic init -t async orm" $"alembic init -t async {FeatureName}"
); );
var env = await context.ReadFile("orm/env.py"); var env = await context.ReadFile($"{FeatureName}/env.py");
env = new OrmEnvInitializer(env).Rewrite(); env = new DbEnvInitializer(env).Rewrite();
env = new OrmEnvUpdater(env, "user", "User").Rewrite(); env = new DbEnvUpdater(env, "user", "User").Rewrite();
await context.WriteFile("orm/env.py", env); await context.WriteFile($"{FeatureName}/env.py", env);
await context.CreateFile("orm/settings.py", Settings); await context.CreateFile($"{FeatureName}/settings.py", Settings);
await context.CreateFile("orm/engine/async_session.py", AsyncSession); await context.CreateFile($"{FeatureName}/engine/async_session.py", AsyncSession);
await context.CreateFile("orm/entities/entity_base.py", EntityBase); await context.CreateFile($"{FeatureName}/entities/entity_base.py", EntityBase);
await context.CreateFile("orm/entities/user.py", User); await context.CreateFile($"{FeatureName}/entities/user.py", User);
await context.SaveConfig(config); await context.SaveConfig(config);
} }