Exemple #1
0
def project_import() -> RespT:
    """Imports a project from execution package.
    ---
    put:
      tags:
        - Build
      summary: Imports a project from execution package.
      operationId: ProjectImport
      parameters:
            - in: query
              name: overwriteScene
              schema:
                type: boolean
                default: false
              description: overwrite Scene
            - in: query
              name: overwriteProject
              schema:
                type: boolean
                default: false
              description: overwrite Project
            - in: query
              name: overwriteObjectTypes
              schema:
                type: boolean
                default: false
              description: overwrite ObjectTypes
            - in: query
              name: overwriteProjectSources
              schema:
                type: boolean
                default: false
              description: overwrite ProjectSources
            - in: query
              name: overwriteCollisionModels
              schema:
                type: boolean
                default: false
              description: overwrite collision models
      requestBody:
              content:
                multipart/form-data:
                  schema:
                    type: object
                    required:
                        - executionPackage
                    properties:
                      executionPackage:
                        type: string
                        format: binary
      responses:
        200:
          description: Ok
          content:
                application/json:
                  schema:
                    $ref: ImportResult
        400:
          description: Some other error occurred.
          content:
                application/json:
                  schema:
                    type: string
        401:
          description: Invalid execution package.
          content:
                application/json:
                  schema:
                    type: string
        402:
          description: A difference between package/project service detected (overwrite needed).
          content:
                application/json:
                  schema:
                    type: string
        404:
          description: Something is missing.
          content:
                application/json:
                  schema:
                    type: string
    """

    file = request.files["executionPackage"]

    overwrite_scene = request.args.get("overwriteScene",
                                       default="false") == "true"
    overwrite_project = request.args.get("overwriteProject",
                                         default="false") == "true"
    overwrite_object_types = request.args.get("overwriteObjectTypes",
                                              default="false") == "true"
    overwrite_project_sources = request.args.get("overwriteProjectSources",
                                                 default="false") == "true"
    overwrite_collision_models = request.args.get("overwriteCollisionModels",
                                                  default="false") == "true"

    objects: dict[str, ObjectType] = {}
    models: dict[str, Models] = {}
    """
    1) get and validate all data from zip
    2) check what is already on the Project service
    3) do updates
    """
    # BytesIO + stream.read() = workaround for a Python bug (SpooledTemporaryFile not seekable)
    with zipfile.ZipFile(BytesIO(file.stream.read())) as zip_file:

        try:
            project = read_dc_from_zip(zip_file, "data/project.json", Project)
        except KeyError:
            raise FlaskException("Could not find project.json.",
                                 error_code=404)
        except (json.JsonException, ValidationError) as e:
            raise FlaskException(f"Failed to process project.json. {str(e)}",
                                 error_code=401)

        try:
            scene = read_dc_from_zip(zip_file, "data/scene.json", Scene)
        except KeyError:
            raise FlaskException("Could not find scene.json.", error_code=404)
        except (json.JsonException, ValidationError) as e:
            return json.dumps(f"Failed to process scene.json. {str(e)}"), 401

        if project.scene_id != scene.id:
            raise FlaskException("Project assigned to different scene id.",
                                 error_code=401)

        with tempfile.TemporaryDirectory() as tmp_dir:

            # restore original environment
            sys.path = list(original_sys_path)
            sys.modules = dict(original_sys_modules)

            prepare_object_types_dir(tmp_dir, OBJECT_TYPE_MODULE)

            for scene_obj in scene.objects:

                obj_type_name = scene_obj.type

                if obj_type_name in objects:  # there might be more instances of the same type
                    continue

                logger.debug(f"Importing {obj_type_name}.")

                try:
                    obj_type_src = read_str_from_zip(
                        zip_file,
                        f"object_types/{humps.depascalize(obj_type_name)}.py")
                except KeyError:
                    raise FlaskException(
                        f"Object type {obj_type_name} is missing in the package.",
                        error_code=404)

                try:
                    ast = parse(obj_type_src)
                except Arcor2Exception:
                    raise FlaskException(
                        f"Invalid code of the {obj_type_name} object type.",
                        error_code=401)

                # TODO fill in OT description (is it used somewhere?)
                objects[obj_type_name] = ObjectType(obj_type_name,
                                                    obj_type_src)
                get_base_from_imported_package(objects[obj_type_name], objects,
                                               zip_file, tmp_dir, ast)

                type_def = save_and_import_type_def(obj_type_src,
                                                    obj_type_name, Generic,
                                                    tmp_dir,
                                                    OBJECT_TYPE_MODULE)

                assert obj_type_name == type_def.__name__

                if type_def.abstract():
                    raise FlaskException(
                        f"Scene contains abstract object type: {obj_type_name}.",
                        error_code=401)

        for obj_type in objects.values():  # handle models

            # TODO rather iterate on content of data/models?
            try:
                model = read_dc_from_zip(
                    zip_file,
                    f"data/models/{humps.depascalize(obj_type.id)}.json",
                    ObjectModel).model()
            except KeyError:
                continue

            logger.debug(f"Found model {model.id} of type {model.type}.")

            obj_type.model = model.metamodel()

            if obj_type.id != obj_type.model.id:
                raise FlaskException(
                    f"Model id ({obj_type.model.id}) has to be the same as ObjectType id ({obj_type.id}).",
                    error_code=401,
                )

            models[obj_type.id] = model

        if not project.has_logic:
            logger.debug("Importing the main script.")

            try:
                script = zip_file.read("script.py").decode("UTF-8")
            except KeyError:
                raise FlaskException("Could not find script.py.",
                                     error_code=404)

            try:
                parse(script)
            except Arcor2Exception:
                raise FlaskException("Invalid code of the main script.",
                                     error_code=401)

    # check that we are not going to overwrite something
    if not overwrite_scene:

        try:
            ps_scene = ps.get_scene(scene.id)
        except ps.ProjectServiceException:
            pass
        else:

            # do not take created / modified into account
            ps_scene.created = scene.created = None
            ps_scene.modified = scene.modified = None

            if ps_scene != scene:
                raise FlaskException(
                    "Scene difference detected. Overwrite needed.",
                    error_code=402)

    if not overwrite_project:

        try:
            ps_project = ps.get_project(project.id)
        except ps.ProjectServiceException:
            pass
        else:

            # do not take created / modified into account
            ps_project.created = project.created = None
            ps_project.modified = project.modified = None

            if ps_project != project:
                raise FlaskException(
                    "Project difference detected. Overwrite needed.",
                    error_code=402)

    if not overwrite_object_types:

        for obj_type in objects.values():

            try:

                ot = ps.get_object_type(obj_type.id)

                # ignore changes in description (no one cares)
                if ot.source != obj_type.source or ot.model != obj_type.model:
                    raise FlaskException(
                        f"Difference detected for {obj_type.id} object type. Overwrite needed.",
                        error_code=402)
            except ps.ProjectServiceException:
                pass

    if not overwrite_project_sources and not project.has_logic:

        try:
            if ps.get_project_sources(project.id).script != script:
                raise FlaskException(
                    "Script difference detected. Overwrite needed.",
                    error_code=402)
        except ps.ProjectServiceException:
            pass

    if not overwrite_collision_models:

        for model in models.values():
            try:
                if model != ps.get_model(model.id, model.type()):
                    raise FlaskException(
                        "Collision model difference detected. Overwrite needed.",
                        error_code=402)
            except ps.ProjectServiceException:
                pass

    for model in models.values():
        ps.put_model(model)

    for obj_type in objects.values():
        ps.update_object_type(obj_type)

    ps.update_scene(scene)
    ps.update_project(project)
    if not project.has_logic:
        ps.update_project_sources(ProjectSources(project.id, script))

    logger.info(
        f"Imported project {project.name} (scene {scene.name}), with {len(objects)} "
        f"object type(s) and {len(models)} model(s).")

    return ImportResult(scene.id, project.id).to_json(), 200
Exemple #2
0
def handle_dobot_exception(e: DobotApiException) -> tuple[str, int]:
    return json.dumps(str(e)), 400
Exemple #3
0
 def value_to_json(cls, value: Any) -> str:
     return json.dumps(value)
Exemple #4
0
def test_project_const(start_processes: None, ars: ARServer) -> None:

    event(ars, events.c.ShowMainScreen)

    assert ars.call_rpc(
        rpc.s.NewScene.Request(uid(),
                               rpc.s.NewScene.Request.Args("Test scene")),
        rpc.s.NewScene.Response).result

    scene_data = event(ars, events.s.OpenScene).data
    assert scene_data
    scene = scene_data.scene

    event(ars, events.s.SceneState)

    assert ars.call_rpc(
        rpc.s.AddObjectToScene.Request(
            uid(),
            rpc.s.AddObjectToScene.Request.Args("random_actions",
                                                RandomActions.__name__)),
        rpc.s.AddObjectToScene.Response,
    ).result

    obj = event(ars, events.s.SceneObjectChanged).data
    assert obj

    # ------------------------------------------------------------------------------------------------------------------

    assert ars.call_rpc(
        rpc.p.NewProject.Request(
            uid(), rpc.p.NewProject.Request.Args(scene.id, "Project name")),
        rpc.p.NewProject.Response,
    ).result

    proj = event(ars, events.p.OpenProject).data
    assert proj

    event(ars, events.s.SceneState)

    assert ars.call_rpc(
        rpc.p.AddConstant.Request(
            uid(),
            rpc.p.AddConstant.Request.Args("min_time", "double",
                                           json.dumps(0.45))),
        rpc.p.AddConstant.Response,
    ).result

    c1 = event(ars, events.p.ProjectConstantChanged).data
    assert c1

    assert not ars.call_rpc(
        rpc.p.AddConstant.Request(
            uid(),
            rpc.p.AddConstant.Request.Args("min_time", "double",
                                           json.dumps(0.62))),
        rpc.p.AddConstant.Response,
    ).result

    assert not ars.call_rpc(  # attempt to update without lock
        rpc.p.UpdateConstant.Request(
            uid(),
            rpc.p.UpdateConstant.Request.Args(c1.id, name="min_time_updated")),
        rpc.p.UpdateConstant.Response,
    ).result

    # ------------------------------------------------------------------------------------------------------------------
    # the user opens a menu and then closes it without actually changing anything

    lock_object(ars, c1.id)

    assert ars.call_rpc(
        rpc.p.UpdateConstant.Request(uid(),
                                     rpc.p.UpdateConstant.Request.Args(
                                         c1.id, name="min_time_1"),
                                     dry_run=True),
        rpc.p.UpdateConstant.Response,
    ).result

    assert ars.call_rpc(
        rpc.p.UpdateConstant.Request(uid(),
                                     rpc.p.UpdateConstant.Request.Args(
                                         c1.id, name="min_time_2"),
                                     dry_run=True),
        rpc.p.UpdateConstant.Response,
    ).result

    unlock_object(ars, c1.id)

    # ------------------------------------------------------------------------------------------------------------------

    lock_object(ars, c1.id)

    assert ars.call_rpc(
        rpc.p.UpdateConstant.Request(
            uid(),
            rpc.p.UpdateConstant.Request.Args(c1.id, name="min_time_updated")),
        rpc.p.UpdateConstant.Response,
    ).result

    c1u = event(ars, events.p.ProjectConstantChanged).data
    assert c1u

    event(ars, events.lk.ObjectsUnlocked)

    assert c1u.id == c1.id
    assert c1.name != c1u.name
    assert c1.type == c1u.type

    # ------------------------------------------------------------------------------------------------------------------
    # try to add and remove

    assert ars.call_rpc(
        rpc.p.AddConstant.Request(
            uid(),
            rpc.p.AddConstant.Request.Args("min_time_2", "double",
                                           json.dumps(0.62))),
        rpc.p.AddConstant.Response,
    ).result

    c2 = event(ars, events.p.ProjectConstantChanged).data
    assert c2

    assert ars.call_rpc(
        rpc.p.RemoveConstant.Request(uid(),
                                     rpc.p.RemoveConstant.Request.Args(c2.id)),
        rpc.p.RemoveConstant.Response,
    ).result

    c2e = event(ars, events.p.ProjectConstantChanged)
    assert c2e.data
    assert c2e.data.id == c2.id
    assert c2e.change_type == c2e.Type.REMOVE

    # ------------------------------------------------------------------------------------------------------------------
    # attempt to add a constant with duplicate name

    assert not ars.call_rpc(
        rpc.p.AddConstant.Request(
            uid(),
            rpc.p.AddConstant.Request.Args(c1u.name, "double",
                                           json.dumps(0.62))),
        rpc.p.AddConstant.Response,
    ).result

    # ------------------------------------------------------------------------------------------------------------------

    assert ars.call_rpc(
        rpc.p.AddActionPoint.Request(
            uid(), rpc.p.AddActionPoint.Request.Args("ap1",
                                                     common.Position())),
        rpc.p.AddActionPoint.Response,
    ).result

    ap = event(ars, events.p.ActionPointChanged).data
    assert ap is not None

    assert ars.call_rpc(
        rpc.p.AddAction.Request(
            uid(),
            rpc.p.AddAction.Request.Args(
                ap.id,
                "test_action",
                f"{obj.id}/{RandomActions.random_double.__name__}",
                [
                    common.ActionParameter(
                        "range_min", common.ActionParameter.TypeEnum.CONSTANT,
                        json.dumps(c1.id)),
                    common.ActionParameter("range_max", "double", "0.55"),
                ],
                [common.Flow(outputs=["random_value"])],
            ),
        ),
        rpc.p.AddAction.Response,
    ).result

    action = event(ars, events.p.ActionChanged).data
    assert action

    assert not ars.call_rpc(
        rpc.p.RemoveConstant.Request(uid(),
                                     rpc.p.RemoveConstant.Request.Args(c1.id)),
        rpc.p.RemoveConstant.Response).result

    # ------------------------------------------------------------------------------------------------------------------
    # try to execute action using constant parameter

    assert ars.call_rpc((rpc.s.StartScene.Request(uid())),
                        rpc.s.StartScene.Response).result

    assert event(ars, events.s.SceneState
                 ).data.state == events.s.SceneState.Data.StateEnum.Starting
    assert event(ars, events.s.SceneState
                 ).data.state == events.s.SceneState.Data.StateEnum.Started

    assert ars.call_rpc(
        rpc.p.ExecuteAction.Request(
            uid(), rpc.p.ExecuteAction.Request.Args(action.id)),
        rpc.p.ExecuteAction.Response)

    event(ars, events.a.ActionExecution)
    res = event(ars, events.a.ActionResult)

    assert res.data
    assert res.data.action_id == action.id
    assert not res.data.error

    assert ars.call_rpc((rpc.s.StopScene.Request(uid())),
                        rpc.s.StopScene.Response).result
    assert event(ars, events.s.SceneState
                 ).data.state == events.s.SceneState.Data.StateEnum.Stopping
    assert event(ars, events.s.SceneState
                 ).data.state == events.s.SceneState.Data.StateEnum.Stopped

    assert ars.call_rpc(
        rpc.p.RemoveAction.Request(uid(), rpc.p.IdArgs(action.id)),
        rpc.p.RemoveAction.Response).result
    assert event(ars, events.p.ActionChanged).data

    assert ars.call_rpc(
        rpc.p.RemoveConstant.Request(uid(),
                                     rpc.p.RemoveConstant.Request.Args(c1.id)),
        rpc.p.RemoveConstant.Response).result
    event(ars, events.p.ProjectConstantChanged)
Exemple #5
0
def call(
    method: Method,
    url: str,
    *,
    return_type: ReturnType = None,
    list_return_type: ReturnType = None,
    body: OptBody = None,
    params: OptParams = None,
    files: OptFiles = None,
    timeout: OptTimeout = None,
) -> ReturnValue:
    """Universal function for calling REST APIs.

    :param method: HTTP method.
    :param url: Resource address.
    :param return_type: If set, function will try to return one value of a given type.
    :param list_return_type: If set, function will try to return list of a given type.
    :param body: Data to be send in the request body.
    :param params: Path parameters.
    :param files: Instead of body, it is possible to send files.
    :param timeout: Specific timeout for a call.
    :return: Return value/type is given by return_type/list_return_type. If both are None, nothing will be returned.
    """
    logger.debug(
        f"{method} {url}, body: {body}, params: {params}, files: {files is not None}, timeout: {timeout}"
    )

    if body and files:
        raise RestException("Can't send data and files at the same time.")

    if return_type and list_return_type:
        raise RestException(
            "Only one argument from 'return_type' and 'list_return_type' can be used."
        )

    if return_type is None:
        return_type = list_return_type

    # prepare data into dict
    if isinstance(body, JsonSchemaMixin):
        d = humps.camelize(body.to_dict())
    elif isinstance(body, list):
        d = []
        for dd in body:
            if isinstance(dd, JsonSchemaMixin):
                d.append(humps.camelize(dd.to_dict()))
            else:
                d.append(dd)
    elif body is not None:
        raise RestException("Unsupported type of data.")
    else:
        d = {}

    if params:
        params = humps.camelize(params)
    else:
        params = {}

    assert params is not None

    # requests just simply stringifies parameters, which does not work for booleans
    for param_name, param_value in params.items():
        if isinstance(param_value, bool):
            params[param_name] = "true" if param_value else "false"

    if timeout is None:
        timeout = Timeout()

    try:
        if files:
            resp = method.value(url,
                                files=files,
                                timeout=timeout,
                                params=params)
        else:
            resp = method.value(url,
                                data=json.dumps(d),
                                timeout=timeout,
                                headers=headers,
                                params=params)
    except requests.exceptions.RequestException as e:
        logger.debug("Request failed.", exc_info=True)
        # TODO would be good to provide more meaningful message but the original one could be very very long
        raise RestException("Catastrophic system error.") from e

    logger.debug(resp.url)  # to see if query parameters are ok

    _handle_response(resp)

    if return_type is None:
        return None

    if issubclass(return_type, BytesIO):

        if list_return_type:
            raise NotImplementedError

        return BytesIO(resp.content)

    logger.debug(f"Response text: {resp.text}")

    try:
        resp_json = resp.json()
    except ValueError as e:
        logger.debug(f"Got invalid JSON in the response: {resp.text}")
        raise RestException("Invalid JSON.") from e

    logger.debug(f"Response json: {resp_json}")
    if isinstance(resp_json, (dict, list)):
        resp_json = humps.decamelize(resp_json)
    logger.debug(f"Decamelized json: {resp_json}")

    if list_return_type and not isinstance(resp_json, list):
        logger.debug(
            f"Expected list of type {return_type}, but got {resp_json}.")
        raise RestException("Response is not a list.")

    if issubclass(return_type, JsonSchemaMixin):

        if list_return_type:
            return [
                dataclass_from_json(item, return_type) for item in resp_json
            ]

        else:
            assert not isinstance(resp_json, list)

            # TODO temporary workaround for bug in humps (https://github.com/nficano/humps/issues/127)
            from arcor2.data.object_type import Box

            if return_type is Box:
                resp_json["size_x"] = resp_json["sizex"]
                resp_json["size_y"] = resp_json["sizey"]
                resp_json["size_z"] = resp_json["sizez"]

            return dataclass_from_json(resp_json, return_type)

    else:  # probably a primitive

        if list_return_type:
            return [
                primitive_from_json(item, return_type) for item in resp_json
            ]
        else:
            assert not isinstance(resp_json, list)
            return primitive_from_json(resp_json, return_type)
Exemple #6
0
def image_to_json(value: Image) -> str:
    return json.dumps(image_to_str(value))
Exemple #7
0
 def value_to_json(cls, value: list[Pose]) -> str:
     return json.dumps([v.to_json() for v in value])
Exemple #8
0
def test_project_ap_rpcs(start_processes: None, ars: ARServer) -> None:

    upload_def(Box, BoxModel(Box.__name__, 1, 2, 3))

    event(ars, events.c.ShowMainScreen)

    assert ars.call_rpc(
        rpc.s.NewScene.Request(get_id(), rpc.s.NewScene.Request.Args("Test scene")), rpc.s.NewScene.Response
    ).result

    assert len(event(ars, events.o.ChangedObjectTypes).data) == 1

    scene_data = event(ars, events.s.OpenScene).data
    assert scene_data
    scene = scene_data.scene

    event(ars, events.s.SceneState)

    assert ars.call_rpc(
        rpc.s.AddObjectToScene.Request(get_id(), rpc.s.AddObjectToScene.Request.Args("box", Box.__name__, Pose())),
        rpc.s.AddObjectToScene.Response,
    ).result

    obj = event(ars, events.s.SceneObjectChanged).data
    assert obj

    # ------------------------------------------------------------------------------------------------------------------

    assert ars.call_rpc(
        rpc.p.NewProject.Request(get_id(), rpc.p.NewProject.Request.Args(scene.id, "Project name")),
        rpc.p.NewProject.Response,
    ).result

    event(ars, events.s.SceneSaved)
    event(ars, events.p.OpenProject)
    event(ars, events.s.SceneState)

    assert ars.call_rpc(
        rpc.p.AddActionPoint.Request(get_id(), rpc.p.AddActionPoint.Request.Args("parent_ap", Position())),
        rpc.p.AddActionPoint.Response,
    ).result

    parent_ap_evt = event(ars, events.p.ActionPointChanged)

    assert ars.call_rpc(
        rpc.p.AddActionPoint.Request(
            get_id(), rpc.p.AddActionPoint.Request.Args("child_ap", Position(-1), parent_ap_evt.data.id)
        ),
        rpc.p.AddActionPoint.Response,
    ).result

    child_ap_evt = event(ars, events.p.ActionPointChanged)
    assert child_ap_evt.data.parent == parent_ap_evt.data.id

    lock_object(ars, child_ap_evt.data.id)

    assert ars.call_rpc(
        rpc.p.AddActionPointOrientation.Request(
            get_id(), rpc.p.AddActionPointOrientation.Request.Args(child_ap_evt.data.id, Orientation())
        ),
        rpc.p.AddActionPointOrientation.Response,
    ).result

    ori = event(ars, events.p.OrientationChanged)

    assert ars.call_rpc(
        rpc.p.AddAction.Request(
            get_id(),
            rpc.p.AddAction.Request.Args(
                child_ap_evt.data.id,
                "act_name",
                f"{obj.id}/{Box.update_pose.__name__}",
                [ActionParameter("new_pose", PosePlugin.type_name(), json.dumps(ori.data.id))],
                [Flow()],
            ),
        ),
        rpc.p.AddAction.Response,
    ).result

    event(ars, events.p.ActionChanged)

    unlock_object(ars, child_ap_evt.data.id)

    ars.event_mapping[ActionChanged.__name__] = ActionChanged

    assert ars.call_rpc(
        rpc.p.CopyActionPoint.Request(get_id(), rpc.p.CopyActionPoint.Request.Args(parent_ap_evt.data.id)),
        rpc.p.CopyActionPoint.Response,
    ).result

    new_parent_ap = event(ars, events.p.ActionPointChanged)
    assert not new_parent_ap.data.parent

    new_child_ap = event(ars, events.p.ActionPointChanged)
    assert new_child_ap.data.parent == new_parent_ap.data.id

    new_ori = event(ars, events.p.OrientationChanged)
    assert new_ori.parent_id == new_child_ap.data.id

    # with events.p.ActionChanged it would return only BareAction (without parameters)
    new_action = event(ars, ActionChanged)
    ars.event_mapping[ActionChanged.__name__] = events.p.ActionChanged
    assert new_action.parent_id == new_child_ap.data.id

    # Pose parameter (orientation id) should be updated now
    assert len(new_action.data.parameters) == 1
    assert json.loads(new_action.data.parameters[0].value) == new_ori.data.id