Skip to content

Commit

Permalink
Feature/clone (#33)
Browse files Browse the repository at this point in the history
* update README

* update readme

* update readme

* update readme

* update readme

* update readme

* create distant cloen

* do a lot a stuff

* fix ruff

* fix ruff

* add dependencies for mypy

* Delete migrations/1710260720_473c1c8fc477.py

* Update blitz/cli/commands/create.py

* Update blitz/cli/commands/create.py

---------

Co-authored-by: mde-pach <[email protected]>
  • Loading branch information
pbrochar and mde-pach authored Mar 13, 2024
1 parent 1a3a656 commit 95477c1
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 53 deletions.
79 changes: 48 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,69 +5,86 @@
<em>⚡️ Lightspeed API builder ⚡️</em>
</p>

![app version](https://img.shields.io/badge/version-0.1.0-brightgreen)
![app version](https://img.shields.io/badge/version-0.2.0-brightgreen)
![app license](https://img.shields.io/badge/license-MIT-brightgreen)

> [!CAUTION]
> Do not use in production, this is an alpha version.
Full [Documentation](https://paperz-org.github.io/blitz/) here.
# :link: Links

:books: [Documentation](https://blitz.paperz.app/)

:game_die: [Live Demo](https://demo.blitz.paperz.app/)

# **What is Blitz ?**
Blitz is a tool that builds restfull APIs on the fly based on a simple and easy to maintain configuration file.

Here is an example of how simple a Blitz file is:
```yaml
config:
name: Hello world
description: Here is a simple blitz configuration file.
version: 0.1.0
resources:
- name: TodoList
fields:
name: str
description: str
- name: Todo
fields:
name: str
due_date: str
todo_list_id: TodoList.id
todo_list: TodoList
config:
name: Hello world
description: Here is a simple blitz configuration file.
version: 0.1.0
resources:
TodoList:
name: str
description: str
Todo:
name: str
due_date: str
todo_list_id: TodoList.id
todo_list: TodoList
```
> [!NOTE]
> Also available in Json format.
# Quickstart
## Installation
# Installation
### Using [pipx](https://pipx.pypa.io/stable/installation/) (recommanded)
```bash
pipx install git+https://github.com/Paperz-org/blitz.git@v0.1.0
pipx install git+https://github.com/Paperz-org/blitz.git@v0.2.0
```

## Create a blitz app
# Quickstart

## Create a demo blitz app

```console
blitz create your-blitz-app
blitz create --demo
```
![Made with VHS](https://vhs.charm.sh/vhs-1tqzLZNxe5qeNhXT26ZCt5.gif)

## Start the demo

## Start your blitz app
```console
blitz start
```

![Made with VHS](https://vhs.charm.sh/vhs-2TEc58IujiV0CB1WoasT99.gif)

## Enjoy the demo

The blitz demo already includes resources to explore all the functionalities of Blitz.
You can see the Dashboard of the demo blitz app in our [Live Demo](https://demo.blitz.paperz.app/).

## Create a blitz app

```console
blitz start your-blitz-app
blitz create
```
![Made with VHS](https://vhs.charm.sh/vhs-2v3VHiIehkXeSkPzXeLch6.gif)

*And yeah, that's it.*

Just add some resources in the blitz file, and you now have a fully functional API and the corresponding database schema, along with all the modern features you can expect from a modern app like:

- Automatic Swagger UI for the API
- Admin
- Dashboard
- Data validation and error messages (thanks to Fastapi and SQLModel)
- Automatic database migration based on the changes of your blitz file
- Generated ERD diagram
- **Automatic Swagger UI** for the API
- **Admin Interface**
- **Dashboard**: including GPT builder, Blitz file editor, logs ...
- **Data Validation and Error Messages** (thanks to Fastapi and SQLModel)
- **Automatic Database Migration**
- **Generated ERD Diagram**
- and more...

<p align="center">
Expand Down
6 changes: 3 additions & 3 deletions blitz/api/blitz_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ def _add_redirect(self) -> None:
)

def _create_blitz_config_router(self) -> APIRouter:
path = "/blitz-config"
path = "/blitz-file"
router = APIRouter()
router.add_api_route(
path=path,
endpoint=lambda: self.blitz_app.file.model_dump(by_alias=True),
methods=["GET"],
summary="Get Blitz Config",
description="Returns the Blitz Config for all resources",
summary="Get the current Blitz file",
description="Returns the current Blitz File.",
)
return router

Expand Down
3 changes: 3 additions & 0 deletions blitz/cli/app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# from .commands.swagger import list_routes
import typer

from blitz.cli.commands.clone import clone_project

from .commands.create import create_blitz_app
from .commands.list import list_blitz_app
from .commands.release import release_blitz
Expand All @@ -13,5 +15,6 @@
app.command(name="start")(start_blitz)
app.command(name="release")(release_blitz)
app.command(name="swagger")(list_routes)
app.command(name="clone")(clone_project)
# dev only
# app.command(name="clean")(clean_blitz)
35 changes: 35 additions & 0 deletions blitz/cli/commands/clone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from json import JSONDecodeError
from typing import Annotated, Optional
from urllib.parse import urlparse
import requests
import typer
from blitz.cli.commands.create import BlitzProjectCreator
from blitz.cli.utils import progress
from blitz.models.blitz.file import BlitzFile


def clone_project(
url: Annotated[str, typer.Argument(help="URL of the project")],
force: Annotated[bool, typer.Option(help="Don't check the URL validity")] = False,
name: Annotated[Optional[str], typer.Option(help="Name of the project")] = None,
format: Annotated[str, typer.Option(help="Format of the project")] = "yaml",
) -> None:
parsed_url = urlparse(url)

if force is False and not parsed_url.path.endswith("blitz-config"):
print(f"Invalid URL: {url}")
raise typer.Exit(1)

with progress("Cloning Blitz App..."):
try:
blitz_file = BlitzFile.from_url(url, name, format=format)
except (requests.HTTPError, JSONDecodeError):
print(f"Failed to clone the project from {url}")
raise typer.Exit(1)

name = name or blitz_file.config.name
blitz_creator = BlitzProjectCreator(name, blitz_file.config.description, format, blitz_file, demo=False)

blitz_creator.create_file_or_exit()
blitz_creator.create_directory_or_exit()
blitz_creator.print_success_message()
38 changes: 23 additions & 15 deletions blitz/cli/commands/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,25 @@ class BlitzProjectCreator:
DEFAULT_VERSION = "0.1.0"
DEFAULT_BLITZ_APP_NAME = "Random Blitz App"
DEFAULT_BLIZ_APP_DESCRIPTION = ""
DEFAULT_BLITZ_FILE_FORMAT = "json"
DEFAULT_BLITZ_FILE_FORMAT = "yaml"
DEMO_BLITZ_APP_DESCRIPTION = "This is a demo blitz app"
DEMO_BLITZ_APP_NAME = "Demo Blitz App"

def __init__(self, name: str, description: str, file_format: str, demo: bool = False) -> None:
def __init__(
self,
name: str,
description: str | None,
file_format: str,
blitz_file: BlitzFile | None = None,
demo: bool = False,
) -> None:
self.name = name
self.description = description
self.description = description or ""
self.file_format = file_format
self.path = Path(self.name.lower().replace(" ", "-"))
self.demo = demo

self.blitz_file: BlitzFile | None = None
self.blitz_file = blitz_file

def create_directory_or_exit(self) -> None:
if not self.blitz_file:
Expand Down Expand Up @@ -107,16 +114,17 @@ def _create_directory(self) -> None:
blitz_app_file.write(str(blitz_file_path))

def _create_file(self) -> None:
self.blitz_file = BlitzFile(
path=self.path / f"blitz.{self.file_format}",
config=BlitzAppConfig(
name=self.name,
description=self.description,
version=self.DEFAULT_VERSION,
),
resources_configs=get_blitz_demo_resources() if self.demo else [],
raw_file={},
)
if not self.blitz_file:
self.blitz_file = BlitzFile(
path=self.path / f"blitz.{self.file_format}",
config=BlitzAppConfig(
name=self.name,
description=self.description,
version=self.DEFAULT_VERSION,
),
resources_configs=get_blitz_demo_resources() if self.demo else [],
raw_file={},
)

def _write_blitz_file(self) -> Path:
if self.blitz_file is None:
Expand Down Expand Up @@ -178,7 +186,7 @@ def create_blitz_app(
default=BlitzProjectCreator.DEFAULT_BLITZ_FILE_FORMAT,
)

blitz_creator = BlitzProjectCreator(name, description, file_format, demo)
blitz_creator = BlitzProjectCreator(name, description, file_format, demo=demo)
blitz_creator.create_file_or_exit()
blitz_creator.create_directory_or_exit()
blitz_creator.print_success_message()
31 changes: 30 additions & 1 deletion blitz/cli/commands/start.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import contextlib
from json import JSONDecodeError
import os
import time
from pathlib import Path
from typing import Annotated, Optional
import requests

import typer
import uvicorn
Expand All @@ -11,24 +13,51 @@

from blitz import __version__
from blitz.api import create_blitz_api
from blitz.app import BlitzApp
from blitz.cli.errors import (
BlitzAppNotFoundError,
BlitzAppVersionNotFoundError,
MissingBlitzAppNameError,
)
from blitz.cli.utils import print_version
from blitz.cli.utils import print_version, progress
from blitz.core import BlitzCore
from blitz.models.blitz.file import BlitzFile
from blitz.settings import get_settings


def create_app_from_url(url: str) -> BlitzApp:
with progress("Fetching Blitz app..."):
try:
blitz_file = BlitzFile.from_url(url)
except (requests.HTTPError, JSONDecodeError):
print(f"Failed to clone the project from {url}")
raise typer.Exit(1)
return BlitzApp(
name=blitz_file.config.name,
path=Path("."),
file=blitz_file,
)


def start_blitz(
blitz_app_name: Annotated[Optional[str], typer.Argument(help="Blitz app name")] = None,
admin: Annotated[bool, typer.Option(help="Don't create admin.")] = True,
port: Annotated[int, typer.Option(help="Define the port of the server")] = get_settings().BLITZ_PORT,
config_route: Annotated[bool, typer.Option(help="Enable the blitz config route.")] = True,
hot_reload: Annotated[bool, typer.Option(help="Enable the hot reload.")] = True,
version: Annotated[Optional[str], typer.Option(help="Define the version of the app.")] = None,
url: Annotated[Optional[str], typer.Option(help="URL of the project")] = None,
) -> None:
# TODO REFACTO THIS FUNCTION
if url is not None:
blitz_app = create_app_from_url(url)
with contextlib.suppress(Exception):
print_version(__version__)
time.sleep(0.3)
blitz_api = create_blitz_api(blitz_app, enable_config_route=config_route, admin=admin)
uvicorn.run(blitz_api, host="0.0.0.0", port=port, log_config=None, log_level="warning")
raise typer.Exit()

blitz = BlitzCore()

if blitz_app_name is None:
Expand Down
15 changes: 15 additions & 0 deletions blitz/cli/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from typing import Any, Generator
from rich import print
from contextlib import contextmanager
from rich.progress import Progress, SpinnerColumn, TextColumn


ZERO = [
" ██████╗ ",
Expand Down Expand Up @@ -127,3 +131,14 @@ def print_version(version: str) -> None:

for line in zip(BLITZ, SPACE, major_list, POINT, minor_list, POINT, patch_list):
print(f"[bold medium_purple1]{''.join(line)}[/bold medium_purple1]")


@contextmanager
def progress(description: str) -> Generator[Any, Any, Any]:
with Progress(
SpinnerColumn(spinner_name="dots"),
TextColumn("[progress.description]{task.description}"),
transient=True,
) as progress:
progress.add_task(description=description, total=None)
yield
14 changes: 14 additions & 0 deletions blitz/models/blitz/file.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Any, ClassVar, NoReturn
from pydantic import BaseModel, Field, field_serializer
import requests
from blitz.models.blitz.config import BlitzAppConfig
from blitz.models.blitz.resource import BlitzResourceConfig
from pathlib import Path
Expand Down Expand Up @@ -93,3 +94,16 @@ def from_dict(
path=path,
file_type=file_type,
)

@classmethod
def from_url(cls, url: str, name: str | None = None, format: str = "yaml") -> "BlitzFile":
response = requests.get(url)
try:
response.raise_for_status()
project_data = response.json()
blitz_file = cls.from_dict(project_data, file_type=cls.FileType(format))
except Exception as err:
raise err
name = name or blitz_file.config.name
blitz_file.path = Path(name.lower().replace(" ", "-")) / f"blitz.{format}"
return blitz_file
2 changes: 1 addition & 1 deletion blitz/ui/components/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def disabled(self) -> Self:
return self


class HeaderComponent(Component[ui.header]):
class HeaderComponent(Component[ui.header], reactive=False):
ThemeButton = IconButton.variant(props="fab-mini disabled")

def __init__(
Expand Down
Loading

0 comments on commit 95477c1

Please sign in to comment.