You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Support src layout and holm app in a subpackage (even with editable installs) #41
Copy application files from the actions-with-htmy example directly into the src folder.
Set up a poethepoet start task like this: start.cmd = "uvicorn main:app --reload" and start.cwd = "src"
Run uv run poe start
If I start this project, imports do fail.
As a workaround, please create a package in src and move the application code into that package. After that, you need to update the start command like this: start.cmd = "uvicorn <app-package-name>.main:app --reload". If you run uv run poe start now, the application will start as expected.
I'll try to fix this issue as soon as as I can, but a contribution would also be very welcome. FWIW: my current priority is to finalize async HTML streaming support in FastHX and then bring the feature to holm to optimize time to first byte and first contentful paint.
Thanks for helping @volfpeter.
Unfortunately, I don't have control on how the server is run, as it is part of a more complex project. For now, I created a link from my_project to src/my_project, but that will only work in development mode. I'll try and see if I can come up with a real fix.
Thanks for your work in the holm/htmy/FastHX stack, it's awesome.
Unfortunately, I don't have control on how the server is run
Ah, okay, I think I get the issue. Basically you need the main entry point to be specific Python module directly in src/.
The best solution/workaround for now is this:
Follow the instructions from my previous comment.
Add a main.py file directly in src/, it should contain a single line: from app.main import app as app.
Change start.cmd back to "main:app".
Run uv run poe start.
With this setup, you can have the main entry point directly in src/. The holm part of the application is located in the src/app (in my example, replace my_project with your example) package, but you have the ASGI app directly in src/main.py. The setup works because the app (my_project) package is on the Python search path, holm also works because the application is wrapped in a package, and you have the entry point exactly as you need it.
(The library uses wherever you have the holm.App() call as your application's root. The application's actual entry point can be in any other place, that's out of scope for holm. In the above case I moved it to src/main.py for uvicorn. You could also wrap the Holm call a factory function and pass a pre-configured FastAPI instance to it that's created somewhere else.)
I hope it helps. It feels better then setting up symlinks. It should work in production.
When it comes to debugging and fixing this issue, the problems is most likely in the holm._model.PackageInfo class. The import_module() method already handles the flat (not packaged) app structure as a special case, it's likely we need special handling for the flat structure with src layout case. My other candidate is from_marker_file.
I'll try to make some time to fix this over the coming days.
Thanks for your work in the holm/htmy/FastHX stack, it's awesome.
Thank you! This is very high praise. I'm happy you like these projects. If you don't mind me asking, please spread the word 🙂 The projects could really use a couple of contributors (especially documentation, which is quite lacking unfortunately).
Not sure what you're building with this stack and at what stage it is, but I have a htmy component library for BasecoatUI in the works. The components, examples, and the example holm app are actually ready, the missing part is the documentation... I got side-tracked with the HTML streaming feature. If you need early access to the component library, get in touch and I'll add you to the project.
I've fixed the problem and released version 0.7.2. The fix worked in my local test project (described in my first comment), please let me know if you still get an error. Also, let me know if you have any other issues with holm/htmy/fasthx.
Thanks for releasing v0.7.2. I tried it but it doesn't fix the issue on my side. I don't know what you mean by "src layout without package", there is indeed a package. Isn't the issue that app_dir here:
is set to my_repo/my_package, which doesn't exist because it's in my_repo/src/my_package? But anyway, this is the development layout, which will be different when installed, so I guess that this is wrong in the first place:
I've created 3 example applications with src layout, you can find all of them in this repo: https://github.com/volfpeter/bin (see the holm_41 dir). See the pyproject.toml for info on how I actually start them (uv run poe start).
The src-no-app-package one failed before holm 0.7.2, now they all work.
The one in the src-entry-point-and-app-package dir uses the init function pattern just to clearly differentiate it from the src-app-package example.
Could you please add minimal example of your project layout and also how to start the application? I could then replicate your case exactly and try to fix it. Thanks!
I didn't want to expose the internal config that's used to discover the app, because then people could expect all kinds of nested setups to work, which could become a pain to maintain. But this setup is sensible and needs this, so it should be supported.
The problem here is that the import root is in the application repo's root, but the application is in a subfolder. The setup only works because the app is installed in editable mode.
The best solution I came up with so far is to add a root_dir: Path | None = None argument to both App and AppConfig.default(). If not None, it's used within AppConfig.default() instead of Path.cwd(). After these simple changes in holm, all you would need to change in your app is to pass root_dir=Path(__file__).parent.parent to App(), and everything would work.
I've attached the holm diff (zipped, because GitHub didn't allow the diff upload...). Could you please test it when you have time? If it works, I'll create a PR and release a new version. Maybe I'll think a bit about whether there's an implementation that doesn't require manual root dir config. If you have an idea for that, please let me know 🙂
Yes, those are because holm tries to import page, layout, actions from every application package now, even if the modules don't exist. I'll restore and update the mentioned if statement, and then these logs will disappear.
This kind of layout (the holm app being in a subpackage) is not supported right now.
I'm not sure how to detect the correct root in this case for imports. Maybe it's possible with deeper call stack inspection. The manual option would be to expose the AppConfig class and make it possible to pass one to the App() function. That way you could use any config object, but even then, I'm not sure all cases would be supported by the internal discovery and path creation logic (maybe we'd need to extend the AppConfig class to handle every case).
If you want to test this approach, you can do so like this:
Add a config: AppConfig | None = None argument to the App() function.
Internally use the received AppConfig instance if there's one, otherwise create the default as it's done now.
Create an AppConfig instance manually that matches your project structure and pass it to App.
In App, you should always print packages variable to see which packages the lib recognized as an application component. This is the set of PackageInfo objects that's used to build the API. I'm mentioning this print, because first we must ensure the correct packages are discovered, then we must check whether the corresponding page, layout, etc. modules can be imported as expected, and finally we can check whether the correct routes are created for them correctly.
If it works, I'd try to implement the automatic discovery logic to handle this setup as well. I may give it a go when I have time. Maybe Claude could give it a go based on your description and the context above.
(I assume placing the holm app package next to my_app in src is not a viable solution for you. If it is, I'd actually recommend doing that.)
Maybe I'm missing something, but I am wondering why the layout discovery is based on packages in the first place. Couldn't it just be based on a root directory?
changed the title [-]Support src layout (even with editable installs)[/-][+]Support src layout and holm app in a subpackage (even with editable installs)[/+]on Jan 3, 2026
@davidbrochart I've opened #45 that adds support for having the holm app in a subpackage (could be deep in the package hierarchy.
I tested it with various src and flat layouts, and also the editable install setup you've created in my "bin" repo. Everything seems to work. Could you please test whether the PR solves the issue in your local setup? If it does, I'll merge the PR and release a new patch version. Here's an example app that only works with the change in linked PR: https://github.com/volfpeter/bin/tree/main/holm_41/app-in-subpackage
Activity
coderabbitai commentedon Dec 22, 2025
volfpeter commentedon Dec 22, 2025
Hi David,
Thanks for giving
holma go and for reporting the issue.You ran into a (so far) half-handled edge case: using the src layout without an application package in it.
I've created an example project from the
actions-with-htmyexample like this:uv inita new projectuv add holm uvicorn poethepoetsrcdir in projectactions-with-htmyexample directly into thesrcfolder.poethepoetstart task like this:start.cmd = "uvicorn main:app --reload"andstart.cwd = "src"uv run poe startIf I start this project, imports do fail.
As a workaround, please create a package in
srcand move the application code into that package. After that, you need to update the start command like this:start.cmd = "uvicorn <app-package-name>.main:app --reload". If you runuv run poe startnow, the application will start as expected.I'll try to fix this issue as soon as as I can, but a contribution would also be very welcome. FWIW: my current priority is to finalize async HTML streaming support in FastHX and then bring the feature to
holmto optimize time to first byte and first contentful paint.davidbrochart commentedon Dec 22, 2025
Thanks for helping @volfpeter.
Unfortunately, I don't have control on how the server is run, as it is part of a more complex project. For now, I created a link from
my_projecttosrc/my_project, but that will only work in development mode. I'll try and see if I can come up with a real fix.Thanks for your work in the
holm/htmy/FastHXstack, it's awesome.volfpeter commentedon Dec 22, 2025
Ah, okay, I think I get the issue. Basically you need the main entry point to be specific Python module directly in
src/.The best solution/workaround for now is this:
main.pyfile directly insrc/, it should contain a single line:from app.main import app as app.start.cmdback to"main:app".uv run poe start.With this setup, you can have the main entry point directly in
src/. Theholmpart of the application is located in thesrc/app(in my example, replacemy_projectwith your example) package, but you have the ASGI app directly insrc/main.py. The setup works because theapp(my_project) package is on the Python search path,holmalso works because the application is wrapped in a package, and you have the entry point exactly as you need it.(The library uses wherever you have the
holm.App()call as your application's root. The application's actual entry point can be in any other place, that's out of scope forholm. In the above case I moved it tosrc/main.pyforuvicorn. You could also wrap theHolmcall a factory function and pass a pre-configured FastAPI instance to it that's created somewhere else.)I hope it helps. It feels better then setting up symlinks. It should work in production.
When it comes to debugging and fixing this issue, the problems is most likely in the
holm._model.PackageInfoclass. Theimport_module()method already handles the flat (not packaged) app structure as a special case, it's likely we need special handling for the flat structure with src layout case. My other candidate isfrom_marker_file.I'll try to make some time to fix this over the coming days.
Thank you! This is very high praise. I'm happy you like these projects. If you don't mind me asking, please spread the word 🙂 The projects could really use a couple of contributors (especially documentation, which is quite lacking unfortunately).
Not sure what you're building with this stack and at what stage it is, but I have a
htmycomponent library for BasecoatUI in the works. The components, examples, and the exampleholmapp are actually ready, the missing part is the documentation... I got side-tracked with the HTML streaming feature. If you need early access to the component library, get in touch and I'll add you to the project.volfpeter commentedon Dec 22, 2025
Hi again,
I've fixed the problem and released version 0.7.2. The fix worked in my local test project (described in my first comment), please let me know if you still get an error. Also, let me know if you have any other issues with
holm/htmy/fasthx.davidbrochart commentedon Dec 23, 2025
Hi @volfpeter,
Thanks for releasing v0.7.2. I tried it but it doesn't fix the issue on my side. I don't know what you mean by "src layout without package", there is indeed a package. Isn't the issue that
app_dirhere:holm/holm/_model.py
Line 57 in 4df765b
is set to
my_repo/my_package, which doesn't exist because it's inmy_repo/src/my_package? But anyway, this is the development layout, which will be different when installed, so I guess that this is wrong in the first place:holm/holm/_model.py
Line 47 in 4df765b
volfpeter commentedon Dec 23, 2025
I've created 3 example applications with src layout, you can find all of them in this repo: https://github.com/volfpeter/bin (see the
holm_41dir). See thepyproject.tomlfor info on how I actually start them (uv run poe start).The
src-no-app-packageone failed beforeholm0.7.2, now they all work.The one in the
src-entry-point-and-app-packagedir uses the init function pattern just to clearly differentiate it from thesrc-app-packageexample.Could you please add minimal example of your project layout and also how to start the application? I could then replicate your case exactly and try to fix it. Thanks!
davidbrochart commentedon Dec 23, 2025
Thanks, I opened volfpeter/bin#1.
volfpeter commentedon Dec 23, 2025
Thanks! I've merged the PR and set it up locally. I'll try to have another look at the issue today and hopefully fix it. Maybe just at night.
volfpeter commentedon Dec 23, 2025
Okay, I had a few minutes and got it working.
I didn't want to expose the internal config that's used to discover the app, because then people could expect all kinds of nested setups to work, which could become a pain to maintain. But this setup is sensible and needs this, so it should be supported.
The problem here is that the import root is in the application repo's root, but the application is in a subfolder. The setup only works because the app is installed in editable mode.
The best solution I came up with so far is to add a
root_dir: Path | None = Noneargument to bothAppandAppConfig.default(). If notNone, it's used withinAppConfig.default()instead ofPath.cwd(). After these simple changes inholm, all you would need to change in your app is to passroot_dir=Path(__file__).parent.parenttoApp(), and everything would work.I've attached the
holmdiff (zipped, because GitHub didn't allow the diff upload...). Could you please test it when you have time? If it works, I'll create a PR and release a new version. Maybe I'll think a bit about whether there's an implementation that doesn't require manual root dir config. If you have an idea for that, please let me know 🙂holm-src-editable-install.zip
davidbrochart commentedon Dec 24, 2025
Thanks for the patch @volfpeter.
I tried it with https://github.com/volfpeter/bin/tree/main/holm_41/src-app-package2 but it doesn't work. I called
App(root_dir=Path(__file__).parent.parent), but maybe I'm missing something else?But even if that worked, it would break the non-editable install, right?
5 remaining items
davidbrochart commentedon Dec 27, 2025
It works indeed, although with a lot of
ModuleNotFoundError.volfpeter commentedon Dec 27, 2025
Great! Thanks for testing the patch!
Yes, those are because
holmtries to importpage,layout,actionsfrom every application package now, even if the modules don't exist. I'll restore and update the mentionedifstatement, and then these logs will disappear.feat: add support for src layout with editable install and run script
volfpeter commentedon Dec 27, 2025
I've released version 0.7.3 with the fix. Let me know if you have any issues after the upgrade.
davidbrochart commentedon Dec 27, 2025
Thanks a lot, everything seems to work fine.
volfpeter commentedon Dec 27, 2025
Perfect! And thanks for reporting the issue. Have fun with
holm.davidbrochart commentedon Jan 2, 2026
I'm running into a similar issue, but I'm not sure I'm missing something. I have a src layout in which my holm app is in a subdirectory, for instance:
I'd like to create my holm app in
app.py, is it currently supported?volfpeter commentedon Jan 2, 2026
This kind of layout (the holm app being in a subpackage) is not supported right now.
I'm not sure how to detect the correct root in this case for imports. Maybe it's possible with deeper call stack inspection. The manual option would be to expose the
AppConfigclass and make it possible to pass one to theApp()function. That way you could use any config object, but even then, I'm not sure all cases would be supported by the internal discovery and path creation logic (maybe we'd need to extend theAppConfigclass to handle every case).If you want to test this approach, you can do so like this:
config: AppConfig | None = Noneargument to theApp()function.AppConfiginstance if there's one, otherwise create the default as it's done now.AppConfiginstance manually that matches your project structure and pass it toApp.In
App, you should always printpackagesvariable to see which packages the lib recognized as an application component. This is the set ofPackageInfoobjects that's used to build the API. I'm mentioning this print, because first we must ensure the correct packages are discovered, then we must check whether the corresponding page, layout, etc. modules can be imported as expected, and finally we can check whether the correct routes are created for them correctly.If it works, I'd try to implement the automatic discovery logic to handle this setup as well. I may give it a go when I have time. Maybe Claude could give it a go based on your description and the context above.
(I assume placing the holm app package next to
my_appinsrcis not a viable solution for you. If it is, I'd actually recommend doing that.)davidbrochart commentedon Jan 2, 2026
Maybe I'm missing something, but I am wondering why the layout discovery is based on packages in the first place. Couldn't it just be based on a root directory?
feat: support application layouts where the holm app is in a subpackage,
[-]Support src layout (even with editable installs)[/-][+]Support src layout and holm app in a subpackage (even with editable installs)[/+]volfpeter commentedon Jan 3, 2026
@davidbrochart I've opened #45 that adds support for having the holm app in a subpackage (could be deep in the package hierarchy.
I tested it with various src and flat layouts, and also the editable install setup you've created in my "bin" repo. Everything seems to work. Could you please test whether the PR solves the issue in your local setup? If it does, I'll merge the PR and release a new patch version. Here's an example app that only works with the change in linked PR: https://github.com/volfpeter/bin/tree/main/holm_41/app-in-subpackage
davidbrochart commentedon Jan 3, 2026
Works like a charm, thanks so much for your help again @volfpeter!
volfpeter commentedon Jan 3, 2026
Great, thanks for testing. I've already started the release, v0.7.4 should be out momentarily.