|
56 | 56 | from ..utils import BrokenLink, ensure_awaitable, patch_mimetypes, path_from_uri |
57 | 57 | from ..validation_registration import ValidationError, ValidationRegistry |
58 | 58 | from . import schemas |
| 59 | +from ._backcompat import ( |
| 60 | + parse_python_tiled_client_version, |
| 61 | + strip_asset_fields_for_client, |
| 62 | +) |
59 | 63 | from .authentication import ( |
60 | 64 | authenticate_websocket_first_message, |
61 | 65 | check_scopes, |
@@ -201,20 +205,16 @@ async def about( |
201 | 205 | "required": not settings.allow_anonymous_access, |
202 | 206 | } |
203 | 207 | provider_specs = [] |
204 | | - user_agent = request.headers.get("user-agent", "") |
205 | 208 | # The name of the "internal" mode used to be "password". |
206 | 209 | # This ensures back-compat with older Python clients. |
207 | 210 | internal_mode_name = "internal" |
208 | 211 | MINIMUM_INTERNAL_PYTHON_CLIENT_VERSION = packaging.version.parse("0.1.0b17") |
209 | | - if user_agent.startswith("python-tiled/"): |
210 | | - agent, _, raw_version = user_agent.partition("/") |
211 | | - try: |
212 | | - parsed_version = packaging.version.parse(raw_version) |
213 | | - except Exception: |
214 | | - pass |
215 | | - else: |
216 | | - if parsed_version < MINIMUM_INTERNAL_PYTHON_CLIENT_VERSION: |
217 | | - internal_mode_name = "password" |
| 212 | + client_version = parse_python_tiled_client_version(request) |
| 213 | + if ( |
| 214 | + client_version is not None |
| 215 | + and client_version < MINIMUM_INTERNAL_PYTHON_CLIENT_VERSION |
| 216 | + ): |
| 217 | + internal_mode_name = "password" |
218 | 218 | for provider, authenticator in authenticators.items(): |
219 | 219 | if isinstance(authenticator, InternalAuthenticator): |
220 | 220 | spec = { |
@@ -1912,10 +1912,14 @@ async def _create_node( |
1912 | 1912 | links = links_for_node( |
1913 | 1913 | structure_family, structure, get_base_url(request), path + f"/{node.key}" |
1914 | 1914 | ) |
| 1915 | + data_sources_dump = [ds.model_dump() for ds in node.data_sources] |
| 1916 | + strip_asset_fields_for_client( |
| 1917 | + data_sources_dump, parse_python_tiled_client_version(request) |
| 1918 | + ) |
1915 | 1919 | response_data = { |
1916 | 1920 | "id": node.key, |
1917 | 1921 | "links": links, |
1918 | | - "data_sources": [ds.model_dump() for ds in node.data_sources], |
| 1922 | + "data_sources": data_sources_dump, |
1919 | 1923 | } |
1920 | 1924 | if metadata_modified: |
1921 | 1925 | response_data["metadata"] = metadata |
@@ -2808,25 +2812,29 @@ async def metrics(request: Request, _=Security(check_scopes, scopes=["metrics"]) |
2808 | 2812 |
|
2809 | 2813 |
|
2810 | 2814 | def _model_dump_backcompat(request: Request, response: schemas.Response) -> dict: |
2811 | | - """Backwards compatibility for clients older than v0.2.4 |
| 2815 | + """Adjust the outgoing response payload to match older client expectations. |
| 2816 | +
|
| 2817 | + - Clients older than v0.2.4 crash on `properties` in data sources. |
| 2818 | + Issue: https://github.com/bluesky/tiled/issues/1300 |
| 2819 | + - Clients older than v0.2.13 crash on `size` in assets, because their |
| 2820 | + `tiled.structures.data_source.Asset` dataclass has no `size` field and |
| 2821 | + `DataSource.from_json` unpacks kwargs directly. |
2812 | 2822 |
|
2813 | | - Older clients expect "data_sources" in the response to not include "properties". |
2814 | 2823 | To be removed in a future major release. |
2815 | | - Issue: https://github.com/bluesky/tiled/issues/1300 |
2816 | 2824 | """ |
2817 | 2825 | response_dict = response.model_dump() |
2818 | | - user_agent = request.headers.get("user-agent", "") |
2819 | | - if user_agent.startswith("python-tiled/"): |
2820 | | - agent, _, raw_version = user_agent.partition("/") |
2821 | | - try: |
2822 | | - parsed_version = packaging.version.parse(raw_version) |
2823 | | - if parsed_version < packaging.version.parse("0.2.4"): |
2824 | | - for ds in response_dict["data"]["attributes"]["data_sources"]: |
2825 | | - ds.pop("properties", None) |
2826 | | - |
2827 | | - return response_dict |
2828 | | - |
2829 | | - except Exception: |
2830 | | - pass |
2831 | | - |
| 2826 | + client_version = parse_python_tiled_client_version(request) |
| 2827 | + if client_version is None: |
| 2828 | + return response_dict |
| 2829 | + data = response_dict.get("data") or [] |
| 2830 | + resources = data if isinstance(data, list) else [data] |
| 2831 | + all_data_sources = [ |
| 2832 | + ds |
| 2833 | + for resource in resources |
| 2834 | + for ds in (resource.get("attributes", {}) or {}).get("data_sources") or [] |
| 2835 | + ] |
| 2836 | + if client_version < packaging.version.parse("0.2.4"): |
| 2837 | + for ds in all_data_sources: |
| 2838 | + ds.pop("properties", None) |
| 2839 | + strip_asset_fields_for_client(all_data_sources, client_version) |
2832 | 2840 | return response_dict |
0 commit comments