def request(self, method, path, query={}, headers={}, body={}, base_url=None):
        '''
        Issues a request to Onshape
        Args:
            - method (str): HTTP method
            - path (str): Path  e.g. /api/documents/:id
            - query (dict, default={}): Query params in key-value pairs
            - headers (dict, default={}): Key-value pairs of headers
            - body (dict, default={}): Body for POST request
            - base_url (str, default=None): Host, including scheme and port (if different from creds file)
        Returns:
            - requests.Response: Object containing the response from Onshape
        '''
        req_headers = self._make_headers(method, path, query, headers)
        if base_url is None:
            base_url = self._url
        url = base_url + path + '?' + urlencode(query)
        if self._logging:
            utils.log(body)
            utils.log(req_headers)
            utils.log('request url: ' + url)
        body = json.dumps(body) if type(body) == dict else body
        res = requests.request(method, url, headers=req_headers, data=body, allow_redirects=False, stream=True)
        if res.status_code == 307:
            location = urlparse(res.headers["Location"])
            querystring = parse_qs(location.query)
            if self._logging:
                utils.log('request redirected to: ' + location.geturl())
            new_query = {}
            new_base_url = location.scheme + '://' + location.netloc
            for key in querystring:
                new_query[key] = querystring[key][0]
            return self.request(method, location.path, query=new_query, headers=headers, base_url=new_base_url)
        elif not 200 <= res.status_code <= 206:
            if self._logging:
                utils.log('request failed, details: ' + res.text, level=1)
        else:
            if self._logging:
                utils.log('request succeeded, details: ' + res.text)
        return res