-
-
Notifications
You must be signed in to change notification settings - Fork 228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: support for structured scaffold in create command #970
base: main
Are you sure you want to change the base?
Changes from 53 commits
9828c99
56e174a
03918ac
10d3c83
0baab2b
481bed8
c0c5dd4
a4c2730
e98ce50
cdcb947
6005967
095916f
832bd8f
09b9262
2a16aff
a4b49b1
25840fa
6f2e58c
4922b1a
f2ae1f7
c964335
4391a39
9d66596
43726fb
9159a71
b5895dd
f155900
182f9cf
054570b
3a93374
155acd7
d2ddba8
d282b36
9d7fcf0
8a3c2ab
8ce0753
0a9cbb6
42c61fc
ffe6629
138e530
646b58b
a6c6e13
4eeab6d
72b66f0
af42e44
da66936
2a8209e
61bac98
5ffbcfd
785dc1d
793f882
7b98147
3f014bd
d855250
5a890cb
e627b59
c716a84
20e1390
8939695
1289421
0d848b5
3b3a51e
f348cac
8976522
170e496
662c009
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,12 +25,25 @@ Batman wanted to create a Robyn app and was about to create an `src/app.py` befo | |
$ python -m robyn --create | ||
``` | ||
|
||
This, would result in the following output. | ||
You can choose to have a simple starter format or an opinionated scaffold ike so... | ||
|
||
```bash | ||
$ python -m robyn --create | ||
? Directory Path: myproject | ||
? Need Docker? (Y/N) Y | ||
? Please choose if you'd like the scaffold to be a simple starter kit or an opinionated structure | ||
Simple | ||
❯ Structured | ||
``` | ||
|
||
|
||
This, would result in the following output if you choose scaffold type as `simple` | ||
|
||
```bash | ||
$ python3 -m robyn --create | ||
? Directory Path: . | ||
? Need Docker? (Y/N) Y | ||
? Please choose if you'd like the scaffold to be a simple starter kit or an opinionated structure | ||
ashupednekar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
? Please select project type (Mongo/Postgres/Sqlalchemy/Prisma): | ||
❯ No DB | ||
Sqlite | ||
|
@@ -65,3 +78,87 @@ And he was done! The Robyn CLI created a new application with the following stru | |
/> | ||
</div> | ||
|
||
If you choose to go with the structured scaffold, this is how your project will look like | ||
|
||
> note: at the moment, only no-db and sqlalchemy are supported here, you can always plug in other integrations as you see fit | ||
ashupednekar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```bash | ||
├── adaptors | ||
│ ├── __init__.py | ||
│ ├── models | ||
│ │ ├── __init__.py | ||
│ │ └── user.py | ||
│ ├── mutators | ||
│ │ └── __init__.py | ||
│ └── selectors | ||
│ ├── __init__.py | ||
│ └── misc.py | ||
├── alembic.ini | ||
├── api | ||
│ ├── handlers | ||
│ │ ├── __init__.py | ||
│ │ ├── probes.py | ||
│ │ └── sample.py | ||
│ └── middlewares | ||
│ └── __init__.py | ||
├── conf.py | ||
├── config.env | ||
├── devops | ||
│ ├── Dockerfile | ||
│ ├── Dockerfile.src | ||
│ └── docker-compose.yaml | ||
├── migrations | ||
│ ├── README | ||
│ ├── env.py | ||
│ ├── script.py.mako | ||
Comment on lines
+105
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to read more about this |
||
│ └── versions | ||
│ └── __init__.py | ||
├── requirements.txt | ||
├── server.py | ||
└── utils | ||
├── __init__.py | ||
└── db.py | ||
|
||
12 directories, 24 files | ||
``` | ||
|
||
Here's what each of these stand for | ||
|
||
- server.py | ||
|
||
This is where you instantiate your robyn server and inject global dependencies | ||
|
||
```python | ||
from robyn.helpers import discover_routes | ||
from robyn import Robyn | ||
|
||
from utils.db import get_pool | ||
from conf import settings | ||
|
||
app: Robyn = discover_routes("api.handlers") | ||
Comment on lines
+127
to
+133
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we don't have a discover_routes function There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's added in helpers as part of this PR... def discover_routes(handler_path: str = "api.handlers") -> Robyn:
mux: Robyn = Robyn(__file__)
package = importlib.import_module(handler_path)
for _, module_name, _ in pkgutil.iter_modules(package.__path__, package.__name__ + "."):
module = importlib.import_module(module_name)
logger.info(f"member: {module}")
mux.include_router(module.router)
return mux |
||
# note: if you prefer to manuall refine routes, use your build_routes function instead | ||
|
||
app.inject_global(pool=get_pool()) | ||
|
||
|
||
if __name__ == "__main__": | ||
app.start(host="0.0.0.0", port=settings.service_port) | ||
``` | ||
|
||
- conf.py/config.env | ||
|
||
Comes with initial settings you need to work with the database. The `BaseConfig` class slight enhancement on pydantic-settings's `BaseSettings` class | ||
|
||
- Your endpoint and middleware handlers live under the `api` package. | ||
> note: the example where the handlers are in a class as static methods is completely up to the developer preference and doesn't impact the actual routing in any way | ||
|
||
- It also comes pre-configured with alembic for database migrations | ||
|
||
- The database mutations and queries will be living under the `adaptors` package where you would do the following | ||
- define your sqlalchemy and pydantic models | ||
- define selectors for reusable query functions | ||
- define mutators for any mutations over the model along with related functionality, usually in a transaction block | ||
|
||
> note: by default, we include sqlalchemy async pool, you can always change it as per your requirements | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,11 +4,27 @@ | |
|
||
|
||
# Unit tests | ||
def test_create_robyn_app(): | ||
def test_create_robyn_app_simple(): | ||
with patch("robyn.cli.prompt") as mock_prompt: | ||
mock_prompt.return_value = { | ||
"directory": "test_dir", | ||
"docker": "N", | ||
"scaffold_type": "simple", | ||
"project_type": "no-db", | ||
} | ||
with patch("robyn.cli.os.makedirs") as mock_makedirs: | ||
with patch("robyn.cli.shutil.copytree") as mock_copytree, patch("robyn.os.remove") as _mock_remove: | ||
Comment on lines
+15
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not move this to line 8 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. didn't follow... added additional key in mock return value |
||
create_robyn_app() | ||
mock_makedirs.assert_called_once() | ||
mock_copytree.assert_called_once() | ||
|
||
|
||
def test_create_robyn_app_structured(): | ||
with patch("robyn.cli.prompt") as mock_prompt: | ||
mock_prompt.return_value = { | ||
"directory": "test_dir", | ||
"docker": "N", | ||
"scaffold_type": "structured", | ||
"project_type": "no-db", | ||
} | ||
with patch("robyn.cli.os.makedirs") as mock_makedirs: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
argcomplete==2.0.6 ; python_version >= "3.8" and python_version < "4.0" | ||
attrs==24.2.0 ; python_version >= "3.8" and python_version < "4.0" | ||
black==23.1.0 ; python_version >= "3.8" and python_version < "4.0" | ||
certifi==2024.8.30 ; python_version >= "3.8" and python_version < "4" | ||
cffi==1.15.1 ; python_version >= "3.8" and python_version < "4.0" | ||
cfgv==3.4.0 ; python_version >= "3.8" and python_version < "4.0" | ||
charset-normalizer==2.1.1 ; python_version >= "3.8" and python_version < "4.0" | ||
click==8.1.7 ; python_version >= "3.8" and python_version < "4.0" | ||
colorama==0.4.6 ; python_version >= "3.8" and python_version < "4.0" | ||
colorlog==6.8.2 ; python_version >= "3.8" and python_version < "4.0" | ||
commitizen==2.40.0 ; python_version >= "3.8" and python_version < "4.0" | ||
decli==0.5.2 ; python_version >= "3.8" and python_version < "4.0" | ||
dill==0.3.8 ; python_version >= "3.8" and python_version < "4.0" | ||
distlib==0.3.8 ; python_version >= "3.8" and python_version < "4.0" | ||
exceptiongroup==1.2.2 ; python_version >= "3.8" and python_version < "3.11" | ||
filelock==3.16.1 ; python_version >= "3.8" and python_version < "4.0" | ||
identify==2.6.1 ; python_version >= "3.8" and python_version < "4.0" | ||
idna==3.10 ; python_version >= "3.8" and python_version < "4" | ||
iniconfig==2.0.0 ; python_version >= "3.8" and python_version < "4.0" | ||
inquirerpy==0.3.4 ; python_version >= "3.8" and python_version < "4.0" | ||
isort==5.11.5 ; python_version >= "3.8" and python_version < "4.0" | ||
jinja2==3.0.1 ; python_version >= "3.8" and python_version < "4.0" | ||
markupsafe==2.1.5 ; python_version >= "3.8" and python_version < "4.0" | ||
maturin==0.14.12 ; python_version >= "3.8" and python_version < "4.0" | ||
multiprocess==0.70.14 ; python_version >= "3.8" and python_version < "4.0" | ||
mypy-extensions==1.0.0 ; python_version >= "3.8" and python_version < "4.0" | ||
nestd==0.3.1 ; python_version >= "3.8" and python_version < "4.0" | ||
nodeenv==1.9.1 ; python_version >= "3.8" and python_version < "4.0" | ||
nox==2023.4.22 ; python_version >= "3.8" and python_version < "4.0" | ||
orjson==3.10.7 ; python_version >= "3.8" and python_version < "4.0" | ||
packaging==24.1 ; python_version >= "3.8" and python_version < "4.0" | ||
pathspec==0.12.1 ; python_version >= "3.8" and python_version < "4.0" | ||
pfzy==0.3.4 ; python_version >= "3.8" and python_version < "4.0" | ||
platformdirs==4.3.6 ; python_version >= "3.8" and python_version < "4.0" | ||
pluggy==1.5.0 ; python_version >= "3.8" and python_version < "4.0" | ||
pre-commit==2.21.0 ; python_version >= "3.8" and python_version < "4.0" | ||
prompt-toolkit==3.0.48 ; python_version >= "3.8" and python_version < "4.0" | ||
pycparser==2.22 ; python_version >= "3.8" and python_version < "4.0" | ||
pytest-codspeed==1.2.2 ; python_version >= "3.8" and python_version < "4.0" | ||
pytest==7.2.1 ; python_version >= "3.8" and python_version < "4.0" | ||
pyyaml==6.0.2 ; python_version >= "3.8" and python_version < "4.0" | ||
questionary==1.10.0 ; python_version >= "3.8" and python_version < "4.0" | ||
requests==2.28.2 ; python_version >= "3.8" and python_version < "4" | ||
ruff==0.1.3 ; python_version >= "3.8" and python_version < "4.0" | ||
rustimport==1.5.0 ; python_version >= "3.8" and python_version < "4.0" | ||
termcolor==2.4.0 ; python_version >= "3.8" and python_version < "4.0" | ||
toml==0.10.2 ; python_version >= "3.8" and python_version < "4.0" | ||
tomli==2.0.1 ; python_version >= "3.8" and python_version < "3.11" | ||
tomlkit==0.13.2 ; python_version >= "3.8" and python_version < "4.0" | ||
typing-extensions==4.12.2 ; python_version >= "3.8" and python_version < "4.0" | ||
urllib3==1.26.20 ; python_version >= "3.8" and python_version < "4" | ||
uvloop==0.19.0 ; sys_platform != "win32" and sys_platform != "cygwin" and platform_python_implementation != "PyPy" and python_version >= "3.8" and python_version < "4.0" | ||
virtualenv==20.26.6 ; python_version >= "3.8" and python_version < "4.0" | ||
watchdog==4.0.1 ; python_version >= "3.8" and python_version < "4.0" | ||
wcwidth==0.2.13 ; python_version >= "3.8" and python_version < "4.0" | ||
websocket-client==1.5.0 ; python_version >= "3.8" and python_version < "4.0" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import importlib | ||
import logging | ||
import pkgutil | ||
from typing import Any, Tuple, Type | ||
|
||
from pydantic import ConfigDict | ||
from pydantic_settings import BaseSettings, EnvSettingsSource, PydanticBaseSettingsSource | ||
|
||
from robyn import Robyn | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def discover_routes(handler_path: str = "api.handlers") -> Robyn: | ||
mux: Robyn = Robyn(__file__) | ||
package = importlib.import_module(handler_path) | ||
for _, module_name, _ in pkgutil.iter_modules(package.__path__, package.__name__ + "."): | ||
module = importlib.import_module(module_name) | ||
logger.info(f"member: {module}") | ||
mux.include_router(module.router) | ||
return mux | ||
|
||
|
||
class AcceptArrayEnvsSource(EnvSettingsSource): | ||
def prepare_field_value(self, field_name: str, field: Any, value: Any, value_is_complex: bool) -> Any: | ||
if isinstance(field.annotation, type) and issubclass(field.annotation, list) and isinstance(value, str): | ||
return [x.strip() for x in value.split(",") if x] | ||
return value | ||
|
||
|
||
class BaseConfig(BaseSettings): | ||
@classmethod | ||
def settings_customise_sources( | ||
cls, | ||
settings_cls: Type[BaseSettings], | ||
init_settings: PydanticBaseSettingsSource, | ||
env_settings: PydanticBaseSettingsSource, | ||
dotenv_settings: PydanticBaseSettingsSource, | ||
file_secret_settings: PydanticBaseSettingsSource, | ||
) -> Tuple[PydanticBaseSettingsSource, ...]: | ||
return (AcceptArrayEnvsSource(settings_cls),) | ||
|
||
model_config = ConfigDict(extra="ignore") # Ignore extra environment variables |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from robyn import SubRouter | ||
|
||
router = SubRouter(__name__, prefix="/") | ||
|
||
|
||
@router.get("/livez/") | ||
def livez() -> str: | ||
return "live" | ||
|
||
|
||
@router.get("/healthz/") | ||
def healthz() -> str: | ||
Comment on lines
+6
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's use more formal names here like
|
||
return "healthy" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from robyn import SubRouter | ||
|
||
router = SubRouter(__name__, "/sample") | ||
|
||
|
||
class SampleHandlers: | ||
""" | ||
note: the handles being grouped in a class like this is complete optional, and doesn't have any impact on routing | ||
""" | ||
|
||
@router.post("/one") | ||
@staticmethod | ||
def one(): ... | ||
|
||
@router.get("/two") | ||
@staticmethod | ||
def two(): ... | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not a big fan of this syntax honestly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could we please change this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should I remove the sample handlers class? it doesn't really serve any purpose as such. |
||
|
||
|
||
@router.get("three/") | ||
def three(): | ||
return {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from robyn.helpers import BaseConfig | ||
|
||
|
||
class Settings(BaseConfig): | ||
service_port: int | ||
|
||
|
||
settings = Settings() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
SERVICE_PORT=3000 | ||
sansyrox marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
FROM python:3.11-bookworm AS builder | ||
|
||
WORKDIR /workspace | ||
|
||
COPY . . | ||
RUN pip install --no-cache-dir --upgrade -r requirements.txt --target=/workspace/deps | ||
|
||
FROM gcr.io/distroless/python3-debian11 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you explain a bit more here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a two stage commit...
|
||
|
||
WORKDIR /workspace | ||
COPY --from=builder /workspace /workspace | ||
ENV PYTHONPATH=/workspace/deps | ||
|
||
CMD ["server.py", "--log-level=DEBUG"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's call it barebones and recommended structured
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oki
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ashupednekar , I don't see an update here