|
1 | | -"""BLOB Output Module. |
| 1 | +r"""BLOB Output Module. |
2 | 2 |
|
3 | 3 | The ``.Blob`` class is used when you need to return something file-like that can't |
4 | 4 | easily (or efficiently) be converted to JSON. This is useful for returning large objects |
@@ -36,6 +36,49 @@ def get_image(self) -> MyImageBlob: |
36 | 36 | action outputs may be retrieved multiple times after the action has |
37 | 37 | completed, possibly concurrently. Creating a temp folder and making a file inside it |
38 | 38 | with `.Blob.from_temporary_directory` is the safest way to deal with this. |
| 39 | +
|
| 40 | +**Serialisation** |
| 41 | +
|
| 42 | +`.Blob` objects are serialised to a JSON representation that includes a download |
| 43 | +``href``\ . This is generated using `.middleware.url_for` which uses a context |
| 44 | +variable to pass the function that generates URLs to the serialiser code. That |
| 45 | +context variable is available in every response handler function in the FastAPI |
| 46 | +app - but it is not, in general, available in action or property code (because |
| 47 | +actions and properties run their code in separate threads). The sequence of events |
| 48 | +that leads to a `Blob` being downloaded as a result of an action is roughly: |
| 49 | +
|
| 50 | +* A `POST` request invokes the action. |
| 51 | + * `.middleware.url_for.url_for_middleware` makes `url_for` accessible via |
| 52 | + a context variable |
| 53 | + * A `201` response is returned that includes an ``href`` to poll the action. |
| 54 | + * Action code is run in a separate thread (without `url_for` in the context): |
| 55 | + * The action creates a `.Blob` object. |
| 56 | + * The function that creates the `.Blob` object also creates a `.BlobData` |
| 57 | + object as a property of the `.Blob` |
| 58 | + * The `.BlobData` object's constructor adds it to the ``blob_manager`` and |
| 59 | + sets its ``id`` property accordingly. |
| 60 | + * The `.Blob` is returned by the action. |
| 61 | + * The output value of the action is stored in the `.Invocation` thread. |
| 62 | +* A `GET` request polls the action. Once it has completed: |
| 63 | + * `.middleware.url_for.url_for_middleware` makes `url_for` accessible via |
| 64 | + a context variable |
| 65 | + * The `.Invocation` model is returned, which includes the `.Blob` in the |
| 66 | + ``output`` field. |
| 67 | + * FastAPI serialises the invocation model, which in turn serialises the `.Blob` |
| 68 | + and uses ``url_for`` to generate a valid download ``href`` including the ``id`` |
| 69 | + of the `.BlobData` object. |
| 70 | +* A further `GET` request actually downloads the `.Blob`\ . |
| 71 | +
|
| 72 | +This slightly complicated sequence ensures that we only ever send URLs back to the |
| 73 | +client using `url_for` from the current `.fastapi.Request` object. That means the |
| 74 | +URL used should be consistent with the URL of the request - so if an action is |
| 75 | +started by a client using one IP address or DNS name, and polled by a different |
| 76 | +client, each client will get a download ``href`` that matches the address they are |
| 77 | +already using. |
| 78 | +
|
| 79 | +In the future, it may be possible to respond directly with the `.Blob` data to |
| 80 | +the original `POST` request, however this only works for quick actions so for now |
| 81 | +we use the sequence above, which will work for both quick and slow actions. |
39 | 82 | """ |
40 | 83 |
|
41 | 84 | from __future__ import annotations |
|
0 commit comments