STAR-322: Adding documentation and fixing model types

This commit is contained in:
Daniel Schiavini 2018-12-11 11:12:32 +01:00
parent fa7cab3246
commit 7668801564
26 changed files with 411 additions and 282 deletions

View file

@ -10,12 +10,12 @@ from UM.Logger import Logger
from cura.API import Account from cura.API import Account
from cura.NetworkClient import NetworkClient from cura.NetworkClient import NetworkClient
from ..Models import BaseModel from ..Models import BaseModel
from .Models.CloudCluster import CloudCluster from .Models.CloudClusterResponse import CloudClusterResponse
from .Models.CloudErrorObject import CloudErrorObject from .Models.CloudErrorObject import CloudErrorObject
from .Models.CloudClusterStatus import CloudClusterStatus from .Models.CloudClusterStatus import CloudClusterStatus
from .Models.CloudJobUploadRequest import CloudJobUploadRequest from .Models.CloudPrintJobUploadRequest import CloudPrintJobUploadRequest
from .Models.CloudPrintResponse import CloudPrintResponse from .Models.CloudPrintResponse import CloudPrintResponse
from .Models.CloudJobResponse import CloudJobResponse from .Models.CloudPrintJobResponse import CloudPrintJobResponse
## The cloud API client is responsible for handling the requests and responses from the cloud. ## The cloud API client is responsible for handling the requests and responses from the cloud.
@ -43,9 +43,9 @@ class CloudApiClient(NetworkClient):
## Retrieves all the clusters for the user that is currently logged in. ## Retrieves all the clusters for the user that is currently logged in.
# \param on_finished: The function to be called after the result is parsed. # \param on_finished: The function to be called after the result is parsed.
def getClusters(self, on_finished: Callable[[List[CloudCluster]], any]) -> None: def getClusters(self, on_finished: Callable[[List[CloudClusterResponse]], any]) -> None:
url = "{}/clusters".format(self.CLUSTER_API_ROOT) url = "{}/clusters".format(self.CLUSTER_API_ROOT)
self.get(url, on_finished=self._wrapCallback(on_finished, CloudCluster)) self.get(url, on_finished=self._wrapCallback(on_finished, CloudClusterResponse))
## Retrieves the status of the given cluster. ## Retrieves the status of the given cluster.
# \param cluster_id: The ID of the cluster. # \param cluster_id: The ID of the cluster.
@ -57,10 +57,11 @@ class CloudApiClient(NetworkClient):
## Requests the cloud to register the upload of a print job mesh. ## Requests the cloud to register the upload of a print job mesh.
# \param request: The request object. # \param request: The request object.
# \param on_finished: The function to be called after the result is parsed. # \param on_finished: The function to be called after the result is parsed.
def requestUpload(self, request: CloudJobUploadRequest, on_finished: Callable[[CloudJobResponse], any]) -> None: def requestUpload(self, request: CloudPrintJobUploadRequest, on_finished: Callable[[CloudPrintJobResponse], any]
) -> None:
url = "{}/jobs/upload".format(self.CURA_API_ROOT) url = "{}/jobs/upload".format(self.CURA_API_ROOT)
body = json.dumps({"data": request.__dict__}) body = json.dumps({"data": request.toDict()})
self.put(url, body, on_finished=self._wrapCallback(on_finished, CloudJobResponse)) self.put(url, body, on_finished=self._wrapCallback(on_finished, CloudPrintJobResponse))
## Requests the cloud to register the upload of a print job mesh. ## Requests the cloud to register the upload of a print job mesh.
# \param upload_response: The object received after requesting an upload with `self.requestUpload`. # \param upload_response: The object received after requesting an upload with `self.requestUpload`.
@ -68,7 +69,7 @@ class CloudApiClient(NetworkClient):
# \param on_finished: The function to be called after the result is parsed. It receives the print job ID. # \param on_finished: The function to be called after the result is parsed. It receives the print job ID.
# \param on_progress: A function to be called during upload progress. It receives a percentage (0-100). # \param on_progress: A function to be called during upload progress. It receives a percentage (0-100).
# \param on_error: A function to be called if the upload fails. It receives a dict with the error. # \param on_error: A function to be called if the upload fails. It receives a dict with the error.
def uploadMesh(self, upload_response: CloudJobResponse, mesh: bytes, on_finished: Callable[[str], any], def uploadMesh(self, upload_response: CloudPrintJobResponse, mesh: bytes, on_finished: Callable[[str], any],
on_progress: Callable[[int], any], on_error: Callable[[dict], any]): on_progress: Callable[[int], any], on_error: Callable[[dict], any]):
def progressCallback(bytes_sent: int, bytes_total: int) -> None: def progressCallback(bytes_sent: int, bytes_total: int) -> None:
@ -126,13 +127,13 @@ class CloudApiClient(NetworkClient):
## Parses the given models and calls the correct callback depending on the result. ## Parses the given models and calls the correct callback depending on the result.
# \param response: The response from the server, after being converted to a dict. # \param response: The response from the server, after being converted to a dict.
# \param on_finished: The callback in case the response is successful. # \param on_finished: The callback in case the response is successful.
# \param model: The type of the model to convert the response to. It may either be a single record or a list. # \param model_class: The type of the model to convert the response to. It may either be a single record or a list.
def _parseModels(self, response: Dict[str, any], def _parseModels(self, response: Dict[str, any],
on_finished: Callable[[Union[Model, List[Model]]], any], on_finished: Callable[[Union[Model, List[Model]]], any],
model: Type[Model]) -> None: model_class: Type[Model]) -> None:
if "data" in response: if "data" in response:
data = response["data"] data = response["data"]
result = [model(**c) for c in data] if isinstance(data, list) else model(**data) result = [model_class(**c) for c in data] if isinstance(data, list) else model_class(**data)
on_finished(result) on_finished(result)
elif "errors" in response: elif "errors" in response:
self._on_error([CloudErrorObject(**error) for error in response["errors"]]) self._on_error([CloudErrorObject(**error) for error in response["errors"]])

View file

@ -20,11 +20,11 @@ from ..MeshFormatHandler import MeshFormatHandler
from ..UM3PrintJobOutputModel import UM3PrintJobOutputModel from ..UM3PrintJobOutputModel import UM3PrintJobOutputModel
from .CloudApiClient import CloudApiClient from .CloudApiClient import CloudApiClient
from .Models.CloudClusterStatus import CloudClusterStatus from .Models.CloudClusterStatus import CloudClusterStatus
from .Models.CloudJobUploadRequest import CloudJobUploadRequest from .Models.CloudPrintJobUploadRequest import CloudPrintJobUploadRequest
from .Models.CloudPrintResponse import CloudPrintResponse from .Models.CloudPrintResponse import CloudPrintResponse
from .Models.CloudJobResponse import CloudJobResponse from .Models.CloudPrintJobResponse import CloudPrintJobResponse
from .Models.CloudClusterPrinter import CloudClusterPrinter from .Models.CloudClusterPrinterStatus import CloudClusterPrinterStatus
from .Models.CloudClusterPrintJob import CloudClusterPrintJob from .Models.CloudClusterPrintJobStatus import CloudClusterPrintJobStatus
from .Utils import findChanges, formatDateCompleted, formatTimeCompleted from .Utils import findChanges, formatDateCompleted, formatTimeCompleted
@ -116,8 +116,8 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
self._progress_message = None # type: Optional[Message] self._progress_message = None # type: Optional[Message]
# Keep server string of the last generated time to avoid updating models more than once for the same response # Keep server string of the last generated time to avoid updating models more than once for the same response
self._received_printers = None # type: Optional[List[CloudClusterPrinter]] self._received_printers = None # type: Optional[List[CloudClusterPrinterStatus]]
self._received_print_jobs = None # type: Optional[List[CloudClusterPrintJob]] self._received_print_jobs = None # type: Optional[List[CloudClusterPrintJobStatus]]
# A set of the user's job IDs that have finished # A set of the user's job IDs that have finished
self._finished_jobs = set() # type: Set[str] self._finished_jobs = set() # type: Set[str]
@ -166,7 +166,7 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
mesh_bytes = mesh_format.getBytes(nodes) mesh_bytes = mesh_format.getBytes(nodes)
request = CloudJobUploadRequest( request = CloudPrintJobUploadRequest(
job_name = file_name, job_name = file_name,
file_size = len(mesh_bytes), file_size = len(mesh_bytes),
content_type = mesh_format.mime_type, content_type = mesh_format.mime_type,
@ -199,9 +199,9 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
## Updates the local list of printers with the list received from the cloud. ## Updates the local list of printers with the list received from the cloud.
# \param jobs: The printers received from the cloud. # \param jobs: The printers received from the cloud.
def _updatePrinters(self, printers: List[CloudClusterPrinter]) -> None: def _updatePrinters(self, printers: List[CloudClusterPrinterStatus]) -> None:
previous = {p.key: p for p in self._printers} # type: Dict[str, PrinterOutputModel] previous = {p.key: p for p in self._printers} # type: Dict[str, PrinterOutputModel]
received = {p.uuid: p for p in printers} # type: Dict[str, CloudClusterPrinter] received = {p.uuid: p for p in printers} # type: Dict[str, CloudClusterPrinterStatus]
removed_printers, added_printers, updated_printers = findChanges(previous, received) removed_printers, added_printers, updated_printers = findChanges(previous, received)
@ -224,8 +224,8 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
## Updates the local list of print jobs with the list received from the cloud. ## Updates the local list of print jobs with the list received from the cloud.
# \param jobs: The print jobs received from the cloud. # \param jobs: The print jobs received from the cloud.
def _updatePrintJobs(self, jobs: List[CloudClusterPrintJob]) -> None: def _updatePrintJobs(self, jobs: List[CloudClusterPrintJobStatus]) -> None:
received = {j.uuid: j for j in jobs} # type: Dict[str, CloudClusterPrintJob] received = {j.uuid: j for j in jobs} # type: Dict[str, CloudClusterPrintJobStatus]
previous = {j.key: j for j in self._print_jobs} # type: Dict[str, UM3PrintJobOutputModel] previous = {j.key: j for j in self._print_jobs} # type: Dict[str, UM3PrintJobOutputModel]
removed_jobs, added_jobs, updated_jobs = findChanges(previous, received) removed_jobs, added_jobs, updated_jobs = findChanges(previous, received)
@ -248,7 +248,7 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
## Registers a new print job received via the cloud API. ## Registers a new print job received via the cloud API.
# \param job: The print job received. # \param job: The print job received.
def _addPrintJob(self, job: CloudClusterPrintJob) -> None: def _addPrintJob(self, job: CloudClusterPrintJobStatus) -> None:
model = job.createOutputModel(CloudOutputController(self)) model = job.createOutputModel(CloudOutputController(self))
model.stateChanged.connect(self._onPrintJobStateChanged) model.stateChanged.connect(self._onPrintJobStateChanged)
if job.printer_uuid: if job.printer_uuid:
@ -284,7 +284,7 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
## Uploads the mesh when the print job was registered with the cloud API. ## Uploads the mesh when the print job was registered with the cloud API.
# \param mesh: The bytes to upload. # \param mesh: The bytes to upload.
# \param job_response: The response received from the cloud API. # \param job_response: The response received from the cloud API.
def _onPrintJobCreated(self, mesh: bytes, job_response: CloudJobResponse) -> None: def _onPrintJobCreated(self, mesh: bytes, job_response: CloudPrintJobResponse) -> None:
self._api.uploadMesh(job_response, mesh, self._onPrintJobUploaded, self._updateUploadProgress, self._api.uploadMesh(job_response, mesh, self._onPrintJobUploaded, self._updateUploadProgress,
lambda _: self._onUploadError(T.UPLOAD_ERROR)) lambda _: self._onUploadError(T.UPLOAD_ERROR))

View file

@ -12,7 +12,7 @@ from cura.CuraApplication import CuraApplication
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
from .CloudApiClient import CloudApiClient from .CloudApiClient import CloudApiClient
from .CloudOutputDevice import CloudOutputDevice from .CloudOutputDevice import CloudOutputDevice
from .Models.CloudCluster import CloudCluster from .Models.CloudClusterResponse import CloudClusterResponse
from .Models.CloudErrorObject import CloudErrorObject from .Models.CloudErrorObject import CloudErrorObject
from .Utils import findChanges from .Utils import findChanges
@ -72,8 +72,8 @@ class CloudOutputDeviceManager:
self._api.getClusters(self._onGetRemoteClustersFinished) self._api.getClusters(self._onGetRemoteClustersFinished)
## Callback for when the request for getting the clusters. is finished. ## Callback for when the request for getting the clusters. is finished.
def _onGetRemoteClustersFinished(self, clusters: List[CloudCluster]) -> None: def _onGetRemoteClustersFinished(self, clusters: List[CloudClusterResponse]) -> None:
online_clusters = {c.cluster_id: c for c in clusters if c.is_online} # type: Dict[str, CloudCluster] online_clusters = {c.cluster_id: c for c in clusters if c.is_online} # type: Dict[str, CloudClusterResponse]
removed_devices, added_clusters, updates = findChanges(self._remote_clusters, online_clusters) removed_devices, added_clusters, updates = findChanges(self._remote_clusters, online_clusters)

View file

@ -1,17 +1,55 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Dict, Union, TypeVar, Type, List
from ...Models import BaseModel from ...Models import BaseModel
## Base class for the models used in the interface with the Ultimaker cloud APIs.
class BaseCloudModel(BaseModel): class BaseCloudModel(BaseModel):
## Checks whether the two models are equal.
# \param other: The other model.
# \return True if they are equal, False if they are different.
def __eq__(self, other): def __eq__(self, other):
return type(self) == type(other) and self.__dict__ == other.__dict__ return type(self) == type(other) and self.toDict() == other.toDict()
def __ne__(self, other): ## Checks whether the two models are different.
return type(self) != type(other) or self.__dict__ != other.__dict__ # \param other: The other model.
# \return True if they are different, False if they are the same.
def __ne__(self, other) -> bool:
return type(self) != type(other) or self.toDict() != other.toDict()
## Converts the model into a serializable dictionary
def toDict(self) -> Dict[str, any]:
return self.__dict__
# Type variable used in the parse methods below, which should be a subclass of BaseModel.
T = TypeVar("T", bound=BaseModel)
## Parses a single model.
# \param model_class: The model class.
# \param values: The value of the model, which is usually a dictionary, but may also be already parsed.
# \return An instance of the model_class given.
@staticmethod @staticmethod
def parseDate(date_str: str) -> datetime: def parseModel(model_class: Type[T], values: Union[T, Dict[str, any]]) -> T:
return datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone.utc) if isinstance(values, dict):
return model_class(**values)
return values
## Parses a list of models.
# \param model_class: The model class.
# \param values: The value of the list. Each value is usually a dictionary, but may also be already parsed.
# \return A list of instances of the model_class given.
@classmethod
def parseModels(cls, model_class: Type[T], values: List[Union[T, Dict[str, any]]]) -> List[T]:
return [cls.parseModel(model_class, value) for value in values]
## Parses the given date string.
# \param date: The date to parse.
# \return The parsed date.
@staticmethod
def parseDate(date: Union[str, datetime]) -> datetime:
if isinstance(date, datetime):
return date
return datetime.strptime(date, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone.utc)

View file

@ -1,21 +0,0 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from .BaseCloudModel import BaseCloudModel
## Class representing a cloud connected cluster.
class CloudCluster(BaseCloudModel):
def __init__(self, **kwargs):
self.cluster_id = None # type: str
self.host_guid = None # type: str
self.host_name = None # type: str
self.host_version = None # type: str
self.status = None # type: str
self.is_online = False # type: bool
super().__init__(**kwargs)
# Validates the model, raising an exception if the model is invalid.
def validate(self) -> None:
super().validate()
if not self.cluster_id:
raise ValueError("cluster_id is required on CloudCluster")

View file

@ -1,65 +0,0 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import List
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputController import CloudOutputController
from .CloudClusterPrinterConfiguration import CloudClusterPrinterConfiguration
from .CloudClusterPrintJobConstraint import CloudClusterPrintJobConstraint
from .BaseCloudModel import BaseCloudModel
## Class representing a print job
from plugins.UM3NetworkPrinting.src.UM3PrintJobOutputModel import UM3PrintJobOutputModel
class CloudClusterPrintJob(BaseCloudModel):
def __init__(self, **kwargs) -> None:
self.assigned_to = None # type: str
self.configuration = [] # type: List[CloudClusterPrinterConfiguration]
self.constraints = [] # type: List[CloudClusterPrintJobConstraint]
self.created_at = None # type: str
self.force = None # type: str
self.last_seen = None # type: str
self.machine_variant = None # type: str
self.name = None # type: str
self.network_error_count = None # type: int
self.owner = None # type: str
self.printer_uuid = None # type: str
self.started = None # type: str
self.status = None # type: str
self.time_elapsed = None # type: str
self.time_total = None # type: str
self.uuid = None # type: str
super().__init__(**kwargs)
self.configuration = [CloudClusterPrinterConfiguration(**c) if isinstance(c, dict) else c
for c in self.configuration]
self.constraints = [CloudClusterPrintJobConstraint(**p) if isinstance(p, dict) else p
for p in self.constraints]
## Creates an UM3 print job output model based on this cloud cluster print job.
# \param printer: The output model of the printer
def createOutputModel(self, controller: CloudOutputController) -> UM3PrintJobOutputModel:
model = UM3PrintJobOutputModel(controller, self.uuid, self.name)
self.updateOutputModel(model)
return model
## Creates a new configuration model
def _createConfigurationModel(self) -> ConfigurationModel:
extruders = [extruder.createConfigurationModel() for extruder in self.configuration or ()]
configuration = ConfigurationModel()
configuration.setExtruderConfigurations(extruders)
return configuration
## Updates an UM3 print job output model based on this cloud cluster print job.
# \param model: The model to update.
def updateOutputModel(self, model: UM3PrintJobOutputModel) -> None:
# TODO: Add `compatible_machine_families` to the cloud, than add model.setCompatibleMachineFamilies()
# TODO: Add `impediments_to_printing` to the cloud, see ClusterUM3OutputDevice._updatePrintJob
# TODO: Use model.updateConfigurationChanges, see ClusterUM3OutputDevice#_createConfigurationChanges
model.updateConfiguration(self._createConfigurationModel())
model.updateTimeTotal(self.time_total)
model.updateTimeElapsed(self.time_elapsed)
model.updateOwner(self.owner)
model.updateState(self.status)

View file

@ -1,10 +1,16 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from .BaseCloudModel import BaseCloudModel from .BaseCloudModel import BaseCloudModel
## Class representing a cloud cluster print job constraint ## Class representing a cloud cluster print job constraint
class CloudClusterPrintJobConstraint(BaseCloudModel): # Spec: https://api-staging.ultimaker.com/connect/v1/spec
def __init__(self, **kwargs) -> None: class CloudClusterPrintJobConstraints(BaseCloudModel):
self.require_printer_name = None # type: str ## Creates a new print job constraint.
# \param require_printer_name: Unique name of the printer that this job should be printed on.
# Should be one of the unique_name field values in the cluster, e.g. 'ultimakersystem-ccbdd30044ec'
def __init__(self, require_printer_name: Optional[str] = None, **kwargs) -> None:
self.require_printer_name = require_printer_name
super().__init__(**kwargs) super().__init__(**kwargs)

View file

@ -0,0 +1,87 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import List, Optional, Union, Dict
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputController import CloudOutputController
from .CloudClusterPrinterConfiguration import CloudClusterPrinterConfiguration
from .CloudClusterPrintJobConstraint import CloudClusterPrintJobConstraints
from .BaseCloudModel import BaseCloudModel
## Class representing a print job
from plugins.UM3NetworkPrinting.src.UM3PrintJobOutputModel import UM3PrintJobOutputModel
## Model for the status of a single print job in a cluster.
# Spec: https://api-staging.ultimaker.com/connect/v1/spec
class CloudClusterPrintJobStatus(BaseCloudModel):
## Creates a new cloud print job status model.
# \param assigned_to: The name of the printer this job is assigned to while being queued.
# \param configuration: The required print core configurations of this print job.
# \param constraints: Print job constraints object.
# \param created_at: The timestamp when the job was created in Cura Connect.
# \param force: Allow this job to be printed despite of mismatching configurations.
# \param last_seen: The number of seconds since this job was checked.
# \param machine_variant: The machine type that this job should be printed on.Coincides with the machine_type field
# of the printer object.
# \param name: The name of the print job. Usually the name of the .gcode file.
# \param network_error_count: The number of errors encountered when requesting data for this print job.
# \param owner: The name of the user who added the print job to Cura Connect.
# \param printer_uuid: UUID of the printer that the job is currently printing on or assigned to.
# \param started: Whether the job has started printing or not.
# \param status: The status of the print job.
# \param time_elapsed: The remaining printing time in seconds.
# \param time_total: The total printing time in seconds.
# \param uuid: UUID of this print job. Should be used for identification purposes.
def __init__(self, created_at: str, force: bool, machine_variant: str, name: str, started: bool, status: str,
time_total: int, uuid: str,
configuration: List[Union[Dict[str, any], CloudClusterPrinterConfiguration]],
constraints: List[Union[Dict[str, any], CloudClusterPrintJobConstraints]],
last_seen: Optional[float] = None, network_error_count: Optional[int] = None,
owner: Optional[str] = None, printer_uuid: Optional[str] = None, time_elapsed: Optional[int] = None,
assigned_to: Optional[str] = None, **kwargs) -> None:
self.assigned_to = assigned_to # type: str
self.configuration = self.parseModels(CloudClusterPrinterConfiguration, configuration)
self.constraints = self.parseModels(CloudClusterPrintJobConstraints, constraints)
self.created_at = created_at
self.force = force
self.last_seen = last_seen
self.machine_variant = machine_variant
self.name = name
self.network_error_count = network_error_count
self.owner = owner
self.printer_uuid = printer_uuid
self.started = started
self.status = status
self.time_elapsed = time_elapsed
self.time_total = time_total
self.uuid = uuid
super().__init__(**kwargs)
## Creates an UM3 print job output model based on this cloud cluster print job.
# \param printer: The output model of the printer
def createOutputModel(self, controller: CloudOutputController) -> UM3PrintJobOutputModel:
model = UM3PrintJobOutputModel(controller, self.uuid, self.name)
self.updateOutputModel(model)
return model
## Creates a new configuration model
def _createConfigurationModel(self) -> ConfigurationModel:
extruders = [extruder.createConfigurationModel() for extruder in self.configuration or ()]
configuration = ConfigurationModel()
configuration.setExtruderConfigurations(extruders)
return configuration
## Updates an UM3 print job output model based on this cloud cluster print job.
# \param model: The model to update.
def updateOutputModel(self, model: UM3PrintJobOutputModel) -> None:
# TODO: Add `compatible_machine_families` to the cloud, than add model.setCompatibleMachineFamilies()
# TODO: Add `impediments_to_printing` to the cloud, see ClusterUM3OutputDevice._updatePrintJob
# TODO: Use model.updateConfigurationChanges, see ClusterUM3OutputDevice#_createConfigurationChanges
model.updateConfiguration(self._createConfigurationModel())
model.updateTimeTotal(self.time_total)
model.updateTimeElapsed(self.time_elapsed)
model.updateOwner(self.owner)
model.updateState(self.status)

View file

@ -1,49 +0,0 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import List
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from .CloudClusterPrinterConfiguration import CloudClusterPrinterConfiguration
from .BaseCloudModel import BaseCloudModel
## Class representing a cluster printer
class CloudClusterPrinter(BaseCloudModel):
def __init__(self, **kwargs) -> None:
self.configuration = [] # type: List[CloudClusterPrinterConfiguration]
self.enabled = None # type: str
self.firmware_version = None # type: str
self.friendly_name = None # type: str
self.ip_address = None # type: str
self.machine_variant = None # type: str
self.status = None # type: str
self.unique_name = None # type: str
self.uuid = None # type: str
super().__init__(**kwargs)
self.configuration = [CloudClusterPrinterConfiguration(**c)
if isinstance(c, dict) else c for c in self.configuration]
## Creates a new output model.
# \param controller - The controller of the model.
def createOutputModel(self, controller: PrinterOutputController) -> PrinterOutputModel:
model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version)
self.updateOutputModel(model)
return model
## Updates the given output model.
# \param model - The output model to update.
def updateOutputModel(self, model: PrinterOutputModel) -> None:
model.updateKey(self.uuid)
model.updateName(self.friendly_name)
model.updateType(self.machine_variant)
model.updateState(self.status if self.enabled else "disabled")
for configuration, extruder_output, extruder_config in \
zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations):
configuration.updateOutputModel(extruder_output)
configuration.updateConfigurationModel(extruder_config)
pass

View file

@ -1,5 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Union, Dict, Optional
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
from .CloudClusterPrinterConfigurationMaterial import CloudClusterPrinterConfigurationMaterial from .CloudClusterPrinterConfigurationMaterial import CloudClusterPrinterConfigurationMaterial
@ -7,17 +9,22 @@ from .BaseCloudModel import BaseCloudModel
## Class representing a cloud cluster printer configuration ## Class representing a cloud cluster printer configuration
# Spec: https://api-staging.ultimaker.com/connect/v1/spec
class CloudClusterPrinterConfiguration(BaseCloudModel): class CloudClusterPrinterConfiguration(BaseCloudModel):
def __init__(self, **kwargs) -> None: ## Creates a new cloud cluster printer configuration object
self.extruder_index = None # type: int # \param extruder_index: The position of the extruder on the machine as list index. Numbered from left to right.
self.material = None # type: CloudClusterPrinterConfigurationMaterial # \param material: The material of a configuration object in a cluster printer. May be in a dict or an object.
self.nozzle_diameter = None # type: str # \param nozzle_diameter: The diameter of the print core at this position in millimeters, e.g. '0.4'.
self.print_core_id = None # type: str # \param print_core_id: The type of print core inserted at this position, e.g. 'AA 0.4'.
def __init__(self, extruder_index: int,
material: Union[None, Dict[str, any], CloudClusterPrinterConfigurationMaterial],
nozzle_diameter: Optional[str] = None, print_core_id: Optional[str] = None, **kwargs) -> None:
self.extruder_index = extruder_index
self.material = self.parseModel(CloudClusterPrinterConfigurationMaterial, material)
self.nozzle_diameter = nozzle_diameter
self.print_core_id = print_core_id
super().__init__(**kwargs) super().__init__(**kwargs)
if isinstance(self.material, dict):
self.material = CloudClusterPrinterConfigurationMaterial(**self.material)
## Updates the given output model. ## Updates the given output model.
# \param model - The output model to update. # \param model - The output model to update.
def updateOutputModel(self, model: ExtruderOutputModel) -> None: def updateOutputModel(self, model: ExtruderOutputModel) -> None:

View file

@ -1,3 +1,5 @@
from typing import Optional
from UM.Logger import Logger from UM.Logger import Logger
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
@ -5,12 +7,19 @@ from .BaseCloudModel import BaseCloudModel
## Class representing a cloud cluster printer configuration ## Class representing a cloud cluster printer configuration
# Spec: https://api-staging.ultimaker.com/connect/v1/spec
class CloudClusterPrinterConfigurationMaterial(BaseCloudModel): class CloudClusterPrinterConfigurationMaterial(BaseCloudModel):
def __init__(self, **kwargs) -> None: ## Creates a new material configuration model.
self.guid = None # type: str # \param brand: The brand of material in this print core, e.g. 'Ultimaker'.
self.brand = None # type: str # \param color: The color of material in this print core, e.g. 'Blue'.
self.color = None # type: str # \param guid: he GUID of the material in this print core, e.g. '506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9'.
self.material = None # type: str # \param material: The type of material in this print core, e.g. 'PLA'.
def __init__(self, brand: Optional[str] = None, color: Optional[str] = None, guid: Optional[str] = None,
material: Optional[str] = None, **kwargs) -> None:
self.guid = guid
self.brand = brand
self.color = color
self.material = material
super().__init__(**kwargs) super().__init__(**kwargs)
## Creates a material output model based on this cloud printer material. ## Creates a material output model based on this cloud printer material.

View file

@ -0,0 +1,60 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import List, Union, Dict, Optional
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from .CloudClusterPrinterConfiguration import CloudClusterPrinterConfiguration
from .BaseCloudModel import BaseCloudModel
## Class representing a cluster printer
# Spec: https://api-staging.ultimaker.com/connect/v1/spec
class CloudClusterPrinterStatus(BaseCloudModel):
## Creates a new cluster printer status
# \param enabled: A printer can be disabled if it should not receive new jobs. By default every printer is enabled.
# \param firmware_version: Firmware version installed on the printer. Can differ for each printer in a cluster.
# \param friendly_name: Human readable name of the printer. Can be used for identification purposes.
# \param ip_address: The IP address of the printer in the local network.
# \param machine_variant: The type of printer. Can be 'Ultimaker 3' or 'Ultimaker 3ext'.
# \param status: The status of the printer.
# \param unique_name: The unique name of the printer in the network.
# \param uuid: The unique ID of the printer, also known as GUID.
# \param configuration: The active print core configurations of this printer.
# \param reserved_by: A printer can be claimed by a specific print job.
def __init__(self, enabled: bool, firmware_version: str, friendly_name: str, ip_address: str, machine_variant: str,
status: str, unique_name: str, uuid: str,
configuration: List[Union[Dict[str, any], CloudClusterPrinterConfiguration]],
reserved_by: Optional[str] = None, **kwargs) -> None:
self.configuration = self.parseModels(CloudClusterPrinterConfiguration, configuration)
self.enabled = enabled
self.firmware_version = firmware_version
self.friendly_name = friendly_name
self.ip_address = ip_address
self.machine_variant = machine_variant
self.status = status
self.unique_name = unique_name
self.uuid = uuid
self.reserved_by = reserved_by
super().__init__(**kwargs)
## Creates a new output model.
# \param controller - The controller of the model.
def createOutputModel(self, controller: PrinterOutputController) -> PrinterOutputModel:
model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version)
self.updateOutputModel(model)
return model
## Updates the given output model.
# \param model - The output model to update.
def updateOutputModel(self, model: PrinterOutputModel) -> None:
model.updateKey(self.uuid)
model.updateName(self.friendly_name)
model.updateType(self.machine_variant)
model.updateState(self.status if self.enabled else "disabled")
for configuration, extruder_output, extruder_config in \
zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations):
configuration.updateOutputModel(extruder_output)
configuration.updateConfigurationModel(extruder_config)

View file

@ -0,0 +1,32 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from .BaseCloudModel import BaseCloudModel
## Class representing a cloud connected cluster.
# Spec: https://api-staging.ultimaker.com/connect/v1/spec
class CloudClusterResponse(BaseCloudModel):
## Creates a new cluster response object.
# \param cluster_id: The secret unique ID, e.g. 'kBEeZWEifXbrXviO8mRYLx45P8k5lHVGs43XKvRniPg='.
# \param host_guid: The unique identifier of the print cluster host, e.g. 'e90ae0ac-1257-4403-91ee-a44c9b7e8050'.
# \param host_name: The name of the printer as configured during the Wi-Fi setup. Used as identifier for end users.
# \param is_online: Whether this cluster is currently connected to the cloud.
# \param status: The status of the cluster authentication (active or inactive).
# \param host_version: The firmware version of the cluster host. This is where the Stardust client is running on.
def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str,
host_version: Optional[str] = None, **kwargs):
self.cluster_id = cluster_id
self.host_guid = host_guid
self.host_name = host_name
self.status = status
self.is_online = is_online
self.host_version = host_version
super().__init__(**kwargs)
# Validates the model, raising an exception if the model is invalid.
def validate(self) -> None:
super().validate()
if not self.cluster_id:
raise ValueError("cluster_id is required on CloudCluster")

View file

@ -1,28 +1,26 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from datetime import datetime from datetime import datetime
from typing import List from typing import List, Dict, Union
from .CloudClusterPrinter import CloudClusterPrinter from .CloudClusterPrinterStatus import CloudClusterPrinterStatus
from .CloudClusterPrintJob import CloudClusterPrintJob from .CloudClusterPrintJobStatus import CloudClusterPrintJobStatus
from .BaseCloudModel import BaseCloudModel from .BaseCloudModel import BaseCloudModel
# Model that represents the status of the cluster for the cloud # Model that represents the status of the cluster for the cloud
# Spec: https://api-staging.ultimaker.com/connect/v1/spec
class CloudClusterStatus(BaseCloudModel): class CloudClusterStatus(BaseCloudModel):
def __init__(self, **kwargs) -> None: ## Creates a new cluster status model object.
self.generated_time = None # type: datetime # \param printers: The latest status of each printer in the cluster.
# a list of the printers # \param print_jobs: The latest status of each print job in the cluster.
self.printers = [] # type: List[CloudClusterPrinter] # \param generated_time: The datetime when the object was generated on the server-side.
# a list of the print jobs def __init__(self,
self.print_jobs = [] # type: List[CloudClusterPrintJob] printers: List[Union[CloudClusterPrinterStatus, Dict[str, any]]],
print_jobs: List[Union[CloudClusterPrintJobStatus, Dict[str, any]]],
generated_time: Union[str, datetime],
**kwargs) -> None:
self.generated_time = self.parseDate(generated_time)
self.printers = self.parseModels(CloudClusterPrinterStatus, printers)
self.print_jobs = self.parseModels(CloudClusterPrintJobStatus, print_jobs)
super().__init__(**kwargs) super().__init__(**kwargs)
# converting any dictionaries into models
self.printers = [CloudClusterPrinter(**p) if isinstance(p, dict) else p for p in self.printers]
self.print_jobs = [CloudClusterPrintJob(**j) if isinstance(j, dict) else j for j in self.print_jobs]
# converting generated time into datetime
if isinstance(self.generated_time, str):
self.generated_time = self.parseDate(self.generated_time)

View file

@ -1,17 +1,28 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Dict from typing import Dict, Optional
from .BaseCloudModel import BaseCloudModel from .BaseCloudModel import BaseCloudModel
## Class representing errors generated by the cloud servers, according to the json-api standard. ## Class representing errors generated by the cloud servers, according to the JSON-API standard.
# Spec: https://api-staging.ultimaker.com/connect/v1/spec
class CloudErrorObject(BaseCloudModel): class CloudErrorObject(BaseCloudModel):
def __init__(self, **kwargs) -> None: ## Creates a new error object.
self.id = None # type: str # \param id: Unique identifier for this particular occurrence of the problem.
self.code = None # type: str # \param title: A short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence
self.http_status = None # type: str # of the problem, except for purposes of localization.
self.title = None # type: str # \param code: An application-specific error code, expressed as a string value.
self.detail = None # type: str # \param detail: A human-readable explanation specific to this occurrence of the problem. Like title, this field's
self.meta = None # type: Dict[str, any] # value can be localized.
# \param http_status: The HTTP status code applicable to this problem, converted to string.
# \param meta: Non-standard meta-information about the error, depending on the error code.
def __init__(self, id: str, code: str, title: str, http_status: str, detail: Optional[str] = None,
meta: Optional[Dict[str, any]] = None, **kwargs) -> None:
self.id = id
self.code = code
self.http_status = http_status
self.title = title
self.detail = detail
self.meta = meta
super().__init__(**kwargs) super().__init__(**kwargs)

View file

@ -1,16 +0,0 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from .BaseCloudModel import BaseCloudModel
# Model that represents the response received from the cloud after requesting to upload a print job
class CloudJobResponse(BaseCloudModel):
def __init__(self, **kwargs) -> None:
self.download_url = None # type: str
self.job_id = None # type: str
self.job_name = None # type: str
self.slicing_details = None # type: str
self.status = None # type: str
self.upload_url = None # type: str
self.content_type = None # type: str
super().__init__(**kwargs)

View file

@ -1,12 +0,0 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from .BaseCloudModel import BaseCloudModel
# Model that represents the request to upload a print job to the cloud
class CloudJobUploadRequest(BaseCloudModel):
def __init__(self, **kwargs) -> None:
self.file_size = None # type: int
self.job_name = None # type: str
self.content_type = None # type: str
super().__init__(**kwargs)

View file

@ -0,0 +1,33 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from .BaseCloudModel import BaseCloudModel
# Model that represents the response received from the cloud after requesting to upload a print job
# Spec: https://api-staging.ultimaker.com/cura/v1/spec
class CloudPrintJobResponse(BaseCloudModel):
## Creates a new print job response model.
# \param job_id: The job unique ID, e.g. 'kBEeZWEifXbrXviO8mRYLx45P8k5lHVGs43XKvRniPg='.
# \param status: The status of the print job.
# \param status_description: Contains more details about the status, e.g. the cause of failures.
# \param download_url: A signed URL to download the resulting status. Only available when the job is finished.
# \param job_name: The name of the print job.
# \param slicing_details: Model for slice information.
# \param upload_url: The one-time use URL where the toolpath must be uploaded to (only if status is uploading).
# \param content_type: The content type of the print job (e.g. text/plain or application/gzip)
# \param generated_time: The datetime when the object was generated on the server-side.
def __init__(self, job_id: str, status: str, download_url: Optional[str] = None, job_name: Optional[str] = None,
upload_url: Optional[str] = None, content_type: Optional[str] = None,
status_description: Optional[str] = None, slicing_details: Optional[dict] = None, **kwargs) -> None:
self.job_id = job_id
self.status = status
self.download_url = download_url
self.job_name = job_name
self.upload_url = upload_url
self.content_type = content_type
self.status_description = status_description
# TODO: Implement slicing details
self.slicing_details = slicing_details
super().__init__(**kwargs)

View file

@ -0,0 +1,17 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from .BaseCloudModel import BaseCloudModel
# Model that represents the request to upload a print job to the cloud
# Spec: https://api-staging.ultimaker.com/cura/v1/spec
class CloudPrintJobUploadRequest(BaseCloudModel):
## Creates a new print job upload request.
# \param job_name: The name of the print job.
# \param file_size: The size of the file in bytes.
# \param content_type: The content type of the print job (e.g. text/plain or application/gzip)
def __init__(self, job_name: str, file_size: int, content_type: str, **kwargs) -> None:
self.job_name = job_name
self.file_size = file_size
self.content_type = content_type
super().__init__(**kwargs)

View file

@ -1,12 +1,23 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from datetime import datetime
from typing import Optional, Union
from .BaseCloudModel import BaseCloudModel from .BaseCloudModel import BaseCloudModel
# Model that represents the responses received from the cloud after requesting a job to be printed. # Model that represents the responses received from the cloud after requesting a job to be printed.
# Spec: https://api-staging.ultimaker.com/connect/v1/spec
class CloudPrintResponse(BaseCloudModel): class CloudPrintResponse(BaseCloudModel):
def __init__(self, **kwargs) -> None: ## Creates a new print response object.
self.cluster_job_id = None # type: str # \param job_id: The unique ID of a print job inside of the cluster. This ID is generated by Cura Connect.
self.job_id = None # type: str # \param status: The status of the print request (queued or failed).
self.status = None # type: str # \param generated_time: The datetime when the object was generated on the server-side.
# \param cluster_job_id: The unique ID of a print job inside of the cluster. This ID is generated by Cura Connect.
def __init__(self, job_id: str, status: str, generated_time: Union[str, datetime],
cluster_job_id: Optional[str] = None, **kwargs) -> None:
self.job_id = job_id
self.status = status
self.cluster_job_id = cluster_job_id
self.generated_time = self.parseDate(generated_time)
super().__init__(**kwargs) super().__init__(**kwargs)

View file

@ -2,6 +2,7 @@
"data": { "data": {
"cluster_job_id": "9a59d8e9-91d3-4ff6-b4cb-9db91c4094dd", "cluster_job_id": "9a59d8e9-91d3-4ff6-b4cb-9db91c4094dd",
"job_id": "ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE=", "job_id": "ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE=",
"status": "queued" "status": "queued",
"generated_time": "2018-12-10T08:23:55.110Z"
} }
} }

View file

@ -1,10 +1,9 @@
{ {
"data": { "data": {
"content_type": "text/plain", "content_type": "text/plain",
"download_url": "https://api.ultimaker.com/print-job-download",
"job_id": "ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE=", "job_id": "ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE=",
"job_name": "Ultimaker Robot v3.0", "job_name": "Ultimaker Robot v3.0",
"status": "queued", "status": "uploading",
"upload_url": "https://api.ultimaker.com/print-job-upload" "upload_url": "https://api.ultimaker.com/print-job-upload"
} }
} }

View file

@ -1,7 +0,0 @@
{
"data": {
"cluster_job_id": "",
"job_id": "db34b096-c4d5-46f3-bea7-da6a19905e6c",
"status": "queued"
}
}

View file

@ -1,11 +0,0 @@
{
"data": {
"content_type": "text/plain",
"generated_time": "2018-12-10T09:33:00.009Z",
"job_id": "j9KUn4D6FRRRmdtbCo4OGAwUf6Ml3p3oU-Zv7RNRv92T",
"job_name": "job name",
"status": "uploading",
"status_description": "The print job has been created. Please upload the file.",
"upload_url": "https://www.googleapis.com/upload/storage/v1/b/ultimaker-storage-1/o?uploadType=resumable&upload_id=AEnB2Uqhg1H7BXQVeLJEWw6AheqMicydZVLuH9bnkh6Oge0e6i5X76MW3NZHWRmUTmjzulAF42mkczcC7rsAuPg1Nn8JeFpnNA"
}
}

View file

@ -1,7 +1,6 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import json
import os import os
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
@ -9,8 +8,9 @@ from unittest.mock import patch, MagicMock
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from src.Cloud.CloudApiClient import CloudApiClient from src.Cloud.CloudApiClient import CloudApiClient
from src.Cloud.CloudOutputDeviceManager import CloudOutputDeviceManager from src.Cloud.CloudOutputDeviceManager import CloudOutputDeviceManager
from src.Cloud.Models.CloudJobResponse import CloudJobResponse from src.Cloud.Models.CloudPrintJobResponse import CloudPrintJobResponse
from src.Cloud.Models.CloudJobUploadRequest import CloudJobUploadRequest from src.Cloud.Models.CloudPrintJobUploadRequest import CloudPrintJobUploadRequest
from tests.Cloud.Fixtures import readFixture, parseFixture
from .NetworkManagerMock import NetworkManagerMock from .NetworkManagerMock import NetworkManagerMock
@ -71,12 +71,11 @@ class TestCloudApiClient(TestCase):
network_mock.return_value = self.network network_mock.return_value = self.network
results = [] results = []
with open("{}/Fixtures/requestUploadResponse.json".format(os.path.dirname(__file__)), "rb") as f: response = readFixture("putJobUploadResponse")
response = f.read()
self.network.prepareReply("PUT", "https://api-staging.ultimaker.com/cura/v1/jobs/upload", 200, response) self.network.prepareReply("PUT", "https://api-staging.ultimaker.com/cura/v1/jobs/upload", 200, response)
self.api.requestUpload(CloudJobUploadRequest(job_name = "job name", file_size = 143234, content_type = "text/plain"), request = CloudPrintJobUploadRequest(job_name = "job name", file_size = 143234, content_type = "text/plain")
lambda r: results.append(r)) self.api.requestUpload(request, lambda r: results.append(r))
self.network.flushReplies() self.network.flushReplies()
self.assertEqual(results[0].content_type, "text/plain") self.assertEqual(results[0].content_type, "text/plain")
@ -87,13 +86,11 @@ class TestCloudApiClient(TestCase):
results = [] results = []
progress = MagicMock() progress = MagicMock()
with open("{}/Fixtures/requestUploadResponse.json".format(os.path.dirname(__file__)), "rb") as f: data = parseFixture("putJobUploadResponse")["data"]
thedata = json.loads(f.read().decode("ascii")) upload_response = CloudPrintJobResponse(**data)
data = thedata["data"]
upload_response = CloudJobResponse(**data)
self.network.prepareReply("PUT", upload_response.upload_url, 200, self.network.prepareReply("PUT", upload_response.upload_url, 200,
'{ data : "" }') # Network client doesn't look into the reply b'{ data : "" }') # Network client doesn't look into the reply
self.api.uploadMesh(upload_response, b'', lambda job_id: results.append(job_id), self.api.uploadMesh(upload_response, b'', lambda job_id: results.append(job_id),
progress.advance, progress.error) progress.advance, progress.error)
@ -107,11 +104,11 @@ class TestCloudApiClient(TestCase):
network_mock.return_value = self.network network_mock.return_value = self.network
results = [] results = []
cluster_id = "NWKV6vJP_LdYsXgXqAcaNCR0YcLJwar1ugh0ikEZsZs8" response = readFixture("postJobPrintResponse")
job_id = "db34b096-c4d5-46f3-bea7-da6a19905e6c"
with open("{}/Fixtures/requestPrintResponse.json".format(os.path.dirname(__file__)), "rb") as f: cluster_id = "NWKV6vJP_LdYsXgXqAcaNCR0YcLJwar1ugh0ikEZsZs8"
response = f.read() cluster_job_id = "9a59d8e9-91d3-4ff6-b4cb-9db91c4094dd"
job_id = "ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE="
self.network.prepareReply("POST", self.network.prepareReply("POST",
"https://api-staging.ultimaker.com/connect/v1/clusters/{}/print/{}" "https://api-staging.ultimaker.com/connect/v1/clusters/{}/print/{}"
@ -123,5 +120,6 @@ class TestCloudApiClient(TestCase):
self.network.flushReplies() self.network.flushReplies()
self.assertEqual(len(results), 1) self.assertEqual(len(results), 1)
self.assertEqual(results[0].job_id, "db34b096-c4d5-46f3-bea7-da6a19905e6c") self.assertEqual(results[0].job_id, job_id)
self.assertEqual(results[0].cluster_job_id, cluster_job_id)
self.assertEqual(results[0].status, "queued") self.assertEqual(results[0].status, "queued")

View file

@ -106,7 +106,9 @@ class TestCloudOutputDeviceManager(TestCase):
@patch("UM.Message.Message.show") @patch("UM.Message.Message.show")
def test_api_error(self, message_mock, network_mock): def test_api_error(self, message_mock, network_mock):
self.clusters_response = {"errors": [{"id": "notFound", "title": "Not found!", "http_status": "404"}]} self.clusters_response = {
"errors": [{"id": "notFound", "title": "Not found!", "http_status": "404", "code": "notFound"}]
}
self.network.prepareReply("GET", self.URL, 200, self.clusters_response) self.network.prepareReply("GET", self.URL, 200, self.clusters_response)
self._loadData(network_mock) self._loadData(network_mock)
self.network.flushReplies() self.network.flushReplies()