diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a7e92962a2f155faffc800405802dd0cfc7d7038..d1c52cbaddd85900968e47b943dae207fa85d892 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -86,12 +86,14 @@ test:pycaosdb:py34: tags: [ py34 ] stage: test script: + - touch ~/.pycaosdb.ini - tox -r -e py34 test:pycaosdb:py27: tags: [ py27 ] stage: test script: + - touch ~/.pycaosdb.ini - tox -r -e py27 # pylint tests for pycaosdb (python 3.4) diff --git a/examples/server_side_script.py b/examples/server_side_script.py new file mode 100755 index 0000000000000000000000000000000000000000..71bd9c05b4e86133cc356e1c15359701642a9486 --- /dev/null +++ b/examples/server_side_script.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2018 Research Group Biomedical Physics, +# Max-Planck-Institute for Dynamics and Self-Organization Göttingen +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# ** end header +# +"""server_side_script.py. + +An example which implements a minimal server-side script. + +1) This script expects to find a *.txt file in the .upload_files dir which is +printed to stdout. + +2) It executes a "Count stars" query and prints the result to stdout. + +3) It will return with code 0 if everything is ok, or with any code that is +specified with the commandline option --exit +""" + +import sys +from os import listdir +from caosdb import configure_connection, execute_query + + +# parse --auth-token option and configure connection +CODE = 0 +QUERY = "COUNT stars" +for arg in sys.argv: + if arg.startswith("--auth-token="): + auth_token = arg[13:] + configure_connection(auth_token=auth_token) + if arg.startswith("--exit="): + CODE = int(arg[7:]) + if arg.startswith("--query="): + QUERY = arg[8:] + + +############################################################ +# 1 # find and print *.txt file ############################ +############################################################ + +try: + for fname in listdir(".upload_files"): + if fname.endswith(".txt"): + with open(".upload_files/{}".format(fname)) as f: + print(f.read()) +except FileNotFoundError: + pass + + +############################################################ +# 2 # query "COUNT stars" ################################## +############################################################ + +RESULT = execute_query(QUERY) +print(RESULT) + +############################################################ +# 3 ######################################################## +############################################################ + +sys.exit(CODE) diff --git a/pytest.ini b/pytest.ini index abdd410e5f9e9835a3233b22687ad49cc1109235..ca6aad829a3e0607292cf69b8b1d4b7f7758993e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ [pytest] -testpaths = unittests -addopts = -vv --cov=caosdb +testpaths=unittests +addopts=-x -vv --cov=caosdb diff --git a/setup.cfg b/setup.cfg index 74c5620b86c6fd5ee96abea95dab4010c9fb87a3..c46089e4d24843d7d4cc4f83dad6ec1351e4cc3f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,4 @@ +[aliases] +test=pytest [pycodestyle] ignore=E501,E121,E123,E126,E226,E24,E704,W503,W504 diff --git a/setup.py b/setup.py index f80c619591a55190d70ee105b0cff2e3d79894c1..146b93ba6a4590c7c54a4367c02e38e952c7a685 100755 --- a/setup.py +++ b/setup.py @@ -32,8 +32,9 @@ setup(name='PyCaosDB', author_email='timm.fitschen@ds.mpg.de', packages=find_packages('src'), package_dir={'': 'src'}, - install_requires=['lxml>=3.6.4', 'coverage>=4.4.2', - 'PyYaml>=3.12', 'future'], + install_requires=['lxml>=3.6.4', + 'PyYaml>=3.12', 'future', 'PySocks>=1.6.7'], extras_require={'keyring': ['keyring>=13.0.0']}, - tests_require=["pytest"], + setup_requires=["pytest-runner>=2.0,<3dev"], + tests_require=["pytest", "pytest-cov", "coverage>=4.4.2"], ) diff --git a/src/caosdb/__init__.py b/src/caosdb/__init__.py index 91b429e602245a8cea6b5c939260e754a6de5f53..69f8f24e8eab9f3528eb2133cf8a49cd0d19acdf 100644 --- a/src/caosdb/__init__.py +++ b/src/caosdb/__init__.py @@ -24,7 +24,7 @@ # Import of the connection function (which is used to connect to the DB): from os.path import expanduser, join -from os import getcwd +from os import getcwd, environ from caosdb.configuration import configure, get_config from caosdb.common import administration @@ -42,5 +42,10 @@ from caosdb.common.models import (delete, execute_query, raise_errors, get_global_acl, get_known_permissions) # Import of convenience methods: import caosdb.apiutils -configure(expanduser('~/.pycaosdb.ini')) + +# read configuration these files +if "PYCAOSDBINI" in environ: + configure(expanduser(environ["PYCAOSDBINI"])) +else: + configure(expanduser('~/.pycaosdb.ini')) configure(join(getcwd(), "pycaosdb.ini")) diff --git a/src/caosdb/common/administration.py b/src/caosdb/common/administration.py index ee57a370db58c3f1b604014a08b8de457724005f..98687e3c25286121decb499e233aa0743eff47a8 100644 --- a/src/caosdb/common/administration.py +++ b/src/caosdb/common/administration.py @@ -31,6 +31,71 @@ from caosdb.connection.connection import get_connection from caosdb.common.utils import xml2str +def set_server_property(key, value): + """set_server_property. + + Set a server property. + + Parameters + ---------- + key : str + The name of the server property. + value : str + The value of the server property. + + + Returns + ------- + None + """ + con = get_connection() + + con._form_data_request(method="POST", path="_server_properties", + params={key: value}).read() + + +def get_server_properties(): + """get_server_properties. + + Get all server properties as a dict. + + Returns + ------- + dict + The server properties. + """ + con = get_connection() + body = con._http_request(method="GET", path="_server_properties").response + xml = etree.parse(body) + props = dict() + for elem in xml.getroot(): + props[elem.tag] = elem.text + return props + + +def get_server_property(key): + """get_server_property. + + Get a server property. + + Parameters + ---------- + key : str + The name of the server property + + Returns + ------- + value : str + The string value of the server property. + + Raises + ------ + KeyError + If the server property is no defined. + """ + return get_server_properties()[key] + + def _retrieve_user(name, realm=None, **kwargs): con = get_connection() try: @@ -154,6 +219,7 @@ def _delete_role(name, **kwargs): def _set_roles(username, roles, realm=None, **kwargs): xml = etree.Element("Roles") + print(roles) for r in roles: xml.append(etree.Element("Role", name=r)) @@ -189,8 +255,9 @@ def _get_roles(username, realm=None, **kwargs): e.msg = "User does not exist." raise ret = set() - for r in etree.fromstring(body)[0]: - ret.add(r.get("name")) + for r in etree.fromstring(body).xpath('/Response/Roles')[0]: + if r.tag == "Role": + ret.add(r.get("name")) return ret @@ -251,11 +318,12 @@ class PermissionRule(): xml = etree.fromstring(body) ret = set() for c in xml: - ret.add(PermissionRule._parse_element(c)) + if c.tag in ["Grant", "Deny"]: + ret.add(PermissionRule._parse_element(c)) return ret def __str__(self): - return self._action + "(" + self._permission + ")" + \ + return str(self._action) + "(" + str(self._permission) + ")" + \ ("P" if self._priority is True else "") def __repr__(self): diff --git a/src/caosdb/common/models.py b/src/caosdb/common/models.py index e07f97134035b79b66f1bfb9c33ee3b6d33ac97d..15d460b2c2a128e9dd878f5c88bccb6220591815 100644 --- a/src/caosdb/common/models.py +++ b/src/caosdb/common/models.py @@ -777,6 +777,7 @@ class Entity(object): e = Container().retrieve(query=self.id, sync=False)[0] e.acl = ACL(self.acl.to_xml()) e.update() + return e def delete(self, raise_exception_on_error=True): return Container().append(self).delete( @@ -1822,11 +1823,13 @@ class Container(list): insertion, update, and deletion which are a applied to all entities in the container or the whole container respectively. """ + _debug = staticmethod( lambda: ( get_config().getint( "Container", - "debug") if get_config().get( + "debug") if get_config().has_section("Container") and + get_config().get( "Container", "debug") is not None else 0)) diff --git a/src/caosdb/configuration.py b/src/caosdb/configuration.py index 9f6b558abd7bd5e291eaa8bd89ca41ebe0c73bf8..4d0797844182b8465ef5f97a869e31ee4fcaf47d 100644 --- a/src/caosdb/configuration.py +++ b/src/caosdb/configuration.py @@ -21,9 +21,6 @@ # # ** end header # - -"""Created on 20.09.2016.""" - try: # python2 from ConfigParser import ConfigParser @@ -31,38 +28,23 @@ except ImportError: # python3 from configparser import ConfigParser -_DEFAULTS = {"Connection": - {"url": None, - "timeout": "200", - "username": None, - "password_method": "plain", - "debug": "0", - "cacert": None}, - "Container": - {"debug": "0"}} - def _reset_config(): global _pycaosdbconf - pycaosdbconf = None - _pycaosdbconf = ConfigParser(allow_no_value=True) - _init_defaults(_pycaosdbconf) - - -def _init_defaults(confpar): - for sec in _DEFAULTS.keys(): - confpar.add_section(sec) - for opt in _DEFAULTS[sec].keys(): - confpar.set(sec, opt, _DEFAULTS[sec][opt]) + _pycaosdbconf = ConfigParser(allow_no_value=False) def configure(inifile): + """read config from file. + + Return a list of files which have successfully been parsed. + """ global _pycaosdbconf if "_pycaosdbconf" not in globals(): _pycaosdbconf = None if _pycaosdbconf is None: _reset_config() - _pycaosdbconf.read(inifile) + return _pycaosdbconf.read(inifile) def get_config(): diff --git a/src/caosdb/connection/SocksiPy.zip b/src/caosdb/connection/SocksiPy.zip new file mode 100644 index 0000000000000000000000000000000000000000..e81f1f9393c766a3acd41b44245f9e17f090cbe5 Binary files /dev/null and b/src/caosdb/connection/SocksiPy.zip differ diff --git a/src/caosdb/connection/connection.py b/src/caosdb/connection/connection.py index 2c6a884cf85843c50fcc3920b9f387fe334be0d3..e6638d2fd8d55874c557f2cae7c0c58c1995f620 100644 --- a/src/caosdb/connection/connection.py +++ b/src/caosdb/connection/connection.py @@ -31,7 +31,7 @@ try: except ImportError: from urllib import quote from urlparse import urlparse - +from errno import EPIPE as BrokenPipe from socket import error as SocketError import ssl import logging @@ -73,6 +73,14 @@ class _WrappedHTTPResponse(CaosDBHTTPResponse): class _DefaultCaosDBServerConnection(CaosDBServerConnection): + """_DefaultCaosDBServerConnection. + + Methods + ------- + configure + request + """ + def __init__(self): self._useragent = ("PyCaosDB - " "DefaultCaosDBServerConnection") @@ -80,6 +88,28 @@ class _DefaultCaosDBServerConnection(CaosDBServerConnection): self._base_path = None def request(self, method, path, headers=None, body=None, **kwargs): + """request. + + Send a HTTP request to the server. + + Parameters + ---------- + method : str + The HTTP request method. + path : str + An URI path segment (without the 'scheme://host:port/' parts), + including query and frament segments. + headers : dict of str -> str, optional + HTTP request headers. (Defautl: None) + body : str or bytes or readable, opional + The body of the HTTP request. Bytes should be a utf-8 encoded + string. + **kwargs : + Any keyword arguments will be ignored. + + Returns + ------- + """ if headers is None: headers = {} try: @@ -87,7 +117,7 @@ class _DefaultCaosDBServerConnection(CaosDBServerConnection): host=self.setup_fields["host"], timeout=self.setup_fields["timeout"], context=self.setup_fields["context"], - ) + socket_proxy=self.setup_fields["socket_proxy"]) self._http_con.request(method=method, url=self._base_path + path, headers=headers, body=body) except SocketError as socket_err: @@ -97,7 +127,32 @@ class _DefaultCaosDBServerConnection(CaosDBServerConnection): return _WrappedHTTPResponse(self._http_con.getresponse()) def configure(self, **config): - context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + """configure. + + Configure the http connection. + + Parameters + ---------- + cacert : str + Path to the CA certificate which will be used to identify the + server. + url : str + The url of the CaosDB Server, e.g. + `https://example.com:443/rootpath`, including a possible root path. + **config : + Any further keyword arguments are being ignored. + + Raises + ------ + ConnectionException + If no url has been specified, or if the CA certificate cannot be + loaded. + """ + if "ssl_version" in config and config["cacert"] is not None: + ssl_version = getattr(ssl, config["ssl_version"]) + else: + ssl_version = ssl.PROTOCOL_TLSv1 + context = ssl.SSLContext(ssl_version) context.verify_mode = ssl.CERT_REQUIRED if config.get("ssl_insecure"): print("Relaxed SSL mode.") @@ -157,12 +212,39 @@ def _make_conf(*conf): _DEFAULT_CONF = { - "password_method": "plain", - "implementation": _DefaultCaosDBServerConnection, + "password_method": "plain", + "implementation": _DefaultCaosDBServerConnection, + "timeout": 210 } def _get_authenticator(**config): + """_get_authenticator. + + Import and configure the password_method. + + Parameters + ---------- + password_method : str + The simple name of a submodule of caosdb.connection.authentication. + Currently, there are three valid values for this parameter: 'plain', + 'pass', and 'keyring'. + **config : + Any other keyword arguments are passed the configre method of the + password_method. + + Returns + ------- + AbstractAuthenticator + An object which implements the password_method and which already + configured. + + Raises + ------ + ConnectionException + If the password_method string cannot be resolved to a CaosAuthenticator + class. + """ auth_module = ("caosdb.connection.authentication." + config["password_method"]) _LOGGER.debug("import auth_module %s", auth_module) @@ -174,37 +256,37 @@ def _get_authenticator(**config): return auth_provider except ImportError: - raise RuntimeError("Password method \"{}\" not implemented. " - "Valid methods: plain, pass, or keyring." - .format(config["password_method"])) + raise ConfigurationException("Password method \"{}\" not implemented. " + "Valid methods: plain, pass, or keyring." + .format(config["password_method"])) def configure_connection(**kwargs): - """Configures the caosdb connection. - - All paramters are optional. The default configuration is taken from the - `Connection` section of the configuration which can be retrieved by - `get_config` - - Returns the Connection object. - - Typical arguments are: - url The URL of the CaosDB server. E.g. https://caosdb.org:433/playground - username The username - password Two options: - 1) The plain text password - 2) A function which returns the password. - timeout A connection timeout in seconds. - password_method The method to use for obtaining the password. Can be for - example: - - "plain" Need username and password arguments. - - "pass" Uses the `pass` password manager. - - "keyring" Uses the `keyring` library. - - "input" Asks for the password. - implementation A class which implements CaosDBServerConnection. (Default: + """Configures the caosdb connection and return the Connection object. + + The effective configuration is governed by the default values (see + 'Parameters'), the global configuration (see `caosdb.get_config()`) and the + parameters which are passed to this function, with ascending priority. + + The parameters which are listed here, are possibly not sufficient for a + working configuration of the connection. Check the `configure` method of + the implementation class and the password_method for more details. + + Parameters + ---------- + implementation : CaosDBServerConnection + The class which implements the connection. (Default: _DefaultCaosDBServerConnection) - ssl_insecure Whether SSL certificate warnings should be ignored. Only use - this fordevelopment purposes! (Default: False) + password_method : str + The name of a submodule of caosdb.connection.authentication which + implements the AbstractAuthenticator interface. (Default: 'plain') + timeout : int + A connection timeout in seconds. (Default: 210) + + Returns + ------- + _Connection + The singleton instance of the _Connection class. """ global_conf = (dict(get_config().items("Connection")) if get_config().has_section("Connection") else dict()) @@ -263,7 +345,8 @@ def _handle_response_status(http_response): class _Connection(object): # pylint: disable=useless-object-inheritance """This connection class provides the interface to the database connection - allowing for retrieval, insertion, update, etc. of entries. + allowing for retrieval, insertion, update, etc. of entities, files, users, + roles and much more. It wrapps an instance of CaosDBServerConnection which actually does the work (how, depends on the instance). @@ -393,9 +476,13 @@ class _Connection(object): # pylint: disable=useless-object-inheritance return self._retry_http_request(method=method, path=path, headers=headers, body=body, **kwargs) - except ConnectionException as conex: - print(conex) - return None + except SocketError as e: + if e.errno != BrokenPipe: + raise + return self._retry_http_request(method=method, path=path, + headers=headers, body=body, + reconnect=False, + **kwargs) except LoginFailedException: if kwargs.get("reconnect", True) is True: self._login() diff --git a/src/caosdb/connection/mockup.py b/src/caosdb/connection/mockup.py index abde5d03da4d553b523c3ecc63ff7552696cc865..6d1bb1f389823c2824bbaa3f3b1b41462e284692 100644 --- a/src/caosdb/connection/mockup.py +++ b/src/caosdb/connection/mockup.py @@ -47,7 +47,7 @@ class MockUpResponse(CaosDBHTTPResponse): def __init__(self, status, headers, body): self._status = status self.headers = headers - self.body = StringIO(body) + self.response = StringIO(body) @property def status(self): @@ -56,7 +56,7 @@ class MockUpResponse(CaosDBHTTPResponse): def read(self, size=-1): """Return the body of the response.""" - return self.body.read(size) + return self.response.read(size) def getheader(self, name, default=None): """Get the contents of the header `name`, or `default` if there is no diff --git a/src/caosdb/connection/streaminghttp.py b/src/caosdb/connection/streaminghttp.py index fa97a964149ff1630bb3bbb3fd1cccf8029f8d6b..01774301b9bdb55bdbf6b56695042aaf354dba97 100644 --- a/src/caosdb/connection/streaminghttp.py +++ b/src/caosdb/connection/streaminghttp.py @@ -51,7 +51,8 @@ since there is no way to determine in advance the total size that will be yielded, and there is no way to reset an interator. """ -from __future__ import unicode_literals, print_function +from __future__ import unicode_literals, print_function, absolute_import +import socks import socket try: # python3 @@ -69,6 +70,14 @@ class StreamingHTTPSConnection(client.HTTPSConnection, object): that overrides the `send()` method to support iterable body objects.""" # pylint: disable=unused-argument, arguments-differ + def __init__(self, socket_proxy=None, **kwargs): + if socket_proxy is not None: + host, port = socket_proxy.split(":") + socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, host, + int(port)) + socket.socket = socks.socksocket + super(StreamingHTTPSConnection, self).__init__(**kwargs) + def _send_output(self, body, **kwargs): """Send the currently buffered request and clear the buffer. @@ -87,7 +96,7 @@ class StreamingHTTPSConnection(client.HTTPSConnection, object): if body is not None: self.send(body) - # pylint: disable=too-complex, too-many-branches + # pylint: disable=too-many-branches def send(self, value): """Send ``value`` to the server. diff --git a/src/caosdb/exceptions.py b/src/caosdb/exceptions.py index a5d6e99662c38e6f74e2e974a68ce8152997e271..45c4570b4227031a35a486b86ea65535ff105852 100644 --- a/src/caosdb/exceptions.py +++ b/src/caosdb/exceptions.py @@ -70,9 +70,10 @@ class ClientErrorException(CaosDBException): class ServerErrorException(CaosDBException): def __init__(self, body): xml = etree.fromstring(body) - msg = xml[0].get("description") - if xml[0].text is not None: - msg = msg + "\n\n" + xml[0].text + error = xml.xpath('/Response/Error')[0] + msg = error.get("description") + if error.text is not None: + msg = msg + "\n\n" + error.text CaosDBException.__init__(self, msg) diff --git a/src/caosdb/utils/caosdb_admin.py b/src/caosdb/utils/caosdb_admin.py index f1b41f60371608b3f19e6dcda85475d7df0361d5..b3145390fc9de36da57fe3dd88996199f50a3ce7 100755 --- a/src/caosdb/utils/caosdb_admin.py +++ b/src/caosdb/utils/caosdb_admin.py @@ -35,9 +35,9 @@ from argparse import ArgumentParser from argparse import RawDescriptionHelpFormatter __all__ = [] -__version__ = 0.2 +__version__ = 0.3 __date__ = '2016-09-19' -__updated__ = '2017-08-30' +__updated__ = '2018-12-11' def do_update_role(args): @@ -98,7 +98,7 @@ def do_insert(args): reconnect=True, query_dict=fdict, body=xml) - db.Container._response_to_entities(ret) + print(db.Container._response_to_entities(ret)) def _promt_for_pw(): @@ -217,6 +217,25 @@ def do_deny_role_permissions(args): admin._set_permissions(role=args.role_name, permission_rules=perms) +def do_retrieve_entity_acl(args): + entities = db.execute_query(q=args.query, flags={"ACL": None}) + for entity in entities: + print(entity.id) + print(entity.acl) + + +def do_action_entity_permissions(args): + entities = db.execute_query(q=args.query, flags={"ACL": None}) + for entity in entities: + for p in args.permissions: + getattr(entity, args.action)(role=args.role, priority=args.priority, + permission=p) + entities.update(flags={"ACL": None}) + for entity in entities: + print(entity.id) + print(entity.acl) + + def main(argv=None): """Command line options.""" @@ -230,46 +249,17 @@ def main(argv=None): program_build_date = str(__updated__) program_version_message = '%%(prog)s %s (%s)' % ( program_version, program_build_date) - program_shortdesc = __import__('__main__').__doc__.split("\n")[1] + program_shortdesc = __import__('__main__').__doc__ program_license = '''%s - Created by timm fitschen on %s. - Copyright 2016 BMPG. All rights reserved. - - Distributed on an "AS IS" basis without warranties - or conditions of any kind, either express or implied. - USAGE -''' % (program_shortdesc, str(__date__)) +''' % (program_shortdesc) # Setup argument parser parser = ArgumentParser(description=program_license, formatter_class=RawDescriptionHelpFormatter) - parser.add_argument( - "-v", - "--verbose", - dest="verbose", - action="count", - help="Set verbosity level [default: %(default)s]", - default=0) parser.add_argument('-V', '--version', action='version', version=program_version_message) - parser.add_argument( - '-t', - '--timeout', - dest="con_timeout", - help="Timeout in seconds for the database requests. [default: %(default)s]", - metavar="TIMEOUT", - default=200) - parser.add_argument( - '-u', - '--user', - dest="con_user", - help="The user name you want to login with and do the administration stuff with. It will be prompted for a password.", - metavar="USER") - parser.add_argument('-c', '--connection_uri', dest="con_uri", - help="The URI of the caosdb server.", metavar="URI") - subparsers = parser.add_subparsers( title="commands", metavar="COMMAND", @@ -555,20 +545,39 @@ USAGE metavar="ROLENAME", help="The name of the existing role.") + # entity acl + retrieve_entity_acl_parser = subparsers.add_parser( + "retrieve_entity_acl", help="Retrieve an entity ACL.") + retrieve_entity_acl_parser.set_defaults(call=do_retrieve_entity_acl) + retrieve_entity_acl_parser.add_argument(dest="query", metavar="QUERY", + help="A FIND query.") + + for action in ["grant", "deny", "revoke_denial", "revoke_grant"]: + action_entity_permissions_parser = subparsers.add_parser( + "{}_entity_permissions".format(action), + help="{} entity permissions to a role.".format(action)) + action_entity_permissions_parser.set_defaults( + call=do_action_entity_permissions, action=action) + action_entity_permissions_parser.add_argument(dest="query", metavar="QUERY", + help="A FIND query.") + action_entity_permissions_parser.add_argument(dest="role", metavar="ROLE", + help="The name of an exising role.") + action_entity_permissions_parser.add_argument( + dest="permissions", + metavar="PERMISSION", + help="A list of permissions", + nargs='+') + action_entity_permissions_parser.add_argument( + '--priority', + dest="priority", + action="store_true", + default=False, + help="This flag enables priority permission rules.") + # Process arguments args = parser.parse_args() - VERBOSITY = args.verbose - timeout = int(args.con_timeout) - - password = None - if args.con_user is not None: - password = getpass.getpass(prompt="login password: ") - - db.configure_connection(url=args.con_uri, username=args.con_user, - password=password, timeout=timeout) - db.get_connection()._debug = lambda: VERBOSITY - db.Container._debug = staticmethod(lambda: VERBOSITY) + db.configure_connection()._login() return args.call(args) diff --git a/unittests/test_add_property.py b/unittests/test_add_property.py index 267e7f00011d55dba4ca5876df383f26dbed1e4a..bd68f31b89c439c2bd333586b65d9f012b09d7e3 100644 --- a/unittests/test_add_property.py +++ b/unittests/test_add_property.py @@ -21,12 +21,7 @@ # # ** end header # -"""Created on 19.06.2017. - -@author: tf -""" import caosdb as db -# @UnresolvedImport from nose.tools import assert_is, assert_is_none, assert_equals, assert_is_not_none, assert_raises diff --git a/unittests/test_administraction.py b/unittests/test_administraction.py new file mode 100644 index 0000000000000000000000000000000000000000..dc05be2ac7c19ae066b9c8829677626796cea5fa --- /dev/null +++ b/unittests/test_administraction.py @@ -0,0 +1,69 @@ +# -*- encoding: utf-8 -*- +# +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2018 Research Group Biomedical Physics, +# Max-Planck-Institute for Dynamics and Self-Organization Göttingen +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# ** end header +# +"""Tests for the administration class.""" +# pylint: disable=missing-docstring +from __future__ import unicode_literals +from pytest import raises +from caosdb import administration, configure_connection, get_connection +from caosdb.connection.mockup import MockUpServerConnection, MockUpResponse + + +def setup(): + configure_connection(url="unittests", username="testuser", + password="testpassword", timeout=200, + implementation=MockUpServerConnection) + + +def test_get_server_properties_success(): + properties = "<Properties><TEST_PROP>TEST_VAL</TEST_PROP></Properties>" + get_connection()._delegate_connection.resources.append( + lambda **kwargs: MockUpResponse(200, {}, properties)) + props = administration.get_server_properties() + assert isinstance(props, dict) + + +def test_get_server_property_success(): + properties = "<Properties><TEST_PROP>TEST_VAL</TEST_PROP></Properties>" + get_connection()._delegate_connection.resources.append( + lambda **kwargs: MockUpResponse(200, {}, properties)) + assert "TEST_VAL" == administration.get_server_property("TEST_PROP") + + +def test_get_server_property_key_error(): + properties = "<Properties><TEST_PROP>TEST_VAL</TEST_PROP></Properties>" + get_connection()._delegate_connection.resources.append( + lambda **kwargs: MockUpResponse(200, {}, properties)) + with raises(KeyError) as e: + assert administration.get_server_property("BLA") + + +def test_set_server_property(): + def check_form(**kwargs): + assert kwargs["path"] == "_server_properties" + assert kwargs["method"] == "POST" + assert kwargs["body"] == "TEST_PROP=TEST_VAL".encode() + assert kwargs["headers"]["Content-Type"] == "application/x-www-form-urlencoded" + return MockUpResponse(200, {}, "<Properties><TEST_PROP>TEST_VAL</TEST_PROP></Properties>") + get_connection()._delegate_connection.resources.append(check_form) + administration.set_server_property("TEST_PROP", "TEST_VAL") diff --git a/unittests/test_configuration.py b/unittests/test_configuration.py new file mode 100644 index 0000000000000000000000000000000000000000..76445b6f262120d6a29c73527a9bf042f85f8a05 --- /dev/null +++ b/unittests/test_configuration.py @@ -0,0 +1,40 @@ +# -*- encoding: utf-8 -*- +# +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2018 Research Group Biomedical Physics, +# Max-Planck-Institute for Dynamics and Self-Organization Göttingen +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# ** end header +# + +import caosdb as db +from pytest import raises + + +def test_config_ini_via_envvar(): + from os import environ + from os.path import expanduser + + with raises(KeyError): + environ["PYCAOSDBINI"] + + environ["PYCAOSDBINI"] = "bla bla" + assert environ["PYCAOSDBINI"] == "bla bla" + assert db.configuration.configure(environ["PYCAOSDBINI"]) == [] + environ["PYCAOSDBINI"] = "~/.pycaosdb.ini" + assert db.configuration.configure(expanduser(environ["PYCAOSDBINI"])) == [expanduser("~/.pycaosdb.ini")] diff --git a/unittests/test_connection.py b/unittests/test_connection.py index 06838d1c15b70787be159b4ad2d7f1bb70abe6c8..26e3388dfa4dd7d360b8a373d631f5a362990f66 100644 --- a/unittests/test_connection.py +++ b/unittests/test_connection.py @@ -92,6 +92,8 @@ def test_make_uri_path(): def test_configure_connection(): + if not get_config().has_section("Connection"): + get_config().add_section("Connection") get_config().set("Connection", "url", "https://host.de") get_config().set("Connection", "username", "test_username") get_config().set("Connection", "password", "test_password")