From 0c98f6f75d82a745cb1ffc9111b460c13592663e Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 20:48:34 -0400 Subject: [PATCH 1/3] chore: update version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d2d6a4b..f96e1f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "appwrite-lab" -version = "0.0.7" +version = "0.1.0" description = "Zero-click Appwrite test environments." readme = "README.md" requires-python = ">=3.11" From 06d82b50cf9d77eb8b7e2842778f784d02756520 Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 21:00:41 -0400 Subject: [PATCH 2/3] feat: make lab svc simpler --- appwrite_lab/_orchestrator.py | 12 ++++++++---- appwrite_lab/automations/__init__.py | 17 ++++++++++++++++- appwrite_lab/automations/models.py | 26 ++++++++++++++++++++++++++ appwrite_lab/cli/list_menu.py | 2 +- appwrite_lab/cli/new_menu.py | 16 ++++++++-------- appwrite_lab/labs.py | 7 +++++-- 6 files changed, 64 insertions(+), 16 deletions(-) diff --git a/appwrite_lab/_orchestrator.py b/appwrite_lab/_orchestrator.py index 35e8774..988a434 100644 --- a/appwrite_lab/_orchestrator.py +++ b/appwrite_lab/_orchestrator.py @@ -6,7 +6,11 @@ import tempfile from pathlib import Path -from appwrite_lab.automations.models import BaseVarModel, AppwriteAPIKeyCreation +from appwrite_lab.automations.models import ( + BaseVarModel, + AppwriteAPIKeyCreation, + AppwriteUserCreation, +) from ._state import State from dataclasses import dataclass from .models import Lab, Automation, Project @@ -162,7 +166,7 @@ def deploy_appwrite_lab( name: str, version: str, port: int, - meta: dict[str, str], + auth: AppwriteUserCreation | None = None, **kwargs: dict[str, str], ): """ @@ -172,11 +176,11 @@ def deploy_appwrite_lab( name: The name to give to the deployment/project. version: The version of the service to deploy. port: The port to use for the Appwrite service. Must not be in use by another service. - meta: Extra metadata to pass to the deployment. + auth: The authentication credentials. """ # sync - appwrite_config = meta.get("appwrite_config", {}) + appwrite_config = asdict(auth) if auth else {} pods_by_project = self.get_pods_by_project(name) if len(pods_by_project) > 0: diff --git a/appwrite_lab/automations/__init__.py b/appwrite_lab/automations/__init__.py index 241fcaf..203a954 100644 --- a/appwrite_lab/automations/__init__.py +++ b/appwrite_lab/automations/__init__.py @@ -1,4 +1,19 @@ from .common import AppwriteCLI from .utils import PlaywrightAutomationError +from .models import ( + AppwriteLabCreation, + AppwriteUserCreation, + AppwriteProjectCreation, + AppwriteSyncProject, + AppwriteAPIKeyCreation, +) -__all__ = ("AppwriteCLI", "PlaywrightAutomationError") +__all__ = ( + "AppwriteCLI", + "PlaywrightAutomationError", + "AppwriteLabCreation", + "AppwriteUserCreation", + "AppwriteProjectCreation", + "AppwriteSyncProject", + "AppwriteAPIKeyCreation", +) diff --git a/appwrite_lab/automations/models.py b/appwrite_lab/automations/models.py index 0acd7aa..3498ce3 100644 --- a/appwrite_lab/automations/models.py +++ b/appwrite_lab/automations/models.py @@ -69,6 +69,32 @@ class AppwriteAPIKeyCreation(BaseVarModel): key_expiry: Expiration +@dataclass +class AppwriteLabCreation(BaseVarModel): + admin_email: str | None = None + admin_password: str | None = None + project_id: str | None = None + project_name: str | None = None + url: str | None = None + + def generate(self): + """Generate random data for model""" + random_key = str(uuid.uuid4()) + password = random_key[:16] + last_six = random_key[-6:] + admin_password = password + admin_email = f"admin{last_six}@local.dev" + project_id = random_key + project_name = f"test-project-{last_six}" + + return AppwriteUserCreation( + admin_email=admin_email, + admin_password=admin_password, + project_id=project_id, + project_name=project_name, + ) + + @dataclass class AppwriteUserCreation(BaseVarModel): url: str diff --git a/appwrite_lab/cli/list_menu.py b/appwrite_lab/cli/list_menu.py index 11ed26f..e0a7dc8 100644 --- a/appwrite_lab/cli/list_menu.py +++ b/appwrite_lab/cli/list_menu.py @@ -8,7 +8,7 @@ labs = get_global_labs() -@list_menu.command(name="labs") +@list_menu.command(name="labs", help="List resources.") def get_labs(): """List all ephemeral Appwrite instances.""" headers, pods = labs.orchestrator.get_formatted_labs(collapsed=True) diff --git a/appwrite_lab/cli/new_menu.py b/appwrite_lab/cli/new_menu.py index 78dcf25..52047f5 100644 --- a/appwrite_lab/cli/new_menu.py +++ b/appwrite_lab/cli/new_menu.py @@ -1,7 +1,7 @@ import typer from appwrite_lab.utils import console from appwrite_lab import get_global_labs -from appwrite_lab.automations.models import Expiration +from appwrite_lab.automations.models import Expiration, AppwriteUserCreation new_menu = typer.Typer(name="new", help="Create a new resource.") @@ -59,18 +59,18 @@ def new_lab( with console.status( f"Creating lab '{name}'{extra_str}...", spinner="dots" ) as status: - creds = { - "admin_email": email, - "admin_password": password, - "project_id": project_id, - "project_name": project_name, - } + creds = AppwriteUserCreation( + admin_email=email, + admin_password=password, + project_id=project_id, + project_name=project_name, + ) labs.new( name=name, version=version, port=port, - meta={"appwrite_config": creds}, + auth=creds, just_deploy=just_deploy, ) status.update(f"Creating lab '{name}'... done") diff --git a/appwrite_lab/labs.py b/appwrite_lab/labs.py index 8ad26f9..418ac54 100644 --- a/appwrite_lab/labs.py +++ b/appwrite_lab/labs.py @@ -3,6 +3,7 @@ from ._orchestrator import ServiceOrchestrator, Response from .models import Automation, Lab from appwrite_lab.automations.models import ( + AppwriteLabCreation, AppwriteProjectCreation, AppwriteSyncProject, AppwriteAPIKeyCreation, @@ -25,7 +26,7 @@ def new( name: str, version: str, port: int, - meta: dict[str, str] = {}, + auth: AppwriteLabCreation | None = None, just_deploy: bool = False, ): """ @@ -35,9 +36,11 @@ def new( name: The name of the lab. version: The version of the lab. port: The port of the lab. + auth: The authentication credentials. + just_deploy: Deploy the lab without creating an API key or project. """ return self.orchestrator.deploy_appwrite_lab( - name, version, port, meta, just_deploy=just_deploy + name, version, port, auth, just_deploy=just_deploy ) def get_lab(self, name: str) -> Lab | None: From 77d719c30e0e1bb48b46d49395039c7853cac04d Mon Sep 17 00:00:00 2001 From: syntaxsdev Date: Sun, 27 Jul 2025 21:04:54 -0400 Subject: [PATCH 3/3] docs: update docs --- README.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 920b753..e6e8e42 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,14 @@ Allows you to spin up versioned Appwrite deployments for easy testing via CLI of ```sh pip install appwrite-lab ``` -## Appwrite Lab features (in progress) +## Appwrite Lab features - [x] Spin up ephemeral Appwrite instances with Docker/Podman - [x] Automatically grab API keys (for programmatic access) -- [x] Environment syncing +- [x] Environment syncing (with `appwrite.json`) - [x] Clean teardowns +- [x] Test suite -## Appwrite Lab in progress features -- [ ] Test suite +## Appwrite Lab (in progress) - [ ] Appwrite data population ## CLI Usage @@ -22,30 +22,89 @@ pip install appwrite-lab ```sh appwrite-lab --help ``` +or +```sh +awlab --help +``` + +### To get started spinning up a lab instance, use: -To get started spinning up a lab instance, use: +```sh +awlab new lab test --version 1.7.4 +``` +#### Example of additional args: +Additional arguments can be found here. +```sh +awlab new lab --help +``` ```sh -appwrite-lab new lab test-lab --version 1.7.4 +awlab new lab test --version 1.7.4 --port 8005 --email test@example.com --password xxxxxxx12 ``` -To teardown, +### To teardown ```sh -appwrite-lab stop test-lab +awlab stop test +``` +### Listing Appwrite Labs +```sh +awlab list labs ``` ### Sync an Appwrite lab from your prod lab schema Run in the same folder where your `appwrite.json` is located to sync `all` resources: ```sh -appwrite-lab sync test-lab +awlab sync test ``` or sync a specific resource: ```sh -appwrite-lab sync test-lab --resource functions +awlab sync test --resource functions ``` +## Python usage + +### Creating a lab +```py +from appwrite_lab import Labs +from appwrite_lab.models import AppwriteLabCreation + +labs = Labs() +lab_res = labs.new( + name="test", + version="1.7.4", + auth=AppwriteLabCreation( + admin_email="test@example.com", + admin_password="xxxxxxx12", + project_id=None, # for auto gen + project_name=None, # for auto gen + ) + port=8005 +) + +assert lab_res.data +``` + +#### Random generation that's compliant +```py +from appwrite_lab.models import AppwriteLabCreation + +auth = AppwriteLabCreation.generate() +``` + +### Syncing a lab +```py +from appwrite_lab import Labs +from appwrite_lab.automations.models import Expiration + +Labs().sync_with_appwrite_config( + name="test", + appwrite_json="appwrite.json", # if not directly in folder + sync_type="all", + expiration=Expiration.THIRTY_DAYS, +) +``` ## Known Troubleshooting ### Podman support and Selinux Since I am mimicking the `compose` file that Appwrite provides, it was not designed to work rootless, but I have adjusted to work also on Fedora. You will need to turn `selinux` off for now to use.