Skip to content
Draft
34 changes: 24 additions & 10 deletions api/oss/src/apis/fastapi/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
ApplicationEdit,
ApplicationQuery,
ApplicationFork,
ApplicationVariantFork,
ApplicationRevisionsLog,
#
ApplicationVariant,
Expand Down Expand Up @@ -157,14 +158,27 @@ class ApplicationForkRequest(BaseModel):
)


class ApplicationVariantForkRequest(BaseModel):
application_variant: ApplicationVariantFork = Field(
description="Config for the new variant (slug, name, description, flags).",
)
application_variant_ref: Reference = Field(
description="Source variant to fork from.",
)
application_revision_ref: Optional[Reference] = Field(
default=None,
description="Pin the fork to this revision; defaults to the source variant's head.",
)


class ApplicationRevisionsLogRequest(BaseModel):
"""Request body for `POST /applications/revisions/log`.

Returns the ordered list of revisions committed to a variant, newest first.
Each entry carries commit metadata and the full revision record.
"""

application: ApplicationRevisionsLog = Field(
application_revisions: ApplicationRevisionsLog = Field(
description=(
"Filter for the log. Typically set `application_variant_id` to list "
"the revision history of a single variant; optionally set "
Expand Down Expand Up @@ -332,14 +346,6 @@ class ApplicationRevisionQueryRequest(BaseModel):
default=None,
description="Cursor pagination and time-range controls.",
)
resolve: Optional[bool] = Field(
default=None,
description=(
"When `true`, resolve embedded references in each returned "
"revision's `data` (for example, snippet references). "
"Defaults to `false`."
),
)


class ApplicationRevisionCommitRequest(BaseModel):
Expand All @@ -350,7 +356,7 @@ class ApplicationRevisionCommitRequest(BaseModel):
See [Versioning](/reference/api-guide/versioning#committing-a-revision).
"""

application_revision_commit: ApplicationRevisionCommit = Field(
application_revision: ApplicationRevisionCommit = Field(
description=(
"Commit payload. Must include `application_variant_id` and `data`. "
"`message` is a human-readable commit message. `slug` is optional; "
Expand Down Expand Up @@ -648,6 +654,14 @@ class ApplicationRevisionResolveRequest(BaseModel):
description="Revision reference; resolves that exact revision.",
)
#
application_revision: Optional[ApplicationRevision] = Field(
default=None,
description=(
"Resolve the references embedded in this revision payload directly, "
"without fetching it first. Only `data` is used; id and metadata are ignored."
),
)
#
max_depth: Optional[int] = Field(
default=10,
description=(
Expand Down
56 changes: 19 additions & 37 deletions api/oss/src/apis/fastapi/applications/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,13 @@
ApplicationCatalogTemplate,
ApplicationCatalogPreset,
ApplicationRevisionCommit,
ApplicationRevisionData,
)

from oss.src.apis.fastapi.applications.models import (
ApplicationCreateRequest,
ApplicationEditRequest,
ApplicationQueryRequest,
ApplicationForkRequest,
ApplicationVariantForkRequest,
ApplicationRevisionsLogRequest,
ApplicationResponse,
ApplicationsResponse,
Expand Down Expand Up @@ -1041,7 +1040,7 @@ async def fork_application_variant(
*,
application_variant_id: Optional[UUID] = None,
#
application_variant_fork_request: ApplicationForkRequest,
application_variant_fork_request: ApplicationVariantForkRequest,
) -> ApplicationVariantResponse:
"""Fork an existing variant into a new variant on the same application.

Expand All @@ -1062,23 +1061,25 @@ async def fork_application_variant(
):
raise FORBIDDEN_EXCEPTION # type: ignore

fork_request = application_variant_fork_request.application

application_variant_ref = (
application_variant_fork_request.application_variant_ref
)
if application_variant_id:
if (
fork_request.application_variant_id
and fork_request.application_variant_id != application_variant_id
application_variant_ref.id
and application_variant_ref.id != application_variant_id
):
return ApplicationVariantResponse()

if not fork_request.application_variant_id:
fork_request.application_variant_id = application_variant_id
if not application_variant_ref.id:
application_variant_ref = Reference(id=application_variant_id)
Comment on lines 1067 to +1074
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Return 400 for conflicting source-variant IDs.

Line 1072 turns contradictory inputs into 200 {count: 0}. That makes a malformed request look like an empty result set instead of a client error.

Suggested fix
         if application_variant_id:
             if (
                 application_variant_ref.id
                 and application_variant_ref.id != application_variant_id
             ):
-                return ApplicationVariantResponse()
+                raise HTTPException(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    detail="application_variant_id does not match application_variant_ref.id.",
+                )
             if not application_variant_ref.id:
                 application_variant_ref = Reference(id=application_variant_id)
📝 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.

Suggested change
if application_variant_id:
if (
fork_request.application_variant_id
and fork_request.application_variant_id != application_variant_id
application_variant_ref.id
and application_variant_ref.id != application_variant_id
):
return ApplicationVariantResponse()
if not fork_request.application_variant_id:
fork_request.application_variant_id = application_variant_id
if not application_variant_ref.id:
application_variant_ref = Reference(id=application_variant_id)
if application_variant_id:
if (
application_variant_ref.id
and application_variant_ref.id != application_variant_id
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="application_variant_id does not match application_variant_ref.id.",
)
if not application_variant_ref.id:
application_variant_ref = Reference(id=application_variant_id)


application_variant = await self.applications_service.fork_application_variant(
project_id=UUID(request.state.project_id),
user_id=UUID(request.state.user_id),
#
application_fork=fork_request,
application_variant_fork=application_variant_fork_request.application_variant,
application_variant_ref=application_variant_ref,
application_revision_ref=application_variant_fork_request.application_revision_ref,
)

application_variant_response = ApplicationVariantResponse(
Expand Down Expand Up @@ -1603,8 +1604,6 @@ async def query_application_revisions(
query, or filter on commit metadata (`author`, `date`, `message`) via
the `application_revision` object. For the ordered history of a
single variant, `POST /applications/revisions/log` is more direct.
Set `resolve: true` to inline embedded references in each revision's
`data`.
"""
if is_ee():
if not await check_action_access( # type: ignore
Expand All @@ -1628,23 +1627,6 @@ async def query_application_revisions(
windowing=application_revision_query_request.windowing,
)

# Optionally resolve embeds for all revisions if requested
if application_revisions and application_revision_query_request.resolve:
embeds_service = self.applications_service.embeds_service

for revision in application_revisions:
if revision and revision.data:
try:
resolved_config, _ = await embeds_service.resolve_configuration(
project_id=UUID(request.state.project_id),
configuration=revision.data.model_dump(),
)
revision.data = ApplicationRevisionData(**resolved_config)
except Exception as e:
log.error(
f"Failed to resolve embeds for revision {revision.id}: {e}"
)

response = ApplicationRevisionsResponse(
count=len(application_revisions),
application_revisions=application_revisions,
Expand Down Expand Up @@ -1686,7 +1668,7 @@ async def commit_application_revision(
project_id=UUID(request.state.project_id),
user_id=UUID(request.state.user_id),
#
application_revision_commit=application_revision_commit_request.application_revision_commit,
application_revision_commit=application_revision_commit_request.application_revision,
)

response = ApplicationRevisionResponse(
Expand Down Expand Up @@ -1720,12 +1702,10 @@ async def log_application_revisions(
):
raise FORBIDDEN_EXCEPTION # type: ignore

application_revisions = (
await self.applications_service.log_application_revisions(
project_id=UUID(request.state.project_id),
#
application_revisions_log=application_revisions_log_request.application,
)
application_revisions = await self.applications_service.log_application_revisions(
project_id=UUID(request.state.project_id),
#
application_revisions_log=application_revisions_log_request.application_revisions,
)
Comment thread
junaway marked this conversation as resolved.

revisions_response = ApplicationRevisionsResponse(
Expand Down Expand Up @@ -1774,6 +1754,8 @@ async def resolve_application_revision(
application_variant_ref=application_revision_resolve_request.application_variant_ref,
application_revision_ref=application_revision_resolve_request.application_revision_ref,
#
application_revision=application_revision_resolve_request.application_revision,
#
max_depth=application_revision_resolve_request.max_depth or 10,
max_embeds=application_revision_resolve_request.max_embeds or 100,
error_policy=application_revision_resolve_request.error_policy.value
Expand Down
36 changes: 32 additions & 4 deletions api/oss/src/apis/fastapi/environments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
EnvironmentCreate,
EnvironmentEdit,
EnvironmentQuery,
EnvironmentFork,
EnvironmentVariantFork,
EnvironmentRevisionsLog,
#
EnvironmentVariant,
Expand Down Expand Up @@ -46,6 +48,25 @@ class EnvironmentEditRequest(BaseModel):
environment: EnvironmentEdit


class EnvironmentForkRequest(BaseModel):
environment: EnvironmentFork = Field(
description="Fork specification. Supply the source variant and optional revision to fork from, plus slug/name for the new variant.",
)


class EnvironmentVariantForkRequest(BaseModel):
environment_variant: EnvironmentVariantFork = Field(
description="Config for the new variant (slug, name, description, flags).",
)
environment_variant_ref: Reference = Field(
description="Source variant to fork from.",
)
environment_revision_ref: Optional[Reference] = Field(
default=None,
description="Pin the fork to this revision; defaults to the source variant's head.",
)


class EnvironmentQueryRequest(BaseModel):
environment: Optional[EnvironmentQuery] = None
#
Expand Down Expand Up @@ -116,16 +137,15 @@ class EnvironmentRevisionQueryRequest(BaseModel):
environment_variant_refs: Optional[List[Reference]] = None
environment_revision_refs: Optional[List[Reference]] = None
#
application_refs: Optional[List[Reference]] = None
references: Optional[List[Reference]] = None
#
include_archived: Optional[bool] = None
#
windowing: Optional[Windowing] = None
resolve: Optional[bool] = None # Optionally resolve embeds on query


class EnvironmentRevisionCommitRequest(BaseModel):
environment_revision_commit: EnvironmentRevisionCommit
environment_revision: EnvironmentRevisionCommit


class EnvironmentRevisionRetrieveRequest(BaseModel):
Expand Down Expand Up @@ -162,7 +182,7 @@ class EnvironmentRevisionRetrieveRequest(BaseModel):


class EnvironmentRevisionsLogRequest(BaseModel):
environment: EnvironmentRevisionsLog
environment_revisions: EnvironmentRevisionsLog

Comment thread
junaway marked this conversation as resolved.
Comment thread
junaway marked this conversation as resolved.

class EnvironmentRevisionResponse(BaseModel):
Expand Down Expand Up @@ -216,6 +236,14 @@ class EnvironmentRevisionResolveRequest(BaseModel):
environment_variant_ref: Optional[Reference] = None
environment_revision_ref: Optional[Reference] = None
#
environment_revision: Optional[EnvironmentRevision] = Field(
default=None,
description=(
"Resolve the references embedded in this revision payload directly, "
"without fetching it first. Only `data` is used; id and metadata are ignored."
),
)
#
max_depth: Optional[int] = 10
max_embeds: Optional[int] = 100
error_policy: Optional[ErrorPolicy] = ErrorPolicy.EXCEPTION
Expand Down
Loading
Loading