Skip to content

Commit 97c32ad

Browse files
devkraltarsil
andauthored
port lilya #269 to esmerald (path option instead of argument) (#594)
* port lilya #269 to esmerald (path option instead of argument) * fix crypto imports * don't fail on missing lifespan --------- Co-authored-by: tarsil <tiago.arasilva@gmail.com>
1 parent 6a3a520 commit 97c32ad

File tree

11 files changed

+109
-53
lines changed

11 files changed

+109
-53
lines changed

docs/en/docs/release-notes.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,33 @@ hide:
55

66
# Release Notes
77

8-
## 3.8.12
8+
## 3.9.0
9+
10+
### Changed
11+
12+
- Morph path argument into path option and expose it for all commands.
913

1014
### Fixed
1115

16+
- Properly detect wrapped Esmerald instances.
17+
- Fix double initialization of app in runserver.
1218
- Fix crash in runserver when no autodiscovery.
1319

1420
### Internal
1521

1622
- Add the new `format` in the `Taskfile` and `pyproject.toml`.
1723

24+
### Breaking
25+
26+
- `esmerald runserver` loses its path argument. You can specify it via `esmerald --path foo runserver`.
27+
1828
## 3.8.11
1929

2030
### Added
2131

2232
- `--version` attribute when running `createapp` directive allowing to generate a versioned scaffold.
2333
- `--location` attribute when using `createapp` and `createproject` directive allowing to specify the location to be created.
24-
34+
2535
### Changed
2636

2737
- To make Esmerald lighter and simpler, the some minimal changes for the `SessionMiddleware` import were added.

esmerald/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from monkay import Monkay
44

5-
__version__ = "3.8.11"
5+
__version__ = "3.9.0"
66

77
if TYPE_CHECKING:
88
from lilya import status

esmerald/applications.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@
1414
)
1515

1616
from lilya.apps import BaseLilya
17-
from lilya.conf import _monkay # noqa
17+
from lilya.conf import _monkay as monkay
1818
from lilya.logging import setup_logging
1919
from lilya.middleware import DefineMiddleware # noqa
2020
from lilya.types import Lifespan, Receive, Scope, Send
2121
from monkay import load
2222
from pydantic import AnyUrl, ValidationError
2323
from typing_extensions import Annotated, Doc
2424

25-
from esmerald.conf import monkay as monkay_for_settings
25+
from esmerald.conf import (
26+
monkay as monkay_for_settings,
27+
)
2628
from esmerald.conf.global_settings import EsmeraldSettings
2729
from esmerald.contrib.schedulers.base import SchedulerConfig
2830
from esmerald.core.config import (
@@ -1767,7 +1769,7 @@ def extend(self, config: PluggableConfig) -> None:
17671769
)
17681770
self.get_default_exception_handlers()
17691771
if self.register_as_global_instance:
1770-
_monkay.set_instance(self)
1772+
monkay.set_instance(self)
17711773
self.user_middleware = self.build_user_middleware_stack()
17721774
self.middleware_stack = self.build_middleware_stack()
17731775
self.template_engine = self.get_template_engine(self.template_config)
@@ -2514,7 +2516,7 @@ async def hello(self) -> str:
25142516
dependencies=route.dependencies,
25152517
exception_handlers=route.exception_handlers,
25162518
name=route.name,
2517-
middleware=cast("list[Middleware]", route.middleware),
2519+
middleware=route.middleware,
25182520
interceptors=route.interceptors,
25192521
permissions=route.permissions,
25202522
routes=cast("Sequence[Union[APIGateHandler, Include]]", route.routes),

esmerald/core/directives/cli.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ def invoke(self, ctx: click.Context) -> typing.Any:
7676
Directives can be ignored depending of the functionality from what is being
7777
called.
7878
"""
79-
path = ctx.params.get("app", None)
79+
app = ctx.params.get("app", None)
80+
path = ctx.params.get("path", None)
8081

8182
# Process any settings
8283
self.process_settings(ctx)
@@ -86,7 +87,7 @@ def invoke(self, ctx: click.Context) -> typing.Any:
8687
):
8788
try:
8889
directive = DirectiveEnv()
89-
app_env = directive.load_from_env(path=path)
90+
app_env = directive.load_from_env(path=app, cwd=path)
9091
ctx.obj = app_env
9192
except OSError as e:
9293
if not any(value in sys.argv for value in IGNORE_DIRECTIVES):
@@ -129,6 +130,13 @@ def esmerald_callback(
129130
help="Module path to the Esmerald application. In a module:path format.",
130131
),
131132
],
133+
path: typing.Annotated[
134+
str | None,
135+
Option(
136+
required=False,
137+
help="A path to a Python file or package directory with ([blue]__init__.py[/blue] files) containing a [bold]Lilya[/bold] app. If not provided, Lilya will try to discover.",
138+
),
139+
],
132140
) -> None: ...
133141

134142

esmerald/core/directives/env.py

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
from pathlib import Path
66

77
from esmerald import ChildEsmerald, Esmerald
8+
from esmerald.conf import monkay
89
from esmerald.core.directives.constants import (
910
DISCOVERY_ATTRS,
1011
DISCOVERY_FILES,
1112
DISCOVERY_FUNCTIONS,
1213
ESMERALD_DISCOVER_APP,
1314
)
1415
from esmerald.core.terminal import Print
16+
from esmerald.types import ASGIApp
1517

1618
printer = Print()
1719

@@ -25,7 +27,8 @@ class Scaffold:
2527
"""
2628

2729
path: str
28-
app: Esmerald | ChildEsmerald
30+
app: ASGIApp
31+
esmerald_app: Esmerald | ChildEsmerald | None = None
2932
app_location: Path | None = None
3033
discovery_file: str | None = None
3134

@@ -46,17 +49,23 @@ class DirectiveEnv:
4649
"""
4750

4851
path: str | None = None
49-
app: Esmerald | ChildEsmerald | None = None
52+
app: ASGIApp | None = None
53+
esmerald_app: Esmerald | ChildEsmerald | None = None
5054
command_path: str | None = None
5155
module_info: ModuleInfo | None = None
5256

53-
def load_from_env(self, path: str | None = None) -> "DirectiveEnv":
57+
def load_from_env(
58+
self, path: str | None = None, cwd: None | str | Path = None
59+
) -> "DirectiveEnv":
5460
"""
5561
Loads the environment variables into the scaffold.
5662
"""
5763
# Adds the current path where the command is being invoked
5864
# To the system path
59-
cwd = Path().cwd()
65+
if cwd is None:
66+
cwd = Path.cwd()
67+
if not isinstance(cwd, Path):
68+
cwd = Path(cwd)
6069
command_path = str(cwd)
6170
if command_path not in sys.path:
6271
sys.path.append(command_path)
@@ -73,6 +82,7 @@ def load_from_env(self, path: str | None = None) -> "DirectiveEnv":
7382
return DirectiveEnv(
7483
path=_app.path,
7584
app=_app.app,
85+
esmerald_app=_app.esmerald_app,
7686
command_path=command_path,
7787
module_info=self.get_module_data_from_path(
7888
_app.app_location, _app.path, _app.discovery_file
@@ -131,7 +141,12 @@ def import_app_from_string(cls, path: str | None = None) -> Scaffold:
131141
module_str_path, app_name = path.split(":")
132142
module = import_module(module_str_path)
133143
app = getattr(module, app_name)
134-
return Scaffold(path=path, app=app)
144+
145+
if isinstance(app, Esmerald):
146+
esmerald = app
147+
else:
148+
esmerald = monkay.instance
149+
return Scaffold(path=path, app=app, esmerald_app=esmerald)
135150

136151
def _get_folders(self, path: Path) -> list[str]:
137152
"""
@@ -154,35 +169,52 @@ def _find_app_in_folder(self, path: Path, cwd: Path) -> Scaffold | None:
154169
# Load file from module
155170
module = import_module(dotted_path)
156171

157-
# FIrst check some attrs
158-
for attr in DISCOVERY_ATTRS:
159-
value = getattr(module, attr, None)
160-
if value:
172+
# Iterates through the elements of the module.
173+
for attr, value in module.__dict__.items():
174+
if isinstance(value, Esmerald):
161175
app_path = f"{dotted_path}:{attr}"
162176
return Scaffold(
163177
app=value,
178+
esmerald_app=value,
164179
path=app_path,
165180
app_location=path,
166181
discovery_file=discovery_file,
167182
)
168183

169-
# Iterates through the elements of the module.
170-
for attr, value in module.__dict__.items():
171-
if isinstance(value, Esmerald):
172-
app_path = f"{dotted_path}:{attr}"
173-
return Scaffold(app=value, path=app_path)
184+
# Is registered
185+
if isinstance((esmerald_instance := monkay.instance), Esmerald):
186+
for attr in DISCOVERY_ATTRS:
187+
if (value := getattr(module, attr, None)) is not None:
188+
app_path = f"{dotted_path}:{attr}"
189+
return Scaffold(
190+
app=value,
191+
esmerald_app=esmerald_instance,
192+
path=app_path,
193+
app_location=path,
194+
discovery_file=discovery_file,
195+
)
174196

175197
# Iterate over default pattern application functions
176198
for func in DISCOVERY_FUNCTIONS:
177-
if hasattr(module, func):
199+
if (fn := getattr(module, func, None)) is not None:
200+
app_candidate = fn()
178201
app_path = f"{dotted_path}:{func}"
179-
fn = getattr(module, func)
180-
return Scaffold(
181-
app=fn(),
182-
path=app_path,
183-
app_location=path,
184-
discovery_file=discovery_file,
185-
)
202+
if isinstance(app_candidate, Esmerald):
203+
return Scaffold(
204+
app=app_candidate,
205+
esmerald_app=app_candidate,
206+
path=app_path,
207+
app_location=path,
208+
discovery_file=discovery_file,
209+
)
210+
if isinstance((esmerald_instance := monkay.instance), Esmerald):
211+
return Scaffold(
212+
app=app_candidate,
213+
esmerald_app=esmerald_instance,
214+
path=app_path,
215+
app_location=path,
216+
discovery_file=discovery_file,
217+
)
186218
return None
187219

188220
def find_app(self, path: str | None, cwd: Path) -> Scaffold:

esmerald/core/directives/operations/list.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def directives(env: DirectiveEnv) -> None:
2626
directives = get_directives(PATH)
2727

2828
# Handles the application directives
29-
if getattr(env, "app", None) is not None:
29+
if getattr(env, "esmerald_app", None) is not None:
3030
app_directives = get_application_directives(env.command_path)
3131
if app_directives:
3232
directives.extend(app_directives)

esmerald/core/directives/operations/run.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@
1010
from lilya._internal._events import generate_lifespan_events # noqa
1111
from lilya.cli.base import BaseDirective
1212
from lilya.context import G, g_context
13-
from lilya.types import Lifespan
13+
from lilya.types import ASGIApp, Lifespan
1414
from sayer import Argument, Option, command, error
1515

16-
from esmerald.applications import ChildEsmerald, Esmerald
1716
from esmerald.core.directives.constants import APP_PARAMETER, ESMERALD_DISCOVER_APP
1817
from esmerald.core.directives.env import DirectiveEnv
1918
from esmerald.core.directives.utils import fetch_directive
@@ -98,7 +97,15 @@ async def run(
9897

9998
## Check if application is up and execute any event
10099
# Shutting down after
101-
lifespan = generate_lifespan_events(env.app.on_startup, env.app.on_shutdown, env.app.lifespan)
100+
lifespan = (
101+
generate_lifespan_events(
102+
env.esmerald_app.on_startup,
103+
env.esmerald_app.on_shutdown,
104+
env.esmerald_app.lifespan,
105+
)
106+
if env.esmerald_app
107+
else generate_lifespan_events()
108+
)
102109
await execute_lifespan(env.app, lifespan, directive, program_name, position)
103110

104111

@@ -136,7 +143,7 @@ async def reset_global_context(token: Any) -> None:
136143

137144

138145
async def execute_lifespan(
139-
app: Esmerald | ChildEsmerald | None,
146+
app: ASGIApp | None,
140147
lifespan: Lifespan,
141148
directive: Any,
142149
program_name: str,

esmerald/core/directives/operations/runserver.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import click
77
from rich.tree import Tree
8-
from sayer import Argument, Option, command, error
8+
from sayer import Option, command, error
99

1010
from esmerald.core.directives.env import DirectiveEnv
1111
from esmerald.core.directives.exceptions import DirectiveError
@@ -46,13 +46,6 @@ def get_app_tree(module_paths: list[Path], discovery_file: str) -> Tree:
4646

4747
@command
4848
def runserver(
49-
path: Annotated[
50-
str | None,
51-
Argument(
52-
required=False,
53-
help="A path to a Python file or package directory with ([blue]__init__.py[/blue] files) containing a [bold]Esmerald[/bold] app. If not provided, Esmerald will try to discover.",
54-
),
55-
],
5649
port: Annotated[
5750
int, Option(8000, "-p", help="Port to run the development server.", show_default=True)
5851
],
@@ -196,11 +189,13 @@ def runserver(
196189
)
197190
toolkit.print_line()
198191

199-
if debug:
200-
app.debug = debug
192+
if debug and env.esmerald_app:
193+
env.esmerald_app.debug = debug
201194

202195
uvicorn.run(
203-
app=path or env.path,
196+
# in case of no reload and workers, we might end up initializing twice when
197+
# using a function, so use app instead
198+
app=app if not reload and not workers else env.path,
204199
port=port,
205200
host=host,
206201
reload=reload,

esmerald/core/directives/operations/show_urls.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,10 @@ def show_urls(env: DirectiveEnv) -> None:
6666
"ESMERALD_DEFAULT_APP environment variable."
6767
)
6868
sys.exit(1)
69-
70-
app = env.app
69+
if getattr(env, "esmerald_app", None) is None:
70+
error("Not an esmerald app.")
71+
sys.exit(1)
72+
app = env.esmerald_app
7173
table = Table(title=app.app_name)
7274
table = get_routes_table(app, table)
7375
echo(table)
@@ -112,7 +114,7 @@ def parse_routes(
112114
# of the table not being able to render the string
113115
route_name = ":".join(names)
114116

115-
http_methods = ", ".join(sorted(route.methods))
117+
http_methods = ", ".join(sorted(route.methods or []))
116118
parameters = ", ".join(sorted(route.stringify_parameters))
117119
table.add_row(path, parameters, route_name, fn_type, http_methods)
118120
continue

esmerald/utils/crypto.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from lilya.crypto import get_random_secret_key, get_random_string
1+
from lilya._internal._crypto import get_random_secret_key, get_random_string
22

33
__all__ = ["get_random_string", "get_random_secret_key"]

0 commit comments

Comments
 (0)