Compare commits

...

4 Commits

Author SHA1 Message Date
Jedd Morgan c6fc5c6bd4 deprecate file upload api 2026-01-13 13:10:49 +00:00
Mucahit Bilal GOKER 2f84214786 feat(ifc): add parentId to nested objects (#481)
* add parentId to nested objects

* rename to parentApplicationId

* implement jedd's feedback

* ruff check

---------

Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2026-01-08 09:04:12 +03:00
Jedd Morgan 0fe1af8e75 Update PostgreSQL connection string in docker-compose (#482) 2026-01-07 15:54:26 +00:00
Gergő Jedlicska 6297943fe1 gergo/version message for ingestion (#480)
* feat: use mise for docs build

* feat(modelingestion): add version message reporting
2026-01-05 11:46:31 +00:00
8 changed files with 115 additions and 72 deletions
+1 -4
View File
@@ -97,10 +97,7 @@ services:
STRATEGY_LOCAL: "true"
POSTGRES_URL: "postgres"
POSTGRES_USER: "speckle"
POSTGRES_PASSWORD: "speckle"
POSTGRES_DB: "speckle"
POSTGRES_URL: 'postgres://speckle:speckle@postgres:5432/speckle'
ENABLE_MP: "false"
LOG_PRETTY: "true"
@@ -12,12 +12,23 @@ def data_object_to_speckle(
step_element: entity_instance,
children: list[Base],
current_storey: str | None = None,
parent_element: entity_instance | None = None,
) -> DataObject:
guid = cast(str, step_element.GlobalId)
name = cast(str, step_element.Name or guid)
properties = extract_properties(step_element)
# Add parent ID only if element's parent is also a DataObject (not a Collection)
# Collections are: IfcProject and IfcSpatialStructureElement types
if (
parent_element
and hasattr(parent_element, "GlobalId")
and not parent_element.is_a("IfcProject")
and not parent_element.is_a("IfcSpatialStructureElement")
):
properties["parentApplicationId"] = parent_element.GlobalId
# Add building storey information if available and not a building storey itself
if current_storey and not step_element.is_a("IfcBuildingStorey"):
properties["Building Storey"] = current_storey
+18 -6
View File
@@ -44,9 +44,13 @@ class ImportJob:
_display_value_cache: dict[int, list[Base]] = field(default_factory=dict)
"""Maps an instance step ID to a list of instances"""
def convert_element(self, step_element: entity_instance) -> Base:
def convert_element(
self,
step_element: entity_instance,
parent_element: entity_instance | None = None,
) -> Base:
try:
return self._convert_element(step_element)
return self._convert_element(step_element, parent_element)
except SpeckleException:
raise
except Exception as ex:
@@ -54,14 +58,18 @@ class ImportJob:
f"Failed to convert {step_element.is_a()} #{step_element.id()}"
) from ex
def _convert_element(self, step_element: entity_instance) -> Base:
def _convert_element(
self,
step_element: entity_instance,
parent_element: entity_instance | None = None,
) -> Base:
# Track current storey context and store for level proxies
previous_storey_data_object = self._current_storey_data_object
if step_element.is_a("IfcBuildingStorey"):
# Convert the building storey to a DataObject for the level proxy
storey_display_value = self._display_value_cache.get(step_element.id(), [])
self._current_storey_data_object = data_object_to_speckle(
storey_display_value, step_element, []
storey_display_value, step_element, [], parent_element=None
)
children = self._convert_children(step_element)
@@ -86,7 +94,11 @@ class ImportJob:
)
else:
result = data_object_to_speckle(
display_value, step_element, children, current_storey_name
display_value,
step_element,
children,
current_storey_name,
parent_element,
)
# Associate non-spatial elements with current storey for level proxies
if self._current_storey_data_object is not None and result.applicationId:
@@ -100,7 +112,7 @@ class ImportJob:
def _convert_children(self, step_element: entity_instance) -> list[Base]:
return [
self.convert_element(i)
self.convert_element(i, parent_element=step_element)
for i in get_children(step_element)
if self._should_convert(i)
]
+2 -1
View File
@@ -23,6 +23,7 @@ from specklepy.transports.server import ServerTransport
def open_and_convert_file(
file_path: str,
project: Project,
version_message: str,
model_ingestion_id: str,
client: SpeckleClient,
) -> Version:
@@ -86,7 +87,7 @@ def open_and_convert_file(
project_id=project.id,
ingestion_id=model_ingestion_id,
root_object_id=root_id,
# version_message=version_message,
version_message=version_message,
)
)
@@ -1,5 +1,6 @@
from pathlib import Path
from deprecated import deprecated
from typing_extensions import override
from specklepy.core.api.inputs import (
@@ -10,7 +11,10 @@ from specklepy.core.api.inputs import (
from specklepy.core.api.models import FileImport, FileUploadUrl
from specklepy.core.api.models.current import ResourceCollection
from specklepy.core.api.resources import FileImportResource as CoreResource
from specklepy.core.api.resources.current.file_import_resource import UploadFileResponse
from specklepy.core.api.resources.current.file_import_resource import (
FILE_UPLOAD_DEPRECATION_WARNING,
UploadFileResponse,
)
from specklepy.logging import metrics
@@ -25,11 +29,6 @@ class FileImportResource(CoreResource):
server_version=server_version,
)
@override
def start_file_import(self, input: StartFileImportInput) -> FileImport:
metrics.track(metrics.SDK, self.account, {"name": "File Import Start"})
return super().start_file_import(input)
@override
def generate_upload_url(self, input: GenerateFileUploadUrlInput) -> FileUploadUrl:
"""
@@ -61,6 +60,13 @@ class FileImportResource(CoreResource):
metrics.track(metrics.SDK, self.account, {"name": "File Import Download File"})
return super().download_file(project_id, file_id, target_file)
@deprecated(**FILE_UPLOAD_DEPRECATION_WARNING)
@override
def start_file_import(self, input: StartFileImportInput) -> FileImport:
metrics.track(metrics.SDK, self.account, {"name": "File Import Start"})
return super().start_file_import(input)
@deprecated(**FILE_UPLOAD_DEPRECATION_WARNING)
@override
def finish_file_import_job(self, input: FinishFileImportInput) -> bool:
"""
@@ -72,6 +78,7 @@ class FileImportResource(CoreResource):
metrics.track(metrics.SDK, self.account, {"name": "File Import Finish Job"})
return super().finish_file_import_job(input)
@deprecated(**FILE_UPLOAD_DEPRECATION_WARNING)
@override
def get_model_file_import_jobs(
self,
@@ -40,6 +40,7 @@ class ModelIngestionSuccessInput(GraphQLBaseModel):
ingestion_id: str
project_id: str
root_object_id: str
version_message: str | None
class ModelIngestionFailedInput(GraphQLBaseModel):
@@ -2,6 +2,7 @@ from pathlib import Path
from typing import Any
import httpx
from deprecated import deprecated
from gql import Client, gql
from specklepy.core.api.credentials import Account
@@ -23,6 +24,16 @@ class UploadFileResponse(GraphQLBaseModel):
etag: str
FILE_UPLOAD_DEPRECATION_WARNING: dict[str, Any] = {
"version": "3.2.4",
"reason": (
"Part of the old API surface"
"and will be removed in the future. Use the new ingestion API instead."
"Field will be deleted on June 1st, 2026"
),
}
class FileImportResource(ResourceBase):
"""API Access class for file imports"""
@@ -41,59 +52,6 @@ class FileImportResource(ResourceBase):
name=NAME,
)
def finish_file_import_job(self, input: FinishFileImportInput) -> bool:
"""
This is mostly an internal api, that marks a file import job finished.
Only use this if you are writing a file importer, that is responsible for
processing file import jobs.
"""
QUERY = gql(
"""
mutation FinishFileImport($input: FinishFileImportInput!) {
data:fileUploadMutations {
data:finishFileImport(input: $input)
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error", by_alias=True),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[bool]], QUERY, variables
).data.data
def start_file_import(self, input: StartFileImportInput) -> FileImport:
QUERY = gql(
"""
mutation StartFileImport($input: StartFileImportInput!) {
data:fileUploadMutations {
data:startFileImport(input: $input) {
id
projectId
convertedVersionId
userId
convertedStatus
convertedMessage
modelId
updatedAt
}
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error", by_alias=True),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[FileImport]], QUERY, variables
).data.data
def generate_upload_url(self, input: GenerateFileUploadUrlInput) -> FileUploadUrl:
"""
Get a file upload url from the Speckle server.
@@ -161,6 +119,62 @@ class FileImportResource(ResourceBase):
_ = f.write(chunk)
return target_file
@deprecated(**FILE_UPLOAD_DEPRECATION_WARNING)
def start_file_import(self, input: StartFileImportInput) -> FileImport:
QUERY = gql(
"""
mutation StartFileImport($input: StartFileImportInput!) {
data:fileUploadMutations {
data:startFileImport(input: $input) {
id
projectId
convertedVersionId
userId
convertedStatus
convertedMessage
modelId
updatedAt
}
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error", by_alias=True),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[FileImport]], QUERY, variables
).data.data
@deprecated(**FILE_UPLOAD_DEPRECATION_WARNING)
def finish_file_import_job(self, input: FinishFileImportInput) -> bool:
"""
This is mostly an internal api, that marks a file import job finished.
Only use this if you are writing a file importer, that is responsible for
processing file import jobs.
"""
QUERY = gql(
"""
mutation FinishFileImport($input: FinishFileImportInput!) {
data:fileUploadMutations {
data:finishFileImport(input: $input)
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error", by_alias=True),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[bool]], QUERY, variables
).data.data
@deprecated(**FILE_UPLOAD_DEPRECATION_WARNING)
def get_model_file_import_jobs(
self,
*,
@@ -207,7 +207,7 @@ class TestIngestionResource:
ingestion_id=ingestion.id,
root_object_id=object_id,
project_id=project.id,
# version_message=None,
version_message=None,
)
res = client.model_ingestion.complete(input)
@@ -271,7 +271,7 @@ class TestIngestionResource:
ingestion_id=ingestion.id,
root_object_id="asdfasdfasdfasfd",
project_id=project.id,
# version_message=None,
version_message=None,
)
with pytest.raises(GraphQLException):
_ = client.model_ingestion.complete(input)