PKS#KQQrpws/__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.0.0" from rpws.exceptions import * from rpws.server import RevitServer PKQ#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}" PKQ#KMf99rpws/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 PKQ#Kcx[rpws/models.pyfrom collections import namedtuple import datetime import enum class DateEntry(datetime.datetime): @classmethod def fromrsdatestring(cls, date_string): seconds_since_epoch = int(date_string[6:-2])/1000 return cls.utcfromtimestamp(seconds_since_epoch) class ServerRole(enum.Enum): Host = 0 Accelerator = 1 Admin = 2 class LockState(enum.Enum): Unlocked = 0 Locked = 1 LockedParent = 2 LockedChild = 3 BeingUnlocked = 4 BeingLocked = 5 class ParamType(enum.Enum): System = 'system' Custom = 'custom' Shared = 'shared' Unknown = 'unknown' class ParamDataType(enum.Enum): Length = 'length' Number = 'number' Material = 'material' Text = 'text' MultilineText = "multiline text" YesNo = 'yes/no' Unknown = 'unknown' ServerInfo = namedtuple('ServerInfo', ['name', 'version', 'machine_name', 'roles', 'access_level_types', 'max_path_length', 'max_name_length', 'servers']) ServerDriveInfo = namedtuple('ServerDriveInfo', ['drive_space', 'drive_freespace']) EntryInfo = namedtuple('EntryInfo', ['path', 'drive_space', 'drive_freespace', 'files', 'folders', 'lock_context', 'lock_state', 'locks_inprogress', 'models']) FileInfo = namedtuple('FileInfo', ['path', 'name', 'size', 'is_text']) FolderInfo = namedtuple('FolderInfo', ['path', 'name', 'size', 'has_contents', 'lock_context', 'lock_state', 'locks_inprogress']) ModelInfo = namedtuple('ModelInfo', ['path', 'name', 'size', 'support_size', 'product_version', 'lock_context', 'lock_state', 'locks_inprogress']) InProgressLockInfo = namedtuple('InProgressLockInfo', ['age', 'lock_options', 'lock_type', 'model_path', 'timestamp', 'username']) DirectoryInfo = namedtuple('DirectoryInfo', ['path', 'name', 'size', 'date_created', 'date_modified', 'exists', 'folder_count', 'is_folder', 'last_modified_by', 'lock_context', 'lock_state', 'model_count', 'model_size', 'locks_inprogress']) ModelInfoEx = namedtuple('ModelInfoEx', ['path', 'name', 'size', 'guid', 'date_created', 'date_modified', 'last_modified_by', 'support_size']) ProjectInfo = namedtuple('ProjectInfo', ['parameters']) ProjectParameter = namedtuple('ProjectParameter', ['name', 'value', 'id', 'category', 'type', 'datatype']) ModelHistoryInfo = namedtuple('ModelHistoryInfo', ['path', 'items']) ModelHistoryItemInfo = namedtuple('ModelHistoryItemInfo', ['id', 'comment', 'date', 'model_size', 'overwrittenby', 'support_size', 'user']) ChildrenLockInfo = namedtuple('ChildrenLockInfo', ['path', 'items', 'lock_context']) PKQ#KS++rpws/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.InProgressLockInfo """ 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 in-progress lock info obj locks_list.append( models.InProgressLockInfo( age=ip_lock[api.NODE_LIP_AGE_KEY], lock_options=ip_lock[api.NODE_LIP_LOCKOPTIONS_KEY], lock_type=ip_lock[api.NODE_LIP_LOCKTYPE_KEY], 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 """ # 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 """ 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.EntryInfo """ # 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.EntryInfo( 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 """ 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 """ 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 """ 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.DirectoryInfo """ 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.DirectoryInfo( 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 """ 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.ModelHistoryInfo """ # 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.ModelHistoryItemInfo( 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.ModelHistoryInfo(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 """ 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.ProjectParameter( 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. """ 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. """ 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. """ 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 """ # 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 """ # 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. """ 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 """ 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. """ 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. """ 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. """ # 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. """ # 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): Parent directory 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) """ 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 PKQ#Kf**rpws-1.0.0.dist-info/LICENSEMIT License Copyright (c) 2017 hdm-dt-fb Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!H١Wdrpws-1.0.0.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,Q0343 /, (-JLR()*M ILR(4KM̫#DPK!HN0Grpws-1.0.0.dist-info/METADATAMMO@+樇AM41Q~G:3[̒"61{'㝆i>SZa/?'E>hy{a} 7i0jX=zKY6hM|mA9¦8= 9IݷZ}T*A;:AejgX?Y4; g];:#\7Lp{3˯EPž@kX%C`?P3eI #1}&I(p1./PK!H/RJrpws-1.0.0.dist-info/RECORDmr@{v0FEȅ"tdm"Of2Vywn"oiZ6嘦B"Da#v&XaՏX=LhX^b_P֕o ڪϸKkHO߶AJVs܍eې JňO/sv̡346#-G%CV4*=+`Zܪ"1a,*Jy!xၩp򒹁{e_PR #-'n`PPKS#KQQrpws/__init__.pyPKQ#K:ĵ rpws/api.pyPKQ#KMf99$rpws/exceptions.pyPKQ#Kcx['rpws/models.pyPKQ#KS++<rpws/server.pyPKQ#Kf**Irpws-1.0.0.dist-info/LICENSEPK!H١Wdrpws-1.0.0.dist-info/WHEELPK!HN0G<rpws-1.0.0.dist-info/METADATAPK!H/RJ_rpws-1.0.0.dist-info/RECORDPK UF