├── LICENSE ├── README.md └── bimserver.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Thomas Krijnen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-bimserver-client 2 | A minimal client for the BIMserver.org REST API 3 | 4 | Example: 5 | 6 | ```python 7 | # Commit an IFC file into a newly created project 8 | 9 | import base64 10 | import bimserver 11 | 12 | client = bimserver.api(server_address, username, password) 13 | 14 | deserializer_id = client.ServiceInterface.getDeserializerByName(deserializerName='Ifc2x3tc1 (Streaming)').get('oid') 15 | project_id = client.ServiceInterface.addProject(projectName="My new project", schema="ifc2x3tc1").get('oid') 16 | 17 | with open(fn, "rb") as f: 18 | ifc_data = f.read() 19 | 20 | client.ServiceInterface.checkin( 21 | poid= project_id, 22 | comment= "my first commit", 23 | deserializerOid= deserializer_id, 24 | fileSize= len(ifc_data), 25 | fileName= "IfcOpenHouse.ifc", 26 | data= base64.b64encode(ifc_data).decode('utf-8'), 27 | sync= "false", 28 | merge= "false" 29 | ) 30 | ``` 31 | 32 | 33 | ```python 34 | # Download latest revision from a specified project 35 | 36 | import json 37 | from urllib import request 38 | 39 | project_name = "MyProject" 40 | client = bimserver.api(server_address, username, password) 41 | projects = client.ServiceInterface.getProjectsByName(name=project_name) 42 | project = projects[0] 43 | 44 | serializer = client.ServiceInterface.getSerializerByName(serializerName='Ifc4 (Streaming)') 45 | serializer_id = serializer.get('oid') 46 | roid = projects[0].get('lastRevisionId') 47 | 48 | topicId = client.ServiceInterface.download( 49 | roids=[roid], 50 | serializerOid=serializer_id, 51 | query=json.dumps({}), 52 | sync='false', 53 | ) 54 | 55 | download_url = f"{server_address}/download?token={client.token}&zip=on&topicId={topicId}" 56 | res = request.urlopen(download_url) 57 | with open('res.zip', 'wb') as d: 58 | d.write(res.read()) 59 | ``` 60 | 61 | Or for BIMserver version 1.4: 62 | 63 | ```python 64 | # Commit an IFC file into a newly created project 65 | 66 | import base64 67 | import bimserver 68 | 69 | client = bimserver.api(server_address, username, password) 70 | 71 | deserializer_id = client.Bimsie1ServiceInterface.getSuggestedDeserializerForExtension(extension="ifc").get('oid') 72 | project_id = client.Bimsie1ServiceInterface.addProject(projectName="My new project").get('oid') 73 | 74 | with open("IfcOpenHouse.ifc", "rb") as f: 75 | ifc_data = f.read() 76 | 77 | client.Bimsie1ServiceInterface.checkin( 78 | poid= project_id, 79 | comment= "my first commit", 80 | deserializerOid= deserializer_id, 81 | fileSize= len(ifc_data), 82 | fileName= "IfcOpenHouse.ifc", 83 | data= base64.b64encode(ifc_data).decode('utf-8'), 84 | sync= "false" 85 | ) 86 | ``` 87 | 88 | Enable autocompletion in Python 2 89 | 90 | ```python 91 | import rlcompleter, readline 92 | readline.parse_and_bind('tab:complete') 93 | ``` 94 | 95 | -------------------------------------------------------------------------------- /bimserver.py: -------------------------------------------------------------------------------- 1 | import json 2 | import types 3 | import inspect 4 | 5 | try: 6 | import urllib2 7 | urlopen = urllib2.urlopen 8 | except: 9 | import urllib.request 10 | urlopen = urllib.request.urlopen 11 | 12 | try: 13 | inspect.signature(Api.minimumBimServerVersion) 14 | canInspectSignature = True 15 | except AttributeError: 16 | canInspectSignature = False 17 | 18 | class Api: 19 | """ 20 | A minimal BIMserver.org API client. Interfaces are obtained from the server 21 | and can be retrieved as attributes from an API instance. The interfaces 22 | expose their methods as functions with keyword arguments. 23 | 24 | Example: 25 | import bimserver 26 | client = bimserver.Api(server_address, username, password) 27 | client.Bimsie1ServiceInterface.addProject(projectName="My new project") 28 | """ 29 | 30 | token = None 31 | 32 | def __init__(self, hostname, username=None, password=None): 33 | self.url = "%s/json" % hostname.strip('/') 34 | if not hostname.startswith('http://') and not hostname.startswith('https://'): 35 | self.url = "http://%s" % self.url 36 | 37 | interfaceMetaList = self.make_request( "MetaInterface", "getServiceInterfaces") 38 | # index by short interface name only might fail with multiple name spaces, but currently there is only one 39 | self.interfaceNames = [interfaceMeta['simpleName'] for interfaceMeta in interfaceMetaList] 40 | for interfaceMeta in interfaceMetaList: 41 | setattr(self, interfaceMeta['simpleName'], Interface(self, interfaceMeta['simpleName'], interfaceMeta['name'])) 42 | 43 | self.version = "1.4" if "Bimsie1AuthInterface" in self.interfaceNames else "1.5" 44 | 45 | if username is not None and password is not None: 46 | self.token = self.AuthInterface.login( 47 | username=username, 48 | password=password 49 | ) 50 | 51 | def __getattr__(self, interface): 52 | # Some form of compatibility: 53 | if self.version == "1.4" and not interface.startswith("Bimsie1"): 54 | return getattr(self, "Bimsie1" + interface) 55 | elif self.version == "1.5" and interface.startswith("Bimsie1"): 56 | return getattr(self, interface[len("Bimsie1"):]) 57 | raise AttributeError("'%s' is does not name a valid interface on this server" % interface) 58 | 59 | def __dir__(self): 60 | return sorted(set(Api.__dict__.keys()).union(self.__dict__.keys()).union(self.interfaceNames)) 61 | 62 | def make_request(self, interface, method, **kwargs): 63 | request = urlopen(self.url, data=json.dumps(dict({ 64 | "request": { 65 | "interface": interface, 66 | "method": method, 67 | "parameters": kwargs 68 | } 69 | }, **({"token": self.token} if self.token else {}))).encode("utf-8")) 70 | response = json.loads(request.read().decode("utf-8")) 71 | exception = response.get("response", {}).get("exception", None) 72 | if exception: 73 | raise BimserverException(exception['message']) 74 | else: 75 | return response["response"]["result"] 76 | 77 | def minimumBimServerVersion(self, requiredVersion): 78 | serverInfo = self.make_request("AdminInterface", "getServerInfo")["version"] 79 | return all(int(serverInfo[pos]) >= required for pos, required in zip(["major","minor","revision"],requiredVersion)) 80 | 81 | 82 | class Interface: 83 | def __init__(self, api, name, longName): 84 | self.api, self.name, self.longName = api, name, longName 85 | methods = self.api.make_request("MetaInterface", "getServiceMethods", serviceInterfaceName=longName) 86 | for method in methods: 87 | self.add_method(method) 88 | self.methodNames = [method["name"] for method in methods] 89 | 90 | def add_method(self, methodMeta): 91 | def method(self, **kwargs): 92 | return self.api.make_request(self.name, methodMeta["name"], **kwargs) 93 | method.__name__ = str(methodMeta["name"]) 94 | method.__doc__ = methodMeta["doc"] 95 | if self.api.minimumBimServerVersion([1,5,183]): 96 | self.add_parameters(method, methodMeta) 97 | setattr(self, methodMeta["name"], types.MethodType(method, self)) 98 | 99 | def add_parameters(self, method, methodMeta): 100 | params = self.api.make_request("MetaInterface", "getServiceMethodParameters", serviceInterfaceName=self.longName, serviceMethodName=method.__name__) 101 | if params: 102 | method.__doc__ += '\n' 103 | if canInspectSignature: # only for Python >= 3.3, modify signature 104 | oldSig = inspect.signature(method) 105 | parameters = list(oldSig.parameters.values())[:1]+[inspect.Parameter(p['name'], inspect.Parameter.KEYWORD_ONLY) for p in params] 106 | # TODO: allow positional (Parameter.POSITIONAL_OR_KEYWORD) and update kwargs from positional args later in method call 107 | method.__signature__ = oldSig.replace(parameters=parameters) # return_annotation=methodMeta["returnDoc"] 108 | for p in params: 109 | method.__doc__ += "\n:param %s %s: %s" % (p['type']['simpleName'], p['name'], p['doc']) 110 | method.__doc__+="\n:returns: %s" % (methodMeta['returnDoc']) 111 | 112 | 113 | 114 | def __repr__(self): 115 | return self.name 116 | 117 | def __dir__(self): 118 | return sorted(set(Interface.__dict__.keys()).union(self.__dict__.keys())) 119 | 120 | class BimserverException(Exception): 121 | pass 122 | 123 | --------------------------------------------------------------------------------