-
-
Notifications
You must be signed in to change notification settings - Fork 229
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 all 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 barebones format or an structured scaffold (recommended) 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 would like the scaffold to be a barebones starter kit or a recommended structure | ||
? Please select project type (Mongo/Postgres/Sqlalchemy/Prisma): | ||
❯ No DB | ||
Sqlite | ||
|
@@ -40,12 +53,7 @@ $ python3 -m robyn --create | |
Prisma | ||
``` | ||
|
||
and the following directory structure. | ||
|
||
|
||
Batman was asked a set of questions to configure the application. He chose to use the default values for most of the questions. | ||
|
||
And he was done! The Robyn CLI created a new application with the following structure. | ||
and the following directory structure | ||
|
||
```bash | ||
|
||
|
@@ -65,3 +73,134 @@ 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 | ||
|
||
Once you have set up your project like so, make sure to update the `sqlalchemy.url` field in your `alembic.ini`file to point to your DB like so... | ||
|
||
``` | ||
sqlalchemy.url = postgresql+psycopg://consoleuser:buddy123@localhost:5432/console | ||
``` | ||
|
||
Then, once you are done writing your sqlalchemy model classes, you can go ahead and generate your migration files like so | ||
|
||
```bash | ||
alembic revision --autogenerate -m initial | ||
``` | ||
You should see an output like so | ||
|
||
``` | ||
/home/batman/.virtualenvs/base/lib/python3.12/site-packages/pydantic/_internal/_config.py:334: UserWarning: Valid config keys have changed in V2: | ||
* 'orm_mode' has been renamed to 'from_attributes' | ||
warnings.warn(message, UserWarning) | ||
INFO [alembic.runtime.migration] Context impl PostgresqlImpl. | ||
INFO [alembic.runtime.migration] Will assume transactional DDL. | ||
INFO [alembic.autogenerate.compare] Detected added table 'users' | ||
INFO [alembic.autogenerate.compare] Detected added index ''ix_users_id'' on '('id',)' | ||
INFO [alembic.autogenerate.compare] Detected added index ''ix_users_username'' on '('username',)' | ||
INFO [alembic.ddl.postgresql] Detected sequence named 'transactions_id_seq' as owned by integer column 'transactions(id)', assuming SERIAL and omitting | ||
INFO [alembic.autogenerate.compare] Detected removed index 'idx_user_id' on 'transactions' | ||
INFO [alembic.autogenerate.compare] Detected removed table 'transactions' | ||
Generating /home/ashu/Desktop/aaa/migrations/versions/cefc632435c7_initial.py ... done | ||
``` | ||
|
||
This will generate the migration files like so | ||
|
||
```bash | ||
$ tree migrations/ | ||
migrations/ | ||
├── env.py | ||
├── README | ||
├── script.py.mako | ||
└── versions | ||
├── cefc632435c7_initial.py | ||
└── __init__.py | ||
|
||
2 directories, 5 files | ||
``` | ||
|
||
Now you can apply the migrations with `alembic upgrade head` | ||
|
||
Batman was asked a set of questions to configure the application. He chose to use the default values for most of the questions. | ||
|
||
And he was done! The Robyn CLI created a new application with the following structure. |
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 |
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