Skip to content

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's True which means this is a new object. Otherwise, it is an existing object. The commit is also boolean and usually it is True, which means the .save() will be executed immediately after object initialization and validation. The kwargs 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.