Beispiel #1
0
    def MgetRubrics(self):
        """Retrieve list of all rubrics from server.

        Raises:
            PlomAuthenticationException: Authentication error.
            PlomSeriousException: any other unexpected error.

        Returns:
            list: list of dicts, possibly an empty list if server has no
                rubrics.
        """
        self.SRmutex.acquire()
        try:
            response = self.session.get(
                "https://{}/MK/rubric".format(self.server),
                json={
                    "user": self.user,
                    "token": self.token,
                },
                verify=False,
            )
            response.raise_for_status()
            return response.json()
        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            else:
                raise PlomSeriousException(
                    "Error of type {} getting rubric list".format(e)) from None
        finally:
            self.SRmutex.release()
Beispiel #2
0
    def MgetRubricsByQuestion(self, question_number):
        """Retrieve list of all rubrics from server for given question.

        Args:
            question_number (int)

        Raises:
            PlomAuthenticationException: Authentication error.
            PlomSeriousException: Other error types, possible needs fix or debugging.

        Returns:
            list: list of dicts, possibly an empty list if server has no
                rubrics for this question.
        """
        self.SRmutex.acquire()
        try:
            response = self.session.get(
                "https://{}/MK/rubric/{}".format(self.server, question_number),
                json={
                    "user": self.user,
                    "token": self.token,
                },
                verify=False,
            )
            response.raise_for_status()
            return response.json()
        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            else:
                raise PlomSeriousException(
                    "Error of type {} getting rubric list".format(e)) from None
        finally:
            self.SRmutex.release()
Beispiel #3
0
    def requestAndSaveToken(self, user, pw):
        """Get a authorisation token from the server.

        The token is then used to authenticate future transactions with the server.

        raises:
            PlomAPIException: a mismatch between server/client versions.
            PlomExistingLoginException: user already has a token:
                currently, we do not support getting another one.
            PlomAuthenticationException: wrong password, account
                disabled, etc: check contents for details.
            PlomSeriousException: something else unexpected such as a
                network failure.
        """
        self.SRmutex.acquire()
        try:
            response = self.session.put(
                "https://{}/users/{}".format(self.server, user),
                json={
                    "user": user,
                    "pw": pw,
                    "api": Plom_API_Version,
                    "client_ver": __version__,
                },
                verify=False,
                timeout=5,
            )
            # throw errors when response code != 200.
            response.raise_for_status()
            self.token = response.json()
            self.user = user
        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException(response.json()) from None
            elif response.status_code == 400:
                raise PlomAPIException(response.json()) from None
            elif response.status_code == 409:
                raise PlomExistingLoginException(response.json()) from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        except requests.ConnectionError as err:
            raise PlomSeriousException(
                "Cannot connect to server\n {}\n Please check details before trying again."
                .format(self.server)) from None
        finally:
            self.SRmutex.release()
Beispiel #4
0
    def McreateRubric(self, new_rubric):
        """Ask server to make a new rubric and get key back.

        Args:
            new_rubric (dict): the new rubric info as dict.

        Raises:
            PlomAuthenticationException: Authentication error.
            PlomSeriousException: Other error types, possible needs fix or debugging.

        Returns:
            list: A list of:
                [False] If operation was unsuccessful.
                [True, updated_commments_list] including the new comments.
        """
        self.SRmutex.acquire()
        try:
            response = self.session.put(
                "https://{}/MK/rubric".format(self.server),
                json={
                    "user": self.user,
                    "token": self.token,
                    "rubric": new_rubric,
                },
                verify=False,
            )
            response.raise_for_status()

            new_key = response.json()
            messenger_response = [True, new_key]

        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            elif response.status_code == 406:
                raise PlomSeriousException(
                    "Rubric sent was incomplete.") from None
            else:
                raise PlomSeriousException(
                    "Error of type {} when creating new rubric".format(
                        e)) from None
            messenger_response = [False]

        finally:
            self.SRmutex.release()
        return messenger_response
Beispiel #5
0
    def MsaveUserRubricTabs(self, question, tab_config):
        """Cache the user's rubric-tabs config for this question onto the server

        Args:
            question (int): the current question number
            tab_config (dict): the user's rubric pane configuration for
                this question.

        Raises:
            PlomAuthenticationException
            PlomSeriousException

        Returns:
            None
        """
        self.SRmutex.acquire()
        try:
            response = self.session.put(
                "https://{}/MK/user/{}/{}".format(self.server, self.user,
                                                  question),
                json={
                    "user": self.user,
                    "token": self.token,
                    "question": question,
                    "rubric_config": tab_config,
                },
                verify=False,
            )
            response.raise_for_status()

        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            elif response.status_code == 403:
                raise PlomSeriousException(response.text) from None
            else:
                raise PlomSeriousException(
                    "Error of type {} when creating new rubric".format(
                        e)) from None

        finally:
            self.SRmutex.release()
Beispiel #6
0
    def getInfoShortName(self):
        self.SRmutex.acquire()
        try:
            response = self.session.get("https://{}/info/shortName".format(
                self.server),
                                        verify=False)
            response.raise_for_status()
            shortName = response.text
        except requests.HTTPError as e:
            if response.status_code == 404:
                raise PlomSeriousException(
                    "Server could not find the spec - this should not happen!"
                ) from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        finally:
            self.SRmutex.release()

        return shortName
Beispiel #7
0
    def IDreturnIDdTask(self, code, studentID, studentName):
        """Return a completed IDing task: identify a paper.

        Exceptions:
            PlomConflict: `studentID` already used on a different paper.
            PlomAuthenticationException: login problems.
            PlomSeriousException: other errors.
        """
        self.SRmutex.acquire()
        try:
            response = self.session.put(
                "https://{}/ID/tasks/{}".format(self.server, code),
                json={
                    "user": self.user,
                    "token": self.token,
                    "sid": studentID,
                    "sname": studentName,
                },
                verify=False,
            )
            response.raise_for_status()
        except requests.HTTPError as e:
            if response.status_code == 409:
                raise PlomConflict(e) from None
            elif response.status_code == 401:
                raise PlomAuthenticationException() from None
            elif response.status_code == 404:
                raise PlomSeriousException(e) from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        finally:
            self.SRmutex.release()

        # TODO - do we need this return value?
        return True
Beispiel #8
0
    def replaceMissingHWQuestion(self,
                                 student_id=None,
                                 test=None,
                                 question=None):
        # can replace by SID or by test-number
        self.SRmutex.acquire()
        try:
            response = self.session.put(
                "https://{}/admin/missingHWQuestion".format(self.server),
                verify=False,
                json={
                    "user": self.user,
                    "token": self.token,
                    "question": question,
                    "sid": student_id,
                    "test": test,
                },
            )
            response.raise_for_status()
            rval = response.json()
        except requests.HTTPError as e:
            if response.status_code == 404:
                raise PlomSeriousException(
                    "Server could not find the TPV - this should not happen!"
                ) from None
            elif response.status_code == 401:
                raise PlomAuthenticationException() from None
            elif response.status_code == 409:  # that question already has pages
                raise PlomTakenException() from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        finally:
            self.SRmutex.release()

        return rval
Beispiel #9
0
    def get_spec(self):
        """Get the specification of the exam from the server.

        Returns:
            dict: the server's spec file, as in :func:`plom.SpecVerifier`.
        """
        self.SRmutex.acquire()
        try:
            response = self.session.get(
                "https://{}/info/spec".format(self.server),
                verify=False,
            )
            response.raise_for_status()
        except requests.HTTPError as e:
            if response.status_code == 404:
                raise PlomSeriousException(
                    "Server could not find the spec") from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e))
        finally:
            self.SRmutex.release()

        return response.json()
Beispiel #10
0
    def MrequestOneImage(self, task_code, image_id, md5sum):
        """Download one image from server by its database id.

        args:
            code (str): the task code such as "q1234g9".
                TODO: consider removing code/`task` from URL.
            image_id (int): TODO: int/str?  The key into the server's
                database of images.
            md5sum (str): the expected md5sum, just for sanity checks or
                something I suppose.

        return:
            bytes: png/jpeg or whatever as bytes.

        Errors/Exceptions
            401: not authenticated
            404: no such image
            409: wrong md5sum provided
        """
        self.SRmutex.acquire()
        try:
            response = self.session.get(
                "https://{}/MK/images/{}/{}/{}".format(self.server, task_code,
                                                       image_id, md5sum),
                json={
                    "user": self.user,
                    "token": self.token
                },
                verify=False,
            )
            response.raise_for_status()
            image = BytesIO(response.content).getvalue()
        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            elif response.status_code == 409:
                raise PlomConflict("Wrong md5sum provided") from None
            elif response.status_code == 404:
                raise PlomNoMoreException("Cannot find image") from None
            else:
                raise PlomSeriousException(
                    "Some other unexpected error {}".format(e)) from None
        finally:
            self.SRmutex.release()
        return image
Beispiel #11
0
    def uploadHWPage(self, sid, question, order, sname, fname, md5sum, bundle,
                     bundle_order):
        self.SRmutex.acquire()
        try:
            param = {
                "user": self.user,
                "token": self.token,
                "fileName": sname,
                "sid": sid,
                "question": question,
                "order": order,
                "md5sum": md5sum,
                "bundle": bundle,
                "bundle_order": bundle_order,
            }
            mime_type = mimetypes.guess_type(sname)[0]
            dat = MultipartEncoder(
                fields={
                    "param": json.dumps(param),
                    "originalImage": (sname, open(fname, "rb"),
                                      mime_type),  # image
                })
            response = self.session.put(
                "https://{}/admin/hwPages".format(self.server),
                json={
                    "user": self.user,
                    "token": self.token
                },
                data=dat,
                headers={"Content-Type": dat.content_type},
                verify=False,
            )
            response.raise_for_status()
        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        finally:
            self.SRmutex.release()

        return response.json()
Beispiel #12
0
    def IDrequestClasslist(self):
        """Ask server for the classlist.

        Returns:
            list: ordered list of (student id, student name) pairs.
                Both are strings.

        Raises:
            PlomAuthenticationException: login troubles.
            PlomBenignException: server has no classlist.
            PlomSeriousException: all other failures.
        """
        self.SRmutex.acquire()
        try:
            response = self.session.get(
                "https://{}/ID/classlist".format(self.server),
                json={
                    "user": self.user,
                    "token": self.token
                },
                verify=False,
            )
            # throw errors when response code != 200.
            response.raise_for_status()
            # you can assign to the encoding to override the autodetection
            # TODO: define API such that classlist must be utf-8?
            # print(response.encoding)
            # response.encoding = 'utf-8'
            # classlist = StringIO(response.text)
            classlist = response.json()
        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            elif response.status_code == 404:
                raise PlomBenignException(
                    "Server cannot find the class list") from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        finally:
            self.SRmutex.release()

        return classlist
Beispiel #13
0
    def MrequestWholePaper(self, code, questionNumber=0):
        self.SRmutex.acquire()
        # note - added default value for questionNumber so that this works correctly
        # when called from identifier. - Fixes #921
        try:
            response = self.session.get(
                "https://{}/MK/whole/{}/{}".format(self.server, code,
                                                   questionNumber),
                json={
                    "user": self.user,
                    "token": self.token
                },
                verify=False,
            )
            response.raise_for_status()

            # response should be multipart = [ pageData, f1,f2,f3..]
            imagesAsBytes = MultipartDecoder.from_response(response).parts
            images = []
            i = 0
            for iab in imagesAsBytes:
                if i == 0:
                    pageData = json.loads(iab.content)
                else:
                    images.append(BytesIO(
                        iab.content).getvalue())  # pass back image as bytes
                i += 1

        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            # TODO?
            elif response.status_code == 409:
                raise PlomTakenException(
                    "Task taken by another user.") from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        finally:
            self.SRmutex.release()

        return [pageData, images]
Beispiel #14
0
 def clearAuthorisation(self, user, pw):
     self.SRmutex.acquire()
     try:
         response = self.session.delete(
             "https://{}/authorisation".format(self.server),
             json={
                 "user": user,
                 "password": pw
             },
             verify=False,
         )
         response.raise_for_status()
     except requests.HTTPError as e:
         if response.status_code == 401:
             raise PlomAuthenticationException() from None
         else:
             raise PlomSeriousException(
                 "Some other sort of error {}".format(e)) from None
     finally:
         self.SRmutex.release()
Beispiel #15
0
    def triggerUpdateAfterLUpload(self):
        self.SRmutex.acquire()
        try:
            response = self.session.put(
                "https://{}/admin/loosePagesUploaded".format(self.server),
                verify=False,
                json={
                    "user": self.user,
                    "token": self.token
                },
            )
            response.raise_for_status()
        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        finally:
            self.SRmutex.release()

        return response.json()
Beispiel #16
0
    def MdidNotFinishTask(self, code):
        self.SRmutex.acquire()
        try:
            response = self.session.delete(
                "https://{}/MK/tasks/{}".format(self.server, code),
                json={
                    "user": self.user,
                    "token": self.token
                },
                verify=False,
            )
            response.raise_for_status()
        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        finally:
            self.SRmutex.release()

        return True
Beispiel #17
0
    def getMissingHW(self):
        self.SRmutex.acquire()
        try:
            response = self.session.get(
                "https://{}/REP/missingHW".format(self.server),
                verify=False,
                json={
                    "user": self.user,
                    "token": self.token
                },
            )
            response.raise_for_status()
        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        finally:
            self.SRmutex.release()

        return response.json()
Beispiel #18
0
    def IDrequestDoneTasks(self):
        self.SRmutex.acquire()
        try:
            response = self.session.get(
                "https://{}/ID/tasks/complete".format(self.server),
                json={
                    "user": self.user,
                    "token": self.token
                },
                verify=False,
            )
            response.raise_for_status()
            idList = response.json()
        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        finally:
            self.SRmutex.release()

        return idList
Beispiel #19
0
    def MgetMaxMark(self, question, ver):
        """Get the maximum mark for this question and version.

        Raises:
            PlomRangeExeception: `question` or `ver` is out of range.
            PlomAuthenticationException:
            PlomSeriousException: something unexpected happened.
        """
        self.SRmutex.acquire()
        try:
            response = self.session.get(
                "https://{}/MK/maxMark".format(self.server),
                json={
                    "user": self.user,
                    "token": self.token,
                    "q": question,
                    "v": ver
                },
                verify=False,
            )
            # throw errors when response code != 200.
            response.raise_for_status()
            # convert the content of the response to a textfile for identifier
            maxMark = response.json()
        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            elif response.status_code == 416:
                raise PlomRangeException(response.text) from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        finally:
            self.SRmutex.release()

        return maxMark
Beispiel #20
0
    def MreturnMarkedTask(
        self,
        code,
        pg,
        ver,
        score,
        mtime,
        tags,
        aname,
        pname,
        rubrics,
        integrity_check,
        image_md5_list,
    ):
        """Upload annotated image and associated data to the server.

        Returns:
            list: a 2-list of the form `[#done, #total]`.

        Raises:
            PlomAuthenticationException
            PlomConflict: integrity check failed, perhaps manager
                altered task.
            PlomTimeoutError: network trouble such as timeouts.
            PlomTaskChangedError
            PlomTaskDeletedError
            PlomSeriousException
        """
        # doesn't like ints, so convert ints to strings
        param = {
            "user": self.user,
            "token": self.token,
            "pg": str(pg),
            "ver": str(ver),
            "score": str(score),
            "mtime": str(mtime),
            "tags": tags,
            "rubrics": rubrics,
            "md5sum": hashlib.md5(open(aname, "rb").read()).hexdigest(),
            "integrity_check": integrity_check,
            "image_md5s": image_md5_list,
        }

        dat = MultipartEncoder(
            fields={
                "param": json.dumps(param),
                "annotated": (aname, open(aname, "rb"), "image/png"),  # image
                "plom": (pname, open(pname, "rb"), "text/plain"),  # plom-file
            })
        self.SRmutex.acquire()
        try:
            response = self.session.put(
                "https://{}/MK/tasks/{}".format(self.server, code),
                data=dat,
                headers={"Content-Type": dat.content_type},
                verify=False,
                timeout=(10, 120),
            )
            response.raise_for_status()
            ret = response.json()
        except (requests.ConnectionError, requests.Timeout) as e:
            raise PlomTimeoutError(
                "Upload timeout/connect error: {}\n\n".format(e) +
                "Retries are NOT YET implemented: as a workaround," +
                "you can re-open the Annotator on '{}'.\n\n".format(code) +
                "We will now process any remaining upload queue.") from None
        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            elif response.status_code == 406:
                raise PlomConflict(
                    "Integrity check failed. This can happen if manager has altered the task while you are annotating it."
                ) from None
            elif response.status_code == 409:
                raise PlomTaskChangedError(
                    "Task ownership has changed.") from None
            elif response.status_code == 410:
                raise PlomTaskDeletedError(
                    "No such task - it has been deleted from server."
                ) from None
            elif response.status_code == 400:
                raise PlomSeriousException(
                    "Image file is corrupted. This should not happen"
                ) from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        finally:
            self.SRmutex.release()
        return ret
Beispiel #21
0
    def MrequestImages(self, code, integrity_check):
        """Download images relevant to a question, both original and annotated.

        Args:
            code (str): the task code such as "q1234g9".

        Returns:
            3-tuple: `(image_metadata, annotated_image, plom_file)`
                `image_metadata` has various stuff: DB ids, md5sums, etc
                `annotated_image` and `plom_file` are the png file and
                and data associated with a previous annotations, or None.

        Raises:
            PlomAuthenticationException
            PlomTaskChangedError: you no longer own this task.
            PlomTaskDeletedError
            PlomSeriousException
        """
        self.SRmutex.acquire()
        try:
            response = self.session.get(
                "https://{}/MK/images/{}".format(self.server, code),
                json={
                    "user": self.user,
                    "token": self.token,
                    "integrity_check": integrity_check,
                },
                verify=False,
            )
            response.raise_for_status()

            # response is either [metadata] or [metadata, annotated_image, plom_file]
            imagesAnnotAndPlom = MultipartDecoder.from_response(response).parts
            image_metadata = json.loads(imagesAnnotAndPlom[0].text)
            if len(imagesAnnotAndPlom) == 1:
                # all is fine - no annotated image or plom data
                anImage = None
                plDat = None
            elif len(imagesAnnotAndPlom) == 3:
                # all fine - last two parts are annotated image + plom-data
                anImage = BytesIO(imagesAnnotAndPlom[1].content).getvalue()
                plDat = BytesIO(imagesAnnotAndPlom[2].content).getvalue()
            else:
                raise PlomSeriousException(
                    "Number of returns doesn't make sense: should be 1 or 3 but is {}"
                    .format(len(imagesAnnotAndPlom)))
        except requests.HTTPError as e:
            if response.status_code == 401:
                raise PlomAuthenticationException() from None
            elif response.status_code == 404:
                raise PlomSeriousException(
                    "Cannot find image file for {}.".format(code)) from None
            elif response.status_code == 409:
                raise PlomTaskChangedError(
                    "Ownership of task {} has changed.".format(code)) from None
            elif response.status_code == 406:
                raise PlomTaskChangedError(
                    "Task {} has been changed by manager.".format(
                        code)) from None
            elif response.status_code == 410:
                raise PlomTaskDeletedError(
                    "Task {} has been deleted by manager.".format(
                        code)) from None
            else:
                raise PlomSeriousException(
                    "Some other sort of error {}".format(e)) from None
        finally:
            self.SRmutex.release()

        return image_metadata, anImage, plDat