PKH'Kgd-yyrpws/__init__.py""" python wrapper for Autodesk Revit Server This is a python module for interacting with Autodesk Revit Server using its RESTful API. This module requires 'requests' module for handling http requests to the Revit Server. Module Files: exceptions.py: Defines module exceptions and custom exceptions for http status codes returned by the server api.py: Documents all standard keys that are returned in JSON dictionaries from server API calls. models.py: Defines classes and namedtuples that wrap the data returned from server API calls. server.py: Defines the server wrapper class. RevitServer class aims to support all the Revit Server API functionality. Example: >>> name = '' >>> version = '2017' # server version in XXXX format >>> rserver = RevitServer(name, version) >>> # listing all files, folders, and models in a server >>> for parent, folders, files, models in rserver.walk(): ... print(parent) ... for fd in folders: ... print('\t@d {}'.format(fd.path)) ... for f in files: ... print('\t@f {}'.format(f.path)) ... for m in models: ... print('\t@m {}'.format(m.path)) """ __version__ = "1.1.0" from rpws.exceptions import * from rpws.server import RevitServer PKPD&K:ĵ rpws/api.py# http request header REQ_HEADER_USERNAME = 'User-Name' REQ_HEADER_MACHINE = 'User-Machine-Name' REQ_HEADER_GUID = 'Operation-GUID' # folder structure DIVIDER = '|' # commands # ------------------------------------------------------------------------------ REQ_CMD_SERVERPROP = "/serverproperties" # { # "AccessLevelTypes": list, # "MachineName": str, # "MaximumFolderPathLength": int, # "MaximumModelNameLength": int, # "ServerRoles": list, # "Servers": list # } SERVER_ACCESSLEVEL_KEY = "AccessLevelTypes" SERVER_MACHINENAME_KEY = "MachineName" SERVER_MAXPATHLENGTH_KEY = "MaximumFolderPathLength" SERVER_MAXNAMELENGTH_KEY = "MaximumModelNameLength" SERVER_ROLES_KEY = "ServerRoles" SERVER_SERVERS_KEY = "Servers" # ------------------------------------------------------------------------------ REQ_CMD_CONTENTS = "/contents" # { # "Path": str, # "DriveFreeSpace": int, # "DriveSpace": int, # "Files": list, # "Folders": list, # "LockContext": str, # "LockState": int, # "ModelLocksInProgress": list, # "Models": list # } NODE_PATH_KEY = "Path" NODE_DRIVE_FREESPACE_KEY = "DriveFreeSpace" NODE_DRIVE_TOTALSPACE_KEY = "DriveSpace" NODE_FILES_KEY = "Files" NODE_FOLDERS_KEY = "Folders" NODE_LOCK_CTX_KEY = "LockContext" NODE_LOCK_STATE_KEY = "LockState" NODE_LOCKS_INPROGRESS_KEY = "ModelLocksInProgress" NODE_MODELS_KEY = "Models" # "Files" key # { # "IsText": bool, # "Name": str, # "Size": int # } NODE_FILES_ISTXT_KEY = "IsText" NODE_FILES_NAME_KEY = "Name" NODE_FILES_SIZE_KEY = "Size" # "Folders" key # { # "HasContents": bool, # "LockContext": str, # "LockState": int, # "ModelLocksInProgress": list, # "Name": str, # "Size": int # } NODE_FOLDERS_HASCONTENTS_KEY = "HasContents" NODE_FOLDERS_LOCKCONTEXT_KEY = "LockContext" NODE_FOLDERS_LOCKSTATE_KEY = "LockState" NODE_FOLDERS_LOCKINPROGRESS_KEY = "ModelLocksInProgress" NODE_FOLDERS_NAME_KEY = "Name" NODE_FOLDERS_SIZE_KEY = "Size" # "Models" key # { # "LockContext": str, # "LockState": int, # "ModelLocksInProgress": list, # "ModelSize": int, # "Name": str, # "ProductVersion": int, # "SupportSize": int # } NODE_MODELS_LOCKCONTEXT_KEY = "LockContext" NODE_MODELS_LOCKSTATE_KEY = "LockState" NODE_MODELS_LOCKINPROGRESS_KEY = "ModelLocksInProgress" NODE_MODELS_SIZE_KEY = "ModelSize" NODE_MODELS_NAME_KEY = "Name" NODE_MODELS_PRODUCTVERSION_KEY = "ProductVersion" NODE_MODELS_SUPPORTSIZE_KEY = "SupportSize" # ------------------------------------------------------------------------------ REQ_CMD_DIRINFO = "/directoryinfo" # { # "Path": str, # "DateCreated": str, # "/Date(1483465123167)/" # "DateModified": str, # "/Date(1501619985043)/" # "Exists": bool, # "FolderCount": int, # "IsFolder": bool, # "LastModifiedBy": str, # "LockContext": str, # "LockState": int, # "ModelCount": int, # "ModelLocksInProgress": list, # "ModelSize": int, # "Size": int # } NODE_DIRINFO_PATH_KEY = "Path" NODE_DIRINFO_DATECREATED_KEY = "DateCreated" NODE_DIRINFO_DATEMODIFIED_KEY = "DateModified" NODE_DIRINFO_EXISTS_KEY = "Exists" NODE_DIRINFO_FOLDERCOUNT_KEY = "FolderCount" NODE_DIRINFO_ISFOLDER_KEY = "IsFolder" NODE_DIRINFO_LASTMODIFIEDBY_KEY = "LastModifiedBy" NODE_DIRINFO_LOCKCTX_KEY = "LockContext" NODE_DIRINFO_LOCKSTATE_KEY = "LockState" NODE_DIRINFO_MODELCOUNT_KEY = "ModelCount" NODE_DIRINFO_LOCKSINPROGRESS_KEY = "ModelLocksInProgress" NODE_DIRINFO_MODELSIZE_KEY = "ModelSize" NODE_DIRINFO_SIZE_KEY = "Size" # ------------------------------------------------------------------------------ REQ_CMD_MODELINFO = "/modelinfo" # { # "Path": str, # "DateCreated": str, #"/Date(1483465179783)/" # "DateModified": str, # "/Date(1503702490000)/" # "LastModifiedBy": str, # "ModelGUID": str, # "b04725c0-9369-4482-aecf-5ad900d4c1bb" # "ModelSize": int, # "SupportSize": int # } NODE_MODELINFO_PATH_KEY = "Path" NODE_MODELINFO_DATECREATED_KEY = "DateCreated" NODE_MODELINFO_DATEMODIFIED_KEY = "DateModified" NODE_MODELINFO_LASTMODIFIEDBY_KEY = "LastModifiedBy" NODE_MODELINFO_MODELGUID_KEY = "ModelGUID" NODE_MODELINFO_MODELSIZE_KEY = "ModelSize" NODE_MODELINFO_SUPPORTSIZE_KEY = "SupportSize" # ------------------------------------------------------------------------------ REQ_CMD_PROJINFO = "/projectinfo" # [ # { # "A:title": "Text", # "<>": dict # } # ] PARAM_CATNAME_KEY = "A:title" # Parameter dict keys # { # "#text": "BLDG", # "@displayName": "Building Name", # "@id": "940f16f5-879a-4bf6-b722-b6a0043ffa9b", # "@type": "system", # "@typeOfParameter": "Text" # } PARAM_VALUE_KEY = "#text" PARAM_NAME_KEY = "@displayName" PARAM_ID_KEY = "@id" PARAM_TYPE_KEY = "@type" PARAM_DTYPE_KEY = "@typeOfParameter" # ------------------------------------------------------------------------------ REQ_CMD_MHISTORY = "/history" # { # "Path": str, # "Items": list # } MHISTORY_PATH_KEY = "Path" MHISTORY_ITEMS_KEY = "Items" # { # "Comment": str, # "Date": str, # "/Date(1483465201000)/" # "ModelSize": int, # "OverwrittenByHistoryNumber": int, # "SupportSize": int, # "User": str, # "VersionNumber": int # } MHISTORYITEM_COMMENT_KEY = "Comment" MHISTORY_DATE_KEY = "Date" MHISTORY_MODELSIZE_KEY = "ModelSize" MHISTORY_OVERWRITE_KEY = "OverwrittenByHistoryNumber" MHISTORY_SUPPORTSIZE_KEY = "SupportSize" MHISTORY_USER_KEY = "User" MHISTORY_VERSION_KEY = "VersionNumber" # ------------------------------------------------------------------------------ # shared data types # "ModelLocksInProgress" key # { # "Age": str, # "PT29M32.1723042S" # "ModelLockOptions": int, # "ModelLockType": int, # "ModelPath": str, # "TimeStamp": str, # "/Date(1504130384000)/" # "UserName": str # } NODE_LIP_AGE_KEY = "Age" NODE_LIP_LOCKOPTIONS_KEY = "ModelLockOptions" NODE_LIP_LOCKTYPE_KEY = "ModelLockType" NODE_LIP_MODELPATH_KEY = "ModelPath" NODE_LIP_TIMESTAMP_KEY = "TimeStamp" NODE_LIP_USERNAME_KEY = "UserName" # ------------------------------------------------------------------------------ REQ_CMD_LOCK = "/lock" # ------------------------------------------------------------------------------ REQ_CMD_CANCELLOCK = "/inProgressLock" # ------------------------------------------------------------------------------ REQ_CMD_UNLOCK = "/lock?objectMustExist=true" # ------------------------------------------------------------------------------ REQ_CMD_CHILDNLOCKS = "/descendent/locks" # { # "Path": str, # "Items": list # "DescendentHasLockContext": bool # } CHILDLOCKS_PATH_KEY = "Path" CHILDLOCKS_ITEMS_KEY = "Items" CHILDLOCKS_LOCKCTX = "DescendentHasLockContext" # { # "Path": str, # "FailedItems": list # } CHILDLOCKS_DELPATH_KEY = "Path" CHILDLOCKS_DELFAILEDITEMS_KEY = "FailedItems" # ------------------------------------------------------------------------------ REQ_CMD_MKDIR = "" # ------------------------------------------------------------------------------ REQ_CMD_DELETE = "?newObjectName" # ------------------------------------------------------------------------------ REQ_CMD_RENAME = "?newObjectName={new_name}" # ------------------------------------------------------------------------------ REQ_CMD_COPY = "?destinationObjectPath={dest_path}"\ "&pasteAction=Copy"\ "&replaceExisting={replace_exist}" # ------------------------------------------------------------------------------ REQ_CMD_MOVE = "?destinationObjectPath={dest_path}"\ "&pasteAction=Move"\ "&replaceExisting={replace_exist}" PKPD&KUmJqqrpws/exceptions.py# Revit server execeptions ----------------------------------------------------- class ServerVersionNotSupported(Exception): pass class ServerConnectionError(Exception): pass class ServerTimeoutError(Exception): pass class UnhandledException(Exception): pass # Revit Server API http status codes ------------------------------------------ # 400 class ServerBadRequestError(Exception): pass # 403 class ServerForbiddenError(Exception): pass # 404 class ServerFileNotFound(Exception): pass # 405 class ServerMethodNotAllowedError(Exception): pass # 414 class ServerURITooLongError(Exception): pass # 500 class ServerInternalError(Exception): pass # 501 class ServerNotImplemented(Exception): pass # 503 class ServerServiceUnavailableError(Exception): pass PKI'K@ou=u=rpws/models.pyimport re from collections import namedtuple import datetime import enum ServerInfo = namedtuple('ServerInfo', ['name', # type: str 'version', # type: str 'machine_name', # type: str 'roles', # type: list 'access_level_types', # type: list 'max_path_length', # type: int 'max_name_length', # type: int 'servers', # type: list ]) """ namedtuple for server properties Attributes: name (str): Server name version (str): Revit Server version machine_name (str): Revit Server machine name roles (list): List of server roles access_level_types (list): List of access level types max_path_length (int): Maximum allowed length for server paths max_name_length (int): Maximum allowed length for server entry names servers (list): List of neighbour servers """ class ServerRole(enum.Enum): """ Enum representing various server role codes Attributes: Host = 0 Accelerator = 1 Admin = 2 """ Host = 0 Accelerator = 1 Admin = 2 ServerDriveInfo = namedtuple('ServerDriveInfo', ['drive_space', # type: int 'drive_freespace', # type: int ]) """ namedtuple for server drive info Attributes: drive_space (int): Total drive space in bytes drive_freespace (int): Free drive space in bytes """ EntryContents = namedtuple('EntryContents', ['path', # type: str 'drive_space', # type: int 'drive_freespace', # type: int 'files', # type: list 'folders', # type: list 'lock_context', # type: str 'lock_state', # type: LockState 'locks_inprogress', # type: list 'models', # type: list ]) """ namedtuple for server entry contents (Encapsulates result of /contents) Attributes: path (str): Full path of this entry on server drive_space (int): Total server drive space in bytes drive_freespace (int): Free server drive space in bytes files (list): List of all files under this entry folders (list): List of all folders under this entry lock_context (str): Lock context on this entry lock_state (LockState): Lock state on this entry locks_inprogress (list): List of all in-progress locks models (list): List of all models under this entry """ EntryDirInfo = namedtuple('EntryDirInfo', ['path', # type: str 'name', # type: str 'size', # type: int 'date_created', # type: DateEntry 'date_modified', # type: DateEntry 'exists', # type: bool 'folder_count', # type: int 'is_folder', # type: bool 'last_modified_by', # type: str 'lock_context', # type: str 'lock_state', # type: LockState 'model_count', # type: int 'model_size', # type: int 'locks_inprogress', # type: list ]) """ namedtuple for server entry directory info (Encapsulates result of /directoryinfo) Attributes: path (str): Full path of this entry on server name (str): Entry name including extension size (int): Entry size in bytes date_created (DateEntry): Date entry was created date_modified (DateEntry): Date entry was last modified exists (bool): True if entry exists folder_count (int): Number of sub folders is_folder (bool): True of this entry if a directory last_modified_by (str): Username of user who last modified the model lock_context (str): Lock context on this entry lock_state (LockState): Lock state on this entry model_count (int): Number of models under this entry model_size (int): If this entry is a model, size of model in bytes locks_inprogress (list): List of all in-progress locks """ class LockState(enum.Enum): """ Enum representing Revit Server lock states Attributes: Unlocked = 0 Locked = 1 LockedParent = 2 LockedChild = 3 BeingUnlocked = 4 BeingLocked = 5 """ Unlocked = 0 Locked = 1 LockedParent = 2 LockedChild = 3 BeingUnlocked = 4 BeingLocked = 5 class LockOptions(enum.Enum): """ Enum representing Revit Server lock options Attributes: Read = 1 Write = 2 NonExclusiveReadWrite = 128 """ NotSet = 0 Read = 1 Write = 2 NonExclusiveReadWrite = 128 ReadAndNonExclusiveReadWrite = 129 WriteAndNonExclusiveReadWrite = 130 ReadWriteAndNonExclusiveReadWrite = 130 class LockType(enum.Enum): """ Enum representing Revit Server lock type Attributes: Data = 0 Permission = 1 """ Data = 0 Permissions = 1 FileInfo = namedtuple('FileInfo', ['path', # type: str 'name', # type: str 'size', # type: int 'is_text', # type: bool ]) """ namedtuple for info on a server file Attributes: path (str): Full path of this file on server name (str): File name including extension size (int): File size in bytes is_text (bool): True if text file """ FolderInfo = namedtuple('FolderInfo', ['path', # type: str 'name', # type: str 'size', # type: int 'has_contents', # type: bool 'lock_context', # type: str 'lock_state', # type: LockState 'locks_inprogress', # type: list ]) """ namedtuple for info on a server folder Attributes: path (str): Full path of this folder on server name (str): File name including extension size (int): File size in bytes has_contents (bool): True if folder has contents lock_context (str): Lock context on this entry lock_state (LockState): Lock state on this entry locks_inprogress (list): List of all in-progress locks """ ModelInfo = namedtuple('ModelInfo', ['path', # type: str 'name', # type: str 'size', # type: int 'support_size', # type: int 'product_version', # type: int 'lock_context', # type: str 'lock_state', # type: LockState 'locks_inprogress', # type: list ]) """ namedtuple for info on a server model Attributes: path (str): Full path of this model on server name (str): File name including extension size (int): File size in bytes support_size (int): Size of all support files in bytes product_version (int): Revit version of this model lock_context (str): Lock context on this entry lock_state (LockState): Lock state on this entry locks_inprogress (list): List of all in-progress locks """ ModelInfoEx = namedtuple('ModelInfoEx', ['path', # type: str 'name', # type: str 'size', # type: int 'guid', # type: str 'date_created', # type: DateEntry 'date_modified', # type: DateEntry 'last_modified_by', # type: str 'support_size', # type: int ]) """ namedtuple for extended info on a server model Attributes: path (str): Full path of this model on server name (str): File name including extension size (int): File size in bytes guid (str): GUID of the model date_created (DateEntry): Date model was created date_modified (DateEntry): Date model was last modified last_modified_by (str): Username of user who last modified the model support_size (int): Size of all support files in bytes """ ProjectInfo = namedtuple('ProjectInfo', ['parameters', # type: list ]) """ namedtuple for project info of a hosted model Attributes: parameters (list): List of project parameters in this model """ ProjParameter = namedtuple('ProjParameter', ['name', # type: str 'value', # type: str 'id', # type: str 'category', # type: str 'type', # type: ParamType 'datatype', # type: ParamDataType ]) """ namedtuple for info project parameters in a hosted model Attributes: name (str): Parameter display name value (str): Parameter value id (str): Parameter unique id category (str): Parameter category name type (ParamType): Parameter Type datatype (ParamDataType): Parameter storage type """ class ParamType(enum.Enum): """ Enum representing parameter types Attributes: System = 'system' Custom = 'custom' Shared = 'shared' Unknown = 'unknown' """ System = 'system' Custom = 'custom' Shared = 'shared' Unknown = 'unknown' class ParamDataType(enum.Enum): """ Enum representing parameter storage types Attributes: Length = 'length' Number = 'number' Material = 'material' Text = 'text' MultilineText = "multiline text" YesNo = 'yes/no' Unknown = 'unknown' """ Length = 'length' Number = 'number' Material = 'material' Text = 'text' MultilineText = "multiline text" YesNo = 'yes/no' Unknown = 'unknown' MHistoryInfo = namedtuple('MHistoryInfo', ['path', # type: str 'items', # type: list ]) """ namedtuple for model history info Attributes: path (str): Full path of the hosted model items (list): List of history items """ MHistoryItemInfo = namedtuple('MHistoryItemInfo', ['id', # type: str 'comment', # type: str 'date', # type: DateEntry 'model_size', # type: int 'overwrittenby', # type: str 'support_size', # type: int 'user', # type: str ]) """ namedtuple for model history item info Attributes: id (str): History item id comment (str): Comment recorded when syncing date (DateEntry): Date this history item was recorded size (int): File size in bytes overwrittenby (str): Id of history item overwriting this item support_size (int): Size of all support files in bytes user (str): Username recording the history item (by syncing model) """ IPLockInfo = namedtuple('IPLockInfo', ['age', # type: TimeSpanEntry 'lock_options', # type: LockOptions 'lock_type', # type: LockType 'model_path', # type: str 'timestamp', # type: DateEntry 'username', # type: str ]) """ namedtuple for info on an in-progress entry lock Attributes: age (str): Age of the lock lock_options (int): Lock options lock_type (int): Lock type model_path (str): Full path of the model being locked timestamp (DateEntry): Timestamp for when the lock was initiated username (str): Username who initiated this lock """ ChildrenLockInfo = namedtuple('ChildrenLockInfo', ['path', # type: str 'items', # type: list 'lock_context', # type: str ]) """ namedtuple for info locked children under an entry Attributes: path (str): Full path of parent entry items (list): List of paths of locked models under this entry lock_context (str): Lock context of parent entry """ class DateEntry(datetime.datetime): """ Timestamp data type converting Revit Server string timestamps to a typical python datetime.datetime object Example: >>> ts = DateEntry.fromrsdatestring("/Date(1483465201000)/") DateEntry(2017, 1, 3, 17, 40, 1) """ @classmethod def fromrsdatestring(cls, date_string): seconds_since_epoch = int(date_string[6:-2])/1000 return cls.utcfromtimestamp(seconds_since_epoch) class TimeSpanEntry(datetime.timedelta): """ Timespan data type converting Revit Server timespan to a typical python datetime.timedelta object Example: >>> ts = TimeSpanEntry.fromrstimespanstring("PT11M42.5154811S") TimeSpanEntry(0, 5856, 811000) """ @classmethod def fromrstimespanstring(cls, timespan_string): days = re.findall('(\d+)D', timespan_string) days = int(days[0]) if days else 0 minutes = re.findall('(\d+)M', timespan_string) minutes = int(minutes[0]) if minutes else 0 seconds = re.findall('(\d+)\.(\d+)S', timespan_string) seconds, millisecs = (int(seconds[0][0]), int(seconds[0][1])) \ if seconds else (0, 0) return cls(days=days, minutes=minutes, seconds=seconds, milliseconds=millisecs) PKH'Kt3ccrpws/server.pyimport os.path as op import uuid import getpass import socket # third party modules import requests # rpws components import rpws import rpws.api as api import rpws.models as models # Default portion of the server url. The only odd one is 2012 which does not # include server version in url. Each Revit server version can only host # Revit models of the same version. Also it's practical for a company to want # a consistent server name to host models of different Revit version. # Thus the server version was added to the url after 2012 version. # Otherwise the url for two servers called X for 2016 and 2017 Revit models # would have been the same url. sroots = {"2012": "/RevitServerAdminRESTService/AdminRESTService.svc", "2013": "/RevitServerAdminRESTService2013/AdminRESTService.svc", "2014": "/RevitServerAdminRESTService2014/AdminRESTService.svc", "2015": "/RevitServerAdminRESTService2015/AdminRESTService.svc", "2016": "/RevitServerAdminRESTService2016/AdminRESTService.svc", "2017": "/RevitServerAdminRESTService2017/AdminRESTService.svc", "2018": "/RevitServerAdminRESTService2018/AdminRESTService.svc", } class RevitServer(object): """Handles all communication with Revit Server as initialized. Args: name (str): Server name. version (str): Server version. username (str, optional): Username to be passed to in http calls. This is set to current user if not provided. machine (str, optional): Machine name to be passed to in http calls This is set to current machine if not provided. Attributes: name (str): Server name. version (str): Server version. _base_uri (str, private): Base URI of the initialized server _huser (str, private): Username of the initialized server. _hmachine (str, private): Machine name of the initialized server Example: >>> rserver = RevitServer('server01', '2017') >>> print(rserver) """ def __init__(self, name, version, username=None, machine=None): # make sure version is of type str if type(version) == int: version = str(version) # verify server version is supported if version not in sroots: raise rpws.ServerVersionNotSupported( 'Supported versions are: {}'.format([x for x in sroots.keys()])) self.name = name self.version = version self._base_uri = "http://" + self.name + sroots[self.version] if username: self._huser = username else: self._huser = getpass.getuser() if machine: self._hmachine = machine else: self._hmachine = socket.gethostname() def __repr__(self): """ repr for RevitServer object Example: >>> rserver = RevitServer('server01', '2017') >>> print(rserver) """ return ''\ .format(self.__class__.__name__, self.name, self.version) @property def _header_dict(self): """ Private property that creates and returns the http header dict Returns: dict: Header dict to be passed to Revit Server """ # using uuid module to generate a unique session id that is required # by the Revit Server for logging purposes return {api.REQ_HEADER_USERNAME: self._huser, api.REQ_HEADER_MACHINE: self._hmachine, api.REQ_HEADER_GUID: str(uuid.uuid4())} def _httpmethod(self, http_method, command, node_uri=None, rootcmd=False): """ The single method that handles all http requests Args: http_method (func): method from requests module (e.g. requests.get) command (str): Revit Server API command from rpws.api node_uri (str, optional): Path to an entry on the server. Root if not provided. rootcmd (bool, optional): True if command should be executed at root level. Default is False. Returns: Depending on the input command returns int: http response status code dict: data dict returned by server as the command result Raises: rpws.ServerNotImplemented: When server does not exist rpws.ServerFileNotFound: When file, folder, or model does not exist rpws.ServerForbiddenError: On accessing forbidden entries rpws.ServerInternalError: When server has internal error rpws.ServerMethodNotAllowedError: When method is not allowed rpws.ServerURITooLongError: When url is too long for server rpws.ServerTimeoutError: On connection timeout rpws.ServerConnectionError: On connection error rpws.ServerBadRequestError: On bad request url rpws.ServerServiceUnavailableError: When service is not available rpws.UnhandledException: Any other http status codes """ # create the http request url if rootcmd: req_url = self._base_uri + command else: req_url = self._base_uri + '/' + self._api_path(node_uri) + command # send to server try: r = http_method(req_url, headers=self._header_dict) except requests.ConnectTimeout: raise rpws.ServerTimeoutError(req_url) except requests.ConnectionError: raise rpws.ServerConnectionError(self._base_uri) # process status codes and results if r.status_code == 200 and r.text: r.encoding = 'utf-8-sig' return r.json() elif 200 <= r.status_code < 300: return True elif r.status_code == 400: raise rpws.ServerBadRequestError(node_uri) elif r.status_code == 403: raise rpws.ServerForbiddenError(node_uri) elif r.status_code == 404: raise rpws.ServerFileNotFound(node_uri) elif r.status_code == 405: raise rpws.ServerMethodNotAllowedError(node_uri) elif r.status_code == 414: raise rpws.ServerURITooLongError(node_uri) elif r.status_code == 500: raise rpws.ServerInternalError(node_uri) elif r.status_code == 501: raise rpws.ServerNotImplemented('name:{} version:{}' .format(self.name, self.version)) elif r.status_code == 503: raise rpws.ServerServiceUnavailableError(node_uri) else: raise rpws.UnhandledException('requests status code:{}' .format(r.status_code)) def _get(self, command, node_uri=None, rootcmd=False): """ Send a GET request to Revit Server Args: command (str): Revit Server API command from rpws.api node_uri (str, optional): Path to an entry on the server. Root if not provided. rootcmd (bool, optional): True if command should be executed at root level. Default is False. Returns: See _httpmethod() method for results """ return self._httpmethod(requests.get, command, node_uri, rootcmd) def _post(self, command, node_uri=None, rootcmd=False): """ Send a POST request to Revit Server Args: command (str): Revit Server API command from rpws.api node_uri (str, optional): Path to an entry on the server. Root if not provided. rootcmd (bool, optional): True if command should be executed at root level. Default is False. Returns: See _httpmethod() method for results """ return self._httpmethod(requests.post, command, node_uri, rootcmd) def _put(self, command, node_uri=None, rootcmd=False): """ Send a PUT request to Revit Server Args: command (str): Revit Server API command from rpws.api node_uri (str, optional): Path to an entry on the server. Root if not provided. rootcmd (bool, optional): True if command should be executed at root level. Default is False. Returns: See _httpmethod() method for results """ return self._httpmethod(requests.put, command, node_uri, rootcmd) def _delete(self, command, node_uri=None, rootcmd=False): """ Send a DELETE request to Revit Server Args: command (str): Revit Server API command from rpws.api node_uri (str, optional): Path to an entry on the server. Root if not provided. rootcmd (bool, optional): True if command should be executed at root level. Default is False. Returns: See _httpmethod() method for results """ return self._httpmethod(requests.delete, command, node_uri, rootcmd) @staticmethod def _api_path(nodepath=None): """ Generates a Revit Server directory structure path from provided file, folder, or model path. Revit server uses '|' to separate directory entries so as not to conflict with '/' in http urls. So: "/Training/MyOffice/2017/Model.rvt" will be changed to: "|Training|MyOffice|2017|Model.rvt" Args: nodepath (str, optional): Path to an entry on the server. Root if not provided. Returns: str: Path formatted for Revit Server urls """ if nodepath: return nodepath.replace(op.sep, api.DIVIDER) else: return api.DIVIDER @staticmethod def _root_path(nodepath=None): """ Makes sure that the path starts with / as root of the server. Args: nodepath (str, optional): Path to an entry on the server. Root if not provided. Returns: str: Reformatted path """ if nodepath: npath = op.normpath(nodepath) return npath if npath.startswith(op.sep) else op.join(op.sep, npath) else: return op.sep @staticmethod def _getserverdriveinfo(contents_dict): """ Returns drive space info acquired from server Data keys: "DriveFreeSpace" and "DriveSpace" Args: contents_dict (dict): Data dict returned by server Returns: rpws.models.ServerDriveInfo """ # make the server drive info obj return models.ServerDriveInfo( drive_space=contents_dict[api.NODE_DRIVE_TOTALSPACE_KEY], drive_freespace=contents_dict[api.NODE_DRIVE_FREESPACE_KEY]) @staticmethod def _getlocks(ip_lock_list): """ Extracts locks from list of locks in progress returned by server Args: ip_lock_list (list): List of dict returned by server representing locks in progress. Returns: rpws.models.IPLockInfo """ locks_list = [] # check to make sure ip_lock_list is not None if ip_lock_list: # for each lock in progress dict for ip_lock in ip_lock_list: # extract and create timestamp obj ts = models.DateEntry.\ fromrsdatestring(ip_lock[api.NODE_LIP_TIMESTAMP_KEY]) # extract and create timespan obj tspan = models.TimeSpanEntry.\ fromrstimespanstring(ip_lock[api.NODE_LIP_AGE_KEY]) # extract and create lock options and type lop = models.LockOptions(ip_lock[api.NODE_LIP_LOCKOPTIONS_KEY]) lt = models.LockType(ip_lock[api.NODE_LIP_LOCKTYPE_KEY]) # extract and create in-progress lock info obj locks_list.append( models.IPLockInfo( age=tspan, lock_options=lop, lock_type=lt, model_path=ip_lock[api.NODE_LIP_MODELPATH_KEY], timestamp=ts, username=ip_lock[api.NODE_LIP_USERNAME_KEY])) return locks_list def _getfiles(self, nodepath, contents_dict): """ Extracts and returns the list of all files under provided server entry. Args: nodepath (str): Path to an entry on the server. contents_dict (dict): data dict from server Returns: list of rpws.models.FileInfo """ return [models.FileInfo( path=op.join(nodepath if nodepath else self.path, x[api.NODE_FILES_NAME_KEY]), name=x[api.NODE_FILES_NAME_KEY], size=x[api.NODE_FILES_SIZE_KEY], is_text=x[api.NODE_FILES_ISTXT_KEY]) for x in contents_dict.get(api.NODE_FILES_KEY, [])] def _getfolders(self, nodepath, contents_dict): """ Extracts and returns the list of all folders under provided server entry. Args: nodepath (str): Path to an entry on the server. contents_dict (dict): data dict from server Returns: list of rpws.models.FolderInfo """ folder_infos = [] for fdict in contents_dict.get(api.NODE_FOLDERS_KEY, []): # get in-progress lock objs ip_locks = self._getlocks( fdict[api.NODE_FOLDERS_LOCKINPROGRESS_KEY]) # make lock state obj lock_state = models.LockState(fdict[api.NODE_FOLDERS_LOCKSTATE_KEY]) # create folder info obj finfo = models.FolderInfo( path=op.join(nodepath if nodepath else self.path, fdict[api.NODE_FOLDERS_NAME_KEY]), name=fdict[api.NODE_FOLDERS_NAME_KEY], size=fdict[api.NODE_FOLDERS_SIZE_KEY], has_contents=fdict[api.NODE_FOLDERS_HASCONTENTS_KEY], lock_context=fdict[api.NODE_FOLDERS_LOCKCONTEXT_KEY], lock_state=lock_state, locks_inprogress=ip_locks) folder_infos.append(finfo) return folder_infos def _getmodels(self, nodepath, contents_dict): """ Extracts and returns the list of all models under provided server entry. Args: nodepath (str): Path to an entry on the server. contents_dict (dict): data dict from server Returns: list of rpws.models.ModelInfo """ model_infos = [] for mdict in contents_dict.get(api.NODE_MODELS_KEY, []): # get in-progress lock objs ip_locks = self._getlocks(mdict[api.NODE_MODELS_LOCKINPROGRESS_KEY]) # make lock state obj lock_state = models.LockState(mdict[api.NODE_MODELS_LOCKSTATE_KEY]) # create model info obj minfo = models.ModelInfo( path=op.join(nodepath if nodepath else self.path, mdict[api.NODE_MODELS_NAME_KEY]), name=mdict[api.NODE_MODELS_NAME_KEY], size=mdict[api.NODE_MODELS_SIZE_KEY], support_size=mdict[api.NODE_MODELS_SUPPORTSIZE_KEY], product_version=mdict[api.NODE_MODELS_PRODUCTVERSION_KEY], lock_context=mdict[api.NODE_MODELS_LOCKCONTEXT_KEY], lock_state=lock_state, locks_inprogress=ip_locks) model_infos.append(minfo) return model_infos @property def path(self): """ Root path of server Returns: str: Root path """ return op.sep def getinfo(self): """ Returns server properties API command: /serverproperties Returns: rpws.models.ServerInfo Example: >>> rserver = RevitServer('server01', '2017') >>> sinfo = rserver.getinfo() """ # get properties dict from server on root props_dict = self._get(api.REQ_CMD_SERVERPROP, rootcmd=True) # server roles sroles = [models.ServerRole(x) for x in props_dict[api.SERVER_ROLES_KEY]] # make the server info obj return models.ServerInfo( name=self.name, version=self.version, machine_name=props_dict[api.SERVER_MACHINENAME_KEY], roles=sroles, access_level_types=props_dict[api.SERVER_ACCESSLEVEL_KEY], max_path_length=props_dict[api.SERVER_MAXPATHLENGTH_KEY], max_name_length=props_dict[api.SERVER_MAXNAMELENGTH_KEY], servers=props_dict[api.SERVER_SERVERS_KEY]) def getdriveinfo(self): """ Returns server drive information API command: /contents API keys: DriveFreeSpace and DriveSpace Returns: rpws.models.ServerDriveInfo Example: >>> rserver = RevitServer('server01', '2017') >>> dinfo = rserver.getdriveinfo() >>> print(dinfo.drive_space) """ return self._getserverdriveinfo(self._get(api.REQ_CMD_CONTENTS)) def scandir(self, nodepath=None): """ Returns files, folders, and models from root or provided directory in an entry info obj API command: /contents Args: nodepath (str, optional): Path to an entry on the server. Root if not provided. Returns: rpws.models.EntryContents Example: >>> rserver = RevitServer('server01', '2017') >>> for entry in rserver.scandir('/example/path'): ... print(entry.path) """ # get the entry contents (root if nodepath is none contents_dict = self._get(api.REQ_CMD_CONTENTS, nodepath) # get drive info sdriveinfo = self._getserverdriveinfo(contents_dict) # get the files, folders, and models node_files = self._getfiles(nodepath, contents_dict) node_folders = self._getfolders(nodepath, contents_dict) node_models = self._getmodels(nodepath, contents_dict) lock_ctx = contents_dict[api.NODE_LOCK_CTX_KEY] lock_state = models.LockState(contents_dict[api.NODE_LOCK_STATE_KEY]) ip_locks = self._getlocks(contents_dict[api.NODE_LOCKS_INPROGRESS_KEY]) return models.EntryContents( path=nodepath, drive_space=sdriveinfo.drive_space, drive_freespace=sdriveinfo.drive_freespace, files=node_files, folders=node_folders, lock_context=lock_ctx, lock_state=lock_state, locks_inprogress=ip_locks, models=node_models) def listfiles(self, nodepath=None): """ Returns files from root or provided directory API command: /contents API key: Files Args: nodepath (str, optional): Path to an entry on the server. Root if not provided. Returns: list of rpws.models.FileInfo Example: >>> rserver = RevitServer('server01', '2017') >>> for file in rserver.listfiles('/example/path'): ... print(file.path) """ return self._getfiles(nodepath, self._get(api.REQ_CMD_CONTENTS, nodepath)) def listfolders(self, nodepath=None): """ Returns folders from root or provided directory API command: /contents API key: Folders Args: nodepath (str, optional): Path to an entry on the server. Root if not provided. Returns: list of rpws.models.FolderInfo Example: >>> rserver = RevitServer('server01', '2017') >>> for folder in rserver.listfolders('/example/path'): ... print(folder.path) """ return self._getfolders(nodepath, self._get(api.REQ_CMD_CONTENTS, nodepath)) def listmodels(self, nodepath=None): """ Returns models from root or provided directory API command: /contents API key: Models Args: nodepath (str, optional): Path to an entry on the server. Root if not provided. Returns: list of rpws.models.ModelInfo Example: >>> rserver = RevitServer('server01', '2017') >>> for model in rserver.listmodels('/example/path'): ... print(model.path) """ return self._getmodels(nodepath, self._get(api.REQ_CMD_CONTENTS, nodepath)) def getfolderinfo(self, nodepath): """ Returns directory info from provided directory API command: /directoryinfo Args: nodepath (str): Path to an entry on the server. Returns: rpws.models.EntryDirInfo Example: >>> rserver = RevitServer('server01', '2017') >>> for folder in rserver.listfolders('/example/path'): ... finfo = rserver.getfolderinfo(folder.path) ... print(finfo.date_created) """ if nodepath: # directory info from the entry ddict = self._get(api.REQ_CMD_DIRINFO, nodepath) # in-progress locks ip_locks = self._getlocks( ddict[api.NODE_DIRINFO_LOCKSINPROGRESS_KEY]) # make lock state lock_state = models.LockState(ddict[api.NODE_DIRINFO_LOCKSTATE_KEY]) # make time stamps date_created = models.DateEntry.\ fromrsdatestring(ddict[api.NODE_DIRINFO_DATECREATED_KEY]) date_modified = models.DateEntry.\ fromrsdatestring(ddict[api.NODE_DIRINFO_DATEMODIFIED_KEY]) # make the directory info obj return models.EntryDirInfo( path=nodepath, name=op.basename(nodepath), size=ddict[api.NODE_DIRINFO_SIZE_KEY], date_created=date_created, date_modified=date_modified, exists=ddict[api.NODE_DIRINFO_EXISTS_KEY], folder_count=ddict[api.NODE_DIRINFO_FOLDERCOUNT_KEY], is_folder=ddict[api.NODE_DIRINFO_ISFOLDER_KEY], last_modified_by=ddict[api.NODE_DIRINFO_LASTMODIFIEDBY_KEY], lock_context=ddict[api.NODE_DIRINFO_LOCKCTX_KEY], lock_state=lock_state, model_count=ddict[api.NODE_DIRINFO_MODELCOUNT_KEY], model_size=ddict[api.NODE_DIRINFO_MODELSIZE_KEY], locks_inprogress=ip_locks) def getmodelinfo(self, nodepath): """ Returns model info from provided model path API command: /modelinfo Args: nodepath (str): Path to a model on the server. Returns: rpws.models.ModelInfoEx Example: >>> rserver = RevitServer('server01', '2017') >>> for model in rserver.listmodels('/example/path'): ... minfo = rserver.getmodelinfo(model.path) ... print(minfo.size) """ if nodepath: # model info from the entry mdict = self._get(api.REQ_CMD_MODELINFO, nodepath) # make time stamps dc = models.DateEntry.\ fromrsdatestring(mdict[api.NODE_MODELINFO_DATECREATED_KEY]) dm = models.DateEntry.\ fromrsdatestring(mdict[api.NODE_MODELINFO_DATEMODIFIED_KEY]) # make the model info obj return models.ModelInfoEx( path=nodepath, name=op.basename(nodepath), size=mdict[api.NODE_MODELINFO_MODELSIZE_KEY], guid=mdict[api.NODE_MODELINFO_MODELGUID_KEY], date_created=dc, date_modified=dm, last_modified_by=mdict[api.NODE_MODELINFO_LASTMODIFIEDBY_KEY], support_size=mdict[api.NODE_MODELINFO_SUPPORTSIZE_KEY]) def getmodelhistory(self, nodepath): """ Returns model info from provided model path API command: /history Args: nodepath (str): Path to a model on the server. Returns: rpws.models.MHistoryInfo Example: >>> rserver = RevitServer('server01', '2017') >>> for model in rserver.listmodels('/example/path'): ... mhist = rserver.getmodelhistory(model.path) ... for hist in mhist.items: ... print(hist.comment, hist.user) """ # get history data from server mhist_dict = self._get(api.REQ_CMD_MHISTORY, nodepath) hist_items = [] for hitem_dict in mhist_dict[api.MHISTORY_ITEMS_KEY]: # make time stamp mhist_date = models.DateEntry.\ fromrsdatestring(hitem_dict[api.MHISTORY_DATE_KEY]) # make model history item info mhist = models.MHistoryItemInfo( id=hitem_dict[api.MHISTORY_VERSION_KEY], comment=hitem_dict[api.MHISTORYITEM_COMMENT_KEY], date=mhist_date, model_size=hitem_dict[api.MHISTORY_MODELSIZE_KEY], overwrittenby=hitem_dict[api.MHISTORY_OVERWRITE_KEY], support_size=hitem_dict[api.MHISTORY_SUPPORTSIZE_KEY], user=hitem_dict[api.MHISTORY_USER_KEY]) hist_items.append(mhist) # make model history info obj return models.MHistoryInfo(path=mhist_dict[api.MHISTORY_PATH_KEY], items=hist_items) def getprojectinfo(self, nodepath): """ Returns project info from provided model path API command: /projectinfo Args: nodepath (str): Path to an entry on the server. Returns: rpws.models.ProjectInfo Example: >>> rserver = RevitServer('server01', '2017') >>> for model in rserver.listmodels('/example/path'): ... pinfo = rserver.getprojectinfo(model.path) ... for pparam in pinfo.paramters: ... print(pparam.name, pparam.value) """ param_list = [] if nodepath: # get all parameter categories from server for this model param_cats = self._get(api.REQ_CMD_PROJINFO, nodepath) for cat in param_cats: # for each category create parameter obj for its parameters catname = cat[api.PARAM_CATNAME_KEY] cat.pop(api.PARAM_CATNAME_KEY) for pname, param in cat.items(): # get the type string for the property # and setup param type obj ptypestr = param.get(api.PARAM_TYPE_KEY, '').lower() if ptypestr: ptype = models.ParamType(ptypestr) else: ptype = models.ParamType.Unknown # get the datatype string for the property # and setup param data type obj pdatatypestr = param.get(api.PARAM_DTYPE_KEY, '').lower() if pdatatypestr: pdatatype = models.ParamDataType(pdatatypestr) else: pdatatype = models.ParamDataType.Unknown # make the project parameter obj pparam = models.ProjParameter( name=param.get(api.PARAM_NAME_KEY, ''), value=param.get(api.PARAM_VALUE_KEY, ''), id=param.get(api.PARAM_ID_KEY, ''), category=catname, type=ptype, datatype=pdatatype) param_list.append(pparam) return models.ProjectInfo(param_list) def lock(self, nodepath): """ Locks the provided model Args: nodepath (str): Path to a model on the server. Example: >>> rserver = RevitServer('server01', '2017') >>> for model in rserver.listmodels('/example/path'): ... rserver.lock(model.path) """ return self._put(api.REQ_CMD_LOCK, nodepath) def cancellock(self, nodepath): """ Cancels any in-progress locks one the provided model Args: nodepath (str): Path to a model on the server. Example: >>> rserver = RevitServer('server01', '2017') >>> for model in rserver.listmodels('/example/path'): ... rserver.cancellock(model.path) """ return self._delete(api.REQ_CMD_CANCELLOCK, nodepath) def unlock(self, nodepath): """ Unlocks the provided model Args: nodepath (str): Path to a model on the server. Example: >>> rserver = RevitServer('server01', '2017') >>> for model in rserver.listmodels('/example/path'): ... rserver.unlock(model.path) """ return self._delete(api.REQ_CMD_UNLOCK, nodepath) def getdescendentlocks(self, nodepath): """ Returns the decendent locks info API command: /descendent/locks Args: nodepath (str): Path to a dirctory on the server. Returns: rpws.models.ChildrenLockInfo Example: >>> rserver = RevitServer('server01', '2017') >>> for folder in rserver.listfolders('/example/path'): ... clockinfo = rserver.getdescendentlocks(folder.path) ... for locked_model_path in clockinfo.items: ... print(locked_model_path) """ # get decendent lock data chlocks_dict = self._get(api.REQ_CMD_CHILDNLOCKS, nodepath) # get the locked children list chlocks = chlocks_dict[api.CHILDLOCKS_ITEMS_KEY] if chlocks: # corrent the paths so they're from root locked_childs = [self._root_path(x) for x in chlocks] else: locked_childs = [] # make the children lock info obj return models.ChildrenLockInfo( path=nodepath, items=locked_childs, lock_context=chlocks_dict[api.CHILDLOCKS_LOCKCTX]) def deletedescendentlocks(self, nodepath): """ Returns the decendent locks info API command: /descendent/locks Args: nodepath (str): Path to a dirctory on the server. Returns: list of str for list of entries with failed locks Example: >>> rserver = RevitServer('server01', '2017') >>> for folder in rserver.listfolders('/example/path'): ... rserver.deletedescendentlocks(folder.path) """ # get decendent lock data chlocks_dict = self._delete(api.REQ_CMD_CHILDNLOCKS, nodepath) # get the list of failed locks failedchlocks = chlocks_dict[api.CHILDLOCKS_DELFAILEDITEMS_KEY] if failedchlocks: # corrent the paths so they're from root return [self._root_path(x) for x in failedchlocks] else: return [] def mkdir(self, nodepath): """ Create a directory under the provided path Args: nodepath (str): Path to a dirctory on the server. Example: >>> rserver = RevitServer('server01', '2017') >>> rserver.mkdir('/example/path') """ return self._put(api.REQ_CMD_MKDIR, nodepath) def rename(self, nodepath, new_nodename): """ Renamed a file, folder, or model under the provided path Args: nodepath (str): Path to a file, folder, or model on the server. new_nodename (str): New name for file, folder, or model Example: >>> rserver = RevitServer('server01', '2017') >>> rserver.rename('/example/path', '/example/newpath') """ return self._delete(api.REQ_CMD_RENAME.format(new_name=new_nodename), nodepath) def rmdir(self, nodepath): """ Deletes a file, folder, or model under the provided path Args: nodepath (str): Path to a file, folder, or model on the server. Example: >>> rserver = RevitServer('server01', '2017') >>> rserver.rmdir('/example/path') """ return self._delete(api.REQ_CMD_DELETE, nodepath) def delete(self, nodepath): """ Deletes a file, folder, or model under the provided path Args: nodepath (str): Path to a file, folder, or model on the server. Example: >>> rserver = RevitServer('server01', '2017') >>> rserver.delete('/example/path/model.rvt') """ return self._delete(api.REQ_CMD_DELETE, nodepath) def copy(self, nodepath, new_nodepath, overwrite=False): """ Copies a file, folder, or model to the new location Args: nodepath (str): Path to a file, folder, or model on the server. new_nodepath (str): Full path to new location and name. overwrite (bool): True to overwrite any existing entries. Example: >>> rserver = RevitServer('server01', '2017') >>> rserver.copy('/example/model.rvt', '/example/newmodel.rvt') """ # get api path for the new location new_apipath = self._api_path(new_nodepath) return self._post(api.REQ_CMD_COPY.format( dest_path=new_apipath, replace_exist=overwrite), nodepath) def move(self, nodepath, new_nodepath, overwrite=False): """ Moves a file, folder, or model to the new location Args: nodepath (str): Path to a file, folder, or model on the server. new_nodepath (str): Full path to new location and name. overwrite (bool): True to overwrite any existing entries. Example: >>> rserver = RevitServer('server01', '2017') >>> rserver.move('/example/model.rvt', '/example/newmodel.rvt') """ # get api path for the new location new_apipath = self._api_path(new_nodepath) return self._post(api.REQ_CMD_MOVE.format(dest_path=new_apipath, replace_exist=overwrite), nodepath) def walk(self, top=None, topdown=True, digmodels=False): """ Walks the provided directory or root and yields a 4-tuple of parent directory, folders, files, and models Args: top (str, optional): Parent directory. Root if not provided. topdown (bool): True to start from top and walk down digmodels (bool): True to list entries under a model folder Revit models on Revit Server are actually folders with files, and other subfolders. Returns: tuple: (parent, folders, files, models) Example: >>> rserver = RevitServer('server01', '2017') >>> for parent, folders, files, models in rserver.walk(): ... print(parent) ... for fd in folders: ... print('\t@d {}'.format(fd.path)) ... for f in files: ... print('\t@f {}'.format(f.path)) ... for m in models: ... print('\t@m {}'.format(m.path)) """ if not top: top = self.path entry_info = self.scandir(top) if topdown: # Yield before recursion if going top down yield top, entry_info.folders, entry_info.files, entry_info.models # Recurse into sub-directories for finfo in entry_info.folders: for x in self.walk(top=finfo.path, topdown=topdown): yield x # Recurse into sub-directories inside models if digmodels: for minfo in entry_info.models: for x in self.walk(top=minfo.path, topdown=topdown, digmodels=digmodels): yield x if not topdown: # Yield before recursion if going top down yield top, entry_info.folders, entry_info.files, entry_info.models PKPD&K'UZa/?ɯT[|!3oX`"zmv1Eњۂ sMq{6roSTv5`464tTP4B"Byx(P|zQ wT+=6^f=,yEF?UYwdIjiYa8j/d-%s1@Z?=ᅩCMaLC3!VH1V1,*_30fYIlYBgYڕ@OV%b:Aɕh7.l8iyN^| 'M__rqs&~#l*n E}IW^.|ha*b\[,VU࠻t1lFå PKH'Kgd-yyrpws/__init__.pyPKPD&K:ĵ rpws/api.pyPKPD&KUmJqq$rpws/exceptions.pyPKI'K@ou=u=N(rpws/models.pyPKH'Kt3ccerpws/server.pyPKPD&K'