Skip to content

Feature request: preserve function parameter names for improved compatibility with Python libraries #1212

@ikappaki

Description

@ikappaki

(See https://clojurians.slack.com/archives/C071RFV2Z1D/p1734990790884169 for context)

Hi,

There appear to exist Python libraries that provide function decorators which expect the function they decorate to have specific parameter names.

One such library is FastAPI. Below is an example taken from the FastAPI documentation:

It creates two endpoints:

  • The / endpoint will return the dictionary {"message": "World"} dict (.e.g. at http://localhost/).
  • The /items/{itemd_id} endpoint will return a dictionary with the item_id, e.g. at http://localhost/items/abcd it will return {"item_id": "abcd"}
@app.get("/")
def read_root():
    return {"message": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}

In this example, the {item_id} in the decorator corresponds to the item_id parameter in the function. This allows FastAPI to bind the path parameter to the function parameter automatically.

However, Basilisp appears to modify function parameter names by appending a suffix through the basilisp.lang.util.genname function. As a result, a hypothetical parameter like abc would become abc_nnn when compiled down to Python. This makes it impossible to align the parameter names with those expected by decorators from libraries like FastAPI.

For instance, if you try to use the following namespace with a FastAPI decorator that expects a name parameter:

(ns basilex-fastapi.ex
  (:import [fastapi :as f]
           [uvicorn :as uv]))

(defonce app (f/FastAPI))

(defn root  {:async true
             :decorators [(.get app "/")]}
  []
  {"message" "Hi there"})

(defn hello  {:async true
              :decorators [(.get app "/hello/{name}")]}
  [name]
  {"name" name})

(comment
  (def server (future (uv/run app ** :host "127.0.0.1" :port 8000))))

The resulting Python code will fail to bind the path parameter name to the function parameter because Basilisp renames the name parameter to name_nnn when creating the function. Therefore, FastAPI will not find a name parameter to bind the value passed in the URL (e.g., http://localhost:8000/hello/xyz).

This issue arises due to Basilisp’s use of genname, which changes the parameter names. The

arg_name = genname(munge(binding.name))
snippet shows how genname is used to alter parameter names:

def __fn_args_to_py_ast(
    ctx: GeneratorContext, params: Iterable[Binding], body: Do
) -> tuple[list[ast.arg], Optional[ast.arg], list[ast.stmt], Iterable[PyASTNode]]:
    """Generate a list of Python AST nodes from function method parameters."""
    fn_args, varg = [], None
    fn_body_ast: list[ast.stmt] = []
    fn_def_deps: list[PyASTNode] = []
    for binding in params:
        assert binding.init is None, ":fn nodes cannot have binding :inits"
        assert varg is None, "Must have at most one variadic arg"
        arg_name = genname(munge(binding.name))
#...

It's unclear why genname is essential in this context. If I remove genname (but keep the mungeing), the isolated example works as expected. However, removing genname causes Basilisp to fail to bootstrap when recompiling the codebase from scratch.

Is there a way to bypass the function parameter name uniquefication process in some cases, particularly when it doesn't appear to be required?

One possible approach would be to introduce metadata to indicate that a parameter name should be preserved. For example, we could use a metadata marker like :preserve-parameter-name to advise the Basilisp generator not to modify the parameter name during compilation.

(defn hello  {:async true
                    :decorators [(.get app "/hello/{name}")]}
  [^:preserve-parameter-name name]
  {"name" name})

Thanks

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions