diff --git a/requirements.txt b/requirements.txt index 205e224ff82115d30a27c04876b3ac6861e66ec8..a79bcb224d8aa0d13239a7460e8eb1f1716c6f07 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ django>=1.11 tox +ibisclient~=1.3 diff --git a/setup.py b/setup.py index 95d4565375a6ba469c6c952eaee6e15800682552..6518737724193348548b44df20f51be4f4a1580d 100755 --- a/setup.py +++ b/setup.py @@ -7,13 +7,13 @@ setup( long_description=open('README.md').read(), long_description_content_type='text/markdown', url='https://gitlab.developers.cam.ac.uk/uis/devops/django/ucamlookup', - version='3.0.4', + version='3.0.5', license='MIT', author='DevOps Division, University Information Services, University of Cambridge', author_email='devops@uis.cam.ac.uk', packages=find_packages(), include_package_data=True, - install_requires=['django>=1.11'], + install_requires=['django>=1.11', 'ibisclient~=1.3'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', diff --git a/ucamlookup/ibisclient.py b/ucamlookup/ibisclient.py new file mode 100644 index 0000000000000000000000000000000000000000..d09ff4017469221033eaeaf2a4184cfe01e448ea --- /dev/null +++ b/ucamlookup/ibisclient.py @@ -0,0 +1,3 @@ +# Compatibility module to provide ibisclient at the same location as when we +# vendored it. +from ibisclient import * # noqa: F401, F403 diff --git a/ucamlookup/ibisclient/__init__.py b/ucamlookup/ibisclient/__init__.py deleted file mode 100644 index c61ea9f674e85d762c7f5b3d435b5a8195d3ceb9..0000000000000000000000000000000000000000 --- a/ucamlookup/ibisclient/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# -------------------------------------------------------------------------- -# Copyright (c) 2012, University of Cambridge Computing Service -# -# This file is part of the Lookup/Ibis client library. -# -# This library is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -# License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see <http://www.gnu.org/licenses/>. -# -# Author: Dean Rasheed (dev-group@ucs.cam.ac.uk) -# -------------------------------------------------------------------------- - -from .connection import * -from .dto import * -from .methods import * diff --git a/ucamlookup/ibisclient/cacerts.txt b/ucamlookup/ibisclient/cacerts.txt deleted file mode 100644 index 104f643e4760c05abda3a2ebdc89c2b435cd4f83..0000000000000000000000000000000000000000 --- a/ucamlookup/ibisclient/cacerts.txt +++ /dev/null @@ -1,89 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs -IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 -MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux -FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h -bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt -H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 -uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX -mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX -a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN -E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 -WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD -VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 -Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU -cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx -IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN -AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH -YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 -6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC -Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX -c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a -mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x -GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv -b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV -BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W -YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa -GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg -Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J -WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB -rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp -+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 -ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i -Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz -PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og -/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH -oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI -yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud -EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 -A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL -MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT -ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f -BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn -g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl -fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K -WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha -B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc -hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR -TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD -mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z -ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y -4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza -8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL -BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc -BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 -MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM -aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf -qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW -n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym -c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ -O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 -o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j -IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq -IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz -8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh -vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l -7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG -cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD -ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 -AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC -roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga -W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n -lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE -+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV -csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd -dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg -KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM -HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 -WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M ------END CERTIFICATE----- diff --git a/ucamlookup/ibisclient/connection.py b/ucamlookup/ibisclient/connection.py deleted file mode 100644 index 33a69eac6e5ec082b00b83f8d76097d431661b02..0000000000000000000000000000000000000000 --- a/ucamlookup/ibisclient/connection.py +++ /dev/null @@ -1,414 +0,0 @@ -# -------------------------------------------------------------------------- -# Copyright (c) 2012, University of Cambridge Computing Service -# -# This file is part of the Lookup/Ibis client library. -# -# This library is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -# License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see <http://www.gnu.org/licenses/>. -# -# Author: Dean Rasheed (dev-group@ucs.cam.ac.uk) -# -------------------------------------------------------------------------- - -""" -Connection classes to connect to the Lookup/Ibis web service and allow API -methods to be invoked. -""" - -import sys -if sys.version_info >= (3, 0): - PY3 = True -else: - PY3 = False -import base64 -from datetime import date -if PY3: - from http.client import HTTPSConnection -else: - from httplib import HTTPSConnection -import socket -import os -if PY3: - import urllib.parse -else: - import urllib -from .dto import IbisDto, IbisError, IbisResult, IbisResultParser - -try: - import ssl - _have_ssl = True -except ImportError: - print("WARNING: No SSL support - connection may be insecure") - _have_ssl = False - -# Use the latest TLS protocol supported by both the client and the server. -# Prior to Python versions 2.7.13 and 3.6, the way to do that was by -# specifying PROTOCOL_SSLv23. In more recent versions, that is deprecated, -# and presumably might go away one day. Instead, the new value PROTOCOL_TLS -# has been added, and is now the recommended way to do it. This is actually -# the same constant (2). Note that, despite the name, this will never choose -# insecure SSL v2 or v3 protocols, because those are disabled on the server. -if hasattr(ssl, 'PROTOCOL_TLS'): - _ssl_protocol = ssl.PROTOCOL_TLS -else: - _ssl_protocol = ssl.PROTOCOL_SSLv23 - -_MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] - -class IbisException(Exception): - """ - Exception thrown when a web service API method fails. This is wrapper - around the :any:`IbisError` object returned by the server, which contains - the full details of what went wrong. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - def __init__(self, error): - Exception.__init__(self, error.message) - self.error = error - - def get_error(self): - """ - Returns the underlying error from the server. - - **Returns** - :any:`IbisError` - The underlying error from the server. - """ - return self.error - -class HTTPSValidatingConnection(HTTPSConnection): - """ - Class extending the standard :py:class:`HTTPSConnection` class from - :any:`http.client`, so that it checks the server's certificates, - validating them against the specified CA certificates. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - def __init__(self, host, port, ca_certs): - HTTPSConnection.__init__(self, host, port) - self.ca_certs = ca_certs - - def connect(self): - """ - Overridden connect() method to wrap the socket using an SSLSocket, - and check the server certificates. - """ - try: - self.sock = socket.create_connection((self.host, self.port)) - except AttributeError: - HTTPSConnection.connect(self) - - if not _have_ssl: - # No SSL available - insecure connection - print("WARNING: No SSL support - connection may be insecure") - elif self.ca_certs: - # Wrap the socket in an SSLSocket, and tell it to validate - # the server certificates. Note that this does not check that - # the certificate's host matches, so we must do that ourselves. - self.sock = ssl.wrap_socket(self.sock, - ca_certs = self.ca_certs, - cert_reqs = ssl.CERT_REQUIRED, - ssl_version = _ssl_protocol) - - cert = self.sock.getpeercert() - cert_hosts = [] - host_valid = False - - if "subject" in cert: - for x in cert["subject"]: - if x[0][0] == "commonName": - cert_hosts.append(x[0][1]) - if "subjectAltName" in cert: - for x in cert["subjectAltName"]: - if x[0] == "dns": - cert_hosts.append(x[1]) - - for cert_host in cert_hosts: - if self.host.startswith(cert_host): - host_valid = True - - if not host_valid: - raise ssl.SSLError("Host name '%s' doesn't match "\ - "certificate host %s"\ - % (self.host, str(cert_hosts))) - else: - # No CA certificates supplied, so can't validate the server - # certificates, but we still wrap the socket in an SSLSocket - # so that all data is encrypted. - self.sock = ssl.wrap_socket(self.sock, - ca_certs = None, - cert_reqs = ssl.CERT_NONE, - ssl_version = _ssl_protocol) - -class IbisClientConnection: - """ - Class to connect to the Lookup/Ibis server and invoke web service API - methods. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - def __init__(self, host, port, url_base, check_certs): - self.host = host - self.port = port - self.url_base = url_base - - if not self.url_base.startswith("/"): - self.url_base = "/%s" % self.url_base - if not self.url_base.endswith("/"): - self.url_base = "%s/" % self.url_base - - if check_certs: - ibisclient_dir = os.path.realpath(os.path.dirname(__file__)) - self.ca_certs = os.path.join(ibisclient_dir, "cacerts.txt") - else: - self.ca_certs = None - - self.username = None - self.password = None - self.set_username("anonymous") - - def _update_authorization(self): - credentials = "%s:%s" % (self.username, self.password) - if PY3: - credential_bytes = bytes(credentials, "UTF-8") - base64_credentials = str(base64.b64encode(credential_bytes), "UTF-8") - self.authorization = "Basic %s" % base64_credentials - else: - self.authorization = "Basic %s" % base64.b64encode(credentials) - - def set_username(self, username): - """ - Set the username to use when connecting to the Lookup/Ibis web - service. By default connections are anonymous, which gives read-only - access. This method enables authentication as a group, using the - group's password, which gives read/write access and also access to - certain non-public data, based on the group's privileges. - - This method may be called at any time, and affects all subsequent - access using this connection, but does not affect any other - :any:`IbisClientConnection` objects. - - **Parameters** - `username` : str - [required] The username to connect as. This should either be - ``"anonymous"`` (the default) or the name of a group. - """ - self.username = username - self._update_authorization() - - def set_password(self, password): - """ - Set the password to use when connecting to the Lookup/Ibis web - service. This is only necessary when connecting as a group, in - which case it should be that group's password. - - **Parameters** - `password` : str - [required] The group password. - """ - self.password = password - self._update_authorization() - - def _params_to_strings(self, params): - """ - Convert the values in a parameter map into strings suitable for - sending to the server. Any null values will be omitted. - """ - new_params = {} - if PY3: - items = params.items() - else: - items = params.iteritems() - for key, value in items: - if value != None: - if isinstance(value, bool): - if value: new_params[key] = "true" - else: new_params[key] = "false" - elif isinstance(value, date): - new_params[key] = "%02d %s %d" % (value.day, - _MONTHS[value.month-1], - value.year) - elif isinstance(value, list) or isinstance(value, tuple): - new_params[key] = ",".join(value) - elif isinstance(value, IbisDto): - new_params[key] = value.encoded_string() - elif not isinstance(value, str): - new_params[key] = str(value) - else: - new_params[key] = value - - return new_params - - def _build_url(self, path, path_params={}, query_params={}): - """ - Build the full URL needed to invoke a method in the web service API. - - The path may contain standard Python format specifiers, which will - be substituted from the path parameters (suitably URL-encoded). Thus - for example, given the following arguments: - - * path = "api/v1/person/%(scheme)s/%(identifier)s" - * path_params = {"scheme": "crsid", "identifier": "dar17"} - * query_params = {"fetch": "email,title"} - - this method will create a URL like the following: - - api/v1/person/crsid/dar17?fetch=email%2Ctitle - - Note that all parameter values are automatically URL-encoded. - """ - if PY3: - for key, value in path_params.items(): - path_params[key] = urllib.parse.quote_plus(value) - else: - for key, value in path_params.iteritems(): - path_params[key] = urllib.quote_plus(value) - path = path % path_params - - - if PY3: - if "flatten" not in query_params: - query_params["flatten"] = "true" - path += "?%s" % urllib.parse.urlencode(query_params) - else: - if not query_params.has_key("flatten"): - query_params["flatten"] = "true" - path += "?%s" % urllib.urlencode(query_params) - - if path.startswith("/"): - return "%s%s" % (self.url_base, path[1:]) - return "%s%s" % (self.url_base, path) - - def invoke_method(self, method, path, path_params={}, - query_params={}, form_params={}): - """ - Invoke a web service GET, POST, PUT or DELETE method. - - The path should be the relative path to the method with standard - Python format specifiers for any path parameters, for example - ``"/api/v1/person/%(scheme)s/%(identifier)s"``. Any path parameters - specified are then substituted into the path. - - **Parameters** - `method` : str - [required] The method type (``"GET"``, ``"POST"``, ``"PUT"`` or - ``"DELETE"``). - - `path` : str - [required] The path to the method to invoke. - - `path_params` : dict - [optional] Any path parameters that should be inserted into the - path in place of any format specifiers. - - `query_params` : dict - [optional] Any query parameters to add as part of the URL's query - string. - - `form_params` : dict - [optional] Any form parameters to submit. - - **Returns** - :any:`IbisResult` - The result of invoking the method. - """ - path_params = self._params_to_strings(path_params) - query_params = self._params_to_strings(query_params) - form_params = self._params_to_strings(form_params) - - conn = HTTPSValidatingConnection(self.host, self.port, self.ca_certs) - url = self._build_url(path, path_params, query_params) - headers = {"Accept": "application/xml", - "Authorization": self.authorization} - - if form_params: - if PY3: - body = urllib.parse.urlencode(form_params) - else: - body = urllib.urlencode(form_params) - conn.request(method, url, body, headers) - else: - conn.request(method, url, headers=headers) - - response = conn.getresponse() - content_type = response.getheader("Content-type") - if content_type != "application/xml": - error = IbisError({"status": response.status, - "code": response.reason}) - error.message = "Unexpected result from server" - error.details = response.read() - - result = IbisResult() - result.error = error - - return result - - parser = IbisResultParser() - result = parser.parse_xml(response.read()) - conn.close() - - return result - -def createConnection(): - """ - Create an IbisClientConnection to the Lookup/Ibis web service API at - https://www.lookup.cam.ac.uk/. - - The connection is initially anonymous, but this may be changed using - its :any:`set_username() <IbisClientConnection.set_username>` and - :any:`set_password() <IbisClientConnection.set_password>` methods. - - **Returns** - :any:`IbisClientConnection` - A new connection to the Lookup server. - """ - return IbisClientConnection("www.lookup.cam.ac.uk", 443, "", True) - -def createTestConnection(): - """ - Create an IbisClientConnection to the Lookup/Ibis test web service API - at https://lookup-test.csx.cam.ac.uk/. - - The connection is initially anonymous, but this may be changed using - its :any:`set_username() <IbisClientConnection.set_username>` and - :any:`set_password() <IbisClientConnection.set_password>` methods. - - .. note:: - This test server is not guaranteed to always be available, and the data - on it may be out of sync with the data on the live system. - - **Returns** - :any:`IbisClientConnection` - A new connection to the Lookup test server. - """ - return IbisClientConnection("lookup-test.csx.cam.ac.uk", 443, "", True) - -def createLocalConnection(): - """ - Create an IbisClientConnection to a Lookup/Ibis web service API running - locally on https://localhost:8443/ibis/. - - The connection is initially anonymous, but this may be changed using - its :any:`set_username() <IbisClientConnection.set_username>` and - :any:`set_password() <IbisClientConnection.set_password>` methods. - - This is intended for testing during development. The local server is - assumed to be using self-signed certificates, which will not be checked. - - **Returns** - :any:`IbisClientConnection` - A new connection to a Lookup server assumed to be running on - localhost. - """ - return IbisClientConnection("localhost", 8443, "ibis", False) diff --git a/ucamlookup/ibisclient/dto.py b/ucamlookup/ibisclient/dto.py deleted file mode 100644 index 62acb3d881f8d4d156d88d79e6041e21373a2845..0000000000000000000000000000000000000000 --- a/ucamlookup/ibisclient/dto.py +++ /dev/null @@ -1,1432 +0,0 @@ -# -------------------------------------------------------------------------- -# Copyright (c) 2012, University of Cambridge Computing Service -# -# This file is part of the Lookup/Ibis client library. -# -# This library is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -# License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see <http://www.gnu.org/licenses/>. -# -# Author: Dean Rasheed (dev-group@ucs.cam.ac.uk) -# -------------------------------------------------------------------------- - -""" -DTO classes for transferring data from the server to client in the web -service API. - -All web service API methods return an instance or a list of one of these DTO -classes, or a primitive type such as a bool, int or string. - -In the case of an error, an :any:`IbisException` will be raised which will -contain an instance of an :any:`IbisError` DTO. -""" - -import sys -if sys.version_info >= (3, 0): - PY3 = True -else: - PY3 = False -import base64 -from datetime import date -from xml.parsers import expat - -import sys -if sys.hexversion < 0x02040000: - from sets import Set as set - -_MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] - -class IbisDto(object): - """ - Base class for all DTO classes. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - # All properties - __slots__ = [] - - # Properties marked as @XmlAttribte in the JAXB class - xml_attrs = set() - - # Properties marked as @XmlElement in the JAXB class - xml_elems = set() - - # Properties marked as @XmlElementWrapper in the JAXB class - xml_arrays = set() - - def __init__(self, attrs={}): - """ - Create an IbisDto from the attributes of an XML node. This just - sets the properties marked as @XmlAttribute in the JAXB class. - """ - for attr in self.__class__.__slots__: - setattr(self, attr, None) - for attr in self.__class__.xml_attrs: - setattr(self, attr, attrs.get(attr)) - - def start_child_element(self, tagname): - """ - Start element callback invoked during XML parsing when the opening - tag of a child element is encountered. This creates and returns any - properties marked as @XmlElementWrapper in the JAXB class, so that - child collections can be populated. - """ - if tagname in self.__class__.xml_arrays: - if getattr(self, tagname) == None: setattr(self, tagname, []) - return getattr(self, tagname) - return None - - def end_child_element(self, tagname, data): - """ - End element callback invoked during XML parsing when the end tag of - a child element is encountered, and the tag's data is available. This - sets the value of any properties marked as @XmlElement in the JAXB - class. - """ - if tagname in self.__class__.xml_elems: - setattr(self, tagname, data) - -# -------------------------------------------------------------------------- -# IbisPerson: see uk.ac.cam.ucs.ibis.dto.IbisPerson.java -# -------------------------------------------------------------------------- -class IbisPerson(IbisDto): - """ - Class representing a person returned by the web service API. Note that - the identifier is the person's primary identifier (typically their CRSid), - regardless of which identifier was used to query for the person. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - __slots__ = { - "cancelled": - """ - bool - Flag indicating if the person is cancelled. - """, - "identifier": - """ - :any:`IbisIdentifier` - The person's primary identifier (typically their CRSid). - """, - "displayName": - """ - str - The person's display name (if visible). - """, - "registeredName": - """ - str - The person's registered name (if visible). - """, - "surname": - """ - str - The person's surname (if visible). - """, - "visibleName": - """ - str - The person's display name if that is visible, otherwise their - registered name if that is visible, otherwise their surname if - that is visible, otherwise the value of their primary identifier - (typically their CRSid) which is always visible. - """, - "misAffiliation": - """ - str - The person's MIS status (``"staff"``, ``"student"``, - ``"staff,student"`` or ``""``). - """, - "identifiers": - """ - list of :any:`IbisIdentifier` - A list of the person's identifiers. This will only be populated - if the `fetch` parameter included the ``"all_identifiers"`` - option. - """, - "attributes": - """ - list of :any:`IbisAttribute` - A list of the person's attributes. This will only be populated - if the `fetch` parameter includes the ``"all_attrs"`` option, or - any specific attribute schemes such as ``"email"`` or - ``"title"``, or the special pseudo-attribute scheme - ``"phone_numbers"``. - """, - "institutions": - """ - list of :any:`IbisInstitution` - A list of all the institution's to which the person belongs. - This will only be populated if the `fetch` parameter includes - the ``"all_insts"`` option. - """, - "groups": - """ - list of :any:`IbisGroup` - A list of all the groups to which the person belongs, including - indirect group memberships, via groups that include other - groups. This will only be populated if the `fetch` parameter - includes the ``"all_groups"`` option. - """, - "directGroups": - """ - list of :any:`IbisGroup` - A list of all the groups that the person directly belongs to. - This does not include indirect group memberships - i.e., groups - that include these groups. This will only be populated if the - `fetch` parameter includes the ``"direct_groups"`` option. - """, - "id": - """ - str - An ID that can uniquely identify this person within the returned - XML/JSON document. This is only used in the flattened XML/JSON - representation (if the "flatten" parameter is specified). - """, - "ref": - """ - str - A reference (by id) to a person element in the XML/JSON - document. This is only used in the flattened XML/JSON - representation (if the "flatten" parameter is specified). - """, - "unflattened": - """ - bool - Flag to prevent infinite recursion due to circular references. - """ - } - - xml_attrs = set(["cancelled", "id", "ref"]) - - xml_elems = set(["identifier", "displayName", "registeredName", - "surname", "visibleName", "misAffiliation"]) - - xml_arrays = set(["identifiers", "attributes", "institutions", - "groups", "directGroups"]) - - def __init__(self, attrs={}): - """ Create an IbisPerson from the attributes of an XML node. """ - IbisDto.__init__(self, attrs) - if self.cancelled != None: - self.cancelled = self.cancelled.lower() == "true" - self.unflattened = False - - def is_staff(self): - """ - Returns :any:`True` if the person is a member of staff. - - Note that this tests for an misAffiliation of ``""``, ``"staff"`` or - ``"staff,student"`` since some members of staff will have a blank - misAffiliation. - """ - return self.misAffiliation == None or\ - self.misAffiliation != "student"; - - def is_student(self): - """ - Returns :any:`True` if the person is a student. - - This tests for an misAffiliation of ``"student"`` or - ``"staff,student"``. - """ - return self.misAffiliation != None and\ - self.misAffiliation.find("student") != -1; - - def unflatten(self, em): - """ Unflatten a single IbisPerson. """ - if self.ref: - person = em.get_person(self.ref) - if not person.unflattened: - person.unflattened = True - unflatten_insts(em, person.institutions) - unflatten_groups(em, person.groups) - unflatten_groups(em, person.directGroups) - return person - return self - -def unflatten_people(em, people): - """ Unflatten a list of IbisPerson objects (done in place). """ - if people: - for idx, person in enumerate(people): - people[idx] = person.unflatten(em) - -# -------------------------------------------------------------------------- -# IbisInstitution: see uk.ac.cam.ucs.ibis.dto.IbisInstitution.java -# -------------------------------------------------------------------------- -class IbisInstitution(IbisDto): - """ - Class representing an institution returned by the web service API. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - __slots__ = { - "cancelled": - """ - bool - Flag indicating if the institution is cancelled. - """, - "instid": - """ - str - The institution's unique ID (e.g., ``"CS"``). - """, - "name": - """ - str - The institution's name. - """, - "acronym": - """ - str - The institution's acronym, if set (e.g., ``"UCS"``). - """, - "attributes": - """ - list of :any:`IbisAttribute` - A list of the institution's attributes. This will only be - populated if the `fetch` parameter includes the ``"all_attrs"`` - option, or any specific attribute schemes such as ``"email"`` or - ``"address"``, or the special pseudo-attribute scheme - ``"phone_numbers"``. - """, - "contactRows": - """ - list of :any:`IbisContactRow` - A list of the institution's contact rows. This will only be - populated if the `fetch` parameter includes the - ``"contact_rows"`` option. - """, - "members": - """ - list of :any:`IbisPerson` - A list of the institution's members. This will only be populated - if the `fetch` parameter includes the ``"all_members"`` option. - """, - "parentInsts": - """ - list of :any:`IbisInstitution` - A list of the institution's parent institutions. This will only - be populated if the `fetch` parameter includes the - ``"parent_insts"`` option. - - .. note:: - Currently all institutions have one parent, but in the future - institutions may have multiple parents. - """, - "childInsts": - """ - list of :any:`IbisInstitution` - A list of the institution's child institutions. This will only - be populated if the `fetch` parameter includes the - ``"child_insts"`` option. - """, - "groups": - """ - list of :any:`IbisGroup` - A list of all the groups that belong to the institution. This - will only be populated if the `fetch` parameter includes the - ``"inst_groups"`` option. - """, - "membersGroups": - """ - list of :any:`IbisGroup` - A list of the groups that form the institution's membership. - This will only be populated if the `fetch` parameter includes - the ``"members_groups"`` option. - """, - "managedByGroups": - """ - list of :any:`IbisGroup` - A list of the groups that manage this institution. This will - only be populated if the `fetch` parameter includes the - ``"managed_by_groups"`` option. - """, - "id": - """ - str - An ID that can uniquely identify this institution within the - returned XML/JSON document. This is only used in the flattened - XML/JSON representation (if the "flatten" parameter is - specified). - """, - "ref": - """ - str - A reference (by id) to an institution element in the XML/JSON - document. This is only used in the flattened XML/JSON - representation (if the "flatten" parameter is specified). - """, - "unflattened": - """ - bool - Flag to prevent infinite recursion due to circular references. - """ - } - - xml_attrs = set(["cancelled", "instid", "id", "ref"]) - - xml_elems = set(["name", "acronym"]) - - xml_arrays = set(["attributes", "contactRows", "members", - "parentInsts", "childInsts", "groups", - "membersGroups", "managedByGroups"]) - - def __init__(self, attrs={}): - """ Create an IbisInstitution from the attributes of an XML node. """ - IbisDto.__init__(self, attrs) - if self.cancelled != None: - self.cancelled = self.cancelled.lower() == "true" - self.unflattened = False - - def unflatten(self, em): - """ Unflatten a single IbisInstitution. """ - if self.ref: - inst = em.get_institution(self.ref) - if not inst.unflattened: - inst.unflattened = True - unflatten_contact_rows(em, inst.contactRows) - unflatten_people(em, inst.members) - unflatten_insts(em, inst.parentInsts) - unflatten_insts(em, inst.childInsts) - unflatten_groups(em, inst.groups) - unflatten_groups(em, inst.membersGroups) - unflatten_groups(em, inst.managedByGroups) - return inst - return self - -def unflatten_insts(em, insts): - """ Unflatten a list of IbisInstitution objects (done in place). """ - if insts: - for idx, inst in enumerate(insts): - insts[idx] = inst.unflatten(em) - -# -------------------------------------------------------------------------- -# IbisGroup: see uk.ac.cam.ucs.ibis.dto.IbisGroup.java -# -------------------------------------------------------------------------- -class IbisGroup(IbisDto): - """ - Class representing a group returned by the web service API. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - __slots__ = { - "cancelled": - """ - bool - Flag indicating if the group is cancelled. - """, - "groupid": - """ - str - The group's numeric ID (actually a string e.g., ``"100656"``). - """, - "name": - """ - str - The group's unique name (e.g., ``"cs-editors"``). - """, - "title": - """ - str - The group's title. - """, - "description": - """ - str - The more detailed description of the group. - """, - "email": - """ - str - The group's email address. - """, - "membersOfInst": - """ - :any:`IbisInstitution` - The details of the institution for which this group forms all - or part of the membership. This will only be set for groups that - are membership groups of institutions if the `fetch` parameter - includes the ``"members_of_inst"`` option. - """, - "members": - """ - list of :any:`IbisPerson` - A list of the group's members, including (recursively) any - members of any included groups. This will only be populated if - the `fetch` parameter includes the ``"all_members"`` option. - """, - "directMembers": - """ - list of :any:`IbisPerson` - A list of the group's direct members, not including any members - included via groups included by this group. This will only be - populated if the `fetch` parameter includes the - ``"direct_members"`` option. - """, - "owningInsts": - """ - list of :any:`IbisInstitution` - A list of the institutions to which this group belongs. This - will only be populated if the `fetch` parameter includes the - ``"owning_insts"`` option. - """, - "managesInsts": - """ - list of :any:`IbisInstitution` - A list of the institutions managed by this group. This will only - be populated if the `fetch` parameter includes the - ``"manages_insts"`` option. - """, - "managesGroups": - """ - list of :any:`IbisGroup` - A list of the groups managed by this group. This will only be - populated if the `fetch` parameter includes the - ``"manages_groups"`` option. - """, - "managedByGroups": - """ - list of :any:`IbisGroup` - A list of the groups that manage this group. This will only be - populated if the `fetch` parameter includes the - ``"managed_by_groups"`` option. - """, - "readsGroups": - """ - list of :any:`IbisGroup` - A list of the groups that this group has privileged access to. - Members of this group will be able to read the members of any of - those groups, regardless of the membership visibilities. This - will only be populated if the `fetch` parameter includes the - ``"reads_groups"`` option. - """, - "readByGroups": - """ - list of :any:`IbisGroup` - A list of the groups that have privileged access to this group. - Members of those groups will be able to read the members of this - group, regardless of the membership visibilities. This will only - be populated if the `fetch` parameter includes the - ``"read_by_groups"`` option. - """, - "includesGroups": - """ - list of :any:`IbisGroup` - A list of the groups directly included in this group. Any - members of the included groups (and recursively any groups that - they include) will automatically be included in this group. This - will only be populated if the `fetch` parameter includes the - ``"includes_groups"`` option. - """, - "includedByGroups": - """ - list of :any:`IbisGroup` - A list of the groups that directly include this group. Any - members of this group will automatically be included in those - groups (and recursively in any groups that include those - groups). This will only be populated if the `fetch` parameter - includes the ``"included_by_groups"`` option. - """, - "id": - """ - str - An ID that can uniquely identify this group within the returned - XML/JSON document. This is only used in the flattened XML/JSON - representation (if the "flatten" parameter is specified). - """, - "ref": - """ - str - A reference (by id) to a group element in the XML/JSON document. - This is only used in the flattened XML/JSON representation (if - the "flatten" parameter is specified). - """, - "unflattened": - """ - bool - Flag to prevent infinite recursion due to circular references. - """ - } - - xml_attrs = set(["cancelled", "groupid", "id", "ref"]) - - xml_elems = set(["name", "title", "description", "emails", - "membersOfInst"]) - - xml_arrays = set(["members", "directMembers", - "owningInsts", "managesInsts", - "managesGroups", "managedByGroups", - "readsGroups", "readByGroups", - "includesGroups", "includedByGroups"]) - - def __init__(self, attrs={}): - """ Create an IbisGroup from the attributes of an XML node. """ - IbisDto.__init__(self, attrs) - if self.cancelled != None: - self.cancelled = self.cancelled.lower() == "true" - self.unflattened = False - - def unflatten(self, em): - """ Unflatten a single IbisGroup. """ - if self.ref: - group = em.get_group(self.ref) - if not group.unflattened: - group.unflattened = True - if group.membersOfInst: - group.membersOfInst = group.membersOfInst.unflatten(em) - unflatten_people(em, group.members) - unflatten_people(em, group.directMembers) - unflatten_insts(em, group.owningInsts) - unflatten_insts(em, group.managesInsts) - unflatten_groups(em, group.managesGroups) - unflatten_groups(em, group.managedByGroups) - unflatten_groups(em, group.readsGroups) - unflatten_groups(em, group.readByGroups) - unflatten_groups(em, group.includesGroups) - unflatten_groups(em, group.includedByGroups) - return group - return self - -def unflatten_groups(em, groups): - """ Unflatten a list of IbisGroup objects (done in place). """ - if groups: - for idx, group in enumerate(groups): - groups[idx] = group.unflatten(em) - -# -------------------------------------------------------------------------- -# IbisIdentifier: see uk.ac.cam.ucs.ibis.dto.IbisIdentifier.java -# -------------------------------------------------------------------------- -class IbisIdentifier(IbisDto): - """ - Class representing a person's identifier, for use by the web service - API. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - __slots__ = { - "scheme": - """ - str - The identifier's scheme (e.g., ``"crsid"``). - """, - "value": - """ - str - The identifier's value in that scheme (e.g., a specific CRSid - value). - """ - } - - xml_attrs = set(["scheme"]) - -# -------------------------------------------------------------------------- -# IbisAttribute: see uk.ac.cam.ucs.ibis.dto.IbisAttribute.java -# -------------------------------------------------------------------------- -class IbisAttribute(IbisDto): - """ - Class representing an attribute of a person or institution returned by - the web service API. Note that for institution attributes, the - :any:`instid <IbisAttribute.instid>`, - :any:`visibility <IbisAttribute.visibility>` and - :any:`owningGroupid <IbisAttribute.owningGroupid>` fields will be - :any:`None`. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - __slots__ = { - "attrid": - """ - int - The unique internal identifier of the attribute. - """, - "scheme": - """ - str - The attribute's scheme. - """, - "value": - """ - str - The attribute's value (except for binary attributes). - """, - "binaryData": - """ - bytes - The binary data held in the attribute (e.g., a JPEG photo). - """, - "comment": - """ - str - Any comment associated with the attribute. - """, - "instid": - """ - str - For a person attribute, the optional institution that the - attribute is associated with. This will not be set for - institution attributes. - """, - "visibility": - """ - str - For a person attribute, it's visibility (``"private"``, - ``"institution"``, ``"university"`` or ``"world"``). This will - not be set for institution attributes. - """, - "effectiveFrom": - """ - date - For time-limited attributes, the date from which it takes - effect. - """, - "effectiveTo": - """ - date - For time-limited attributes, the date after which it is no - longer effective. - """, - "owningGroupid": - """ - str - For a person attribute, the ID of the group that owns it - (typically the user agent group that created it). - """ - } - - xml_attrs = set(["attrid", "scheme", "instid", "visibility", - "effectiveFrom", "effectiveTo", "owningGroupid"]) - - xml_elems = set(["value", "binaryData", "comment"]) - - def __init__(self, attrs={}): - """ Create an IbisAttribute from the attributes of an XML node. """ - IbisDto.__init__(self, attrs) - if self.attrid != None: - self.attrid = int(self.attrid) - if self.effectiveFrom != None: - self.effectiveFrom = parse_date(self.effectiveFrom) - if self.effectiveTo != None: - self.effectiveTo = parse_date(self.effectiveTo) - - def end_child_element(self, tagname, data): - """ - Overridden end element callback to decode binary data. - """ - IbisDto.end_child_element(self, tagname, data) - if tagname == "binaryData" and self.binaryData != None: - self.binaryData = base64.b64decode(self.binaryData) - - def encoded_string(self): - """ - Encode this attribute as an ASCII string suitable for passing as a - parameter to a web service API method. This string is compatible with - `valueOf(java.lang.String)` on the corresponding Java class, - used on the server to decode the attribute parameter. - - .. note:: - This requires that the attribute's - :any:`scheme <IbisAttribute.scheme>` field be set, and typically the - :any:`value <IbisAttribute.value>` or - :any:`binaryData <IbisAttribute.binaryData>` should also be set. - - **Returns** - str - The string encoding of this attribute. - """ - if not self.scheme: - raise ValueError("Attribute scheme must be set") - - result = "scheme:%s" % base64.b64encode(self.scheme) - if self.attrid != None: - result = "%s,attrid:%d" % (result, self.attrid) - if self.value != None: - result = "%s,value:%s" % (result, base64.b64encode(self.value)) - if self.binaryData != None: - result = "%s,binaryData:%s" %\ - (result, base64.b64encode(self.binaryData)) - if self.comment != None: - result = "%s,comment:%s" %\ - (result, base64.b64encode(self.comment)) - if self.instid != None: - result = "%s,instid:%s" %\ - (result, base64.b64encode(self.instid)) - if self.visibility != None: - result = "%s,visibility:%s" %\ - (result, base64.b64encode(self.visibility)) - if self.effectiveFrom != None: - result = "%s,effectiveFrom:%02d %s %d" %\ - (result, - self.effectiveFrom.day, - _MONTHS[self.effectiveFrom.month-1], - self.effectiveFrom.year) - if self.effectiveTo != None: - result = "%s,effectiveTo:%02d %s %d" %\ - (result, - self.effectiveTo.day, - _MONTHS[self.effectiveTo.month-1], - self.effectiveTo.year) - if self.owningGroupid != None: - result = "%s,owningGroupid:%s" %\ - (result, base64.b64encode(self.owningGroupid)) - return result - -def parse_date(s): - """ Parse a date string from XML. """ - s = s.strip() - return date(int(s[:4]), int(s[5:7]), int(s[8:10])) - -# -------------------------------------------------------------------------- -# IbisError: see uk.ac.cam.ucs.ibis.dto.IbisError.java -# -------------------------------------------------------------------------- -class IbisError(IbisDto): - """ - Class representing an error returned by the web service API. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - __slots__ = { - "status": - """ - int - The HTTP error status code. - """, - "code": - """ - str - A short textual description of the error status code. - """, - "message": - """ - str - A short textual description of the error message (typically one - line). - """, - "details": - """ - str - The full details of the error (e.g., a Java stack trace). - """ - } - - xml_attrs = set(["status"]) - - xml_elems = set(["code", "message", "details"]) - - def __init__(self, attrs={}): - """ Create an IbisError from the attributes of an XML node. """ - IbisDto.__init__(self, attrs) - if self.status != None: - self.status = int(self.status) - -# -------------------------------------------------------------------------- -# IbisAttributeScheme: see uk.ac.cam.ucs.ibis.dto.IbisAttributeScheme.java -# -------------------------------------------------------------------------- -class IbisAttributeScheme(IbisDto): - """ - Class representing an attribute scheme. This may apply to attributes of - people or institutions. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - __slots__ = { - "schemeid": - """ - str - The unique identifier of the attribute scheme. - """, - "precedence": - """ - int - The attribute scheme's precedence. Methods that return or - display attributes sort the results primarily in order of - increasing values of attribute scheme precedence. - """, - "ldapName": - """ - str - The name of the attribute scheme in LDAP, if it is exported to - LDAP. Note that many attributes are not exported to LDAP, in - which case this name is typically just equal to the scheme's ID. - """, - "displayName": - """ - str - The display name for labelling attributes in this scheme. - """, - "dataType": - """ - str - The attribute scheme's datatype. - """, - "multiValued": - """ - bool - Flag indicating whether attributes in this scheme can be - multi-valued. - """, - "multiLined": - """ - bool - Flag for textual attributes schemes indicating whether they are - multi-lined. - """, - "searchable": - """ - bool - Flag indicating whether attributes of this scheme are searched - by the default search functionality. - """, - "regexp": - """ - str - For textual attributes, an optional regular expression that all - attributes in this scheme match. - """ - } - - xml_attrs = set(["schemeid", "precedence", "multiValued", "multiLined", - "searchable"]) - - xml_elems = set(["ldapName", "displayName", "dataType", "regexp"]) - - def __init__(self, attrs={}): - """ - Create an IbisAttributeScheme from the attributes of an XML node. - """ - IbisDto.__init__(self, attrs) - if self.precedence != None: - self.precedence = int(self.precedence) - if self.multiValued != None: - self.multiValued = self.multiValued.lower() == "true" - if self.multiLined != None: - self.multiLined = self.multiLined.lower() == "true" - if self.searchable != None: - self.searchable = self.searchable.lower() == "true" - -# -------------------------------------------------------------------------- -# IbisContactRow: see uk.ac.cam.ucs.ibis.dto.IbisContactRow.java -# -------------------------------------------------------------------------- -class IbisContactRow(IbisDto): - """ - Class representing an institution contact row, for use by the web - services API. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - __slots__ = { - "description": - """ - str - The contact row's text. - """, - "bold": - """ - bool - Flag indicating if the contact row's text is normally displayed - in bold. - """, - "italic": - """ - bool - Flag indicating if the contact row's text is normally displayed - in italics. - """, - "addresses": - """ - list of str - A list of the contact row's addresses. This will never be - :any:`None`, but it may be an empty list. - """, - "emails": - """ - list of str - A list of the contact row's email addresses. This will never be - :any:`None`, but it may be an empty list. - """, - "people": - """ - list of :any:`IbisPerson` - A list of the people referred to by the contact row. This will - never by :any:`None`, but it may be an empty list. - """, - "phoneNumbers": - """ - list of :any:`IbisContactPhoneNumber` - A list of the contact row's phone numbers. This will never be - :any:`None`, but it may be an empty list. - """, - "webPages": - """ - list of :any:`IbisContactWebPage` - A list of the contact row's web pages. This will never be - :any:`None`, but it may be an empty list. - """, - "unflattened": - """ - bool - Flag to prevent infinite recursion due to circular references. - """ - } - - xml_attrs = set(["bold", "italic"]) - - xml_elems = set(["description"]) - - xml_arrays = set(["addresses", "emails", "people", "phoneNumbers", - "webPages"]) - - def __init__(self, attrs={}): - """ Create an IbisContactRow from the attributes of an XML node. """ - IbisDto.__init__(self, attrs) - if self.bold != None: - self.bold = self.bold.lower() == "true" - if self.italic != None: - self.italic = self.italic.lower() == "true" - self.unflattened = False - - def unflatten(self, em): - """ Unflatten a single IbisContactRow. """ - if not self.unflattened: - self.unflattened = True - unflatten_people(em, self.people) - return self - -def unflatten_contact_rows(em, contact_rows): - """ Unflatten a list of IbisContactRow objects (done in place). """ - if contact_rows: - for idx, contact_row in enumerate(contact_rows): - contact_rows[idx] = contact_row.unflatten(em) - -# -------------------------------------------------------------------------- -# IbisContactPhoneNumber: -# see uk.ac.cam.ucs.ibis.dto.IbisContactPhoneNumber.java -# -------------------------------------------------------------------------- -class IbisContactPhoneNumber(IbisDto): - """ - Class representing a phone number held on an institution contact row, for - use by the web service API. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - __slots__ = { - "phoneType": - """ - str - The phone number's type. - """, - "number": - """ - str - The phone number. - """, - "comment": - """ - str - Any comment associated with the phone number. - """ - } - - xml_attrs = set(["phoneType"]) - - xml_elems = set(["number", "comment"]) - -# -------------------------------------------------------------------------- -# IbisContactWebPage: see uk.ac.cam.ucs.ibis.dto.IbisContactWebPage.java -# -------------------------------------------------------------------------- -class IbisContactWebPage(IbisDto): - """ - Class representing a web page referred to by an institution contact row, - for use by the web service API. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - __slots__ = { - "url": - """ - str - The web page's URL. - """, - "label": - """ - str - The web page's label (link text) if set. - """ - } - - xml_elems = set(["url", "label"]) - -# -------------------------------------------------------------------------- -# IbisResult: see uk.ac.cam.ucs.ibis.dto.IbisResult.java -# -------------------------------------------------------------------------- -class IbisResult(IbisDto): - """ - Class representing the top-level container for all results. - - This may be just a simple textual value or it may contain more complex - entities such as people, institutions, groups, attributes, etc. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - __slots__ = { - "version": - """ - str - The web service API version number. - """, - "value": - """ - str - The value returned by methods that return a simple textual - value. - """, - "person": - """ - :any:`IbisPerson` - The person returned by methods that return a single person. - - Note that methods that may return multiple people will always - use the :any:`people <IbisResult.people>` field, even if only - one person was returned. - """, - "institution": - """ - :any:`IbisInstitution` - The institution returned by methods that return a single - institution. - - Note that methods that may return multiple institutions will - always use the :any:`institutions <IbisResult.institutions>` - field, even if only one institution was returned. - """, - "group": - """ - :any:`IbisGroup` - The group returned by methods that return a single group. - - Note that methods that may return multiple groups will always - use the :any:`groups <IbisResult.groups>` field, even if only - one group was returned. - """, - "identifier": - """ - :any:`IbisIdentifier` - The identifier returned by methods that return a single - identifier. - """, - "attribute": - """ - :any:`IbisAttribute` - The person or institution attribute returned by methods that - return a single attribute. - """, - "error": - """ - :any:`IbisError` - If the method failed, details of the error. - """, - "people": - """ - list of :any:`IbisPerson` - The list of people returned by methods that may return multiple - people. This may be empty, or contain one or more people. - """, - "institutions": - """ - list of :any:`IbisInstitution` - The list of institutions returned by methods that may return - multiple institutions. This may be empty, or contain one or more - institutions. - """, - "groups": - """ - list of :any:`IbisGroup` - The list of groups returned by methods that may return multiple - groups. This may be empty, or contain one or more groups. - """, - "attributes": - """ - list of :any:`IbisAttribute` - The list of attributes returned by methods that return lists of - person/institution attributes. - """, - "attributeSchemes": - """ - list of :any:`IbisAttributeScheme` - The list of attribute schemes returned by methods that return - lists of person/institution attribute schemes. - """, - "entities": - """ - :any:`IbisResult.Entities` - In the flattened XML/JSON representation, all the unique - entities returned by the method. - - .. note:: - This will be :any:`None` unless the "flatten" parameter is - :any:`True`. - """ - } - - xml_attrs = set(["version"]) - - xml_elems = set(["value", "person", "institution", "group", - "identifier", "attribute", "error", "entities"]) - - xml_arrays = set(["people", "institutions", "groups", - "attributes", "attributeSchemes"]) - - class Entities(IbisDto): - """ - Nested class to hold the full details of all the entities returned - in a result. This is used only in the flattened result representation, - where each of these entities will have a unique textual ID, and be - referred to from the top-level objects returned (and by each other). - - In the hierarchical representation, this is not used, since all - entities returned will be at the top-level, or directly contained in - those top-level entities. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - __slots__ = { - "people": - """ - list of :any:`IbisPerson` - A list of all the unique people returned by the method. This - may include additional people returned as a result of the - `fetch` parameter, so this list may contain more entries - than the corresponding field on the enclosing class. - """, - "institutions": - """ - list of :any:`IbisInstitution` - A list of all the unique institutions returned by the - method. This may include additional institutions returned as - a result of the `fetch` parameter, so this list may contain - more entries than the corresponding field on the enclosing - class. - """, - "groups": - """ - list of :any:`IbisGroup` - A list of all the unique groups returned by the method. This - may include additional groups returned as a result of the - `fetch` parameter, so this list may contain more entries - than the corresponding field on the enclosing class. - """ - } - - xml_arrays = set(["people", "institutions", "groups"]) - - class EntityMap: - """ - Nested class to assist during the unflattening process, maintaining - efficient maps from IDs to entities (people, institutions and groups). - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - def __init__(self, result): - """ - Construct an entity map from a flattened IbisResult. - """ - self.people_by_id = {} - self.insts_by_id = {} - self.groups_by_id = {} - - if result.entities.people: - for person in result.entities.people: - self.people_by_id[person.id] = person - if result.entities.institutions: - for inst in result.entities.institutions: - self.insts_by_id[inst.id] = inst - if result.entities.groups: - for group in result.entities.groups: - self.groups_by_id[group.id] = group - - def get_person(self, id): - """ - Get a person from the entity map, given their ID. - """ - return self.people_by_id.get(id) - - def get_institution(self, id): - """ - Get an institution from the entity map, given its ID. - """ - return self.insts_by_id.get(id) - - def get_group(self, id): - """ - Get a group from the entity map, given its ID. - """ - return self.groups_by_id.get(id) - - def unflatten(self): - """ - Unflatten this IbisResult object, resolving any internal ID refs - to build a fully fledged object tree. - - This is necessary if the IbisResult was constructed from XML/JSON in - its flattened representation (with the "flatten" parameter set to - :any:`True`). - - On entry, the IbisResult object may have people, institutions or - groups in it with "ref" fields referring to objects held in the - "entities" lists. After unflattening, all such references will have - been replaced by actual object references, giving an object tree that - can be traversed normally. - - Returns this IbisResult object, with its internals unflattened. - """ - if self.entities: - em = IbisResult.EntityMap(self) - - if self.person: - self.person = self.person.unflatten(em) - if self.institution: - self.institution = self.institution.unflatten(em) - if self.group: - self.group = self.group.unflatten(em) - - unflatten_people(em, self.people) - unflatten_insts(em, self.institutions) - unflatten_groups(em, self.groups) - - return self - -# -------------------------------------------------------------------------- -# IbisResultParser: unmarshaller for IbisResult objects -# -------------------------------------------------------------------------- -class IbisResultParser: - """ - Class to parse the XML from the server and produce an IbisResult. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - def __init__(self): - self.result = None - self.node_stack = [] - self.parser = expat.ParserCreate() - self.parser.StartElementHandler = self.start_element - self.parser.EndElementHandler = self.end_element - self.parser.CharacterDataHandler = self.char_data - - def start_element(self, tagname, attrs): - element = None - if self.node_stack: - if tagname == "person": - element = IbisPerson(attrs) - elif tagname == "institution": - element = IbisInstitution(attrs) - elif tagname == "membersOfInst": - element = IbisInstitution(attrs) - elif tagname == "group": - element = IbisGroup(attrs) - elif tagname == "identifier": - element = IbisIdentifier(attrs) - elif tagname == "attribute": - element = IbisAttribute(attrs) - elif tagname == "error": - element = IbisError(attrs) - elif tagname == "attributeScheme": - element = IbisAttributeScheme(attrs) - elif tagname == "contactRow": - element = IbisContactRow(attrs) - elif tagname == "phoneNumber": - element = IbisContactPhoneNumber(attrs) - elif tagname == "webPage": - element = IbisContactWebPage(attrs) - elif tagname == "entities": - element = IbisResult.Entities(attrs) - else: - parent = self.node_stack[-1] - if (not isinstance(parent, list)) and\ - (not isinstance(parent, dict)): - element = parent.start_child_element(tagname) - if element == None: - element = {"tagname": tagname} - elif tagname != "result": - raise Exception("Invalid root element: '%s'" % tagname) - else: - element = IbisResult(attrs) - self.result = element - self.node_stack.append(element) - - def end_element(self, tagname): - if self.node_stack: - element = self.node_stack[-1] - self.node_stack.pop() - if self.node_stack: - parent = self.node_stack[-1] - if isinstance(parent, list): - if isinstance(element, dict): - parent.append(element.get("data")) - else: - parent.append(element) - elif not isinstance(parent, dict): - if isinstance(element, dict): - data = element.get("data") - else: - data = element - parent.end_child_element(tagname, data) - else: - raise Exception("Unexpected closing tag: '%s'" % tagname) - - def char_data(self, data): - if self.node_stack: - element = self.node_stack[-1] - if isinstance(element, IbisIdentifier): - if element.value != None: element.value += data - else: element.value = data - elif isinstance(element, dict): - if PY3: - if "data" in element: element["data"] += data - else: element["data"] = data - else: - if element.has_key("data"): element["data"] += data - else: element["data"] = data - - def parse_xml(self, data): - """ - Parse XML data from the specified string and return an IbisResult. - - **Parameters** - `data` : str - [required] The XML string returned from the server. - - **Returns** - :any:`IbisResult` - The parsed results. This may contain lists or trees of objects - representing people, institutions and groups returned from the - server. - """ - self.parser.Parse(data) - return self.result.unflatten() - - def parse_xml_file(self, file): - """ - Parse XML data from the specified file and return an IbisResult. - - **Parameters** - `file` : file - [required] A file object containing XML returned from the server. - - **Returns** - :any:`IbisResult` - The parsed results. This may contain lists or trees of objects - representing people, institutions and groups returned from the - server. - """ - self.parser.ParseFile(file) - return self.result.unflatten() diff --git a/ucamlookup/ibisclient/methods.py b/ucamlookup/ibisclient/methods.py deleted file mode 100644 index 724f8ad964627fb043fa46b1a284bbfbc196709f..0000000000000000000000000000000000000000 --- a/ucamlookup/ibisclient/methods.py +++ /dev/null @@ -1,2201 +0,0 @@ -# === AUTO-GENERATED - DO NOT EDIT === - -# -------------------------------------------------------------------------- -# Copyright (c) 2012, University of Cambridge Computing Service -# -# This file is part of the Lookup/Ibis client library. -# -# This library is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -# License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see <http://www.gnu.org/licenses/>. -# -------------------------------------------------------------------------- - -""" -Web service API methods. This module is fully auto-generated, and contains -the Python equivalent of the `XxxMethods` Java classes for executing all API -methods. -""" - -from .connection import IbisException - -class IbisMethods: - """ - Common methods for searching for objects in the Lookup/Ibis database. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - def __init__(self, conn): - self.conn = conn - - def getVersion(self): - """ - Get the current API version number. - - ``[ HTTP: GET /api/v1/version ]`` - - **Returns** - str - The API version number string. - """ - path = "api/v1/version" - path_params = {} - query_params = {} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.value - -class GroupMethods: - """ - Methods for querying and manipulating groups. - - **The fetch parameter for groups** - - All methods that return groups also accept an optional `fetch` - parameter that may be used to request additional information about the - groups returned. For more details about the general rules that apply to - the `fetch` parameter, refer to the :any:`PersonMethods` - documentation. - - For groups the `fetch` parameter may be used to fetch references - to people, institutions or other groups. In each case, only non-cancelled - people, institutions and groups will be included when fetching references. - The following references are supported: - - * ``"all_members"`` - fetches all the people who are members of the - group, including members of groups included by the group, and groups - included by those groups, and so on. - - * ``"direct_members"`` - fetches all the poeple who are direct - members of the group, not taking into account any included groups. - - * ``"members_of_inst"`` - if the group is a membership group for an - institution, this fetches that institution. - - * ``"owning_insts"`` - fetches all the institutions to which the - group belongs. - - * ``"manages_insts"`` - fetches all the institutions that the group - manages. Typically this only applies to "Editor" groups. - - * ``"manages_groups"`` - fetches all the groups that this group - manages. Note that some groups are self-managed, so this may be a - self-reference. - - * ``"managed_by_groups"`` - fetches all the groups that manage this - group. - - * ``"reads_groups"`` - fetches all the groups that this group has - privileged access to. This means that members of this group can see the - members of the referenced groups regardless of the membership visibility - settings. - - * ``"read_by_groups"`` - fetches all the groups that have privileged - access to this group. - - * ``"includes_groups"`` - fetches all the groups included by this - group. - - * ``"included_by_groups"`` - fetches all the groups that include - this group. - - As with person `fetch` parameters, the references may be used - in a chain by using the "dot" notation to fetch additional information - about referenced people, institutions or groups. For example - ``"all_members.email"`` will fetch the email addresses of all members - of the group. For more information about what can be fetched from - referenced people and institutions, refer to the documentation for - :any:`PersonMethods` and :any:`InstitutionMethods`. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - def __init__(self, conn): - self.conn = conn - - def allGroups(self, - includeCancelled, - fetch=None): - """ - Return a list of all groups. - - By default, only a few basic details about each group are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references. - - ``[ HTTP: GET /api/v1/group/all-groups ]`` - - **Parameters** - `includeCancelled` : bool - [optional] Whether or not to include cancelled - groups. By default, only live groups are returned. - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - list of :any:`IbisGroup` - The requested groups (in groupid order). - """ - path = "api/v1/group/all-groups" - path_params = {} - query_params = {"includeCancelled": includeCancelled, - "fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.groups - - def listGroups(self, - groupids, - fetch=None): - """ - Get the groups with the specified IDs or names. - - By default, only a few basic details about each group are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references. - - The results are sorted by groupid. - - .. note:: - The URL path length is limited to around 8000 characters, - which limits the number of groups that this method can fetch. Group - IDs are currently 6 characters long, and must be comma separated and - URL encoded, which limits this method to around 800 groups by ID, - but probably fewer by name, depending on the group name lengths. - - .. note:: - The groups returned may include cancelled groups. It is the - caller's repsonsibility to check their cancelled flags. - - ``[ HTTP: GET /api/v1/group/list?groupids=... ]`` - - **Parameters** - `groupids` : str - [required] A comma-separated list of group IDs or - group names (may be a mix of both). - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - list of :any:`IbisGroup` - The requested groups (in groupid order). - """ - path = "api/v1/group/list" - path_params = {} - query_params = {"groupids": groupids, - "fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.groups - - def search(self, - query, - approxMatches=None, - includeCancelled=None, - offset=None, - limit=None, - orderBy=None, - fetch=None): - """ - Search for groups using a free text query string. This is the same - search function that is used in the Lookup web application. - - By default, only a few basic details about each group are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references. - - ``[ HTTP: GET /api/v1/group/search?query=... ]`` - - **Parameters** - `query` : str - [required] The search string. - - `approxMatches` : bool - [optional] Flag to enable more approximate - matching in the search, causing more results to be returned. Defaults - to :any:`False`. - - `includeCancelled` : bool - [optional] Flag to allow cancelled groups to - be included. Defaults to :any:`False`. - - `offset` : int - [optional] The number of results to skip at the start - of the search. Defaults to 0. - - `limit` : int - [optional] The maximum number of results to return. - Defaults to 100. - - `orderBy` : str - [optional] The order in which to list the results. - This may be ``"groupid"``, ``"name"`` (the default) or - ``"title"``. - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - list of :any:`IbisGroup` - The matching groups. - """ - path = "api/v1/group/search" - path_params = {} - query_params = {"query": query, - "approxMatches": approxMatches, - "includeCancelled": includeCancelled, - "offset": offset, - "limit": limit, - "orderBy": orderBy, - "fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.groups - - def searchCount(self, - query, - approxMatches=None, - includeCancelled=None): - """ - Count the number of groups that would be returned by a search using - a free text query string. - - ``[ HTTP: GET /api/v1/group/search-count?query=... ]`` - - **Parameters** - `query` : str - [required] The search string. - - `approxMatches` : bool - [optional] Flag to enable more approximate - matching in the search, causing more results to be returned. Defaults - to :any:`False`. - - `includeCancelled` : bool - [optional] Flag to allow cancelled groups to - be included. Defaults to :any:`False`. - - **Returns** - int - The number of matching groups. - """ - path = "api/v1/group/search-count" - path_params = {} - query_params = {"query": query, - "approxMatches": approxMatches, - "includeCancelled": includeCancelled} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return int(result.value) - - def getGroup(self, - groupid, - fetch=None): - """ - Get the group with the specified ID or name. - - By default, only a few basic details about the group are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references of the group. - - .. note:: - The group returned may be a cancelled group. It is the caller's - repsonsibility to check its cancelled flag. - - ``[ HTTP: GET /api/v1/group/{groupid} ]`` - - **Parameters** - `groupid` : str - [required] The ID or name of the group to fetch. This - may be either the numeric ID or the short hyphenated group name (for - example ``"100656"`` or ``"cs-editors"``). - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - :any:`IbisGroup` - The requested group or :any:`None` if it was not found. - """ - path = "api/v1/group/%(groupid)s" - path_params = {"groupid": groupid} - query_params = {"fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.group - - def getCancelledMembers(self, - groupid, - fetch=None): - """ - Get all the cancelled members of the specified group, including - cancelled members of groups included by the group, and groups included - by those groups, and so on. - - By default, only a few basic details about each member are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references of each person. - - .. note:: - This method returns only cancelled people. It does not include - people who were removed from the group. Cancelled people are no longer - considered to be current staff, students or accredited visitors, and - are no longer regarded as belonging to any groups or institutions. The - list returned here reflects their group memberships just before they - were cancelled, and so is out-of-date data that should be used with - caution. - - ``[ HTTP: GET /api/v1/group/{groupid}/cancelled-members ]`` - - **Parameters** - `groupid` : str - [required] The ID or name of the group. - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch for each person. - - **Returns** - list of :any:`IbisPerson` - The group's cancelled members (in identifier order). - """ - path = "api/v1/group/%(groupid)s/cancelled-members" - path_params = {"groupid": groupid} - query_params = {"fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.people - - def getDirectMembers(self, - groupid, - fetch=None): - """ - Get the direct members of the specified group, not including members - included via groups included by the group. - - By default, only a few basic details about each member are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references of each person. - - .. note:: - This method will not include cancelled people. - - ``[ HTTP: GET /api/v1/group/{groupid}/direct-members ]`` - - **Parameters** - `groupid` : str - [required] The ID or name of the group. - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch for each person. - - **Returns** - list of :any:`IbisPerson` - The group's direct members (in identifier order). - """ - path = "api/v1/group/%(groupid)s/direct-members" - path_params = {"groupid": groupid} - query_params = {"fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.people - - def updateDirectMembers(self, - groupid, - addIds=None, - removeIds=None, - commitComment=None): - """ - Update the list of people who are direct members of the group. This - will not affect people who are included in the group due to the - inclusion of other groups. - - Any non-cancelled people in the list of identifiers specified by - `addIds` will be added to the group. This list should be a - comma-separated list of identifiers, each of which may be either a - CRSid or an identifier from another identifier scheme, prefixed with - that scheme's name and a slash. For example ``"mug99"`` or - ``"usn/123456789"``. - - Any people in the list of identifiers specified by `removeIds` - will be removed from the group, except if they are also in the list - `addIds`. The special identifier ``"all-members"`` may be - used to remove all existing group members, replacing them with the - list specified by `newIds`. - - **Examples:** - - .. code-block:: python - - updateDirectMembers("test-group", - "mug99,crsid/yyy99,usn/123456789", - "xxx99", - "Remove xxx99 and add mug99, yyy99 and usn/123456789 to test-group"); - - .. code-block:: python - - updateDirectMembers("test-group", - "xxx99,yyy99", - "all-members", - "Set the membership of test-group to include only xxx99 and yyy99"); - - ``[ HTTP: PUT /api/v1/group/{groupid}/direct-members ]`` - - **Parameters** - `groupid` : str - [required] The ID or name of the group. - - `addIds` : str - [optional] The identifiers of people to add to the group. - - `removeIds` : str - [optional] The identifiers of people to remove from - the group. - - `commitComment` : str - [recommended] A short textual description of - the change made (will be visible on the history tab of the group and - all the affected people in the web application). - - **Returns** - list of :any:`IbisPerson` - The updated list of direct members of the group (in identifier - order). - """ - path = "api/v1/group/%(groupid)s/direct-members" - path_params = {"groupid": groupid} - query_params = {} - form_params = {"addIds": addIds, - "removeIds": removeIds, - "commitComment": commitComment} - result = self.conn.invoke_method("PUT", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.people - - def getMembers(self, - groupid, - fetch=None): - """ - Get all the members of the specified group, including members of - groups included by the group, and groups included by those groups, - and so on. - - By default, only a few basic details about each member are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references of each person. - - .. note:: - This method will not include cancelled people. - - ``[ HTTP: GET /api/v1/group/{groupid}/members ]`` - - **Parameters** - `groupid` : str - [required] The ID or name of the group. - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch for each person. - - **Returns** - list of :any:`IbisPerson` - The group's members (in identifier order). - """ - path = "api/v1/group/%(groupid)s/members" - path_params = {"groupid": groupid} - query_params = {"fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.people - -class InstitutionMethods: - """ - Methods for querying and manipulating institutions. - - **The fetch parameter for institutions** - - All methods that return institutions also accept an optional - `fetch` parameter that may be used to request additional - information about the institutions returned. For more details about - the general rules that apply to the `fetch` parameter, - refer to the :any:`PersonMethods` documentation. - - For institutions the `fetch` parameter may be used to fetch - any institution attribute by specifying the `schemeid` of an - institution attribute scheme. Examples include ``"address"``, - ``"jpegPhoto"``, ``"universityPhone"``, ``"instPhone"``, - ``"landlinePhone"``, ``"mobilePhone"``, ``"faxNumber"``, - ``"email"`` and ``"labeledURI"``. The full list (which may be - extended over time) may be obtained using :any:`InstitutionMethods.allAttributeSchemes()`. - - In addition the following pseudo-attributes are supported: - - * ``"phone_numbers"`` - fetches all phone numbers. This is - equivalent to - ``"universityPhone,instPhone,landlinePhone,mobilePhone"``. - - * ``"all_attrs"`` - fetches all attributes from all institution - attribute schemes. This does not include references. - - * ``"contact_rows"`` - fetches all institution contact rows. Any - chained fetches from contact rows are used to fetch attributes from any - people referred to by the contact rows. - - The `fetch` parameter may also be used to fetch referenced - people, institutions or groups. This will only include references to - non-cancelled entities. The following references are supported: - - * ``"all_members"`` - fetches all the people who are members of the - institution. - - * ``"parent_insts"`` - fetches all the parent institutions. Note - that currently all institutions have only one parent, but this may change - in the future, and client applications should be prepared to handle - multiple parents. - - * ``"child_insts"`` - fetches all the child institutions. - - * ``"inst_groups"`` - fetches all the groups that belong to the - institution. - - * ``"members_groups"`` - fetches all the groups that form the - institution's membership list. - - * ``"managed_by_groups"`` - fetches all the groups that manage the - institution's data (commonly called "Editor" groups). - - As with person `fetch` parameters, the references may be used - in a chain by using the "dot" notation to fetch additional information - about referenced people, institutions or groups. For example - ``"all_members.email"`` will fetch the email addresses of all members - of the institution. For more information about what can be fetched from - referenced people and groups, refer to the documentation for - :any:`PersonMethods` and :any:`GroupMethods`. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - def __init__(self, conn): - self.conn = conn - - def allAttributeSchemes(self): - """ - Return a list of all the institution attribute schemes available. - The `schemeid` values of these schemes may be used in the - `fetch` parameter of other methods that return institutions. - - ``[ HTTP: GET /api/v1/inst/all-attr-schemes ]`` - - **Returns** - list of :any:`IbisAttributeScheme` - All the available institution attribute schemes (in precedence - order). - """ - path = "api/v1/inst/all-attr-schemes" - path_params = {} - query_params = {} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.attributeSchemes - - def allInsts(self, - includeCancelled, - fetch=None): - """ - Return a list of all institutions. - - By default, only a few basic details about each institution are - returned, but the optional `fetch` parameter may be used - to fetch additional attributes or references. - - ``[ HTTP: GET /api/v1/inst/all-insts ]`` - - **Parameters** - `includeCancelled` : bool - [optional] Whether or not to include cancelled - institutions. By default, only live institutions are returned. - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - list of :any:`IbisInstitution` - The requested institutions (in instid order). - """ - path = "api/v1/inst/all-insts" - path_params = {} - query_params = {"includeCancelled": includeCancelled, - "fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.institutions - - def listInsts(self, - instids, - fetch=None): - """ - Get the institutions with the specified IDs. - - By default, only a few basic details about each institution are - returned, but the optional `fetch` parameter may be used - to fetch additional attributes or references. - - The results are sorted by ID. - - .. note:: - The URL path length is limited to around 8000 characters, and - an instid is up to 8 characters long. Allowing for comma separators - and URL encoding, this limits the number of institutions that this - method may fetch to around 700. - - .. note:: - The institutions returned may include cancelled institutions. - It is the caller's repsonsibility to check their cancelled flags. - - ``[ HTTP: GET /api/v1/inst/list?instids=... ]`` - - **Parameters** - `instids` : str - [required] A comma-separated list of instids. - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - list of :any:`IbisInstitution` - The requested institutions (in instid order). - """ - path = "api/v1/inst/list" - path_params = {} - query_params = {"instids": instids, - "fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.institutions - - def search(self, - query, - approxMatches=None, - includeCancelled=None, - attributes=None, - offset=None, - limit=None, - orderBy=None, - fetch=None): - """ - Search for institutions using a free text query string. This is the - same search function that is used in the Lookup web application. - - By default, only a few basic details about each institution are - returned, but the optional `fetch` parameter may be used - to fetch additional attributes or references. - - ``[ HTTP: GET /api/v1/inst/search?query=... ]`` - - **Parameters** - `query` : str - [required] The search string. - - `approxMatches` : bool - [optional] Flag to enable more approximate - matching in the search, causing more results to be returned. Defaults - to :any:`False`. - - `includeCancelled` : bool - [optional] Flag to allow cancelled institutions - to be included. Defaults to :any:`False`. - - `attributes` : str - [optional] A comma-separated list of attributes to - consider when searching. If this is :any:`None` (the default) then - all attribute schemes marked as searchable will be included. - - `offset` : int - [optional] The number of results to skip at the start - of the search. Defaults to 0. - - `limit` : int - [optional] The maximum number of results to return. - Defaults to 100. - - `orderBy` : str - [optional] The order in which to list the results. - This may be either ``"instid"`` or ``"name"`` (the default). - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - list of :any:`IbisInstitution` - The matching institutions. - """ - path = "api/v1/inst/search" - path_params = {} - query_params = {"query": query, - "approxMatches": approxMatches, - "includeCancelled": includeCancelled, - "attributes": attributes, - "offset": offset, - "limit": limit, - "orderBy": orderBy, - "fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.institutions - - def searchCount(self, - query, - approxMatches=None, - includeCancelled=None, - attributes=None): - """ - Count the number of institutions that would be returned by a search - using a free text query string. - - ``[ HTTP: GET /api/v1/inst/search-count?query=... ]`` - - **Parameters** - `query` : str - [required] The search string. - - `approxMatches` : bool - [optional] Flag to enable more approximate - matching in the search, causing more results to be returned. Defaults - to :any:`False`. - - `includeCancelled` : bool - [optional] Flag to allow cancelled institutions - to be included. Defaults to :any:`False`. - - `attributes` : str - [optional] A comma-separated list of attributes to - consider when searching. If this is :any:`None` (the default) then - all attribute schemes marked as searchable will be included. - - **Returns** - int - The number of matching institutions. - """ - path = "api/v1/inst/search-count" - path_params = {} - query_params = {"query": query, - "approxMatches": approxMatches, - "includeCancelled": includeCancelled, - "attributes": attributes} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return int(result.value) - - def getInst(self, - instid, - fetch=None): - """ - Get the institution with the specified ID. - - By default, only a few basic details about the institution are - returned, but the optional `fetch` parameter may be used - to fetch additional attributes or references of the institution. - - .. note:: - The institution returned may be a cancelled institution. It is - the caller's repsonsibility to check its cancelled flag. - - ``[ HTTP: GET /api/v1/inst/{instid} ]`` - - **Parameters** - `instid` : str - [required] The ID of the institution to fetch. - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - :any:`IbisInstitution` - The requested institution or :any:`None` if it was not found. - """ - path = "api/v1/inst/%(instid)s" - path_params = {"instid": instid} - query_params = {"fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.institution - - def addAttribute(self, - instid, - attr, - position=None, - allowDuplicates=None, - commitComment=None): - """ - Add an attribute to an institution. By default, this will not add the - attribute again if it already exists. - - When adding an attribute, the new attribute's scheme must be set. - In addition, either its value or its binaryData field should be set. - All the remaining fields of the attribute are optional. - - ``[ HTTP: POST /api/v1/inst/{instid}/add-attribute ]`` - - **Parameters** - `instid` : str - [required] The ID of the institution. - - `attr` : :any:`IbisAttribute` - [required] The new attribute to add. - - `position` : int - [optional] The position of the new attribute in the - list of attributes of the same attribute scheme (1, 2, 3,...). A value - of 0 (the default) will cause the new attribute to be added to the end - of the list of existing attributes for the scheme. - - `allowDuplicates` : bool - [optional] If :any:`True`, the new attribute - will always be added, even if another identical attribute already - exists. If :any:`False` (the default), the new attribute will only be - added if it doesn't already exist. - - `commitComment` : str - [recommended] A short textual description of - the change made (will be visible on the history tab in the web - application). - - **Returns** - :any:`IbisAttribute` - The newly created or existing attribute. - """ - path = "api/v1/inst/%(instid)s/add-attribute" - path_params = {"instid": instid} - query_params = {} - form_params = {"attr": attr, - "position": position, - "allowDuplicates": allowDuplicates, - "commitComment": commitComment} - result = self.conn.invoke_method("POST", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.attribute - - def getCancelledMembers(self, - instid, - fetch=None): - """ - Get all the cancelled members of the specified institution. - - By default, only a few basic details about each member are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references of each person. - - .. note:: - This method returns only cancelled people. It does not include - people who were removed from the institution. Cancelled people are no - longer considered to be current staff, students or accredited visitors, - and are no longer regarded as belonging to any groups or institutions. - The list returned here reflects their institutional memberships just - before they were cancelled, and so is out-of-date data that should be - used with caution. - - ``[ HTTP: GET /api/v1/inst/{instid}/cancelled-members ]`` - - **Parameters** - `instid` : str - [required] The ID of the institution. - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch for each person. - - **Returns** - list of :any:`IbisPerson` - The institution's cancelled members (in identifier order). - """ - path = "api/v1/inst/%(instid)s/cancelled-members" - path_params = {"instid": instid} - query_params = {"fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.people - - def getContactRows(self, - instid, - fetch=None): - """ - Get all the contact rows of the specified institution. - - Any addresses, email addresses, phone numbers and web pages - associated with the contact rows are automatically returned, as - well as any people referred to by the contact rows. - - If any of the contact rows refer to people, then only a few basic - details about each person are returned, but the optional - `fetch` parameter may be used to fetch additional - attributes or references of each person. - - .. note:: - This method will not include cancelled people. - - ``[ HTTP: GET /api/v1/inst/{instid}/contact-rows ]`` - - **Parameters** - `instid` : str - [required] The ID of the institution. - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch for any people referred to by any - of the contact rows. - - **Returns** - list of :any:`IbisContactRow` - The institution's contact rows. - """ - path = "api/v1/inst/%(instid)s/contact-rows" - path_params = {"instid": instid} - query_params = {"fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.institution.contactRows - - def getAttributes(self, - instid, - attrs): - """ - Get one or more (possibly multi-valued) attributes of an institution. - The returned attributes are sorted by attribute scheme precedence and - then attribute precedence. - - ``[ HTTP: GET /api/v1/inst/{instid}/get-attributes?attrs=... ]`` - - **Parameters** - `instid` : str - [required] The ID of the institution. - - `attrs` : str - [required] The attribute scheme(s) to fetch. This may - include any number of the attributes or pseudo-attributes, but it - may not include references or attribute chains (see the documentation - for the `fetch` parameter in this class). - - **Returns** - list of :any:`IbisAttribute` - The requested attributes. - """ - path = "api/v1/inst/%(instid)s/get-attributes" - path_params = {"instid": instid} - query_params = {"attrs": attrs} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.attributes - - def getMembers(self, - instid, - fetch=None): - """ - Get all the members of the specified institution. - - By default, only a few basic details about each member are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references of each person. - - .. note:: - This method will not include cancelled people. - - ``[ HTTP: GET /api/v1/inst/{instid}/members ]`` - - **Parameters** - `instid` : str - [required] The ID of the institution. - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch for each person. - - **Returns** - list of :any:`IbisPerson` - The institution's members (in identifier order). - """ - path = "api/v1/inst/%(instid)s/members" - path_params = {"instid": instid} - query_params = {"fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.people - - def deleteAttribute(self, - instid, - attrid, - commitComment=None): - """ - Delete an attribute of an institution. It is not an error if the - attribute does not exist. - - Note that in this method, the `commitComment` is passed - as a query parameter, rather than as a form parameter, for greater - client compatibility. - - ``[ HTTP: DELETE /api/v1/inst/{instid}/{attrid} ]`` - - **Parameters** - `instid` : str - [required] The ID of the institution. - - `attrid` : long - [required] The ID of the attribute to delete. - - `commitComment` : str - [recommended] A short textual description of - the change made (will be visible on the history tab in the web - application). - - **Returns** - bool - :any:`True` if the attribute was deleted by this method, or - :any:`False` if it did not exist. - """ - path = "api/v1/inst/%(instid)s/%(attrid)s" - path_params = {"instid": instid, - "attrid": attrid} - query_params = {"commitComment": commitComment} - form_params = {} - result = self.conn.invoke_method("DELETE", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.value and result.value.lower() == "true" - - def getAttribute(self, - instid, - attrid): - """ - Get a specific attribute of an institution. - - ``[ HTTP: GET /api/v1/inst/{instid}/{attrid} ]`` - - **Parameters** - `instid` : str - [required] The ID of the institution. - - `attrid` : long - [required] The ID of the attribute to fetch. - - **Returns** - :any:`IbisAttribute` - The requested attribute. - """ - path = "api/v1/inst/%(instid)s/%(attrid)s" - path_params = {"instid": instid, - "attrid": attrid} - query_params = {} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.attribute - - def updateAttribute(self, - instid, - attrid, - attr, - commitComment=None): - """ - Update an attribute of an institution. - - The attribute's value, binaryData, comment and effective date fields - will all be updated using the data supplied. All other fields will be - left unchanged. - - To avoid inadvertently changing fields of the attribute, it is - recommended that :any:`getAttribute() <InstitutionMethods.getAttribute>` be used to - retrieve the current value of the attribute, before calling this - method with the required changes. - - ``[ HTTP: PUT /api/v1/inst/{instid}/{attrid} ]`` - - **Parameters** - `instid` : str - [required] The ID of the institution. - - `attrid` : long - [required] The ID of the attribute to update. - - `attr` : :any:`IbisAttribute` - [required] The new attribute values to apply. - - `commitComment` : str - [recommended] A short textual description of - the change made (will be visible on the history tab in the web - application). - - **Returns** - :any:`IbisAttribute` - The updated attribute. - """ - path = "api/v1/inst/%(instid)s/%(attrid)s" - path_params = {"instid": instid, - "attrid": attrid} - query_params = {} - form_params = {"attr": attr, - "commitComment": commitComment} - result = self.conn.invoke_method("PUT", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.attribute - -class PersonMethods: - """ - Methods for querying and manipulating people. - - **Notes on the fetch parameter** - - All methods that return people, institutions or groups also accept an - optional `fetch` parameter that may be used to request - additional information about the entities returned. Without this - parameter, only a few basic details about each person, institution or - group are returned. The `fetch` parameter is quite flexible, - and may be used in a number of different ways: - - * **Attribute fetching**. Attributes may be fetched by specifying the - `schemeid` of an attribute scheme. For example to fetch a - person's email addresses, use the value ``"email"``. For people common - attribute schemes include ``"jpegPhoto"``, ``"misAffiliation"``, - ``"title"``, ``"universityPhone"``, ``"mobexPhone"``, - ``"landlinePhone"``, ``"mobilePhone"``, ``"pager"``, - ``"labeledURI"`` and ``"address"``. The full list of person - attribute schemes may be obtained using :any:`PersonMethods.allAttributeSchemes()`. - - * **Pseudo-attributes**. Certain special pseudo-attributes are defined - for convenience. For people, the following pseudo-attributes are supported: - - * ``"phone_numbers"`` - fetches all phone numbers. This is - equivalent to - ``"universityPhone,instPhone,mobexPhone,landlinePhone,mobilePhone,pager"``. - - * ``"all_identifiers"`` - fetches all identifiers. Currently people - only have CRSid identifiers, but in the future additional identifiers such - as USN or staffNumber may be added. - - * ``"all_attrs"`` - fetches all attributes from all person attribute - schemes. This does not include identifiers or references. - - * **Reference fetching**. For people, the following references are - supported (and will fetch only non-cancelled institutions and groups): - - * ``"all_insts"`` - fetches all the institutions to which the person - belongs (sorted in name order). - - * ``"all_groups"`` - fetches all the groups that the person is a - member of, including indirect group memberships, via groups that include - other groups. - - * ``"direct_groups"`` - fetches all the groups that the person is - directly a member of. This does not include indirect group memberships - - i.e., groups that include these groups. - - * **Chained reference fetching**. To fetch properties of referenced - objects, the "dot" notation may be used. For example, to fetch the email - addresses of all the institutions to which a person belongs, use - ``"all_insts.email"``. Chains may include a number of reference - following steps, for example - ``"all_insts.managed_by_groups.all_members.email"`` will fetch all the - institutions to which the person belongs, all the groups that manage those - institutions, all the visible members of those groups and all the email - addresses of those managing group members. For more information about what - can be fetched from referenced institutions and groups, refer to the - documentation for :any:`InstitutionMethods` and :any:`GroupMethods`. - - Multiple values of the `fetch` parameter should be separated - by commas. - - **Fetch parameter examples** - - ``fetch = "email"`` - This fetches all the person's email addresses. - - ``fetch = "title,address"`` - This fetches all the person's titles (roles) and addresses. - - ``fetch = "all_attrs"`` - This fetches all the person's attributes. - - ``fetch = "all_groups,all_insts"`` - This fetches all the groups and institutions to which the person belongs. - - ``fetch = "all_insts.parent_insts"`` - This fetches all the person's institutions, and their parent institutions. - - ``fetch = "all_insts.email,all_insts.all_members.email"`` - This fetches all the person's institutions and their email addresses, and - all the members of those institutions, and the email addresses of all - those members. - - .. codeauthor:: Dean Rasheed (dev-group@ucs.cam.ac.uk) - """ - def __init__(self, conn): - self.conn = conn - - def allAttributeSchemes(self): - """ - Return a list of all the person attribute schemes available. The - `schemeid` values of these schemes may be used in the - `fetch` parameter of other methods that return people. - - .. note:: - Some of these attribute schemes are not currently used (no - people have attribute values in the scheme). These schemes are - reserved for possible future use. - - ``[ HTTP: GET /api/v1/person/all-attr-schemes ]`` - - **Returns** - list of :any:`IbisAttributeScheme` - All the available person attribute schemes (in precedence - order). - """ - path = "api/v1/person/all-attr-schemes" - path_params = {} - query_params = {} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.attributeSchemes - - def allPeople(self, - includeCancelled, - identifier=None, - limit=None, - fetch=None): - """ - Return a list of all people (in batches). - - The results are sorted by identifier, starting with the first person - after the person with the specified identifier. Thus, to iterate over - all people, pass a :any:`None` identifier to get the first batch of - people, then pass the last identifier from the previous batch to get - the next batch, and repeat until no more people are returned. - - By default, only a few basic details about each person are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references. - - ``[ HTTP: GET /api/v1/person/all-people ]`` - - **Parameters** - `includeCancelled` : bool - [optional] Flag to allow cancelled people to - be included (people who are no longer members of the University). - Defaults to :any:`False`. - - `identifier` : str - [optional] The identifier (CRSid) of the person to - start after, or :any:`None` to start from the first person. - - `limit` : int - [optional] The maximum number of people to return. - Defaults to 100. - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - list of :any:`IbisPerson` - The requested people (in identifier order). - """ - path = "api/v1/person/all-people" - path_params = {} - query_params = {"includeCancelled": includeCancelled, - "identifier": identifier, - "limit": limit, - "fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.people - - def listPeople(self, - crsids, - fetch=None): - """ - Get the people with the specified identifiers (typically CRSids). - - Each identifier may be either a CRSid, or an identifier from another - identifier scheme, prefixed with that scheme's name and a slash. For - example ``"mug99"`` or ``"usn/123456789"``. - - By default, only a few basic details about each person are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references. - - The results are sorted by identifier scheme and value. - - .. note:: - The number of people that may be fetched in a single call is - limited by the URL path length limit (around 8000 characters). A - CRSid is up to 7 characters long, and other identifiers are typically - longer, since they must also include the identifier scheme. Thus the - number of people that this method may fetch is typically limited to a - few hundred. - - .. note:: - The people returned may include cancelled people. It is the - caller's repsonsibility to check their cancelled flags. - - ``[ HTTP: GET /api/v1/person/list?crsids=... ]`` - - **Parameters** - `crsids` : str - [required] A comma-separated list of identifiers. - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - list of :any:`IbisPerson` - The requested people (in identifier order). - """ - path = "api/v1/person/list" - path_params = {} - query_params = {"crsids": crsids, - "fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.people - - def search(self, - query, - approxMatches=None, - includeCancelled=None, - misStatus=None, - attributes=None, - offset=None, - limit=None, - orderBy=None, - fetch=None): - """ - Search for people using a free text query string. This is the same - search function that is used in the Lookup web application. - - By default, only a few basic details about each person are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references. - - ``[ HTTP: GET /api/v1/person/search?query=... ]`` - - **Parameters** - `query` : str - [required] The search string. - - `approxMatches` : bool - [optional] Flag to enable more approximate - matching in the search, causing more results to be returned. Defaults - to :any:`False`. - - `includeCancelled` : bool - [optional] Flag to allow cancelled people to - be included (people who are no longer members of the University). - Defaults to :any:`False`. - - `misStatus` : str - [optional] The type of people to search for. This may - be - - * ``"staff"`` - only include people whose MIS status is - ``""`` (empty string), ``"staff"``, or - ``"staff,student"``. - - * ``"student"`` - only include people whose MIS status is set to - ``"student"`` or ``"staff,student"``. - - Otherwise all matching people will be included (the default). Note - that the ``"staff"`` and ``"student"`` options are not - mutually exclusive. - - `attributes` : str - [optional] A comma-separated list of attributes to - consider when searching. If this is :any:`None` (the default) then - all attribute schemes marked as searchable will be included. - - `offset` : int - [optional] The number of results to skip at the start - of the search. Defaults to 0. - - `limit` : int - [optional] The maximum number of results to return. - Defaults to 100. - - `orderBy` : str - [optional] The order in which to list the results. - This may be either ``"identifier"`` or ``"surname"`` (the - default). - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - list of :any:`IbisPerson` - The matching people. - """ - path = "api/v1/person/search" - path_params = {} - query_params = {"query": query, - "approxMatches": approxMatches, - "includeCancelled": includeCancelled, - "misStatus": misStatus, - "attributes": attributes, - "offset": offset, - "limit": limit, - "orderBy": orderBy, - "fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.people - - def searchCount(self, - query, - approxMatches=None, - includeCancelled=None, - misStatus=None, - attributes=None): - """ - Count the number of people that would be returned by a search using - a free text query string. - - ``[ HTTP: GET /api/v1/person/search-count?query=... ]`` - - **Parameters** - `query` : str - [required] The search string. - - `approxMatches` : bool - [optional] Flag to enable more approximate - matching in the search, causing more results to be returned. Defaults - to :any:`False`. - - `includeCancelled` : bool - [optional] Flag to allow cancelled people to - be included (people who are no longer members of the University). - Defaults to :any:`False`. - - `misStatus` : str - [optional] The type of people to search for. This may - be - - * ``"staff"`` - only include people whose MIS status is - ``""`` (empty string), ``"staff"``, or - ``"staff,student"``. - - * ``"student"`` - only include people whose MIS status is set to - ``"student"`` or ``"staff,student"``. - - Otherwise all matching people will be included (the default). Note - that the ``"staff"`` and ``"student"`` options are not - mutually exclusive. - - `attributes` : str - [optional] A comma-separated list of attributes to - consider when searching. If this is :any:`None` (the default) then - all attribute schemes marked as searchable will be included. - - **Returns** - int - The number of matching people. - """ - path = "api/v1/person/search-count" - path_params = {} - query_params = {"query": query, - "approxMatches": approxMatches, - "includeCancelled": includeCancelled, - "misStatus": misStatus, - "attributes": attributes} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return int(result.value) - - def getPerson(self, - scheme, - identifier, - fetch=None): - """ - Get the person with the specified identifier. - - By default, only a few basic details about the person are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references of the person. - - .. note:: - The person returned may be a cancelled person. It is the - caller's repsonsibility to check its cancelled flag. - - ``[ HTTP: GET /api/v1/person/{scheme}/{identifier} ]`` - - **Parameters** - `scheme` : str - [required] The person identifier scheme. Typically this - should be ``"crsid"``, but other identifier schemes may be - available in the future, such as ``"usn"`` or - ``"staffNumber"``. - - `identifier` : str - [required] The identifier of the person to fetch - (typically their CRSid). - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - :any:`IbisPerson` - The requested person or :any:`None` if they were not found. - """ - path = "api/v1/person/%(scheme)s/%(identifier)s" - path_params = {"scheme": scheme, - "identifier": identifier} - query_params = {"fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.person - - def addAttribute(self, - scheme, - identifier, - attr, - position=None, - allowDuplicates=None, - commitComment=None): - """ - Add an attribute to a person. By default, this will not add the - attribute again if it already exists. - - When adding an attribute, the new attribute's scheme must be set. - In addition, either its value or its binaryData field should be set. - All the remaining fields of the attribute are optional. - - ``[ HTTP: POST /api/v1/person/{scheme}/{identifier}/add-attribute ]`` - - **Parameters** - `scheme` : str - [required] The person identifier scheme. Typically this - should be ``"crsid"``, but other identifier schemes may be - available in the future, such as ``"usn"`` or - ``"staffNumber"``. - - `identifier` : str - [required] The identifier of the person to udpate - (typically their CRSid). - - `attr` : :any:`IbisAttribute` - [required] The new attribute to add. - - `position` : int - [optional] The position of the new attribute in the - list of attributes of the same attribute scheme (1, 2, 3,...). A value - of 0 (the default) will cause the new attribute to be added to the end - of the list of existing attributes for the scheme. - - `allowDuplicates` : bool - [optional] If :any:`True`, the new attribute - will always be added, even if another identical attribute already - exists. If :any:`False` (the default), the new attribute will only be - added if it doesn't already exist. - - `commitComment` : str - [recommended] A short textual description of - the change made (will be visible on the history tab in the web - application). - - **Returns** - :any:`IbisAttribute` - The newly created or existing attribute. - """ - path = "api/v1/person/%(scheme)s/%(identifier)s/add-attribute" - path_params = {"scheme": scheme, - "identifier": identifier} - query_params = {} - form_params = {"attr": attr, - "position": position, - "allowDuplicates": allowDuplicates, - "commitComment": commitComment} - result = self.conn.invoke_method("POST", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.attribute - - def getAttributes(self, - scheme, - identifier, - attrs): - """ - Get one or more (possibly multi-valued) attributes of a person. The - returned attributes are sorted by attribute scheme precedence and - then attribute precedence. - - ``[ HTTP: GET /api/v1/person/{scheme}/{identifier}/get-attributes?attrs=... ]`` - - **Parameters** - `scheme` : str - [required] The person identifier scheme. Typically this - should be ``"crsid"``, but other identifier schemes may be - available in the future, such as ``"usn"`` or - ``"staffNumber"``. - - `identifier` : str - [required] The identifier of the person (typically - their CRSid). - - `attrs` : str - [required] The attribute scheme(s) to fetch. This may - include any number of the attributes or pseudo-attributes, but it - may not include references or attribute chains (see the documentation - for the `fetch` parameter in this class). - - **Returns** - list of :any:`IbisAttribute` - The requested attributes. - """ - path = "api/v1/person/%(scheme)s/%(identifier)s/get-attributes" - path_params = {"scheme": scheme, - "identifier": identifier} - query_params = {"attrs": attrs} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.attributes - - def getGroups(self, - scheme, - identifier, - fetch=None): - """ - Get all the groups to which the specified person belongs, including - indirect group memberships, via groups that include other groups. - The returned list of groups is sorted by groupid. - - Note that some group memberships may not be visible to you. This - method will only return those group memberships that you have - permission to see. - - By default, only a few basic details about each group are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references of each group. - - .. note:: - This method will not include cancelled groups. - - ``[ HTTP: GET /api/v1/person/{scheme}/{identifier}/groups ]`` - - **Parameters** - `scheme` : str - [required] The person identifier scheme. Typically this - should be ``"crsid"``, but other identifier schemes may be - available in the future, such as ``"usn"`` or - ``"staffNumber"``. - - `identifier` : str - [required] The identifier of the person (typically - their CRSid). - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - list of :any:`IbisGroup` - The person's groups (in groupid order). - """ - path = "api/v1/person/%(scheme)s/%(identifier)s/groups" - path_params = {"scheme": scheme, - "identifier": identifier} - query_params = {"fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.groups - - def getInsts(self, - scheme, - identifier, - fetch=None): - """ - Get all the institutions to which the specified person belongs. The - returned list of institutions is sorted by name. - - By default, only a few basic details about each institution are - returned, but the optional `fetch` parameter may be used - to fetch additional attributes or references of each institution. - - .. note:: - This method will not include cancelled institutions. - - ``[ HTTP: GET /api/v1/person/{scheme}/{identifier}/insts ]`` - - **Parameters** - `scheme` : str - [required] The person identifier scheme. Typically this - should be ``"crsid"``, but other identifier schemes may be - available in the future, such as ``"usn"`` or - ``"staffNumber"``. - - `identifier` : str - [required] The identifier of the person (typically - their CRSid). - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - list of :any:`IbisInstitution` - The person's institutions (in name order). - """ - path = "api/v1/person/%(scheme)s/%(identifier)s/insts" - path_params = {"scheme": scheme, - "identifier": identifier} - query_params = {"fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.institutions - - def isMemberOfGroup(self, - scheme, - identifier, - groupid): - """ - Test if the specified person is a member of the specified group. - - .. note:: - This may be used with cancelled people and groups. - - ``[ HTTP: GET /api/v1/person/{scheme}/{identifier}/is-member-of-group/{groupid} ]`` - - **Parameters** - `scheme` : str - [required] The person identifier scheme. Typically this - should be ``"crsid"``, but other identifier schemes may be - available in the future, such as ``"usn"`` or - ``"staffNumber"``. - - `identifier` : str - [required] The identifier of the person (typically - their CRSid). - - `groupid` : str - [required] The ID or name of the group. - - **Returns** - bool - :any:`True` if the specified person is in the specified - group, :any:`False` otherwise (or if the person or group does not - exist). - """ - path = "api/v1/person/%(scheme)s/%(identifier)s/is-member-of-group/%(groupid)s" - path_params = {"scheme": scheme, - "identifier": identifier, - "groupid": groupid} - query_params = {} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.value and result.value.lower() == "true" - - def isMemberOfInst(self, - scheme, - identifier, - instid): - """ - Test if the specified person is a member of the specified institution. - - .. note:: - This may be used with cancelled people and institutions, but - it will not include cancelled membership groups. - - ``[ HTTP: GET /api/v1/person/{scheme}/{identifier}/is-member-of-inst/{instid} ]`` - - **Parameters** - `scheme` : str - [required] The person identifier scheme. Typically this - should be ``"crsid"``, but other identifier schemes may be - available in the future, such as ``"usn"`` or - ``"staffNumber"``. - - `identifier` : str - [required] The identifier of the person (typically - their CRSid). - - `instid` : str - [required] The ID of the institution. - - **Returns** - bool - :any:`True` if the specified person is in the specified - institution, :any:`False` otherwise (or if the person or institution - does not exist). - """ - path = "api/v1/person/%(scheme)s/%(identifier)s/is-member-of-inst/%(instid)s" - path_params = {"scheme": scheme, - "identifier": identifier, - "instid": instid} - query_params = {} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.value and result.value.lower() == "true" - - def getManagedGroups(self, - scheme, - identifier, - fetch=None): - """ - Get all the groups that the specified person has persmission to edit. - The returned list of groups is sorted by groupid. - - Note that some group memberships may not be visible to you. This - method will only include groups for which you have persmission to - see the applicable manager group memberships. - - By default, only a few basic details about each group are returned, - but the optional `fetch` parameter may be used to fetch - additional attributes or references of each group. - - .. note:: - This method will not include cancelled groups. - - ``[ HTTP: GET /api/v1/person/{scheme}/{identifier}/manages-groups ]`` - - **Parameters** - `scheme` : str - [required] The person identifier scheme. Typically this - should be ``"crsid"``, but other identifier schemes may be - available in the future, such as ``"usn"`` or - ``"staffNumber"``. - - `identifier` : str - [required] The identifier of the person (typically - their CRSid). - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - list of :any:`IbisGroup` - The groups that the person manages (in groupid order). - """ - path = "api/v1/person/%(scheme)s/%(identifier)s/manages-groups" - path_params = {"scheme": scheme, - "identifier": identifier} - query_params = {"fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.groups - - def getManagedInsts(self, - scheme, - identifier, - fetch=None): - """ - Get all the institutions that the specified person has permission to - edit. The returned list of institutions is sorted by name. - - Note that some group memberships may not be visible to you. This - method will only include institutions for which you have permission - to see the applicable editor group memberships. - - By default, only a few basic details about each institution are - returned, but the optional `fetch` parameter may be used - to fetch additional attributes or references of each institution. - - .. note:: - This method will not include cancelled institutions. - - ``[ HTTP: GET /api/v1/person/{scheme}/{identifier}/manages-insts ]`` - - **Parameters** - `scheme` : str - [required] The person identifier scheme. Typically this - should be ``"crsid"``, but other identifier schemes may be - available in the future, such as ``"usn"`` or - ``"staffNumber"``. - - `identifier` : str - [required] The identifier of the person (typically - their CRSid). - - `fetch` : str - [optional] A comma-separated list of any additional - attributes or references to fetch. - - **Returns** - list of :any:`IbisInstitution` - The institutions that the person manages (in name order). - """ - path = "api/v1/person/%(scheme)s/%(identifier)s/manages-insts" - path_params = {"scheme": scheme, - "identifier": identifier} - query_params = {"fetch": fetch} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.institutions - - def deleteAttribute(self, - scheme, - identifier, - attrid, - commitComment=None): - """ - Delete an attribute of a person. It is not an error if the attribute - does not exist. - - Note that in this method, the `commitComment` is passed - as a query parameter, rather than as a form parameter, for greater - client compatibility. - - ``[ HTTP: DELETE /api/v1/person/{scheme}/{identifier}/{attrid} ]`` - - **Parameters** - `scheme` : str - [required] The person identifier scheme. Typically this - should be ``"crsid"``, but other identifier schemes may be - available in the future, such as ``"usn"`` or - ``"staffNumber"``. - - `identifier` : str - [required] The identifier of the person to udpate - (typically their CRSid). - - `attrid` : long - [required] The ID of the attribute to delete. - - `commitComment` : str - [recommended] A short textual description of - the change made (will be visible on the history tab in the web - application). - - **Returns** - bool - :any:`True` if the attribute was deleted by this method, or - :any:`False` if it did not exist. - """ - path = "api/v1/person/%(scheme)s/%(identifier)s/%(attrid)s" - path_params = {"scheme": scheme, - "identifier": identifier, - "attrid": attrid} - query_params = {"commitComment": commitComment} - form_params = {} - result = self.conn.invoke_method("DELETE", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.value and result.value.lower() == "true" - - def getAttribute(self, - scheme, - identifier, - attrid): - """ - Get a specific attribute of a person. - - ``[ HTTP: GET /api/v1/person/{scheme}/{identifier}/{attrid} ]`` - - **Parameters** - `scheme` : str - [required] The person identifier scheme. Typically this - should be ``"crsid"``, but other identifier schemes may be - available in the future, such as ``"usn"`` or - ``"staffNumber"``. - - `identifier` : str - [required] The identifier of the person (typically - their CRSid). - - `attrid` : long - [required] The ID of the attribute to fetch. - - **Returns** - :any:`IbisAttribute` - The requested attribute. - """ - path = "api/v1/person/%(scheme)s/%(identifier)s/%(attrid)s" - path_params = {"scheme": scheme, - "identifier": identifier, - "attrid": attrid} - query_params = {} - form_params = {} - result = self.conn.invoke_method("GET", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.attribute - - def updateAttribute(self, - scheme, - identifier, - attrid, - attr, - commitComment=None): - """ - Update an attribute of a person. - - The attribute's value, binaryData, comment, instid and effective date - fields will all be updated using the data supplied. All other fields - will be left unchanged. - - To avoid inadvertently changing fields of the attribute, it is - recommended that :any:`getAttribute() <PersonMethods.getAttribute>` be used to - retrieve the current value of the attribute, before calling this - method with the required changes. - - ``[ HTTP: PUT /api/v1/person/{scheme}/{identifier}/{attrid} ]`` - - **Parameters** - `scheme` : str - [required] The person identifier scheme. Typically this - should be ``"crsid"``, but other identifier schemes may be - available in the future, such as ``"usn"`` or - ``"staffNumber"``. - - `identifier` : str - [required] The identifier of the person to udpate - (typically their CRSid). - - `attrid` : long - [required] The ID of the attribute to update. - - `attr` : :any:`IbisAttribute` - [required] The new attribute values to apply. - - `commitComment` : str - [recommended] A short textual description of - the change made (will be visible on the history tab in the web - application). - - **Returns** - :any:`IbisAttribute` - The updated attribute. - """ - path = "api/v1/person/%(scheme)s/%(identifier)s/%(attrid)s" - path_params = {"scheme": scheme, - "identifier": identifier, - "attrid": attrid} - query_params = {} - form_params = {"attr": attr, - "commitComment": commitComment} - result = self.conn.invoke_method("PUT", path, path_params, - query_params, form_params) - if result.error: - raise IbisException(result.error) - return result.attribute