AMSDAL CLI¶
AMSDAL CLI is a powerful developer tool that enables you to create, update, migrate and serve an AMSDAL application quickly an efficiently. With the AMSDAL CLI, you can easily generate the skeletons of the main entities such as models, transactions, properties, and more. Additionally, it allows you to verify your code, apply code formatting, and build the end models.
One of the most useful features of the AMSDAL CLI is the ability to run a local test HTTP server. This server allows you to preview your changes before deploying them to UAT or production, ensuring that your code works as expected.
Installation¶
pip install amsdal[cli]
Create Application using CLI¶
With the AMSDAL CLI installed, you can create your first application.
Open a terminal and enter the amsdal --help
command.
Usage: amsdal [OPTIONS] COMMAND [ARGS]...
AMSDAL CLI - a tool that provides the ability to create a new app, generate models, transactions, build, serve, and other useful features for the efficient building of new apps using AMSDAL Framework.
Args: ctx (typer.Context): The Typer context object. version (bool, optional): If True, check and show versions of AMSDAL packages. Defaults to False.
Returns: None
╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --version -v Check and show versions of amsdal packages │
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to copy it or customize the installation. │
│ --help Show this message and exit. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ build, bld, b Build the app and generate the models and other files. │
│ ci-cd Generate CI/CD pipeline files for specified VCS. │
│ clean, cln Cleans project's folder by removing all generated files and optionally local database. │
│ cloud, cld Commands to interact with the Cloud Server. │
│ generate, gen, g Generates application's files such as models, properties, transactions, etc. │
│ migrate DEPRECATED: Commands to manage migrations. (deprecated) │
│ migrations, migs, mgs Commands to manage migrations. │
│ new, n Generates a new AMSDAL application. │
│ restore, rst Restores the models JSON schemas to /src directory from the database. │
│ serve, srv, s Starts a test FastAPI server based on your app's models. │
│ tests, test Commands to run tests. │
│ verify, vrf, v Verifies all application's files such as models, properties, transactions, etc. │
│ worker, w Commands to run worker. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Note
The list of supported commands will continue to be extended.
Note
Run amsdal --install-completion
to provide auto-completion functionality for all arguments and other supported options (it might require to re-open the terminal or even
re-login on your machine),
To create a new application, use the new
command.
For help with any command add the --help
arguement (e.g. amsdal new --help
)
Usage: amsdal new [OPTIONS] APP_NAME OUTPUT_PATH
Generates a new AMSDAL application.
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ * app_name TEXT The Application name. For example: MyApplication │
│ [default: None] │
│ [required] │
│ * output_path PATH Output path, where the app will be created. │
│ [default: None] │
│ [required] │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --help Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
Now create an application using amsdal new DataManager ~/
command.
The application is successfully created in $HOME/data_manager
Let’s see what happened. Navigate to the newly created directory:
cd ~/data_manager
And using ls -la
let’s see what we have there:
- 📁 src
- 📄 .amsdal-cli
- 📄 .gitignore
- 📄 config.yml
- 📄 README.md
- 📄 requirements.txt
Not too much yet... but a few things to note:
The src
folder is empty, but it will contain all our sources in the future (models, hooks, transactions, etc.).
The .amsdal-cli
is the app configuration for AMSDAL CLI, in JSON format:
{
"config_path": "./config.yml",
"http_port": 8080,
"check_model_exists": true,
"json_indent": 4
}
Where the config_path
it’s a path to the default AMSDAL config.yml
that was also generated automatically.
The http_port
is the port where will be hosted the test server.
The check_model_exists
stores a boolean value related to generate commands.
The json_indent
stores the indentation configuration (how many spaces) for formatting JSON files.
See below for more detailed descriptions.
Warning
You should not remove or rename the .amsdal-cli
file.
The README.md
contains this guide. You can change this file as you wish. Note do we need to update readme?
The requirements.txt
contains amsdal[cli]
with the latest version you have installed locally. It will be used
to install all dependencies required by your application during deploying to cloud.
Generate Models¶
Let's generate a model:
amsdal generate model Person --format json
Tip
Any other commands except the new
should be executed only from the folder of your application. So make sure
you are inside the ~/data_manager
folder.
Note
The --format
option is required and currently it supports only json
. It means the models will be generated in
JSON format. In the future we will also support python
formats.
Now if we check the directory of our application we will see that we have some new folders and files:
- 📁 src
- 📁 models
- 📁 person
- 📄 model.json
And the model.json
will have the following JSON content:
{
"title": "Person",
"type": "object",
"properties": {},
"required": [],
"indexed": []
}
The amsdal generate model
also provides ability to generate the properties. Let’s re-generate this model and add some
properties to our model:
amsdal generate model Person -attrs "first_name:string last_name:string email:string:required:index age:number:default=21" --format json
The file "~/data_manager/src/models/person/model.json" already exists. Would you like to overwrite it? [y/N]:
Type y
and press enter. Now, if will check our model cat src/models/person/model.json
it will contain the following:
{
"title": "Person",
"type": "object",
"properties": {
"first_name": {
"title": "first_name",
"type": "String"
},
"last_name": {
"title": "last_name",
"type": "String"
},
"email": {
"title": "email",
"type": "String"
},
"age": {
"title": "age",
"type": "number",
"default": 21.0
}
},
"required": [
"email"
],
"indexed": [
"email"
]
}
You can also change this models manually by editing the model.json
file directly to add new properties etc.
Let’s generate a new model using the following command:
amsdal generate model PersonProfile -attrs "bio:string phone:string person:belongs-to:Person:required friends:dict:string:Person family:has-many:Person" --format json
And let’s see out what we have in the new model with: cat src/models/person_profile/model.json
:
{
"title": "PersonProfile",
"type": "object",
"properties": {
"bio": {
"title": "bio",
"type": "string"
},
"phone": {
"title": "phone",
"type": "string"
},
"person": {
"title": "person",
"type": "Person"
},
"friends": {
"title": "friends",
"type": "dictionary",
"items": {
"key_type": "string",
"value_type": "Person"
}
},
"family": {
"title": "family",
"type": "array",
"items": {
"type": "Person"
}
}
},
"required": [
"person"
],
"indexed": []
}
This displays the functionality of the belongs-to
and has-many
properties are generated, in addition to a dictionary
property. In this case the dictionary
is one in which the key is a string and the value is a model.
You can create/modify this file manually as usually. The details about the format of JSON model files you can find in the AMSDAL documentation.
Supported types for properties¶
Currently, amsdal generate model
CLI command supports the following types of attributes:
- prop:string - It means the
prop
is a string. - prop:number - It means the
prop
is a number. - prop:boolean - It means the
prop
is a boolean. - prop:belongs-to:MyModel - It means the
prop
is referenced to the MyModel. - prop:has-many:MyModel - It means the
prop
is an array of MyModel. - prop:dict:string:MyModel - It means the
prop
is a dictionary where the key is a string and the value is MyModel.
Generate custom properties¶
Let’s now generate a custom property.
Run amsdal generate property --model Person full_name
command.
It will generate a new file src/models/person/properties/full_name.py
with following content:
@property
def full_name(self):
# TODO: implementation here
...
Now let’s add implementation for this property. Change it to the following:
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
Now the Person
model has the custom property full_name
.
Note: If the check_model_exists
in .amsdal-cli
configuration file is false
, we will be able to generate properties (and other entities) even if we don’t have the model JSON file yet.
With this configuration:
{
"config_path": "./config.yml",
"http_port": 8080,
"check_model_exists": false,
"json_indent": 4
}
We can generate a property for non-existing model:
amsdal generate property --model Address country_name
And successfully create ./src/models/address/properties/country_name.py
file.
Generate modifiers¶
Modifiers are predefined built-in methods that can be overriden.
Currently AMSDAL supports:
- constructor - ability to override the
__init__
method of the model - display_name - it's built-in property that is used to display the object on UI
- version_name - it's built-in property that is used to display the object version on UI
To generate the constructor modifier for PersonProfile model, run:
amsdal generate modifier --model PersonProfile constructor
It will generate the ./src/models/person_profile/modifiers/constructor.py
file with the following content:
def __init__(self, *args, **kwargs):
# TODO: implementation here
super().__init__(*args, **kwargs)
Now let’s implement some business logic, in this case, let's make sure that a peron's age cannot be less the 21:
def __init__(self, *args, **kwargs):
age = kwargs.get("age") or 0
if age < 21:
kwargs["age"] = 21
super().__init__(*args, **kwargs)
Note
In the most cases it's better to use the corresponding hooks - pre_init
and post_init
to add logic to the constructor (see the next topic
Generate Hooks).
Generate hooks¶
Hooks are actions that are triggered before or after certain events occur in the AMSDAL. They allow you to customize the behavior of your application by executing custom code in response to specific events, such as creating or updating a model instance. Hooks can be used to perform a variety of tasks, such as validating data, sending notifications, or updating related models.
Currently, AMSDAL support's the following hooks:
- pre_init(self, *, is_new_object: bool, kwargs: dict[str, Any]) - just before the model is initialized and before validation is run. The
is_new_object
is boolean and it is'sTrue
which means this is a new object. Otherwise, it is an existing object. Thecommit
is also boolean and usually it is True, which means the.save()
will be executed immediately after object initialization and validation. Thekwargs
holds all other attributes that are passed explicitly during the construction of the object instance. - post_init(self, *, is_new_object: bool, kwargs: dict[str, Any]) - just after model initialization and validation, although before it will be stored in DB.
- pre_create - just before the model will be marked as created (version_id is generated).
- post_create - just after the model will be marked as created.
- pre_update - just before the model will be marked as updated (a new version_id is generated).
- post_updated - just after the model will be marked as updated.
- pre_delete - just before the model will be marked as deleted (we set
metadata.is_deleted
and generate a new version_id). - post_delete - just after the model will be marked as deleted.
Let’s create a hook for the Person model:
amsdal generate hook --model Person post_create
Generating the ./src/models/person/hooks/post_create.py
:
def post_create(self):
# TODO: implementation here
...
Let’s implement the hook and add some business business logic:
def post_create(self):
from models.user.person_profile import PersonProfile
# Create automatically PersonProfile for each new Person
PersonProfile(
person=self,
bio="",
phone="",
friends={},
family=[],
).save()
Now each time a new person is created, a corresponding profile will be created for the person as well.
Note
from models.user.person_profile import PersonProfile
it is how we can import our models, although it is not yet generated (we have only the schema of the future model in JSON format).
Generate transactions¶
AMSDAL Transaction's are python functions that are decorated with @transaction
.
Transactions allows you to implement business logic that will be executed on the server and are the main entry point your business logic (similar to RPC).
Trnasactions can also be executed transaction via API.
To generate a transaction, run:
amsdal generate transaction MarkBestFriends
And the ./src/transactions/mark_best_friends.py
file is generated:
from amsdal_data.transactions import transaction
@transaction(name='MarkBestFriends')
async def mark_best_friends():
# TODO: implementation here
...
Now let's add the arguements and business logic to the Transaction that accepts two Person
objects and makes them best friends
from models.user.person import Person
from models.user.person_profile import PersonProfile
from amsdal_data.transactions import transaction
@transaction(name='MarkBestFriends')
def mark_best_friends(
first_person: Person,
second_person: Person,
):
first_profile = PersonProfile.objects.first(person=first_person).execute()
second_profile = PersonProfile.objects.first(person=second_person).execute()
if not first_profile or not second_profile:
return
first_profile.friends = {"best": second_person}
first_profile.save()
second_profile.friends = {"best": first_person}
second_profile.save()
Verify the application¶
AMSDAL CLI provides a command to verify your application. It will check the models definitions and everything related to them, including:
- custom properties
- modifiers
- hooks
Run amsdal verify
. This command only checks syntax errors in your Python and JSON files. It also can verify the building of end models by using the option:
amsdal verify --building
And you will see something like this:
Syntax checking... OK!
Pre-building app...
Building model "PersonProfile"... OK!
Building modifiers...
Processing ./data_manager/models/person_profile/modifiers/constructor.py...
OK!
Building model "Person"... OK!
Building properties...
Processing ./data_manager/models/person/properties/full_name.py...
OK!
Building hooks...
Processing ./data_manager/models/person/hooks/on_create.py...
OK!
Neither Python nor JSON model was not found in "./data_manager/models/address". Skipped!
Building transactions... OK!
OK!
Verifying models... OK!
As you see, it checks the syntax and also the building of your all models, properties, and other entities.
Note
The ./data_manager/models/address
directory was skipped due to missing the model itself.
Let’s adjust our Person model and add into, for example, indexes
undefined property is_active
:
{
"title": "Person",
"type": "object",
"properties": {
"first_name": {
"title": "first_name",
"type": "string"
},
"last_name": {
"title": "last_name",
"type": "string"
},
"email": {
"title": "email",
"type": "string"
},
"age": {
"title": "age",
"type": "number",
"default": 21.0
}
},
"required": [
"email"
],
"indexed": [
"email",
"is_active"
]
}
And try to run verify command with the building again:
amsdal verify --building
And you will see the corresponding error message:
Verifying models... Failed: Property is_active marked as indexed but wasn't found in the class schema's properties.
Fix it and add the property by changing the schema of the Person model to the following:
{
"title": "Person",
"type": "object",
"properties": {
"first_name": {
"title": "first_name",
"type": "string"
},
"last_name": {
"title": "last_name",
"type": "string"
},
"email": {
"title": "email",
"type": "string"
},
"age": {
"title": "age",
"type": "number",
"default": 21.0
},
"is_active": {
"title": "is_active",
"type": "boolean",
"default": true
}
},
"required": [
"email"
],
"indexed": [
"email",
"is_active"
]
}
Run the verify command again. You should see that verification was done successfully.
Run Local Server¶
To run a local server, from the root directory of our project, run:
amsdal serve
Note, the first run of this command will initiate the sign-up flow. You will be asked to
accept the end-user license agreement, to provide organization name and email address. Follow the instruction, and you
will get AMSDAL credentials (AMSDAL_ACCESS_KEY_ID
and AMSDAL_SECRET_ACCESS_KEY
) that you need to put
as environment variables. The easiest way to do that is just to create a .env
file in your root application directory
and put the printed credentials to it as it is. After that just re-run the amsdal serve
again.
And...
INFO: Started server process [149586]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://localhost:8080 (Press CTRL+C to quit)
This means everything was generated properly and the test server was started successfully.
Now you can navigate to http://localhost:8080/docs in your browser to view API documentation.
We are also able to use https://console.amsdal.com/ the builtin admin portal that links to our locally running test server and tries to create a Person record.
Write and run tests¶
AMSDAL CLI provides a command to generate tests for your application. It will generate the basic tests for your models, allowing you to extend them with your own test cases.
In order to generate tests for Person
model, run:
amsdal generate tests --model-name Person
This will generate the ./src/tests/unit/test_person.py
file with three basic tests for creation, update, and deletion
of the Person
object. Also, it will generate the ./src/tests/conftest.py
file with the basic configuration for the
tests if it doesn't exist yet. You can modify them like you want, and further generation commands will not overwrite them.
Then, in order to run the tests, execute:
amsdal tests run
That's it! You have successfully generated and run the tests for your application.
AMSDAL CLI uses pytest
under the hood, so you can also use pytest
commands directly to run the tests,
or use any plugins.