Skip to content

Commit 608fff1

Browse files
devkraltarsil
andauthored
allow wrapped lilya instances, add lilya_app (#269)
* allow wrapped lilya instances, add lilya_app - recheck some app variables app, appilication if there is a wrapped lilya instance - fix crash in show-urls - fix double import in runserver * fix ignored path * use baselilya as type --------- Co-authored-by: tarsil <tiago.arasilva@gmail.com>
1 parent 9159e2d commit 608fff1

7 files changed

Lines changed: 111 additions & 39 deletions

File tree

docs/en/docs/release-notes.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ hide:
1414

1515
### Changed
1616

17+
- Morph path argument into path option and expose it for all commands.
18+
19+
### Fixed
20+
21+
- Properly detect wrapped Lilya instances.
22+
- Fix crash in show-urls.
23+
- Fix double initialization of app in runserver.
24+
25+
### Breaking
26+
27+
- lilya runserver loses its path argument. You can specify it via `lilya --path foo runserver`.
28+
29+
### Changed
30+
1731
- Add `wrap_dependency` for inherited Controller dependencies.
1832

1933
## 0.20.0

lilya/cli/cli.py

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

8485
# Process any settings
8586
self.process_settings(ctx)
@@ -89,7 +90,7 @@ def invoke(self, ctx: click.Context) -> typing.Any:
8990
):
9091
try:
9192
directive = DirectiveEnv()
92-
app_env = directive.load_from_env(path=path)
93+
app_env = directive.load_from_env(path=app, cwd=path)
9394
ctx.obj = app_env
9495
except EnvError as e:
9596
if not any(value in sys.argv for value in IGNORE_DIRECTIVES):
@@ -132,6 +133,13 @@ def lilya_callback(
132133
required=False, help="Module path to the Lilya application. In a module:path format."
133134
),
134135
],
136+
path: typing.Annotated[
137+
str | None,
138+
Option(
139+
required=False,
140+
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.",
141+
),
142+
],
135143
) -> None: ...
136144

137145

lilya/cli/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
IGNORE_DIRECTIVES = ["directives"]
77
DISCOVERY_FILES = ["application.py", "app.py", "main.py"]
88
DISCOVERY_FUNCTIONS = ["get_application", "get_app"]
9+
DISCOVERY_ATTRS = ["application", "app"]
910
TREAT_AS_PROJECT_DIRECTIVE = ["deployment"]

lilya/cli/directives/operations/run.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
from lilya.cli.env import DirectiveEnv
1616
from lilya.cli.utils import fetch_directive
1717
from lilya.context import G, g_context
18-
from lilya.types import Lifespan
18+
from lilya.types import ASGIApp, Lifespan
1919

2020
if TYPE_CHECKING:
21-
from lilya.apps import ChildLilya, Lilya
21+
pass
2222

2323
T = TypeVar("T")
2424

@@ -94,9 +94,9 @@ async def run(
9494
## Check if application is up and execute any event
9595
# Shutting down after
9696
lifespan = generate_lifespan_events(
97-
env.app.router.on_startup,
98-
env.app.router.on_shutdown,
99-
env.app.router.lifespan_context,
97+
env.lilya_app.router.on_startup,
98+
env.lilya_app.router.on_shutdown,
99+
env.lilya_app.router.lifespan_context,
100100
)
101101
await execute_lifespan(env.app, lifespan, directive, program_name, position)
102102

@@ -135,7 +135,7 @@ async def reset_global_context(token: Any) -> None:
135135

136136

137137
async def execute_lifespan(
138-
app: Lilya | ChildLilya | None,
138+
app: ASGIApp | None,
139139
lifespan: Lifespan,
140140
directive: Any,
141141
program_name: str,

lilya/cli/directives/operations/runserver.py

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

88
import click
99
from rich.tree import Tree
10-
from sayer import Argument, Option, command, error
10+
from sayer import Option, command, error
1111

1212
from lilya.cli.env import DirectiveEnv
1313
from lilya.cli.exceptions import DirectiveError
@@ -42,13 +42,6 @@ def get_app_tree(module_paths: list[Path], discovery_file: str) -> Tree:
4242

4343
@command
4444
def runserver(
45-
path: Annotated[
46-
str | None,
47-
Argument(
48-
required=False,
49-
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.",
50-
),
51-
],
5245
port: Annotated[
5346
int, Option(8000, "-p", help="Port to run the development server.", show_default=True)
5447
],
@@ -187,13 +180,15 @@ def runserver(
187180
tag="note",
188181
)
189182

190-
if debug:
191-
app.debug = debug
183+
if debug and env.lilya_app:
184+
env.lilya_app.debug = debug
192185

193186
toolkit.print_line()
194187

195188
uvicorn.run(
196-
app=path or env.path,
189+
# in case of no reload and workers, we might end up initializing twice when
190+
# using a function, so use app instead
191+
app=app if not reload and not workers else env.path,
197192
port=port,
198193
host=host,
199194
reload=reload,

lilya/cli/directives/operations/show_urls.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from lilya.routing import Path
1919

2020
if TYPE_CHECKING:
21-
from lilya.apps import ChildLilya, Lilya
21+
from lilya.apps import BaseLilya
2222
from lilya.routing import BasePath, Router
2323

2424
console = Console()
@@ -63,14 +63,16 @@ def show_urls(env: DirectiveEnv) -> None:
6363
"LILYA_DEFAULT_APP environment variable."
6464
)
6565
sys.exit(1)
66-
67-
app = env.app
66+
if getattr(env, "lilya_app", None) is None:
67+
error("Not a lilya app")
68+
sys.exit(1)
69+
app = env.lilya_app
6870
table = Table(title="Application Paths")
6971
table = get_routes_table(app, table)
7072
echo(table)
7173

7274

73-
def get_routes_table(app: Lilya | ChildLilya | None, table: Table) -> Table:
75+
def get_routes_table(app: BaseLilya | None, table: Table) -> Table:
7476
"""Prints the routing system"""
7577
table.add_column("Path", style=OutputColour.GREEN, vertical="middle")
7678
table.add_column("Path Parameters", style=OutputColour.BRIGHT_CYAN, vertical="middle")
@@ -79,7 +81,7 @@ def get_routes_table(app: Lilya | ChildLilya | None, table: Table) -> Table:
7981
table.add_column("HTTP Methods", style=OutputColour.RED, vertical="middle")
8082

8183
def parse_routes(
82-
app: Lilya | ChildLilya | Router | BasePath | None,
84+
app: BaseLilya | Router | BasePath | None,
8385
table: Table,
8486
route: Any | None = None,
8587
prefix: str | None = "",
@@ -100,7 +102,7 @@ def parse_routes(
100102
else:
101103
fn_type = "sync"
102104

103-
http_methods = ", ".join(sorted(route.methods))
105+
http_methods = ", ".join(sorted(route.methods or []))
104106
parameters = ", ".join(sorted(route.stringify_parameters))
105107
table.add_row(path, parameters, route.name, fn_type, http_methods)
106108
continue

lilya/cli/env.py

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,16 @@
66
from importlib import import_module
77
from pathlib import Path
88

9-
from lilya.apps import ChildLilya, Lilya
10-
from lilya.cli.constants import DISCOVERY_FILES, DISCOVERY_FUNCTIONS, LILYA_DISCOVER_APP
9+
from lilya.apps import BaseLilya, Lilya
10+
from lilya.cli.constants import (
11+
DISCOVERY_ATTRS,
12+
DISCOVERY_FILES,
13+
DISCOVERY_FUNCTIONS,
14+
LILYA_DISCOVER_APP,
15+
)
16+
from lilya.conf import _monkay
1117
from lilya.exceptions import EnvError
18+
from lilya.types import ASGIApp
1219

1320

1421
@dataclass
@@ -20,9 +27,10 @@ class Scaffold:
2027
"""
2128

2229
path: str
23-
app: Lilya | ChildLilya
30+
app: ASGIApp
2431
app_location: Path | None = None
2532
discovery_file: str | None = None
33+
lilya_app: BaseLilya | None = None
2634

2735

2836
@dataclass
@@ -41,17 +49,23 @@ class DirectiveEnv:
4149
"""
4250

4351
path: str | None = None
44-
app: Lilya | ChildLilya | None = None
52+
app: ASGIApp | None = None
53+
lilya_app: BaseLilya | None = None
4554
command_path: str | None = None
4655
module_info: ModuleInfo | None = None
4756

48-
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:
4960
"""
5061
Loads the environment variables into the scaffold.
5162
"""
5263
# Adds the current path where the command is being invoked
5364
# To the system path
54-
cwd = Path().cwd()
65+
if cwd is None:
66+
cwd = Path.cwd()
67+
if not isinstance(cwd, Path):
68+
cwd = Path(cwd)
5569
command_path = str(cwd)
5670
if command_path not in sys.path:
5771
sys.path.append(command_path)
@@ -68,6 +82,7 @@ def load_from_env(self, path: str | None = None) -> DirectiveEnv:
6882
return DirectiveEnv(
6983
path=_app.path,
7084
app=_app.app,
85+
lilya_app=_app.lilya_app,
7186
command_path=command_path,
7287
module_info=self.get_module_data_from_path(
7388
_app.app_location, _app.path, _app.discovery_file
@@ -126,7 +141,11 @@ def import_app_from_string(cls, path: str | None = None) -> Scaffold:
126141
module_str_path, app_name = path.split(":")
127142
module = import_module(module_str_path)
128143
app = getattr(module, app_name)
129-
return Scaffold(path=path, app=app)
144+
if isinstance(app, BaseLilya):
145+
lilya = app
146+
else:
147+
lilya = _monkay.instance
148+
return Scaffold(path=path, app=app, lilya_app=lilya)
130149

131150
def _get_folders(self, path: Path) -> list[str]:
132151
"""
@@ -151,20 +170,53 @@ def _find_app_in_folder(self, path: Path, cwd: Path) -> Scaffold | None:
151170

152171
# Iterates through the elements of the module.
153172
for attr, value in module.__dict__.items():
154-
if isinstance(value, Lilya):
173+
if isinstance(value, BaseLilya):
155174
app_path = f"{dotted_path}:{attr}"
156175
return Scaffold(
157-
app=value, path=app_path, app_location=path, discovery_file=discovery_file
176+
app=value,
177+
lilya_app=value,
178+
path=app_path,
179+
app_location=path,
180+
discovery_file=discovery_file,
158181
)
159182

183+
# Check if a lilya app has self registered and is just wrapped
184+
if (lilya_instance := _monkay.instance) is not None:
185+
# Iterate over default pattern application names
186+
for variable in DISCOVERY_ATTRS:
187+
if app_candidate := getattr(module, variable, None):
188+
app_path = f"{dotted_path}:{variable}"
189+
return Scaffold(
190+
app=app_candidate,
191+
lilya_app=lilya_instance,
192+
path=app_path,
193+
app_location=path,
194+
discovery_file=discovery_file,
195+
)
196+
160197
# Iterate over default pattern application functions
161198
for func in DISCOVERY_FUNCTIONS:
162-
if hasattr(module, func):
199+
if fn := getattr(module, func, None):
163200
app_path = f"{dotted_path}:{func}"
164-
fn = getattr(module, func)
165-
return Scaffold(
166-
app=fn(), path=app_path, app_location=path, discovery_file=discovery_file
167-
)
201+
app_candidate = fn()
202+
if isinstance(app_candidate, Lilya):
203+
return Scaffold(
204+
app=app_candidate,
205+
lilya_app=app_candidate,
206+
path=app_path,
207+
app_location=path,
208+
discovery_file=discovery_file,
209+
)
210+
211+
# monkay.instance is a dynamic property, so recheck
212+
if (lilya_instance := _monkay.instance) is not None:
213+
return Scaffold(
214+
app=app_candidate,
215+
lilya_app=lilya_instance,
216+
path=app_path,
217+
app_location=path,
218+
discovery_file=discovery_file,
219+
)
168220
return None
169221

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

0 commit comments

Comments
 (0)