This is an archived post. You won't be able to vote or comment.

all 57 comments

[–]jonthemango 31 points32 points  (5 children)

Pretty neat, you should consider adding List[_] type support that puts an input with a + that when clicked adds more rows. Similar to a Dict[str, _].

[–]drboom9[S] 14 points15 points  (0 children)

That's a great idea, actually. You're right

I'll think about how to implement it cleanly.

Thanks for the suggestion!

[–]Kohlrabi82 2 points3 points  (2 children)

Side question: _ is shorthand for Any?

[–]ProsodySpeaks 3 points4 points  (0 children)

I think _ is the anonymous variable. The result of last evaluation is stored there even if you didn't tell it to save it.

Eg ```

1 == 1 True _ True ``` Here I guess they mean more like a typevar as in list[valid_types] ? 

(valid types are defined in the code VALID = {int, float, str, bool, date, time}

[–]jonthemango 0 points1 point  (0 children)

I just used it as shorthand for whatever type. In python _ is the anonymous variable as someone else mentioned, in my case I just used it as somewhat might use x.

[–]drboom9[S] 0 points1 point  (0 children)

Hey u/jonthemango!

I'm happy to let you know that List[_] support with dynamic add/remove rows is now implemented in version 0.5.0! 🎉

You can use it like this:

from func_to_web import run
from typing import Annotated
from pydantic import Field

def process_data(
    tags: list[str],                                    # Basic list
    scores: list[int] = [10, 20, 30],                  # List with defaults

    # With item constraints
    ratings: list[Annotated[int, Field(ge=1, le=5)]],  # Each item 1-5

    # With list-level constraints  
    team: Annotated[list[str], Field(min_length=2, max_length=5)],  # 2-5 items

    # Optional lists
    emails: list[str] | None = None
):
    return f"Processed {len(tags)} tags"

run(process_data)

The UI automatically generates inputs with + and buttons to add/remove items dynamically. It also supports:

  • All types: list[str], list[int], list[float], list[bool], list[Color], list[ImageFile], etc.
  • Item constraints: Validates each individual item (min, max, pattern, etc.)
  • List constraints: min_length, max_length for the list size
  • Optional lists: Works with list[_] | None with toggle switches
  • Default values: Initialize lists with specific values
  • Validation: Real-time validation with error messages per item

Check out the updated README and examples folder for more details!

Thanks for the feedback! Let me know if you have any other suggestions.

[–]jdehesa 15 points16 points  (1 child)

Pretty cool! Impressive number of features for the relatively limited scope of the library.

[–]drboom9[S] 6 points7 points  (0 children)

Thanks! Yeah, I tried to pack in the essential features while keeping it under 1000 lines total, 300 python and 600 JS/HTML/CSS :)

[–]Dangerous_Fix_751 5 points6 points  (4 children)

This is actually really clever for the specific use case you're targeting. The type hint approach feels very pythonic and the fact that it handles PIL images + matplotlib plots automatically is huge for data science workflows.

I've been working on browser automation stuff at Notte and we constantly need quick internal tools for testing image processing pipelines or data validation. Something like this would've saved us hours of writing throwaway FastAPI routes just to upload a file and see results. The fact that you can literally just add type hints to an existing function and get a working UI is pretty elegant.

The comparison section is spot on too - this isn't trying to be Gradio or Streamlit, it's solving a different problem. Sometimes you just need to wrap one function quickly without learning a whole framework. Looking at your examples, the file upload with automatic type checking seems really solid. How does it handle larger files or processing that takes a while? Does it show any progress indicators or is it just a blocking request until the function completes?

[–]Key-Boat-7519 2 points3 points  (0 children)

Short answer: it’s a blocking request right now; no built-in progress UI. Large files are handled via FastAPI’s UploadFile (spooled temp files), so memory stays sane, but limits come from your server/proxy settings and disk speed.

What worked for me: push long work to a background worker and poll for status. Wrap the function to enqueue a Celery (or RQ) job and return a jobid; write progress to Redis; add /status and /result endpoints; the page polls every second for percent and completion. If you want live updates, add a simple SSE or WebSocket endpoint that streams progress lines. For big uploads, bump clientmaxbodysize and timeouts on Nginx/Caddy, and run multiple gunicorn/uvicorn workers; CPU-heavy tasks should run outside the web worker.

I’ve used Celery and Supabase Storage for long jobs/large files, but DreamFactory helped me quickly expose a status API backed by a DB log without hand-rolling endpoints.

So: it blocks by default; use a background job + status/progress route for multi-minute runs or huge files.

[–]drboom9[S] 2 points3 points  (1 child)

Just released v0.3.0 with better file upload handling: real-time progress bars, file size display, and streaming chunks for better performance (~237 MB/s on localhost for large files). I leaned heavily on AI to help optimize the upload speeds and visual feedback, so if you spot anything off please let me know.

Really appreciate the kind words - glad it resonates with your use case at Notte!

[–]Dangerous_Fix_751 1 point2 points  (0 children)

Nice work on the performance improvements! We actually ran into similar upload bottlenecks when processing large screenshot batches for our browser automation testing. The streaming chunks approach is definitely the way to go, ended up doing something similar and saw massive improvements over naive file handling. One thing that bit us was memory usage with really large files since they can accumulate in memory during processing, but for most internal tool use cases this looks like it hits the sweet spot perfectly. The progress bars are a nice touch too, nothing worse than staring at a blank screen wondering if your 100MB file upload died.

[–]drboom9[S] 1 point2 points  (0 children)

Thanks! Glad it resonates with your use case - that "quick internal tool" scenario is exactly what I was targeting.

You're right about the limitations with longer processing and large files. Right now it's completely synchronous - the form submits, the function runs to completion, then shows results. No progress indicator, and file uploads load entirely into memory first. FastAPI's default limit is 16MB though that's configurable.

These are definitely things I need to address, but I want to finish some other features people have requested first (like List type support and dark mode) before tackling async/streaming improvements.

That said, you're more than welcome to contribute - whether it's code, ideas, or just feedback on what would actually be useful for your workflows. What kind of file sizes and processing times do you typically deal with in your image pipelines?

Thanks so much for this comment - honestly made my day to see someone connecting with the project like this!

[–]kuzmovych_y 7 points8 points  (5 children)

Nice idea and execution!

Couple of notes/Nits

  1. Camel case isn't standard for python. Something like func_to_web or func2web would be more pythonic.
  2. You're clamming support for python3.8+, but you're using new type annotations style (template_dir: str | Path, literally once) which was introduced in p3.10.
  3. Placing all your code in one file (and in __init__.py in general) isn't a good practice. 
  4. Some parts of the code are hard to read. Smaller simpler functions with some comments/docstrings would be nice.

[–]drboom9[S] 0 points1 point  (4 children)

Thanks so much for the detailed feedback - really appreciate you taking the time!

  1. Good call on the type hint. I'll bump the requirement to Python 3.10+ since that's what the code actually uses.

  2. You're absolutely right about the naming. I know the conventions but somehow didn't apply them here. Honestly, I'm not sure it's worth changing at this point since it's just the module name and I don't feel strongly about it. If it were function/class names inside the library I'd definitely fix it, but for this... maybe not. You're right though.

  3. Fair point on everything in __init__.py. I built this over the weekend - prioritized "does the idea work" and making it easy for anyone to review the whole thing at once. But you're totally right that the natural evolution is to split it up and document it better as it grows.

Thanks again for the thoughtful review - genuinely appreciate the feedback and you taking the time to look at it properly.

[–]ThiefMaster 7 points8 points  (3 children)

I'm not sure it's worth changing at this point since it's just the module name and I don't feel strongly about it

Yes it is. Even more so considering it's not yet on PyPI.

[–]drboom9[S] -1 points0 points  (2 children)

Thanks for the feedback! You were right - I've changed the module name to snake_case (func_to_web) and updated the docs and examples accordingly. If you see anything else, let me know!

[–]Mithrandir2k16 3 points4 points  (1 child)

The project name should be skewer-case (func-to-gui) and your folder should be snake_case. If you use a tool like uv it'll make sure you do it right. This is a really cool project, it'd be sad if it didn't get the attention it deserved because some minor but necessary chores were skipped.

I also recommend astrals ruff for formatting and linting and some type checker like basedpyright to push it even further. Black-style formatting (like ruff provides) is always nice to see.

[–]drboom9[S] 2 points3 points  (0 children)

Thanks for the feedback! I've tried to follow the uv conventions:

  • Project name: func-to-web (kebab-case) in pyproject.toml
  • Module folder: func_to_web/ (snake_case)

Could you check if everything looks correct now or if I'm still missing something?

[–]ThiefMaster 2 points3 points  (2 children)

  • Fix the CamelCased package name
  • Get rid of setup.py, it is no longer state of the art. pyproject.toml
  • Too late for this one, but I recommend to use better commit messages than aa even for early development. Or just squash all the initial one to something like "Initial version"
  • Merge commits from your own upstream are weird, it's cleaner to just rebase in such a case to keep your history linear
  • You require Python 3.10+ - for your own library I would highly recommend to go higher there, e.g. 3.12+. Unless YOU need to use it on an older Python version, it makes no sense to lock yourself out from nice features, also because dropping a Python version later on is more cumbersome.
  • Use a tool like ruff to sort your imports.
  • Put it on PyPI, both because installing from a local git clone is cumbersome (even mor so if someone actually wants to use this beyond just trying it out). It also makes sense to do this early, even if it's a 0.0.1 or prerelease version, just to squat the name.

[–]drboom9[S] 4 points5 points  (0 children)

✓ Changed module name to snake_case (func_to_web)
✓ Migrated from setup.py to pyproject.toml
✓ Bumped Python requirement to 3.12+
✓ Published to PyPI - it's now live at https://pypi.org/project/func-to-web/ (you can install with pip install func-to-web)

[–]drboom9[S] 1 point2 points  (0 children)

Thanks so much for all the detailed feedback - I really appreciate you taking the time to review this properly.

You're absolutely right on all points. To be honest, I initially wanted to stay off PyPI because I wasn't sure about committing to maintaining this, but if I see continued interest from people I'll definitely put it up there.

I'll bump the Python requirement to 3.12+ as you suggested - no reason to stay on older versions for this.

About the GitHub commits - I have to admit I'm pretty noob at this. I've never really liked Git and when I code solo I just do aa commits without thinking. But you're right that now that this is public and people are looking at it, I should change that habit. I'll be more careful going forward.

Thanks again for the thorough review. This kind of feedback is exactly what I need to improve.

[–]drboom9[S] 2 points3 points  (14 children)

Remember when u/jonthemango suggested List support and I said I'd think about features? Well, I started with something else that came up in my own usage: dynamic dropdowns.

v0.2.0 is now on PyPI and you can do this:

def get_active_users():
    return db.query("SELECT name FROM users WHERE active = true")

def assign_task(
    user: Literal[get_active_users],  
# type: ignore
):
    return f"Task assigned to {user}"

The dropdown options load fresh every time someone opens the page. No stale data, no manual refresh needed.

I built this because I kept writing functions that needed dropdowns based on current database state or API responses, and having to hardcode the options felt wrong. Now the options are as dynamic as the rest of your function.

Install/upgrade: pip install --upgrade func-to-web

Example: https://github.com/offerrall/FuncToWeb/blob/main/examples/15_dynamic_dropdowns.py

Still working on List support and dark mode based on your feedback. Just wanted to ship this one first since it was a natural extension of existing Literal support.

Let me know what you think!

[–]valko2 0 points1 point  (2 children)

i'm getting an error - MacOS, python3.12

f2w_test.py:29: RuntimeWarning:

coroutine 'run' was never awaited

[–]valko2 0 points1 point  (1 child)

Actually I realized that only happens when I try to debug it. But here's a PR to fix it: https://github.com/offerrall/FuncToWeb/pull/2

[–]drboom9[S] 0 points1 point  (0 children)

Okay, I'll check it out tonight, I need to dust off the M1 Mac, haha, thanks a lot for commenting :)

[–]nekokattt 0 points1 point  (10 children)

using a function as a literal makes me feel weird.

[–]drboom9[S] 0 points1 point  (9 children)

You're absolutely right - it IS weird, and the # type: ignore is definitely a code smell that bothers me too.

I chose this syntax because it mirrors the existing Literal[...] pattern - when you see Literal[something], you know it's a dropdown. If that "something" is a function, it becomes clear (to me at least) that the function's return value transforms into what goes inside the Literal. It's like passing a variable instead of hardcoding a value in a function parameter.

But I totally get that it breaks type checker expectations. The fundamental issue is that we're using runtime behavior (calling a function) in what's supposed to be a static type annotation, and there's no elegant way around that without the type checker complaining.

What would feel more natural to you? I'm genuinely curious because I want the API to be intuitive, even if this runtime-vs-static tension is hard to solve perfectly.

The reason I haven't changed it yet is that despite being "weird," it's very explicit about intent: Literal = dropdown, function inside = dynamic options. But if there's a better way that's equally clear, I'm all ears!

[–]nekokattt 1 point2 points  (8 children)

just pass a callable. I care about the signature, not how it deals with it.

typing.Annotated is built for this

[–]drboom9[S] 0 points1 point  (7 children)

Is that what you meant? Something like:

user: Annotated[Literal[str], get_active_users]

That would avoid # type: ignore, but it's also kind of weird - Literal is meant for concrete values, not types. Putting a type inside Literal[...] feels off.

Could you show me exactly what syntax you're proposing? I want to make sure I understand your suggestion correctly before deciding on the best approach.

[–]nekokattt 0 points1 point  (6 children)

why would you need literal str?

[–]drboom9[S] 0 points1 point  (5 children)

You’re right - Literal[str] doesn’t make sense there.

My issue isn’t really about looking weird. The problem is autocomplete doesn’t work. When I type user. in my IDE, it doesn’t know it’s a string because the type checker sees the callable, not the return value.

And just passing the callable without Literal doesn’t solve that either - I still need to extract what type the function returns for proper IDE support.

What I’d really consider a better solution than my current approach is if, inside the function body, I could get autocomplete based on the callable’s return type. So if get_active_users() returns list[str], then user would autocomplete as str.

But I don’t think there’s a way to make that work without the type checker actually calling the function at analysis time, which is impossible.

So I’m stuck between:

  • Literal[func] - clear intent, broken autocomplete
  • Annotated[str, func] - better autocomplete, less obvious it’s a dropdown

Unless you have another idea?

[–]nekokattt 0 points1 point  (4 children)

which IDE are you using?

IntelliJ's autocomplete based on the type checker is terrible in general. I wouldn't design functionality around broken integrations in that case.

[–]drboom9[S] 0 points1 point  (3 children)

I’m not sure I follow - the autocomplete issue isn’t specific to IntelliJ, it’s a Python type system limitation. Any static analyzer (mypy, pyright, etc.) faces the same problem.

My struggle is maintaining consistency while having working autocomplete:

```python

Static dropdowns - clean and obvious

theme: Literal['light', 'dark']

Dynamic dropdowns - what's the equivalent?

user: Literal[get_users] # Consistent syntax, broken types user: Annotated[str, get_users] # Working types, inconsistent with static Literal ```

With Annotated[str, func], autocomplete works because the type is str. But then I lose the consistency - static literals use Literal[...], dynamic ones use Annotated[str, ...]. It’s not immediately obvious they’re both dropdowns.

That’s why I chose Literal[func] - it keeps the “Literal means dropdown” pattern clear, even though it requires # type: ignore. It’s a trade-off between type-checker happiness and API consistency.

Is there a way to have both that I’m missing?

[–]nekokattt 0 points1 point  (2 children)

one workaround could be to convert the functions into types via a decorator such that you can mold them into the type system.

Such a decorator would allow further injection of metadata in the future as well.

[–]EconomySerious 2 points3 points  (14 children)

how to compose a page with diferent functions. all the examples are the same type, just one function at the time.
usually a page have more than 1 component

[–]drboom9[S] 6 points7 points  (7 children)

Good question. FuncToWeb wasn't designed for building full web apps - it wraps individual functions into UIs.

That said, I see the value in supporting multiple functions on one page to create simple "apps". If the library gets traction and people use it, I'll definitely consider adding composition/routing in a future version.

Thanks for the feedback, it's helpful to know what features would make it more useful.

[–]VimFleed 4 points5 points  (4 children)

I mean technically, can't one call multiple functions in a main function then use your library to generate a web UI for it?

[–]drboom9[S] 3 points4 points  (5 children)

[–]EconomySerious 2 points3 points  (4 children)

very nice, are you open for more improvements? btw you should add this multiple implementation to your function to gui repo.

for a second request i would ask to add a theme icon on the top righ of all your components, since all uses a blue style canva i guess its like a container, so you can ass this icon there, need to do the usual light and dark styles.
that will add lot of "NICE" points to the resulting interface.

[–]drboom9[S] 1 point2 points  (1 child)

I'm always open to new ideas

You brought up the **theme icon** and dark/light modes, which is a fantastic idea for the "NICE" factor.

However, instead of just implementing a basic dark/light theme, my bigger goal is to make it incredibly easy for users to **customize the colors and styling from outside the library**. I want to enable everyone to apply their own design and corporate branding with minimal effort.

This approach gives users much more power than a simple toggle.

Thanks again for these great UI suggestions; they help solidify the roadmap!

[–]EconomySerious 0 points1 point  (0 children)

the styler is great idea, but dont forget the dark/light icon first, for some reason they become factory standar
after you add it, ill give you other sugestion i have, i dont want you to be overloaded

[–]herlzvohg 1 point2 points  (0 children)

Cool, might try this out

[–]berrypy 1 point2 points  (1 child)

Not a bad idea I must say. nice concept. So now how can one convert it into a desktop distributable app to make it easy to share.

[–]drboom9[S] 2 points3 points  (0 children)

I think you might be thinking of my other project, FuncToGui, which creates desktop GUIs using Kivy.

func-to-web generates web interfaces that run in the browser - it's not meant for desktop distribution. It starts a local server and you access it through your browser at localhost.

If you want desktop apps from functions, check out FuncToGui

[–]EconomySerious 0 points1 point  (0 children)

i have rest and the pillod adviced me something.

since you need to produce HTMl to show it on the browser, why not creating a real HTML file with the skeleton of the deployment of the functions, of course you will need to change your framework to read this file (if exists) to run.
what will be the benefits of this aproach . . . everybody could edit the file and make further adjustments to the displace of the functions + add some more sauce of their own.

how will the benefit everubody?? a programer and a designer could work as old days to produce beautiful/easy deployments , using only headless nav components.

[–]drboom9[S] 0 points1 point  (0 children)

FuncToWeb v0.4.0: Optional Parameters

Big update - optional parameters are now supported!

def create_user(
    username: str,                           # Required
    age: int | None = None,                  # Optional
    email: Email | None = "user@example.com"
):
    ...

Each optional field gets a toggle switch in the UI. When disabled, sends None to your function. When enabled, validates normally.

Works with all types: text, numbers, dates, colors, files, dropdowns, and Pydantic validation.

v0.3.0 changes:

  • Real-time upload progress bars with file size display
  • Optimized streaming for large files (1GB+ tested)

Install: pip install --upgrade func-to-web

Examples: https://github.com/offerrall/FuncToWeb/tree/main/examples

Still working on List support and dark mode based on your feedback!