From 96b74e60482006cb40912141abf4e2711175fcc8 Mon Sep 17 00:00:00 2001 From: mdnapo Date: Fri, 28 Jun 2024 12:48:43 +0200 Subject: [PATCH] Intermediate commit --- .../Commands/MycroForge.Generate.Service.cs | 2 +- MycroForge.Core/MycroForge.Core.csproj | 9 ++ MycroForge.sln | 6 + README.md | 1 + docs/README.md | 48 ++++---- docs/todo-example/README.md | 104 +++++++++++++++--- 6 files changed, 135 insertions(+), 35 deletions(-) create mode 100644 MycroForge.Core/MycroForge.Core.csproj diff --git a/MycroForge.CLI/Commands/MycroForge.Generate.Service.cs b/MycroForge.CLI/Commands/MycroForge.Generate.Service.cs index 0a75bf2..898ddcc 100644 --- a/MycroForge.CLI/Commands/MycroForge.Generate.Service.cs +++ b/MycroForge.CLI/Commands/MycroForge.Generate.Service.cs @@ -36,7 +36,7 @@ public partial class MycroForge "", "\tasync def list(self, value: str) -> List[Entity]:", "\t\tasync with async_session() as session:", - "\t\t\t# stmt = select(User).where(Entity.value == value)", + "\t\t\t# stmt = select(Entity).where(Entity.value == value)", "\t\t\t# results = (await session.scalars(stmt)).all()", "\t\t\t# return results", "\t\t\tpass", diff --git a/MycroForge.Core/MycroForge.Core.csproj b/MycroForge.Core/MycroForge.Core.csproj new file mode 100644 index 0000000..3a63532 --- /dev/null +++ b/MycroForge.Core/MycroForge.Core.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/MycroForge.sln b/MycroForge.sln index 8c590de..be1bffa 100644 --- a/MycroForge.sln +++ b/MycroForge.sln @@ -2,6 +2,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MycroForge.CLI", "MycroForge.CLI\MycroForge.CLI.csproj", "{27EFB015-AFC3-4046-8D9A-DD5C5D3B35E0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MycroForge.Core", "MycroForge.Core\MycroForge.Core.csproj", "{CFF8BD4E-520D-4319-BA80-3F49B5F493BA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +14,9 @@ Global {27EFB015-AFC3-4046-8D9A-DD5C5D3B35E0}.Debug|Any CPU.Build.0 = Debug|Any CPU {27EFB015-AFC3-4046-8D9A-DD5C5D3B35E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {27EFB015-AFC3-4046-8D9A-DD5C5D3B35E0}.Release|Any CPU.Build.0 = Release|Any CPU + {CFF8BD4E-520D-4319-BA80-3F49B5F493BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFF8BD4E-520D-4319-BA80-3F49B5F493BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFF8BD4E-520D-4319-BA80-3F49B5F493BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFF8BD4E-520D-4319-BA80-3F49B5F493BA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 3bbe090..529bb77 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ ### Dependencies +- git - Docker - bash (/bin/bash) - Python 3.10.2 (/usr/bin/python3) diff --git a/docs/README.md b/docs/README.md index df60f81..fffe18d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -61,29 +61,32 @@ Let's go through these one by one. ### .git -The project is automatically initialised with git by the `m4g init` command. -If you don't want to use git you can run `m4g init --without git`. +The `m4g init` command will initialize new projects with git by default. +If you don't want to use git you can pass the option `--without git` to `m4g init`. ### .venv -To promote isolation of Python dependencies, the project is initialized with a virtual environment by default. +To promote isolation of Python dependencies, new projects are initialized with a virtual environment by default. +TODO: This is a good section to introduce the `m4g hydrate` command. ### api/routers/hello.py -This file defines a basic example router, which is imported and mapped in `main.py`. +This file defines a basic example router, which is imported and mapped in `main.py`. This router is just an example and +can be removed or modified at you discretion. ### db/engine/async_session.py -This file defines the `async_session` function, which can be used to asynchronously connect to a database. +This file defines the `async_session` function, which can be used to open an asynchronous session to a database. ### db/entities/entity_base.py -This is the automatically generated base class that all database entities will inherit from. -SQLAlchemy requires a base entity class to properly function. +This file contains an automatically generated entity base class that derives from the DeclarativeBase. +All entities must inherit from this class, so that SQLAlchemy & alembic can track them. The entities directory is also +where all newly generated entities will be stored. -### db/entities/versions +### db/versions -This is where the generated database migrations will be kept. +This is where the generated database migrations will be stored. ### db/README @@ -92,10 +95,10 @@ This README file is automatically generated by the alembic init command. ### db/env.py This is the database environment file that is used by alembic to interact with the database. -If you take a close look at the imports, you'll see that the file has been modified to include the entity metadata, -i.e. `EntityBase.metadata` and you should also notice that the `DbSettings` class is used to get the connectionstring. -Any time you generate a new database entity or create a many-to-many relation between two entities, this file will also -be modified. +If you take a closer look at the imports, you'll see that the file has been modified to assign `EntityBase.metadata` to +a variable called `target_metadata`, this will allow alembic to track changes in your entities. You'll also find that +the `DbSettings` class is used to get the connectionstring. Any time you generate a new database entity, or create a +many-to-many relation between two entities, this file will also be modified to include the generated classes. ### db/script.py.mako @@ -103,15 +106,16 @@ This file is automatically generated by the alembic init command. ### db/settings.py -This file defines a class that is responsible for retrieving the connectionstring. +This file defines the `DbSettings` class, that is responsible for retrieving the database connectionstring. +You will probably want to modify this class to retrieve the connectionstring from a secret manager at some point. ### .gitignore -The default .gitignore file/ +The default .gitignore file that is generated by the `m4g init` command. Modify this file at your discretion. ### alembic.ini -The alembic ini file +This file is automatically generated by the alembic init command. ### db.docker-compose.yml @@ -119,13 +123,19 @@ A docker compose file for running a database locally. ### m4g.json -The m4g config file, this file contains some configs that are used by the CLI, -for example the ports to map to the API and database. +This file contains some configs that are used by the CLI, for example the ports to map to the API and database. ### main.py -The entrypoint for the application. +The entrypoint for the application. When generating entities, many-to-many relations or routers, this file will be +modified to include the generated files. ### requirements.txt The requirements file containing the Python dependencies. +TODO: introduce the `m4g install` & `m4g uninstall` commands. + + +## Scripting + +TODO: Dedicate a section to scripting \ No newline at end of file diff --git a/docs/todo-example/README.md b/docs/todo-example/README.md index 432b390..f94b80b 100644 --- a/docs/todo-example/README.md +++ b/docs/todo-example/README.md @@ -1,54 +1,123 @@ # Quick tutorial -Summary: We're gonna build a todo app to demonstrate how the `m4g` command line tool works.
-TODO: Supplement this section. +We're going to build a simple todo app, to demonstrate the capabilities of the MycroForge CLI. +After this tutorial, you should have a solid foundation to start exploring and using MycroForge to develop your +projects. + +## General notes + +The commands in this tutorial assume that you're running them from a MycroForge root directory. ## Initialize the project -Run `m4g init todo-example` to initialize a new project. - -Open the newly created project by running `code todo-example`, make sure you have `vscode` on you machine before running -this command. If you prefer using another editor, then manually open the generated folder. +Open a terminal and `cd` into the directory where your project should be created. +Run `m4g init todo-app` to initialize a new project and open the newly created project by running `code todo-app`. +Make sure you have `vscode` on you machine before running this command. If you prefer using another editor, then +manually open the generated `todo-app` folder. ## Setup the database -### Start the database +Our todo app needs to keep track of todos, so it needs a storage mechanism of some sorts. A database should be one +of the first things, if not THE first thing, that comes to mind. Luckily, MycroForge provides you with a locally hosted +database for you project out of the box. This setup, is powered by docker compose and can be examined by opening the +`db.docker-compose.yml` file in the project. Follow along to learn how to use the docker based database when developing +locally. + +### Run the database + +The first step is to start the database, you can do this by running the following command in a terminal. `m4g db run` +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 +command should output the service names defined in `db.docker-compose.yml`. + +If you go to [PhpMyAdmin (i.e. http://localhost:5051)](http://localhost:5051), you should now be able to login with the +following credentials. +- user: root +- pass: password + +When you're done developing, you can shut down the local database by running `m4g db stop` + + ### Create the entities -`m4g db generate entity Tag -c "description:str:String(255)"` +Now that the database is running, we can start to create our entities. Run the commands below to create the `Todo` & +`Tag` entities. -`m4g db generate entity Todo -c "description:str:String(255)" -c "is_done:bool:Boolean()"` +`m4g db generate entity Tag --column "description:str:String(255)"` -### Define a many to many relation between Todo & Tag +`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. +You should also see that the `main.py` & `db/env.py` files have been modified to include the newly generated entity. + +For more information about the `m4g db generate entity` command, you can run `m4g db generate entity -?`. + +### Define a many-to-many relation between Todo & Tag + +To allow for relations between `Todo` & `Tag`, we'll define a many-to-many relation between the two entities. +This relation makes sense, because a `Todo` can have many `Tags` and a `Tag` could belong to many `Todos`. +You can generate this relation by running the following command from you terminal. +Creating a one-to-many relation would also make sense, but for the purpose of demonstration we're going to demonstrate +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. `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 +a `List` containing instances of the other entity. + +For more information about the `m4g db link` command try running `m4g db link -?`. Note that you can do the same thing +for all sub commands, so if you want to know more about `m4g db link many` you can simply run `m4g db link many -?` to +examine the command. The same is true for all the other commands as well. + ### Generate the migration +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. + `m4g db generate migration initial_migration` +After running this command, you should see the new migration in the `db/version` directory. + ### Apply the migration +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. + `m4g db migrate` -If you inspect the database in [PhpMyAdmin](http://localhost:5051), you should now see a populated schema. - -To stop the database, you can run `m4g db stop` +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`. ## Setup the API ### Generate CRUD for Tag & Todo +Our API should provide use with basic endpoint to manage the `Todo` & `Tag` entities, i.e. CRUD functionality. +Writing this code can be boring, since it's pretty much boilerplate with some custom additions sprinkled here and there. +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. + `m4g api generate crud Tag` `m4g api generate crud Todo` +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 +we won't dive into it, but feel free to take a break and explore what the generated code actually does. Another thing to +note, is that the generated routers are also automatically included in `main.py`. + ### Modify the generated Todo request classes -Modify `CreateTodoRequest` in `api/requests/create_todo_request.py`, you might need to import `List` from `typing` +Since we have a many-to-many relationship between `Todo` & `Tag`, the generated CRUD functionality isn't quite ready +yet. We need to be able to specify which `Tags` to add to a `Todo` when creating or updating it. +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`. + +Modify `CreateTodoRequest` in `api/requests/create_todo_request.py`, you might need to import `List` from `typing`. ```python # Before @@ -63,7 +132,7 @@ class CreateTodoRequest(BaseModel): tag_ids: Optional[List[int]] = [] ``` -Modify `UpdateTodoRequest` in `api/requests/update_todo_request.py`, you might need to import `List` from `typing` +Modify `UpdateTodoRequest` in `api/requests/update_todo_request.py`, you might need to import `List` from `typing`. ```python # Before @@ -81,6 +150,8 @@ class UpdateTodoRequest(BaseModel): ### Modify generated TodoService +The `TodoService` will also need to be updated to accomodate the management of `tag_ids`. + Add the following imports in `api/services/todo_service.py`. ```python @@ -203,3 +274,6 @@ Modify `TodoService.update` await session.commit() return True ``` + +At this point, the app should be ready to test. +TODO: Elaborate! \ No newline at end of file