fastapi-hypermodel icon indicating copy to clipboard operation
fastapi-hypermodel copied to clipboard

Fix an error when variable in links can't be added to url

Open exorevan opened this issue 11 months ago • 2 comments

Fix an error when links build with not internal model variable. Method resolve_param_values of AbstractHyperField deletes all rest parameters those are not got from HALHyperModel parameters. Now those parameters update original parameters dict.

fixes #104

Error example:

>>> from fastapi import FastAPI
>>> from fastapi_hypermodel import FrozenDict, HALFor, HALLinks, HALHyperModel
>>>
>>> app = FastAPI()
>>> @app.get(path="/rest/{process}/{operation}", response_model=int)
... def get_process(process: int, operation: str):
...     return process
... 
>>> class Process(HALHyperModel):
...     process: int
...     links: HALLinks = FrozenDict({"self": HALFor("get_process", {"process": "<process>", "operation": "wait"})})
... 
>>> Process(process=5)
Process(links=FrozenDict({'curies': []}), process=5)
>>> HALHyperModel.init_app(app)
>>> Process(process=5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/custom-statemachine-service/.venv/lib/python3.12/site-packages/pydantic/main.py", line 214, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/custom-statemachine-service/.venv/lib/python3.12/site-packages/fastapi_hypermodel/hal/hal_hypermodel.py", line 202, in add_links
    valid_links = self._validate_factory(link_, vars(self))
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/custom-statemachine-service/.venv/lib/python3.12/site-packages/fastapi_hypermodel/base/hypermodel.py", line 130, in _validate_factory
    element = element_factory(self._app, properties)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/custom-statemachine-service/.venv/lib/python3.12/site-packages/fastapi_hypermodel/hal/hal_hypermodel.py", line 119, in __call__
    uri_path = self._get_uri_path(
               ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/custom-statemachine-service/.venv/lib/python3.12/site-packages/fastapi_hypermodel/base/hypermodel.py", line 63, in _get_uri_path
    return UrlType(app.url_path_for(endpoint, **params))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/custom-statemachine-service/.venv/lib/python3.12/site-packages/starlette/applications.py", line 106, in url_path_for
    return self.router.url_path_for(name, **path_params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/custom-statemachine-service/.venv/lib/python3.12/site-packages/starlette/routing.py", line 661, in url_path_for
    raise NoMatchFound(name, path_params)
starlette.routing.NoMatchFound: No route exists for name "get_process" and params "process".

exorevan avatar Mar 20 '25 09:03 exorevan

Please add a unit test to show he new expected behaviour

Another thing is that it is unexpected that Hypernodels are used as path params

ELC avatar Mar 20 '25 10:03 ELC

Where should I put the test? Also what about #102

The example how it would work:

    from fastapi import FastAPI
    from fastapi_hypermodel import FrozenDict, HALFor, HALLinks, HALHyperModel
    
    app = FastAPI()

    @app.get(path="/rest/{process}/{operation}", response_model=int)
    def get_process(process: int, operation: str):
        return process

    class Process(HALHyperModel):
        process: int
        links: HALLinks = FrozenDict({
            "self": HALFor("get_process", {
                "process": "<process>",  # internal model parameter
                "operation": "wait"       # external hardcoded parameter
            })
        })
    
    HALHyperModel.init_app(app)

    process_instance = Process(process=5)

    assert "self" in process_instance.links
    assert process_instance.links["self"].href == "/rest/5/wait"

    process_instance_2 = Process(process=10)
    assert process_instance_2.links["self"].href == "/rest/10/wait"

exorevan avatar Nov 26 '25 18:11 exorevan