Stabilize the stubbed workspace APIs#5
Conversation
📝 WalkthroughWalkthroughThis PR refactors workspace quest and imagery definition management by introducing JSON schema validation infrastructure, typed API request/response schemas, and refactoring repository CRUD methods into type-safe upsert operations. It adds configuration for remote schema URLs, implements lazy-cached schema fetching with double-checked locking, updates all affected endpoints to use the new schemas, and adds schema validation during definition updates. ChangesWorkspace Quest and Imagery Definition Management
Sequence DiagramsequenceDiagram
participant Client
participant Route
participant json_schema
participant RemoteSchema
participant Repository
Client->>Route: PATCH quest with definition
Route->>json_schema: validate_quest_definition_schema(definition)
json_schema->>json_schema: get_longform_schema (lazy cached)
json_schema->>RemoteSchema: fetch schema.json (first time)
RemoteSchema-->>json_schema: schema cached
json_schema->>json_schema: jsonschema.validate(definition)
alt schema valid
json_schema-->>Route: validation passed
Route->>Repository: save_longform_quest()
Repository-->>Route: saved
Route-->>Client: 204
else schema invalid
json_schema-->>Route: HTTPException 400
Route-->>Client: 400 error
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
api/src/workspaces/repository.py (1)
144-150: ⚡ Quick winNarrow the exception catch to specific HTTP errors.
The bare
except Exceptioncan inadvertently swallow programming errors (e.g.,AttributeError,TypeError). Consider catching specific httpx exceptions to preserve the "return None on fetch failure" behavior while letting unexpected errors propagate.♻️ Suggested narrower exception handling
+from httpx import HTTPError, TimeoutException + # ... in resolve_quest_def: if quest.url: try: response = await get_http_client().get(quest.url, timeout=10) return response.text - except Exception: + except (HTTPError, TimeoutException): return None🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api/src/workspaces/repository.py` around lines 144 - 150, The current bare `except Exception` around the HTTP fetch for `quest.url` (using `get_http_client()` and returning `response.text`) swallows all errors; change it to only catch HTTP/client errors (e.g., httpx.RequestError, httpx.HTTPStatusError, httpx.TimeoutException) and return None for those cases, while letting other unexpected exceptions propagate; import the appropriate httpx exception classes and replace the generic except with a specific except clause (optionally binding the exception to a variable like `e` for logging) so programming errors (AttributeError/TypeError) are not suppressed.api/src/workspaces/routes.py (1)
339-343: 💤 Low valueTruthy check skips validation for empty imagery lists.
The condition
if imagery_data.definition:evaluates toFalsefor empty lists[], meaningvalidate_imagery_definition_schemawon't be called when an empty list is explicitly provided. If empty lists should also be schema-validated (or at minimum pass through the validator), consider an explicitNonecheck instead.If empty lists are trivially valid and this is intentional, no change needed.
Suggested fix if validation is desired for empty lists
- if imagery_data.definition: + if imagery_data.definition is not None: await validate_imagery_definition_schema(imagery_data.definition)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api/src/workspaces/routes.py` around lines 339 - 343, The current truthy check skips validation for empty lists because `if imagery_data.definition:` treats [] as False; change the condition to an explicit None check so empty lists are passed into the validator. Modify the block around `imagery_data.definition`, calling `validate_imagery_definition_schema(imagery_data.definition)` when `imagery_data.definition is not None`, ensuring `validate_imagery_definition_schema` is invoked for [] as well before calling `repository_ws.save_imagery_def(current_user, workspace_id, imagery_data)`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@api/core/config.py`:
- Around line 29-34: The schema URLs LONGFORM_SCHEMA_URL and IMAGERY_SCHEMA_URL
currently point to refs/heads/main and must be pinned to immutable references;
update these constants in api/core/config.py to use either the exact commit SHA
or a release tag (or switch to vendored local schema files and point to local
file paths), e.g., replace the GitHub raw URLs containing refs/heads/main with
raw URLs containing the commit hash or change to "file://" or package-relative
paths to bundled schemas; ensure any code that loads these constants (config
consumers) still reads the new values and update tests or deployment docs if
needed.
In `@api/core/json_schema.py`:
- Around line 95-96: Replace the broad except Exception handlers around the
HTTP/schema fetch logic with specific exception catches: catch httpx.HTTPError
(to cover network, status and timeout errors from
_http_client.get()/response.raise_for_status()) and json.JSONDecodeError (for
response.json() failures), and pass those exceptions into _raise_for_fetch_error
with the appropriate schema identifier ("quest" and "imagery"); update both
catch sites that currently call _raise_for_fetch_error(e, "quest") and
_raise_for_fetch_error(e, "imagery") so only these two exception types are
converted to 502s while letting other exceptions propagate.
In `@api/main.py`:
- Around line 147-150: The whitelist regex r"^/api/0\.6/workspaces.*$" is too
permissive and bypasses X-Workspace checks for all /workspaces paths; narrow the
pattern(s) in api/main.py (and the similar entries around lines 228-232) to
match only the specific endpoints that should bypass workspace checks (e.g., the
exact create/delete/JOSM rewrite endpoints) by using precise path tokens and
anchors (avoid trailing ".*"), and if the bypass is conditional on HTTP method,
ensure the logic checks the request method in the same handler (refer to the
whitelist entries containing r"^/api/0\.6/workspaces" and the corresponding
JOSM/provisioning whitelist entries to implement the stricter regexes or
method-aware checks).
In `@api/src/workspaces/schemas.py`:
- Around line 153-170: The code currently uses truthiness checks on
self.definition and self.url which lets empty strings bypass forbidden/required
rules; update the conditional checks in the validation block to use explicit
None checks: use "is None" when checking required fields (e.g., in the
QuestDefinitionTypeName.JSON branch check "if self.definition is None: raise
...") and use "is not None" when forbidding fields (e.g., in the
QuestDefinitionTypeName.NONE and URL branches check "if self.definition is not
None: raise ..." and similarly for self.url). Keep the existing .strip() JSON
object check but only after confirming the value is not None. Ensure you update
all branches that reference self.definition/self.url (the validation code
handling QuestDefinitionTypeName.NONE, .JSON, and .URL).
---
Nitpick comments:
In `@api/src/workspaces/repository.py`:
- Around line 144-150: The current bare `except Exception` around the HTTP fetch
for `quest.url` (using `get_http_client()` and returning `response.text`)
swallows all errors; change it to only catch HTTP/client errors (e.g.,
httpx.RequestError, httpx.HTTPStatusError, httpx.TimeoutException) and return
None for those cases, while letting other unexpected exceptions propagate;
import the appropriate httpx exception classes and replace the generic except
with a specific except clause (optionally binding the exception to a variable
like `e` for logging) so programming errors (AttributeError/TypeError) are not
suppressed.
In `@api/src/workspaces/routes.py`:
- Around line 339-343: The current truthy check skips validation for empty lists
because `if imagery_data.definition:` treats [] as False; change the condition
to an explicit None check so empty lists are passed into the validator. Modify
the block around `imagery_data.definition`, calling
`validate_imagery_definition_schema(imagery_data.definition)` when
`imagery_data.definition is not None`, ensuring
`validate_imagery_definition_schema` is invoked for [] as well before calling
`repository_ws.save_imagery_def(current_user, workspace_id, imagery_data)`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ee657b0c-ba88-49a7-8e52-b6dd16f44410
📒 Files selected for processing (6)
api/core/config.pyapi/core/json_schema.pyapi/main.pyapi/src/workspaces/repository.pyapi/src/workspaces/routes.pyapi/src/workspaces/schemas.py
| except Exception as e: | ||
| _raise_for_fetch_error(e, "quest") |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify current broad exception handling and where it appears.
rg -n -C2 'except Exception as e:' api/core/json_schema.py
# Confirm narrowed fetch-only exception handling after applying the fix.
rg -n -C2 'except \(httpx\.TimeoutException, httpx\.HTTPError\) as e:' api/core/json_schema.pyRepository: TaskarCenterAtUW/workspaces-backend
Length of output: 341
🏁 Script executed:
# Read the file to see the full context around the problematic lines
head -120 api/core/json_schema.py | tail -30Repository: TaskarCenterAtUW/workspaces-backend
Length of output: 956
🏁 Script executed:
# Check the imports at the top of the file to see what's imported
head -20 api/core/json_schema.pyRepository: TaskarCenterAtUW/workspaces-backend
Length of output: 497
🏁 Script executed:
# Look for _raise_for_fetch_error function definition
rg -n -A 5 'def _raise_for_fetch_error' api/core/json_schema.pyRepository: TaskarCenterAtUW/workspaces-backend
Length of output: 358
🏁 Script executed:
# Search for httpx imports to verify the exception types
rg -n 'import.*httpx|from.*httpx' api/core/json_schema.pyRepository: TaskarCenterAtUW/workspaces-backend
Length of output: 94
🏁 Script executed:
# Get the complete _raise_for_fetch_error function
sed -n '61,80p' api/core/json_schema.pyRepository: TaskarCenterAtUW/workspaces-backend
Length of output: 757
🏁 Script executed:
# Look for the _fetch_longform_schema and _fetch_imagery_schema functions to see what they do
rg -n -A 10 'async def _fetch_longform_schema|async def _fetch_imagery_schema' api/core/json_schema.pyRepository: TaskarCenterAtUW/workspaces-backend
Length of output: 923
🏁 Script executed:
# Check what httpx exception types are available and used
rg -n 'httpx\.' api/core/json_schema.pyRepository: TaskarCenterAtUW/workspaces-backend
Length of output: 346
🌐 Web query:
httpx exception hierarchy TimeoutException HTTPError HTTPStatusError
💡 Result:
In the httpx library, all exceptions inherit from a common base class, HTTPError [1]. The exception hierarchy is structured to allow for granular error handling, distinguishing between general request issues, transport-level problems, and HTTP status errors [1][2]. The relationship between the requested exceptions is as follows: 1. HTTPError: This is the base class for all exceptions raised by the httpx library [1][2]. 2. TimeoutException: This class inherits from TransportError, which in turn inherits from RequestError, and finally from HTTPError [1]. It serves as the base class for all timeout-related errors, including ConnectTimeout, ReadTimeout, WriteTimeout, and PoolTimeout [1][2]. 3. HTTPStatusError: This class inherits directly from HTTPError [1]. It is raised when a response has an error status code (e.g., 4xx or 5xx) and raise_for_status is called [2]. The full hierarchy is organized as follows [1]: - HTTPError - RequestError - TransportError - TimeoutException (base for ConnectTimeout, ReadTimeout, WriteTimeout, PoolTimeout) - NetworkError (base for ConnectError, ReadError, WriteError, CloseError) - ProtocolError (base for LocalProtocolError, RemoteProtocolError) - ProxyError - UnsupportedProtocol - DecodingError - TooManyRedirects - HTTPStatusError Other notable exceptions, such as InvalidURL, CookieConflict, and various StreamError subclasses, also exist outside of the RequestError branch but remain under the HTTPError umbrella [1][2].
Citations:
- 1: https://github.com/encode/httpx/blob/master/httpx/_exceptions.py
- 2: https://www.python-httpx.org/exceptions/
🏁 Script executed:
# Check if there are other exception handlers in the codebase for httpx
rg -n 'except.*httpx\.' api/core/json_schema.pyRepository: TaskarCenterAtUW/workspaces-backend
Length of output: 61
🏁 Script executed:
# Check if json.JSONDecodeError is handled elsewhere or if there are similar patterns in the codebase
rg -n 'json\.JSONDecodeError' api/core/json_schema.pyRepository: TaskarCenterAtUW/workspaces-backend
Length of output: 120
🏁 Script executed:
# Look at the usage context - what calls validate_quest_definition_schema
rg -rn 'validate_quest_definition_schema|validate_imagery_definition_schema' --type pyRepository: TaskarCenterAtUW/workspaces-backend
Length of output: 483
Catch specific exceptions instead of all exceptions around schema fetch.
Lines 95-96 and 115-116 currently catch all exceptions and convert them to 502 errors, which can hide real app bugs like malformed JSON responses. The fetch operations call _http_client.get(), response.raise_for_status(), and response.json(), which can raise httpx.HTTPError (covers all httpx exceptions including timeouts) and json.JSONDecodeError. Catch these specific exceptions:
- except Exception as e:
+ except (httpx.HTTPError, json.JSONDecodeError) as e:
_raise_for_fetch_error(e, "quest")Apply the same fix at line 115-116 for the imagery schema fetch.
Also applies to: 115-116
🧰 Tools
🪛 Ruff (0.15.13)
[warning] 95-95: Do not catch blind exception: Exception
(BLE001)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@api/core/json_schema.py` around lines 95 - 96, Replace the broad except
Exception handlers around the HTTP/schema fetch logic with specific exception
catches: catch httpx.HTTPError (to cover network, status and timeout errors from
_http_client.get()/response.raise_for_status()) and json.JSONDecodeError (for
response.json() failures), and pass those exceptions into _raise_for_fetch_error
with the appropriate schema identifier ("quest" and "imagery"); update both
catch sites that currently call _raise_for_fetch_error(e, "quest") and
_raise_for_fetch_error(e, "imagery") so only these two exception types are
converted to 502s while letting other exceptions propagate.
| # Creating/deleting workspaces and JOSM path rewriting: | ||
| r"^/api/0\.6/workspaces.*$", | ||
| # Provisioning users during authentication: | ||
| r"^/api/0\.6/user/.*$", |
There was a problem hiding this comment.
Whitelist regex is overly broad and weakens access control.
Line 148 (^/api/0\.6/workspaces.*$) matches every /workspaces proxy path, so requests can skip X-Workspace checks far beyond create/delete operations.
Suggested change
-AUTH_WHITELIST_PATTERNS = [
- re.compile(p)
- for p in [
- r"^/api/0\.6/workspaces.*$",
- r"^/api/0\.6/user/.*$",
- ]
-]
+AUTH_WHITELIST = [
+ (re.compile(r"^/api/0\.6/workspaces/create\.json$"), {"POST"}),
+ (re.compile(r"^/api/0\.6/workspaces/[0-9]+/delete\.json$"), {"POST", "DELETE"}),
+ (re.compile(r"^/api/0\.6/user/.*$"), {"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"}),
+]
...
- if not any(p.fullmatch(request.url.path) for p in AUTH_WHITELIST_PATTERNS):
+ if not any(
+ p.fullmatch(request.url.path) and request.method in methods
+ for p, methods in AUTH_WHITELIST
+ ):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="No X-Workspace header supplied",
)Also applies to: 228-232
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@api/main.py` around lines 147 - 150, The whitelist regex
r"^/api/0\.6/workspaces.*$" is too permissive and bypasses X-Workspace checks
for all /workspaces paths; narrow the pattern(s) in api/main.py (and the similar
entries around lines 228-232) to match only the specific endpoints that should
bypass workspace checks (e.g., the exact create/delete/JOSM rewrite endpoints)
by using precise path tokens and anchors (avoid trailing ".*"), and if the
bypass is conditional on HTTP method, ensure the logic checks the request method
in the same handler (refer to the whitelist entries containing
r"^/api/0\.6/workspaces" and the corresponding JOSM/provisioning whitelist
entries to implement the stricter regexes or method-aware checks).
| if self.definition: | ||
| raise ValueError("'definition' field not allowed when type is NONE.") | ||
| if self.url: | ||
| raise ValueError("'url' field not allowed when type is NONE.") | ||
| elif self.type == QuestDefinitionTypeName.JSON: | ||
| if not self.definition: | ||
| raise ValueError("'definition' is required when type is JSON.") | ||
| if self.url: | ||
| raise ValueError("'url' field not allowed when type is JSON.") | ||
| # Inexpensive early check. Full JSON parse and schema validation | ||
| # must call validate_quest_definition_schema(): | ||
| if not self.definition.strip().startswith("{"): | ||
| raise ValueError("'definition' must be a JSON object.") | ||
| elif self.type == QuestDefinitionTypeName.URL: | ||
| if not self.url: | ||
| raise ValueError("'url' is required when type is URL.") | ||
| if self.definition: | ||
| raise ValueError("'definition' field not allowed when type is URL.") |
There was a problem hiding this comment.
Use explicit None checks for forbidden quest fields.
Lines 153-170 rely on truthiness, so empty strings can slip through forbidden-field rules (e.g., definition="" when type is NONE).
Suggested change
if self.type == QuestDefinitionTypeName.NONE:
- if self.definition:
+ if self.definition is not None:
raise ValueError("'definition' field not allowed when type is NONE.")
- if self.url:
+ if self.url is not None:
raise ValueError("'url' field not allowed when type is NONE.")
elif self.type == QuestDefinitionTypeName.JSON:
- if not self.definition:
+ if self.definition is None or not self.definition.strip():
raise ValueError("'definition' is required when type is JSON.")
- if self.url:
+ if self.url is not None:
raise ValueError("'url' field not allowed when type is JSON.")
# Inexpensive early check. Full JSON parse and schema validation
# must call validate_quest_definition_schema():
if not self.definition.strip().startswith("{"):
raise ValueError("'definition' must be a JSON object.")
elif self.type == QuestDefinitionTypeName.URL:
- if not self.url:
+ if self.url is None or not self.url.strip():
raise ValueError("'url' is required when type is URL.")
- if self.definition:
+ if self.definition is not None:
raise ValueError("'definition' field not allowed when type is URL.")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if self.definition: | |
| raise ValueError("'definition' field not allowed when type is NONE.") | |
| if self.url: | |
| raise ValueError("'url' field not allowed when type is NONE.") | |
| elif self.type == QuestDefinitionTypeName.JSON: | |
| if not self.definition: | |
| raise ValueError("'definition' is required when type is JSON.") | |
| if self.url: | |
| raise ValueError("'url' field not allowed when type is JSON.") | |
| # Inexpensive early check. Full JSON parse and schema validation | |
| # must call validate_quest_definition_schema(): | |
| if not self.definition.strip().startswith("{"): | |
| raise ValueError("'definition' must be a JSON object.") | |
| elif self.type == QuestDefinitionTypeName.URL: | |
| if not self.url: | |
| raise ValueError("'url' is required when type is URL.") | |
| if self.definition: | |
| raise ValueError("'definition' field not allowed when type is URL.") | |
| if self.type == QuestDefinitionTypeName.NONE: | |
| if self.definition is not None: | |
| raise ValueError("'definition' field not allowed when type is NONE.") | |
| if self.url is not None: | |
| raise ValueError("'url' field not allowed when type is NONE.") | |
| elif self.type == QuestDefinitionTypeName.JSON: | |
| if self.definition is None or not self.definition.strip(): | |
| raise ValueError("'definition' is required when type is JSON.") | |
| if self.url is not None: | |
| raise ValueError("'url' field not allowed when type is JSON.") | |
| # Inexpensive early check. Full JSON parse and schema validation | |
| # must call validate_quest_definition_schema(): | |
| if not self.definition.strip().startswith("{"): | |
| raise ValueError("'definition' must be a JSON object.") | |
| elif self.type == QuestDefinitionTypeName.URL: | |
| if self.url is None or not self.url.strip(): | |
| raise ValueError("'url' is required when type is URL.") | |
| if self.definition is not None: | |
| raise ValueError("'definition' field not allowed when type is URL.") |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@api/src/workspaces/schemas.py` around lines 153 - 170, The code currently
uses truthiness checks on self.definition and self.url which lets empty strings
bypass forbidden/required rules; update the conditional checks in the validation
block to use explicit None checks: use "is None" when checking required fields
(e.g., in the QuestDefinitionTypeName.JSON branch check "if self.definition is
None: raise ...") and use "is not None" when forbidding fields (e.g., in the
QuestDefinitionTypeName.NONE and URL branches check "if self.definition is not
None: raise ..." and similarly for self.url). Keep the existing .strip() JSON
object check but only after confirming the value is not None. Ensure you update
all branches that reference self.definition/self.url (the validation code
handling QuestDefinitionTypeName.NONE, .JSON, and .URL).
|
|
||
| # used for validation | ||
| WS_LONGFORM_SCHEMA_URL: str = ( | ||
| LONGFORM_SCHEMA_URL: str = ( |
There was a problem hiding this comment.
You saw the things here can be overridden by env vars? Just checking the var is named this and not what was there before? :-)
There was a problem hiding this comment.
When you see this and respond, you can either merge or fix + merge. ty!
There was a problem hiding this comment.
Yep, this one didn't match the existing variable name, so I dropped the WS_ prefix for consistency with the rest of these vars. It gets mapped here:
There was a problem hiding this comment.
Perfect, copy that. Merging then.
This is a bag of changes that fix various omissions, security issues, and behavior differences in the stubbed workspaces APIs that were modeled from the old embedded backend. The changes align behavior as expected by several parts of the frontend and AVIV ScoutRoute.
Please see the individual commits for details.
Configuration and Infrastructure
The configuration module now exposes schema-related URLs for workspace validation:
LONGFORM_SCHEMA_URL(renamed fromWS_LONGFORM_SCHEMA_URL) and a newIMAGERY_SCHEMA_URLsetting for imagery list schemas.A new
json_schema.pymodule provides shared JSON schema fetching and validation utilities. It manages a global async HTTP client with timeouts, implements lazy-loading of remote JSON schemas using double-checked locking to prevent redundant fetches, and provides two validation functions (validate_quest_definition_schemaandvalidate_imagery_definition_schema) that translate validation and fetch failures into appropriate FastAPI HTTP exceptions (504 for timeouts, 502 for fetch errors, 400 for validation errors).The main FastAPI app lifespan now initializes and tears down the JSON schema client alongside existing OSM and TDEI clients. The authentication bypass whitelist was updated to allow workspace creation/deletion paths (
/api/0.6/workspaces) and provisioning paths (/api/0.6/user/), replacing the previous specific bbox retrieval endpoint.Workspace Repository Updates
The
WorkspaceRepositorynow enforces stricter access control and type safety:create()now accepts typedWorkspaceCreateinput and validates project-group membership usingtdeiProjectGroupId, raisingForbiddenExceptionon access violationsupdate()now acceptsWorkspacePatchand applies only explicitly set fieldssave_longform_questandsave_imagery_def) that perform update-then-insert logicresolve_quest_def()helper to handle quest definition resolution from either JSON or remote URLs with a 10-second timeoutThe
OSMRepository.getWorkspaceBBox()method signature was simplified by removing the unusedcurrent_userparameter.API Routes and Response Contracts
Workspace endpoints now align with API expectations:
workspaceId(status 201) instead of the full workspace modelWorkspacePatchvalidation, rejecting requests with no fields setNew quest-focused endpoints:
GET /{workspace_id}/quests/longendpoint returns the raw JSON definition (or 204 if not set)QuestSettingsResponsewith aNONEtype mapping for unset questsQuestSettingsPatchwith schema validation when type isJSONImagery endpoints similarly updated:
workspace.imageryListDefdirectly when presentImagerySettingsPatchwith schema validation when definition is providedSchema Models
New Pydantic models provide type-safe request/response contracts:
WorkspaceCreate,WorkspacePatch,QuestSettingsPatch(with conditional validation based on quest type),QuestSettingsResponse, andImagerySettingsPatch. A newQuestDefinitionTypeNameenum standardizes quest type naming.The
WorkspaceResponsemodel was extended with optionallongFormQuestDefandimageryListDeffields for mobile consumption, and itsfrom_workspaceconstructor now accepts these as keyword-only parameters.