Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • caosdb/src/caosdb-pylib
1 result
Show changes
Commits on Source (19)
......@@ -61,16 +61,6 @@ mypy:
- make mypy
# run unit tests
unittest_py3.8:
tags: [ docker ]
stage: test
needs: [ ]
image: python:3.8
script: &python_test_script
# Python docker has problems with tox and pip so use plain pytest here
- touch ~/.pylinkahead.ini
- pip install .[test]
- python -m pytest unittests
# This needs to be changed once Python 3.9 isn't the standard Python in Debian
# anymore.
......@@ -90,7 +80,11 @@ unittest_py3.10:
stage: test
needs: [ ]
image: python:3.10
script: *python_test_script
script: &python_test_script
# Python docker has problems with tox and pip so use plain pytest here
- touch ~/.pylinkahead.ini
- pip install .[test]
- python -m pytest unittests
unittest_py3.11:
tags: [ docker ]
......@@ -158,7 +152,7 @@ build-testenv:
pages_prepare: &pages_prepare
tags: [ cached-dind ]
stage: deploy
needs: [ code_style, pylint, unittest_py3.8, unittest_py3.9, unittest_py3.10 ]
needs: [ code_style, pylint, unittest_py3.9, unittest_py3.10 ]
only:
refs:
- /^release-.*$/i
......
......@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* New setup extra `test` which installs the dependencies for testing.
* The Container class has a new member function `filter` which is based o `_filter_entity_list`.
* The `Entity` properties `_cuid` and `_flags` are now available for read-only access
as `cuid` and `flags`, respectively.
### Changed ###
......@@ -18,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed ###
* Support for Python 3.8
### Fixed ###
* [#73](https://gitlab.com/linkahead/linkahead-pylib/-/issues/73)
......@@ -33,6 +37,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#87](https://gitlab.com/linkahead/linkahead-pylib/-/issues/87)
`XMLSyntaxError` messages when parsing (incomplete) responses in
case of certain connection timeouts.
The diff returned by compare_entities now uses id instead of name as key if either property does not have a name
* [#127](https://gitlab.com/linkahead/linkahead-pylib/-/issues/127)
pylinkahead.ini now supports None and tuples as values for the `timeout` keyword
### Security ###
......
* caosdb-server >= 0.12.0
* Python >= 3.8
* Python >= 3.9
* pip >= 20.0.2
Any other dependencies are defined in the setup.py and are being installed via pip
......@@ -4,7 +4,7 @@
### How to install ###
First ensure that python with at least version 3.8 is installed. Should this not be
First ensure that python with at least version 3.9 is installed. Should this not be
the case, you can use the [Installing python](#installing-python-) guide for your OS.
#### Generic installation ####
......@@ -39,7 +39,7 @@ entries `install_requires` and `extras_require`.
#### Linux ####
Make sure that Python (at least version 3.8) and pip is installed, using your system tools and
Make sure that Python (at least version 3.9) and pip is installed, using your system tools and
documentation.
Then open a terminal and continue in the [Generic installation](#generic-installation) section.
......
......@@ -179,7 +179,7 @@ def setup_package():
"Topic :: Scientific/Engineering :: Information Analysis",
],
packages=find_packages('src'),
python_requires='>=3.8',
python_requires='>=3.9',
package_dir={'': 'src'},
install_requires=['lxml>=4.6.3',
"requests[socks]>=2.26",
......
......@@ -349,6 +349,15 @@ class Entity:
def pickup(self, new_pickup):
self.__pickup = new_pickup
@property # getter for _cuid
def cuid(self):
# Set if None?
return self._cuid
@property # getter for _flags
def flags(self):
return self._flags.copy() # for dict[str, str] shallow copy is enough
def grant(
self,
realm: Optional[str] = None,
......@@ -1342,7 +1351,8 @@ class Entity:
else:
dt_str = xml2str(self.datatype.to_xml(visited_entities=visited_entities.copy()))
# Todo: Use for pretty-printing with calls from _repr_ only?
# dt_str = dt_str.replace('<', 'ᐸ').replace('>', 'ᐳ').replace(' ', '⠀').replace('"', '\'').replace('\n', '')
# dt_str = dt_str.replace('<', 'ᐸ').replace('>', 'ᐳ').replace(' ', '⠀').replace(
# '"', '\'').replace('\n', '')
xml.set("datatype", dt_str)
else:
xml.set("datatype", str(self.datatype))
......@@ -3758,6 +3768,7 @@ class Container(list):
"""
return _filter_entity_list(self, pid=pid, name=name, entity=entity,
conjunction=conjunction)
@staticmethod
def _find_dependencies_in_container(container: Container):
"""Find elements in a container that are a dependency of another element of the same.
......
......@@ -30,6 +30,15 @@ import yaml
try:
optional_jsonschema_validate: Optional[Callable] = None
from jsonschema import validate as optional_jsonschema_validate
# Adapted from https://github.com/python-jsonschema/jsonschema/issues/148
# Defines Validator to allow parsing of all iterables as array in jsonschema
# CustomValidator can be removed if/once jsonschema allows tuples for arrays
from collections.abc import Iterable
from jsonschema import validators
default = validators.validator_for(True) # Returns latest supported draft
t_c = (default.TYPE_CHECKER.redefine('array', lambda x, y: isinstance(y, Iterable)))
CustomValidator = validators.extend(default, type_checker=t_c)
except ImportError:
pass
......@@ -72,14 +81,40 @@ def get_config() -> ConfigParser:
return _pycaosdbconf
def config_to_yaml(config: ConfigParser) -> dict[str, dict[str, Union[int, str, bool]]]:
valobj: dict[str, dict[str, Union[int, str, bool]]] = {}
def config_to_yaml(config: ConfigParser) -> dict[str, dict[str, Union[int, str, bool, tuple, None]]]:
"""
Generates and returns a dict with all config options and their values
defined in the config.
The values of the options 'debug', 'timeout', and 'ssl_insecure' are
parsed, all other values are saved as string.
Parameters
----------
config : ConfigParser
The config to be converted to a dict
Returns
-------
valobj : dict
A dict with config options and their values as key value pairs
"""
valobj: dict[str, dict[str, Union[int, str, bool, tuple, None]]] = {}
for s in config.sections():
valobj[s] = {}
for key, value in config[s].items():
# TODO: Can the type be inferred from the config object?
if key in ["timeout", "debug"]:
if key in ["debug"]:
valobj[s][key] = int(value)
elif key in ["timeout"]:
value = "".join(value.split()) # Remove whitespace
if str(value).lower() in ["none", "null"]:
valobj[s][key] = None
elif value.startswith('(') and value.endswith(')'):
content = [None if str(s).lower() in ["none", "null"] else int(s)
for s in value[1:-1].split(',')]
valobj[s][key] = tuple(content)
else:
valobj[s][key] = int(value)
elif key in ["ssl_insecure"]:
valobj[s][key] = bool(value)
else:
......@@ -88,11 +123,12 @@ def config_to_yaml(config: ConfigParser) -> dict[str, dict[str, Union[int, str,
return valobj
def validate_yaml_schema(valobj: dict[str, dict[str, Union[int, str, bool]]]):
def validate_yaml_schema(valobj: dict[str, dict[str, Union[int, str, bool, tuple, None]]]):
if optional_jsonschema_validate:
with open(os.path.join(os.path.dirname(__file__), "schema-pycaosdb-ini.yml")) as f:
schema = yaml.load(f, Loader=yaml.SafeLoader)
optional_jsonschema_validate(instance=valobj, schema=schema["schema-pycaosdb-ini"])
optional_jsonschema_validate(instance=valobj, schema=schema["schema-pycaosdb-ini"],
cls=CustomValidator)
else:
warnings.warn("""
Warning: The validation could not be performed because `jsonschema` is not installed.
......
......@@ -39,7 +39,7 @@ from requests.adapters import HTTPAdapter
from requests.exceptions import ConnectionError as HTTPConnectionError
from urllib3.poolmanager import PoolManager
from ..configuration import get_config
from ..configuration import get_config, config_to_yaml
from ..exceptions import (ConfigurationError, HTTPClientError,
HTTPForbiddenError, HTTPResourceNotFoundError,
HTTPServerError, HTTPURITooLongError,
......@@ -422,8 +422,10 @@ def configure_connection(**kwargs):
- "keyring" Uses the `keyring` library.
- "auth_token" Uses only a given auth_token.
timeout : int
timeout : int, tuple, or None
A connection timeout in seconds. (Default: 210)
If a tuple is given, they are used as connect and read timeouts
respectively, timeout None disables the timeout.
ssl_insecure : bool
Whether SSL certificate warnings should be ignored. Only use this for
......@@ -465,21 +467,29 @@ def configure_connection(**kwargs):
global_conf = {}
conf = get_config()
# Convert config to dict, with preserving types
int_opts = ["timeout"]
int_opts = []
bool_opts = ["ssl_insecure"]
other_opts = ["timeout"]
if conf.has_section("Connection"):
global_conf = dict(conf.items("Connection"))
# Integer options
# Integer options
for opt in int_opts:
if opt in global_conf:
global_conf[opt] = conf.getint("Connection", opt)
# Boolean options
# Boolean options
for opt in bool_opts:
if opt in global_conf:
global_conf[opt] = conf.getboolean("Connection", opt)
# Other options, defer parsing to configuration.config_to_yaml:
connection_config = config_to_yaml(conf)["Connection"]
for opt in other_opts:
if opt in global_conf:
global_conf[opt] = connection_config[opt]
local_conf = _make_conf(_DEFAULT_CONF, global_conf, kwargs)
connection = _Connection.get_instance()
......
......@@ -67,7 +67,13 @@ schema-pycaosdb-ini:
description: This option is used internally and for testing. Do not override.
examples: [_DefaultCaosDBServerConnection]
timeout:
type: integer
oneOf:
- type: [integer, "null"]
- type: array
items:
type: [integer, "null"]
minItems: 2
maxItems: 2
allOf:
- if:
properties:
......
[Connection]
url=https://localhost:10443/
password_method = unauthenticated
timeout = None
[Connection]
url=https://localhost:10443/
password_method = unauthenticated
timeout = (1,20)
......@@ -24,6 +24,7 @@
from os import environ, getcwd, remove
from os.path import expanduser, isfile, join
from pathlib import Path
import linkahead as db
import pytest
......@@ -66,3 +67,18 @@ def test_config_ini_via_envvar(temp_ini_files):
assert expanduser("~/.pylinkahead.ini") in db.configuration._read_config_files()
# test configuration file in cwd
assert join(getcwd(), "pylinkahead.ini") in db.configuration._read_config_files()
def test_config_timeout_option():
expected_results = [None, (1, 20)]
# Iterate through timeout test configs
test_configs = Path(__file__).parent/'test_configs'
for test_config in test_configs.rglob('pylinkahead-timeout*.ini'):
# Test that test configs can be parsed
db.configure(str(test_config))
dct = db.configuration.config_to_yaml(db.get_config())
# Test that resulting dict has correct content for timeout
assert 'Connection' in dct
assert 'timeout' in dct['Connection']
assert dct['Connection']['timeout'] in expected_results
expected_results.remove(dct['Connection']['timeout'])
......@@ -70,7 +70,8 @@ def test_get_property_values():
)
assert len(table) == 2
house_row = table[0]
assert house_row == (house.name, 40.2, "ft", window.id, None, None, None, 20.5, 20.5, "m", owner.name)
assert house_row == (house.name, 40.2, "ft", window.id, None, None, None, 20.5, 20.5, "m",
owner.name)
owner_row = table[1]
assert owner_row == (owner.name, None, None, None, None, None, None, None, None, None, None)
......@@ -200,11 +201,12 @@ def test_container_slicing():
with pytest.raises(TypeError):
cont[[0, 2, 3]]
def test_container_filter():
# this is a very rudimentary test since filter is based on _filter_entity_list which is tested
# separately
cont = db.Container()
cont.extend([db.Record(name=f"TestRec{ii+1}") for ii in range(5)])
recs = cont.filter(name="TestRec2")
assert len(recs)==1
recs[0].name =="TestRec2"
assert len(recs) == 1
recs[0].name == "TestRec2"