diff --git a/docs_src/src/pages/documentation/en/api_reference/index.mdx b/docs_src/src/pages/documentation/en/api_reference/index.mdx index 637c5b9ad..01ac7bbb0 100644 --- a/docs_src/src/pages/documentation/en/api_reference/index.mdx +++ b/docs_src/src/pages/documentation/en/api_reference/index.mdx @@ -28,6 +28,9 @@ While there are other more extensions of Robyn like ```bash {{ title: 'pip' }} pip install "robyn[templating]" + pip install "robyn[pydantic]" + pip install "robyn[robyn-config]" + pip install "robyn[all]" ``` ```bash {{ title: 'conda' }} diff --git a/docs_src/src/pages/documentation/en/plugins.mdx b/docs_src/src/pages/documentation/en/plugins.mdx index 512336342..24cbc9af5 100644 --- a/docs_src/src/pages/documentation/en/plugins.mdx +++ b/docs_src/src/pages/documentation/en/plugins.mdx @@ -34,6 +34,33 @@ In this example, robyn-rate-limits is used to enforce a rate limit of 3 requests The plugin integrates seamlessly with the Robyn web framework, enhancing the security and stability of your application by preventing excessive requests from a single client. +### Robyn Config + +- Description: A powerful CLI tool to bootstrap and manage production-ready Robyn applications with best practices built-in. It scaffolds projects using DDD or MVC architecture with SQLAlchemy or Tortoise ORM, adds new entities/routes/repositories, sets up admin panels, and configures monitoring pipelines. +- GitHub repository: [robyn-config](https://github.com/Lehsqa/robyn-config) +- Installation: + `python -m pip install robyn-config` + + Or as a Robyn optional dependency: + `python -m pip install robyn[robyn-config]` +- Usage: + +```bash +# Create a new Robyn project with DDD design pattern and SQLAlchemy +robyn-config create my_project ./my_project --design ddd --orm sqlalchemy + +# Add new business logic entity to an existing project +robyn-config add users ./my_project + +# Add admin panel scaffolding +robyn-config adminpanel ./my_project + +# Add monitoring pipeline (Alloy + Loki + Grafana) +robyn-config monitoring ./my_project +``` + +The `robyn-config` CLI helps you quickly scaffold production-ready Robyn applications following established patterns and best practices. It generates boilerplate code for routes, repositories, services, and models so you can focus on business logic. + ## What's next? After exploring the plugins, Batman wanted to explore the community.So, Robyn pointed him to diff --git a/integration_tests/test_robyn_config.py b/integration_tests/test_robyn_config.py new file mode 100644 index 000000000..c0d5bdb14 --- /dev/null +++ b/integration_tests/test_robyn_config.py @@ -0,0 +1,134 @@ +import os +import shutil +import subprocess +import tempfile + +import pytest + +_HAS_ROBYN_CONFIG = shutil.which("robyn-config") is not None + +pytestmark = pytest.mark.skipif(not _HAS_ROBYN_CONFIG, reason="robyn-config not installed") + + +class TestRobynConfigCLI: + """Verify that the robyn-config CLI is accessible and functional.""" + + def test_cli_help_exits_zero(self): + """robyn-config --help should exit with code 0.""" + result = subprocess.run( + ["robyn-config", "--help"], + capture_output=True, + text=True, + timeout=30, + ) + assert result.returncode == 0 + assert "Usage" in result.stdout + + def test_cli_create_help(self): + """robyn-config create --help should list available options.""" + result = subprocess.run( + ["robyn-config", "create", "--help"], + capture_output=True, + text=True, + timeout=30, + ) + assert result.returncode == 0 + assert "create" in result.stdout.lower() or "Usage" in result.stdout + + def test_cli_add_help(self): + """robyn-config add --help should list available options.""" + result = subprocess.run( + ["robyn-config", "add", "--help"], + capture_output=True, + text=True, + timeout=30, + ) + assert result.returncode == 0 + + +class TestRobynConfigScaffolding: + """Integration tests for project scaffolding.""" + + @pytest.fixture + def temp_dir(self): + """Create a temporary directory for scaffolding tests.""" + tmp = tempfile.mkdtemp(prefix="robyn_config_test_") + yield tmp + shutil.rmtree(tmp, ignore_errors=True) + + @pytest.fixture + def scaffolded_project(self): + """Create a scaffolded project for tests that build on top of it.""" + tmp = tempfile.mkdtemp(prefix="robyn_config_scaffolded_") + subprocess.run( + ["robyn-config", "create", "testproj", tmp, "--design", "ddd", "--orm", "sqlalchemy"], + capture_output=True, + text=True, + timeout=60, + ) + yield tmp + shutil.rmtree(tmp, ignore_errors=True) + + @pytest.mark.parametrize("design", ["ddd", "mvc"]) + @pytest.mark.parametrize("orm", ["sqlalchemy", "tortoise"]) + def test_create_project_scaffold(self, temp_dir, design, orm): + """robyn-config create should scaffold a valid project structure.""" + project_name = f"test_project_{design}_{orm}" + + result = subprocess.run( + ["robyn-config", "create", project_name, temp_dir, "--design", design, "--orm", orm], + capture_output=True, + text=True, + timeout=60, + ) + assert result.returncode == 0, f"stderr: {result.stderr}" + + # Verify essential files/directories exist in the scaffolded project + assert os.path.isfile(os.path.join(temp_dir, "pyproject.toml")), "pyproject.toml not found" + assert os.path.isdir(os.path.join(temp_dir, "src")), "src/ directory not found" + assert os.path.isfile(os.path.join(temp_dir, "Makefile")), "Makefile not found" + + def test_adminpanel_scaffolding(self, scaffolded_project): + """robyn-config adminpanel should add admin panel files to an existing project.""" + result = subprocess.run( + ["robyn-config", "adminpanel", scaffolded_project], + capture_output=True, + text=True, + timeout=60, + ) + assert result.returncode == 0, f"stderr: {result.stderr}" + + # Verify admin panel files were created + adminpanel_dir = os.path.join(scaffolded_project, "src", "app", "infrastructure", "adminpanel") + assert os.path.isdir(adminpanel_dir), "adminpanel directory not created" + + def test_adminpanel_with_custom_credentials(self, scaffolded_project): + """robyn-config adminpanel should accept custom username and password.""" + result = subprocess.run( + ["robyn-config", "adminpanel", scaffolded_project, "--username", "testadmin", "--password", "testpass"], + capture_output=True, + text=True, + timeout=60, + ) + assert result.returncode == 0, f"stderr: {result.stderr}" + + adminpanel_dir = os.path.join(scaffolded_project, "src", "app", "infrastructure", "adminpanel") + assert os.path.isdir(adminpanel_dir), "adminpanel directory not created" + + def test_monitoring_scaffolding(self, scaffolded_project): + """robyn-config monitoring should add monitoring pipeline to an existing project.""" + result = subprocess.run( + ["robyn-config", "monitoring", scaffolded_project], + capture_output=True, + text=True, + timeout=60, + ) + assert result.returncode == 0, f"stderr: {result.stderr}" + + # Verify monitoring files were created + assert os.path.isfile(os.path.join(scaffolded_project, "docker-compose.monitoring.yml")), "docker-compose.monitoring.yml not found" + + monitoring_dir = os.path.join(scaffolded_project, "compose", "monitoring") + assert os.path.isdir(monitoring_dir), "compose/monitoring directory not created" + assert os.path.isdir(os.path.join(monitoring_dir, "alloy")), "alloy config not found" + assert os.path.isdir(os.path.join(monitoring_dir, "grafana")), "grafana config not found" diff --git a/pyproject.toml b/pyproject.toml index 4ece61070..d494667a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,8 @@ dependencies = [ [project.optional-dependencies] "templating" = ["jinja2 >= 3.1.6, < 4.0.0"] "pydantic" = ["pydantic >= 2.0.0, < 3.0.0"] -"all" = ["jinja2 >= 3.1.6, < 4.0.0", "pydantic >= 2.0.0, < 3.0.0"] +"robyn-config" = ["robyn-config >= 1.0.0, < 2.0.0"] +"all" = ["jinja2 >= 3.1.6, < 4.0.0", "pydantic >= 2.0.0, < 3.0.0", "robyn-config >= 1.0.0, < 2.0.0"] [project.urls] Documentation = "https://robyn.tech/" @@ -83,13 +84,15 @@ multiprocess = "^0.70.18" uvloop = { version = "0.22.1", markers = "sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')" } jinja2 = { version = "^3.1.6", optional = true } pydantic = { version = "^2.0.0", optional = true } +robyn-config = { version = "^1.0.0", optional = true } rustimport = "^1.3.4" orjson = "^3.11.5" [tool.poetry.extras] templating = ["jinja2"] pydantic = ["pydantic"] -all = ["jinja2", "pydantic"] +robyn-config = ["robyn-config"] +all = ["jinja2", "pydantic", "robyn-config"] [tool.poetry.group.dev] optional = true