diff --git a/.editorconfig b/.editorconfig index adc8c9f724a1bf935bb232bf0c9a3b768569ff15..2a4c182c786edccff66d8f14ee57948b3d4bc382 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,4 +15,4 @@ trim_trailing_whitespace = true [*.py] indent_size = 4 -line_length = 88 +line_length = 100 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6042a3e5ab227c1c5dbc7afe3cc7f3afc973dc71..6b02abe4685d98620a23fdacddf993f71209cb60 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,13 +10,19 @@ repos: exclude: ^tests/fixtures - id: check-yaml repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.5.0 +- hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format + repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.9 - hooks: - id: commitizen stages: - commit-msg repo: https://github.com/commitizen-tools/commitizen - rev: v2.20.0 + rev: v3.27.0 - hooks: - id: darglint repo: https://github.com/terrencepreilly/darglint diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index ac4e47a831d131ddd7344148f17497921f4471e3..0000000000000000000000000000000000000000 --- a/.pylintrc +++ /dev/null @@ -1,564 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Georg-August-Universität Göttingen -# -# SPDX-License-Identifier: CC0-1.0 - -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. -extension-pkg-allow-list= - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. (This is an alternative name to extension-pkg-allow-list -# for backward compatibility.) -extension-pkg-whitelist= - -# Return non-zero exit code if any of these messages/categories are detected, -# even if score is above --fail-under value. Syntax same as enable. Messages -# specified are enabled, while categories only check already-enabled messages. -fail-on= - -# Specify a score threshold to be exceeded before program exits with error. -fail-under=10.0 - -# Files or directories to be skipped. They should be base names, not paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the ignore-list. The -# regex matches against paths. -ignore-paths= - -# Files or directories matching the regex patterns are skipped. The regex -# matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use. -jobs=1 - -# Control the amount of potential inferred values when inferring a single -# object. This can help the performance when dealing with large functions or -# complex, nested conditions. -limit-inference-results=100 - -# List of plugins (as comma separated values of python module names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Min Python version to use for version dependend checks. Will default to the -# version used to run pylint. -py-version=3.8 - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=raw-checker-failed, - bad-inline-option, - locally-disabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[REPORTS] - -# Python expression which should return a score less than or equal to 10. You -# have access to the variables 'error', 'warning', 'refactor', and 'convention' -# which contain the number of messages in each category, as well as 'statement' -# which is the total number of statements analyzed. This score is used by the -# global evaluation report (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit,argparse.parse_error - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of names allowed to shadow builtins -allowed-redefined-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io - - -[BASIC] - -# Naming style matching correct argument names. -argument-naming-style=snake_case - -# Regular expression matching correct argument names. Overrides argument- -# naming-style. -#argument-rgx= - -# Naming style matching correct attribute names. -attr-naming-style=snake_case - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style. -#attr-rgx= - -# Bad variable names which should always be refused, separated by a comma. -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Bad variable names regexes, separated by a comma. If names match any regex, -# they will always be refused -bad-names-rgxs= - -# Naming style matching correct class attribute names. -class-attribute-naming-style=any - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. -#class-attribute-rgx= - -# Naming style matching correct class constant names. -class-const-naming-style=UPPER_CASE - -# Regular expression matching correct class constant names. Overrides class- -# const-naming-style. -#class-const-rgx= - -# Naming style matching correct class names. -class-naming-style=PascalCase - -# Regular expression matching correct class names. Overrides class-naming- -# style. -#class-rgx= - -# Naming style matching correct constant names. -const-naming-style=UPPER_CASE - -# Regular expression matching correct constant names. Overrides const-naming- -# style. -#const-rgx= - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names. -function-naming-style=snake_case - -# Regular expression matching correct function names. Overrides function- -# naming-style. -#function-rgx= - -# Good variable names which should always be accepted, separated by a comma. -good-names=i, - j, - k, - ex, - Run, - _ - -# Good variable names regexes, separated by a comma. If names match any regex, -# they will always be accepted -good-names-rgxs= - -# Include a hint for the correct naming format with invalid-name. -include-naming-hint=no - -# Naming style matching correct inline iteration names. -inlinevar-naming-style=any - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. -#inlinevar-rgx= - -# Naming style matching correct method names. -method-naming-style=snake_case - -# Regular expression matching correct method names. Overrides method-naming- -# style. -#method-rgx= - -# Naming style matching correct module names. -module-naming-style=snake_case - -# Regular expression matching correct module names. Overrides module-naming- -# style. -#module-rgx= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -# These decorators are taken in consideration only for invalid-name. -property-classes=abc.abstractproperty - -# Naming style matching correct variable names. -variable-naming-style=snake_case - -# Regular expression matching correct variable names. Overrides variable- -# naming-style. -#variable-rgx= - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )?<?https?://\S+>?$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module. -max-module-lines=1000 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[SIMILARITIES] - -# Comments are removed from the similarity computation -ignore-comments=yes - -# Docstrings are removed from the similarity computation -ignore-docstrings=yes - -# Imports are removed from the similarity computation -ignore-imports=no - -# Signatures are removed from the similarity computation -ignore-signatures=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it work, -# install the 'python-enchant' package. -spelling-dict= - -# List of comma separated words that should be considered directives if they -# appear and the beginning of a comment and should not be checked. -spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains the private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to the private dictionary (see the -# --spelling-private-dict-file option) instead of raising a message. -spelling-store-unknown-words=no - - -[STRING] - -# This flag controls whether inconsistent-quotes generates a warning when the -# character used as a quote delimiter is used inconsistently within a module. -check-quote-consistency=yes - -# This flag controls whether the implicit-str-concat should generate a warning -# on implicit string concatenation in sequences defined over several lines. -check-str-concat-over-line-jumps=yes - - -[LOGGING] - -# The type of string formatting that logging methods do. `old` means using % -# formatting, `new` is for `{}` formatting. -logging-format-style=old - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis). It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - -# List of decorators that change the signature of a decorated function. -signature-mutators= - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - -# Regular expression of note tags to take in consideration. -#notes-rgx= - - -[IMPORTS] - -# List of modules that can be imported at any level, not just the top level -# one. -allow-any-import-level= - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules= - -# Output a graph (.gv or any supported image format) of external dependencies -# to the given file (report RP0402 must not be disabled). -ext-import-graph= - -# Output a graph (.gv or any supported image format) of all (i.e. internal and -# external) dependencies to the given file (report RP0402 must not be -# disabled). -import-graph= - -# Output a graph (.gv or any supported image format) of internal dependencies -# to the given file (report RP0402 must not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - -# Couples of modules and preferred modules, separated by a comma. -preferred-modules= - - -[CLASSES] - -# Warn about protected attribute access inside special methods -check-protected-access-in-special-methods=no - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp, - __post_init__ - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls - - -[DESIGN] - -# List of qualified class names to ignore when counting class parents (see -# R0901) -ignored-parents= - -# Maximum number of arguments for function / method. -max-args=5 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Maximum number of boolean expressions in an if statement (see R0916). -max-bool-expr=5 - -# Maximum number of branch for function / method body. -max-branches=12 - -# Maximum number of locals for function / method body. -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body. -max-returns=6 - -# Maximum number of statements in function / method body. -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "BaseException, Exception". -overgeneral-exceptions=BaseException, - Exception diff --git a/.vscode/settings.json b/.vscode/settings.json index aeb1ebe51c04180e9616078d90c2fe6565cc7b9f..5f262aaf62aea3344fba287a7330bf195e6d3b11 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,5 @@ { - "python.formatting.provider": "autopep8", "python.defaultInterpreterPath": "venv/bin/python", - "pylint.enabled": true, - "pylint.path": ["venv/bin/pylint"], "ruff.enable": true, "python.testing.pytestArgs": [ "tests", diff --git a/conftest.py b/conftest.py index 2b2e879267f9ee28ebb3bc13b7ee7ada376acda5..7f300cf63b2c8bfdeca4e9252caba549a154d85d 100644 --- a/conftest.py +++ b/conftest.py @@ -2,12 +2,14 @@ # # SPDX-License-Identifier: CC0-1.0 -import pytest # pytest finds added options only in root conftest.py if not configured otherwise def pytest_addoption(parser): - parser.addoption("--integration", action="store_true", default=False, help="run integration tests") + parser.addoption( + '--integration', action='store_true', default=False, help='run integration tests' + ) + # list marker if requested with 'pytest --markers' def pytest_configure(config): - config.addinivalue_line("markers", "integration: mark a test as integration test") + config.addinivalue_line('markers', 'integration: mark a test as integration test') diff --git a/docs/conf.py b/docs/conf.py index 9d95d77a8529303eb0b27017b60d0861205e2e27..f4c9e2dfe9236d3ef5f2bc244528e87c548305bd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,6 +16,7 @@ # import os import sys + import tgclients sys.path.insert(0, os.path.abspath('..')) @@ -29,10 +30,10 @@ author = 'Stefan Hynek, Ubbo Veentjer' # The short X.Y version. -#version = '.'.join(tgclients.__version__.split('.')[0:2]) +# version = '.'.join(tgclients.__version__.split('.')[0:2]) # The full version, including alpha/beta/rc tags -#release = tgclients.__version__ +# release = tgclients.__version__ version = tgclients.__version__ @@ -43,13 +44,13 @@ version = tgclients.__version__ # ones. extensions = [ 'sphinx.ext.autodoc', - 'sphinx.ext.napoleon', # https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html - 'sphinx.ext.intersphinx', # https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html + 'sphinx.ext.napoleon', # https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html + 'sphinx.ext.intersphinx', # https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html # decide wether to use myst_parser & nbshinx or myst_nb # 'myst_parser', # https://myst-parser.readthedocs.io/en/latest/ # 'nbsphinx' 'myst_nb', - 'IPython.sphinxext.ipython_console_highlighting', # https://github.com/spatialaudio/nbsphinx/issues/687 + 'IPython.sphinxext.ipython_console_highlighting', # https://github.com/spatialaudio/nbsphinx/issues/687 ] # Add any paths that contain templates here, relative to this directory. @@ -79,7 +80,7 @@ html_theme = 'sphinx_rtd_theme' # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] -#html_favicon = '_static/favicon.ico' +# html_favicon = '_static/favicon.ico' html_favicon = 'favicon.ico' # Example configuration for intersphinx: refer to the Python standard library. @@ -91,7 +92,7 @@ intersphinx_mapping = { # autodoc options - https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html autodoc_typehints_format = 'short' -#autodoc_class_signature = 'separated' +# autodoc_class_signature = 'separated' myst_enable_extensions = [ 'tasklist', diff --git a/pyproject.toml b/pyproject.toml index 4d241e7b49166901801993ea95b0811ca737abe6..7fcf336eaa05549924bacaffff335231d82067ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,3 +5,26 @@ [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" + +[tool.ruff] +exclude = [ + "src/tgclients/databinding", +] + +line-length = 100 + +[tool.ruff.lint] +# Enable `pydocstyle` rules, in addition to the defaults. +select = ["E4", "E7", "E9", "F", "D"] +# ignore "Missing docstring in `__init__`" because its opposed to DOC301 from pydocstyle +ignore = ["D107"] + +[tool.ruff.lint.per-file-ignores] +# Ignore `D` rules everywhere except for the `src/` directory. +"!src/**.py" = ["D"] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.format] +quote-style = "single" diff --git a/setup.cfg b/setup.cfg index 7019db69a8d387bbde82999eb8eda2addcf6a414..c231f065fe26c442e85bd1755416965c23abeb6a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,21 +45,18 @@ install_requires = [options.extras_require] dev = - autopep8 - bandit commitizen coverage darglint ipykernel ipywidgets - mypy pip-tools pre-commit - pylint pytest requests-mock reuse rstcheck + ruff Sphinx testbook tqdm diff --git a/src/tgclients/__init__.py b/src/tgclients/__init__.py index ab2abfda68133f52776e258870b4ce660ee4b79c..d4a07df1b65c6e8ec88a0b7cb3b15c7eb8cc5e25 100644 --- a/src/tgclients/__init__.py +++ b/src/tgclients/__init__.py @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: CC0-1.0 +"""tgclients provide access to TextGrid services.""" + __version__ = '0.16.0' from tgclients.aggregator import ( @@ -16,20 +18,21 @@ from tgclients.config import ( ) from tgclients.crud import ( TextgridCrud, - TextgridCrudRequest, TextgridCrudException, + TextgridCrudRequest, ) from tgclients.metadata import ( TextgridMetadata, ) from tgclients.search import ( TextgridSearch, - TextgridSearchRequest, TextgridSearchException, + TextgridSearchRequest, ) from tgclients.utils import ( Utils, ) + __all__ = [ 'Aggregator', 'TextgridAuth', @@ -38,6 +41,7 @@ __all__ = [ 'TextgridCrud', 'TextgridCrudRequest', 'TextgridCrudException', + 'TextgridMetadata', 'TextgridSearch', 'TextgridSearchRequest', 'TextgridSearchException', diff --git a/src/tgclients/aggregator.py b/src/tgclients/aggregator.py index 73d111510098c21e68e5a5d3f0ee8f5d2781795b..0166953024f2d9b7f534dca16fbd71a4368343a5 100644 --- a/src/tgclients/aggregator.py +++ b/src/tgclients/aggregator.py @@ -2,7 +2,8 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -"""API for the TextGrid aggregator service""" +"""API for the TextGrid aggregator service.""" + import logging from typing import List, Optional, Union, overload @@ -16,7 +17,9 @@ logger = logging.getLogger(__name__) class Aggregator: """Provide access to the Textgrid Aggregator Service. - API docs: https://textgridlab.org/doc/services/submodules/aggregator/docs/api.html""" + + API docs: https://textgridlab.org/doc/services/submodules/aggregator/docs/api.html + """ def __init__(self, config: TextgridConfig = TextgridConfig()) -> None: self._url = config.aggregator @@ -24,15 +27,14 @@ class Aggregator: self._requests = requests.Session() @overload - def zip(self, textgrid_uris: str, sid: Optional[str] = None) -> Response: - ... + def zip(self, textgrid_uris: str, sid: Optional[str] = None) -> Response: ... @overload - def zip(self, textgrid_uris: List[str], sid: Optional[str] = None) -> Response: - ... + def zip(self, textgrid_uris: List[str], sid: Optional[str] = None) -> Response: ... def zip(self, textgrid_uris: Union[str, List[str]], sid: Optional[str] = None) -> Response: """Download aggregated TextGrid objects as ZIP file. + https://textgridlab.org/doc/services/submodules/aggregator/docs/zip.html Args: @@ -45,17 +47,16 @@ class Aggregator: if isinstance(textgrid_uris, list): textgrid_uris = ','.join(textgrid_uris) url = self._url + '/zip/' - response = self._requests.get(url + textgrid_uris, params={ 'sid': sid }, - timeout=self._config.http_timeout) + response = self._requests.get( + url + textgrid_uris, params={'sid': sid}, timeout=self._config.http_timeout + ) return response @overload - def text(self, textgrid_uris: str, sid: Optional[str] = None) -> Response: - ... + def text(self, textgrid_uris: str, sid: Optional[str] = None) -> Response: ... @overload - def text(self, textgrid_uris: List[str], sid: Optional[str] = None) -> Response: - ... + def text(self, textgrid_uris: List[str], sid: Optional[str] = None) -> Response: ... # python 3.10 allows writinh Union as | # https://www.blog.pythonlibrary.org/2021/09/11/python-3-10-simplifies-unions-in-type-annotations/ @@ -72,20 +73,20 @@ class Aggregator: if isinstance(textgrid_uris, list): textgrid_uris = ','.join(textgrid_uris) url = self._url + '/text/' - response = self._requests.get(url + textgrid_uris, params={ 'sid': sid }, - timeout=self._config.http_timeout) + response = self._requests.get( + url + textgrid_uris, params={'sid': sid}, timeout=self._config.http_timeout + ) return response @overload - def teicorpus(self, textgrid_uris: str, sid: Optional[str] = None) -> Response: - ... + def teicorpus(self, textgrid_uris: str, sid: Optional[str] = None) -> Response: ... @overload - def teicorpus(self, textgrid_uris: List[str], sid: Optional[str] = None) -> Response: - ... + def teicorpus(self, textgrid_uris: List[str], sid: Optional[str] = None) -> Response: ... - def teicorpus(self, textgrid_uris: Union[str, List[str]], sid: Optional[str] = None - ) -> Response: + def teicorpus( + self, textgrid_uris: Union[str, List[str]], sid: Optional[str] = None + ) -> Response: """Download aggregated TextGrid objects as TEI corpus. Args: @@ -98,40 +99,44 @@ class Aggregator: if isinstance(textgrid_uris, list): textgrid_uris = ','.join(textgrid_uris) url = self._url + '/teicorpus/' - response = self._requests.get(url + textgrid_uris, params={ 'sid': sid }, - timeout=self._config.http_timeout) + response = self._requests.get( + url + textgrid_uris, params={'sid': sid}, timeout=self._config.http_timeout + ) return response @overload - def render(self, + def render( + self, textgrid_uris: str, sid: Optional[str] = None, stylesheet_uri: Optional[str] = None, mediatype: Optional[str] = None, link_pattern: Optional[str] = None, - sandbox: Optional[bool] = None - ) -> Response: - ... + sandbox: Optional[bool] = None, + ) -> Response: ... @overload - def render(self, textgrid_uris: List[str], + def render( + self, + textgrid_uris: List[str], sid: Optional[str] = None, stylesheet_uri: Optional[str] = None, mediatype: Optional[str] = None, link_pattern: Optional[str] = None, - sandbox: Optional[bool] = None - ) -> Response: - ... + sandbox: Optional[bool] = None, + ) -> Response: ... - def render(self, + def render( + self, textgrid_uris: Union[str, List[str]], sid: Optional[str] = None, stylesheet_uri: Optional[str] = None, mediatype: Optional[str] = None, link_pattern: Optional[str] = None, - sandbox: Optional[bool] = None - ) -> Response: + sandbox: Optional[bool] = None, + ) -> Response: """Apply an XSLT stylesheet to one or more TextGrid URIs. + Will render (X)HTML by default with XSLT stylesheets from tei-c.org see https://textgridlab.org/doc/services/submodules/aggregator/docs/html.html @@ -152,13 +157,15 @@ class Aggregator: if isinstance(textgrid_uris, list): textgrid_uris = ','.join(textgrid_uris) url = self._url + '/html/' - response = self._requests.get(url + textgrid_uris, - params={ - 'sid': sid, - 'stylesheet': stylesheet_uri, - 'mediatype': mediatype, - 'linkPattern': link_pattern, - 'sandbox': sandbox - }, - timeout=self._config.http_timeout) + response = self._requests.get( + url + textgrid_uris, + params={ + 'sid': sid, + 'stylesheet': stylesheet_uri, + 'mediatype': mediatype, + 'linkPattern': link_pattern, + 'sandbox': sandbox, + }, + timeout=self._config.http_timeout, + ) return response diff --git a/src/tgclients/auth.py b/src/tgclients/auth.py index 4d89678212272cdd10f4828772fb8952752a7276..64ac167691f53df41aada32851f1e2072618872b 100644 --- a/src/tgclients/auth.py +++ b/src/tgclients/auth.py @@ -3,8 +3,9 @@ # SPDX-License-Identifier: LGPL-3.0-or-later """Provide access to the TextGrid Authorization Service.""" + import logging -from typing import Optional, List +from typing import List, Optional from zeep import Client from zeep.exceptions import Fault, TransportError @@ -30,7 +31,9 @@ class TextgridAuth: self._extra_crud_client = self._connect_extra_crud() def _connect(self) -> Client: - """Internal helper that provides a SOAP client that is configured for + """Create standard SOAP client. + + Internal helper that provides a SOAP client that is configured for the use with the Textgrid Auth service. Returns: @@ -42,7 +45,9 @@ class TextgridAuth: return client def _connect_extra_crud(self) -> Client: - """Internal helper that provides a SOAP client that is configured for + """Create tgextra SOAP client. + + Internal helper that provides a SOAP client that is configured for the use with the Textgrid Auth service (the extra crud service). Returns: @@ -73,7 +78,9 @@ class TextgridAuth: raise TextgridAuthException(message) from error def list_all_projects(self) -> List[str]: - """Returns all projects stored in this RBAC instance with ID, name, + """List all projects. + + Returns all projects stored in this RBAC instance with ID, name, and description. See also getProjectDescription(). SID is not needed as this information can be reviewed publicly. @@ -94,9 +101,10 @@ class TextgridAuth: """ return self._client.service.getProjectDescription('', '', project_id) - def create_project(self, sid: str, name: str, description: str, - default_owner_roles: Optional[bool] = True) -> str: - """Create a new project + def create_project( + self, sid: str, name: str, description: str, default_owner_roles: Optional[bool] = True + ) -> str: + """Create a new project. Args: sid (str): TextGrid Session ID @@ -112,8 +120,9 @@ class TextgridAuth: str: the project ID of the created project """ try: - project_id = self._client.service.createProject(auth=sid, name=name, - description=description) + project_id = self._client.service.createProject( + auth=sid, name=name, description=description + ) except Fault as fault: message = 'Error creating project. Is your sessionID valid?' logger.warning(message) @@ -127,7 +136,7 @@ class TextgridAuth: return project_id def get_eppn_for_sid(self, sid: str) -> str: - """Get the EPPN belonging to a sessionID + """Get the EPPN belonging to a sessionID. Args: sid (str): TextGrid Session ID @@ -147,7 +156,7 @@ class TextgridAuth: return eppn def delete_project(self, sid: str, project_id: str) -> bool: - """Delete a project + """Delete a project. Args: sid (str): TextGrid Session ID @@ -160,8 +169,7 @@ class TextgridAuth: bool: true in case of success """ try: - status = self._client.service.deleteProject( - auth=sid, project=project_id) + status = self._client.service.deleteProject(auth=sid, project=project_id) except Fault as fault: message = 'Error deleting project. Is your sessionID valid?' logger.warning(message) @@ -169,7 +177,7 @@ class TextgridAuth: return status def add_admin_to_project(self, sid: str, project_id: str, eppn: str) -> bool: - """Give an user the admin role in a project + """Give an user the admin role in a project. Args: sid (str): TextGrid Session ID @@ -188,7 +196,7 @@ class TextgridAuth: raise exception def add_editor_to_project(self, sid: str, project_id: str, eppn: str) -> bool: - """Give an user the editor role in a project + """Give an user the editor role in a project. Args: sid (str): TextGrid Session ID @@ -207,7 +215,7 @@ class TextgridAuth: raise exception def add_manager_to_project(self, sid: str, project_id: str, eppn: str) -> bool: - """Give an user the manager role in a project + """Give an user the manager role in a project. Args: sid (str): TextGrid Session ID @@ -226,7 +234,7 @@ class TextgridAuth: raise exception def add_observer_to_project(self, sid: str, project_id: str, eppn: str) -> bool: - """Give an user the observer role in a project + """Give an user the observer role in a project. Args: sid (str): TextGrid Session ID @@ -247,8 +255,7 @@ class TextgridAuth: def _add_role_to_project(self, sid: str, project_id: str, eppn: str, role: str) -> bool: rolename = role + ',' + project_id + ',Projekt-Teilnehmer' try: - status = self._client.service.addMember( - auth=sid, username=eppn, role=rolename) + status = self._client.service.addMember(auth=sid, username=eppn, role=rolename) except Fault as fault: message = 'Error adding role to project. Is your sessionID valid?' logger.warning(message) @@ -257,4 +264,4 @@ class TextgridAuth: class TextgridAuthException(Exception): - """Exception communicating with tgauth""" + """Exception communicating with tgauth!""" diff --git a/src/tgclients/config.py b/src/tgclients/config.py index b1bb99c418e520e4340d26adfc5284210c761f76..855aac46fe8729397ce68f2bc79c97744e483406 100644 --- a/src/tgclients/config.py +++ b/src/tgclients/config.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: CC0-1.0 """Variable config options with defaults to be used with the TextGrid clients library.""" + import logging from typing import Optional @@ -17,6 +18,7 @@ TEST_SERVER: str = 'https://test.textgridlab.org' class TextgridConfig: """Provide standard configuration / URLs for TextGrid services. + Default is to connect to the TextGrid production server (https://textgridlab.org). Pass the constants tgclients.config.DEV_SERVER or @@ -37,8 +39,7 @@ class TextgridConfig: host (Optional[str]):TextGrid server. Defaults to 'https://textgridlab.org'. """ if not isinstance(host, str) or host == '': - logger.info( - 'host param was None or emtpy, default to: %s', PROD_SERVER) + logger.info('host param was None or emtpy, default to: %s', PROD_SERVER) host = PROD_SERVER if host.endswith('/'): logger.info('trailing slash in hostname detected and removed') @@ -48,7 +49,7 @@ class TextgridConfig: @property def host(self) -> str: - """the host URL + """The host URL. Returns: str: the configured host URL @@ -57,16 +58,16 @@ class TextgridConfig: @property def auth_wsdl(self) -> str: - """the tgauth wsdl location + """The tgauth WSDL location. Returns: - str: the tgauth wsdl location + str: the tgauth WSDL location """ return self._host + '/1.0/tgauth/wsdl/tgextra.wsdl' @property def auth_address(self) -> str: - """the tgauth service location + """The tgauth service location. Returns: str: the tgauth service location @@ -75,16 +76,16 @@ class TextgridConfig: @property def extra_crud_wsdl(self) -> str: - """the tgextra wsdl location + """The tgextra WSDL location. Returns: - str: the tgextra wsdl location + str: the tgextra WSDL location """ return self._host + '/1.0/tgauth/wsdl/tgextra-crud.wsdl' @property def extra_crud_address(self) -> str: - """the tgextra service location + """The tgextra service location. Returns: str: the tgextra service location @@ -93,7 +94,7 @@ class TextgridConfig: @property def search(self) -> str: - """the nonpublic tgsearch service location + """The nonpublic tgsearch service location. Returns: str: the nonpublic tgsearch service location @@ -102,7 +103,7 @@ class TextgridConfig: @property def search_public(self) -> str: - """the public tgsearch service location + """The public tgsearch service location. Returns: str: the public tgsearch service location @@ -111,7 +112,7 @@ class TextgridConfig: @property def crud(self) -> str: - """the nonpublic tgcrud REST service location + """The nonpublic tgcrud REST service location. Returns: str: the nonpublic tgcrud REST service location @@ -120,7 +121,7 @@ class TextgridConfig: @property def crud_public(self) -> str: - """the public tgcrud REST service location + """The public tgcrud REST service location. Returns: str: the public tgcrud REST service location @@ -129,7 +130,7 @@ class TextgridConfig: @property def aggregator(self) -> str: - """the aggregator service location + """The aggregator service location. Returns: str: the aggregator service location @@ -138,7 +139,7 @@ class TextgridConfig: @property def http_timeout(self) -> float: - """HTTP timeout to be used when accessing TextGrid services + """HTTP timeout to be used when accessing TextGrid services. Returns: float: http timeout in seconds @@ -147,7 +148,7 @@ class TextgridConfig: @http_timeout.setter def http_timeout(self, value: float) -> None: - """Set HTTP timeout to be used when accessing TextGrid services + """Set HTTP timeout to be used when accessing TextGrid services. Args: value (float): the timeout diff --git a/src/tgclients/crud.py b/src/tgclients/crud.py index e168c812a93f7b9f156adc002d58c523b451068f..92200f2f2a2117a3b0f48400d6d06e7b3d803d6a 100644 --- a/src/tgclients/crud.py +++ b/src/tgclients/crud.py @@ -3,13 +3,13 @@ # SPDX-License-Identifier: LGPL-3.0-or-later """TextGrid CRUD API.""" + import logging from io import BytesIO from typing import Optional -from bs4 import BeautifulSoup - import requests +from bs4 import BeautifulSoup from requests.models import Response from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor from xsdata.formats.dataclass.context import XmlContext @@ -26,11 +26,14 @@ RESPONSE_ENCODING = 'utf-8' class TextgridCrudRequest: """Provide low level access to the TextGrid CRUD Service.""" - def __init__(self, config: TextgridConfig = TextgridConfig(), - for_publication: bool = False) -> None: + def __init__( + self, config: TextgridConfig = TextgridConfig(), for_publication: bool = False + ) -> None: if for_publication: - logger.warning('for_publication set. this tgcrud client is able to publish data to ' - + 'the public repository, please make sure you know what you are doing.') + logger.warning( + 'for_publication set. this tgcrud client is able to publish data to ' + + 'the public repository, please make sure you know what you are doing.' + ) self._url = config.crud_public else: self._url = config.crud @@ -45,7 +48,7 @@ class TextgridCrudRequest: self._serializer = XmlSerializer() def read_data(self, textgrid_uri: str, sid: Optional[str] = None) -> Response: - """Read Data + """Read Data. Args: textgrid_uri (str): Textgrid URI @@ -58,14 +61,16 @@ class TextgridCrudRequest: Response: HTTP response from service """ # defer downloading the response body until accessing Response.content - response = self._requests.get(self._url + '/' + textgrid_uri + '/data', - params={'sessionId': sid}, - stream=True, - timeout=self._config.http_timeout) + response = self._requests.get( + self._url + '/' + textgrid_uri + '/data', + params={'sessionId': sid}, + stream=True, + timeout=self._config.http_timeout, + ) return self._handle_response(response) def read_metadata(self, textgrid_uri: str, sid: Optional[str] = None) -> Response: - """Read Metadata + """Read Metadata. Args: textgrid_uri (str): Textgrid URI @@ -77,14 +82,16 @@ class TextgridCrudRequest: Returns: Response: HTTP response from service """ - response = self._requests.get(self._url + '/' + textgrid_uri + '/metadata', - params={'sessionId': sid}, - stream=True, - timeout=self._config.http_timeout) + response = self._requests.get( + self._url + '/' + textgrid_uri + '/metadata', + params={'sessionId': sid}, + stream=True, + timeout=self._config.http_timeout, + ) return self._handle_response(response) def create_resource(self, sid: str, project_id: str, data, metadata) -> Response: - """Create a TextGrid object + """Create a TextGrid object. Args: sid (str): Session ID @@ -99,16 +106,19 @@ class TextgridCrudRequest: Response: HTTP response from service with metadata from newly created object """ encoder = self._prepare_multipart(metadata, data) - params = {'sessionId': sid, 'projectId': project_id, - 'createRevision': 'false'} + params = {'sessionId': sid, 'projectId': project_id, 'createRevision': 'false'} response = self._requests.post( - self._url + '/' + 'create', params=params, data=encoder, + self._url + '/' + 'create', + params=params, + data=encoder, headers={'Content-Type': encoder.content_type}, - timeout=self._config.http_timeout) + timeout=self._config.http_timeout, + ) return self._handle_response(response) - def create_revision(self, sid: str, project_id: str, textgrid_uri: str, - data, metadata: str) -> Response: + def create_revision( + self, sid: str, project_id: str, textgrid_uri: str, data, metadata: str + ) -> Response: """Create a TextGrid object revision. Args: @@ -125,17 +135,25 @@ class TextgridCrudRequest: Response: HTTP response from service with metadata from newly created object revision """ encoder = self._prepare_multipart(metadata, data) - params = {'sessionId': sid, 'uri': textgrid_uri, - 'createRevision': 'true', 'projectId': project_id} + params = { + 'sessionId': sid, + 'uri': textgrid_uri, + 'createRevision': 'true', + 'projectId': project_id, + } response = self._requests.post( - self._url + '/' + 'create', params=params, data=encoder, + self._url + '/' + 'create', + params=params, + data=encoder, headers={'Content-Type': encoder.content_type}, - timeout=self._config.http_timeout) + timeout=self._config.http_timeout, + ) return self._handle_response(response) - def update_resource(self, sid: str, textgrid_uri: str, data, metadata, - create_revision: bool = False) -> Response: - """Update a TextGrid object + def update_resource( + self, sid: str, textgrid_uri: str, data, metadata, create_revision: bool = False + ) -> Response: + """Update a TextGrid object. Args: sid (str): Session ID @@ -158,13 +176,16 @@ class TextgridCrudRequest: encoder = self._prepare_multipart(metadata, data) params = {'sessionId': sid} response = self._requests.post( - self._url + '/' + textgrid_uri + '/update', params=params, data=encoder, + self._url + '/' + textgrid_uri + '/update', + params=params, + data=encoder, headers={'Content-Type': encoder.content_type}, - timeout=self._config.http_timeout) + timeout=self._config.http_timeout, + ) return self._handle_response(response) def update_metadata(self, sid: str, textgrid_uri: str, metadata) -> Response: - """Update metadata for TextGrid object + """Update metadata for TextGrid object. Args: sid (str): Session ID @@ -177,13 +198,16 @@ class TextgridCrudRequest: encoder = self._prepare_multipart(metadata) params = {'sessionId': sid} response = self._requests.post( - self._url + '/' + textgrid_uri + '/updateMetadata', params=params, data=encoder, + self._url + '/' + textgrid_uri + '/updateMetadata', + params=params, + data=encoder, headers={'Content-Type': encoder.content_type}, - timeout=self._config.http_timeout) + timeout=self._config.http_timeout, + ) return self._handle_response(response) def delete_resource(self, sid: str, textgrid_uri: str) -> Response: - """Delete a TextGrid object + """Delete a TextGrid object. Args: sid (str): Session ID @@ -197,13 +221,15 @@ class TextgridCrudRequest: """ params = {'sessionId': sid} response = self._requests.get( - self._url + '/' + textgrid_uri + '/delete', params=params, - timeout=self._config.http_timeout) + self._url + '/' + textgrid_uri + '/delete', + params=params, + timeout=self._config.http_timeout, + ) return self._handle_response(response) @staticmethod def _handle_response(response: Response) -> Response: - """Error handling for responses from crud + """Error handling for responses from crud. Args: response (Response): a response from tgcrud @@ -217,16 +243,17 @@ class TextgridCrudRequest: response.encoding = RESPONSE_ENCODING if not response.ok: error = TextgridCrudRequest._error_msg_from_html(response.text) - message = '[Error] HTTP Code: ' + \ - str(response.status_code) + ' - ' + error + message = '[Error] HTTP Code: ' + str(response.status_code) + ' - ' + error logger.warning(message) raise TextgridCrudException(message) return response @staticmethod def _error_msg_from_html(html: str): - """ Extract error message from html, as the text string for - the error is in <meta name="description" content="error message"> + """Extract error message from html. + + The text string for the error is in + <meta name="description" content="error message">. Args: html: an error response body from tgcrud @@ -237,7 +264,7 @@ class TextgridCrudRequest: # soup = BeautifulSoup(html, 'html.parser') metatag = soup.select_one('meta[name="description"]') - if metatag != None: + if metatag is not None: msg = metatag['content'] else: msg = html[0:255] @@ -246,6 +273,7 @@ class TextgridCrudRequest: @staticmethod def _prepare_multipart(metadata, data=None): """Create a streaming multipart object. + Monitor the upload progress if log level is DEBUG. See also: https://toolbelt.readthedocs.io/en/latest/uploading-data.html @@ -257,12 +285,9 @@ class TextgridCrudRequest: Returns: [MultipartEncoder]: Multipart containing data and metadata """ - fields = { - 'tgObjectMetadata': ('tgObjectMetadata', metadata, 'text/xml') - } + fields = {'tgObjectMetadata': ('tgObjectMetadata', metadata, 'text/xml')} if data: - fields['tgObjectData'] = ( - 'tgObjectData', data, 'application/octet-stream') + fields['tgObjectData'] = ('tgObjectData', data, 'application/octet-stream') encoder = MultipartEncoder(fields=fields) if logger.isEnabledFor(logging.DEBUG): @@ -273,29 +298,31 @@ class TextgridCrudRequest: @staticmethod def _debug_monitor_callback(monitor: MultipartEncoderMonitor): """Callback for _prepare_multipart. + Helper to log upload progress for streaming multipart when log level is DEBUG. Args: monitor (MultipartEncoderMonitor): the monitor """ - logger.debug('[debug multipart upload] bytes read: %s ', - monitor.bytes_read) + logger.debug('[debug multipart upload] bytes read: %s ', monitor.bytes_read) class TextgridCrudException(Exception): - """Exception communicating with tgcrud""" + """Exception communicating with tgcrud!""" class TextgridCrud(TextgridCrudRequest): - """Provide access to the Textgrid CRUD Service using a XML data binding """ + """Provide access to the Textgrid CRUD Service using a XML data binding.""" - def __init__(self, config: TextgridConfig = TextgridConfig(), - for_publication: bool = False) -> None: + def __init__( + self, config: TextgridConfig = TextgridConfig(), for_publication: bool = False + ) -> None: super().__init__(config, for_publication) - def create_resource(self, sid: str, project_id: str, - data, metadata: MetadataContainerType) -> MetadataContainerType: - """Create a TextGrid object + def create_resource( + self, sid: str, project_id: str, data, metadata: MetadataContainerType + ) -> MetadataContainerType: + """Create a TextGrid object. Args: sid (str): Session ID @@ -313,8 +340,9 @@ class TextgridCrud(TextgridCrudRequest): response = super().create_resource(sid, project_id, data, metadata_string) return self._parser.parse(BytesIO(response.content), MetadataContainerType) - def create_revision(self, sid: str, project_id: str, textgrid_uri: str, - data, metadata: MetadataContainerType) -> MetadataContainerType: + def create_revision( + self, sid: str, project_id: str, textgrid_uri: str, data, metadata: MetadataContainerType + ) -> MetadataContainerType: """Create a TextGrid object revision. Args: @@ -335,7 +363,7 @@ class TextgridCrud(TextgridCrudRequest): return self._parser.parse(BytesIO(response.content), MetadataContainerType) def read_metadata(self, textgrid_uri: str, sid: Optional[str] = None) -> MetadataContainerType: - """Read Metadata + """Read Metadata. Args: textgrid_uri (str): Textgrid URI @@ -350,9 +378,10 @@ class TextgridCrud(TextgridCrudRequest): response = super().read_metadata(textgrid_uri, sid) return self._parser.parse(BytesIO(response.content), MetadataContainerType) - def update_metadata(self, sid: str, textgrid_uri: str, - metadata: MetadataContainerType) -> MetadataContainerType: - """Update metadata for TextGrid object + def update_metadata( + self, sid: str, textgrid_uri: str, metadata: MetadataContainerType + ) -> MetadataContainerType: + """Update metadata for TextGrid object. Args: sid (str): Session ID @@ -366,10 +395,15 @@ class TextgridCrud(TextgridCrudRequest): response = super().update_metadata(sid, textgrid_uri, metadata_string) return self._parser.parse(BytesIO(response.content), MetadataContainerType) - def update_resource(self, sid: str, textgrid_uri: str, - data, metadata: MetadataContainerType, - create_revision: bool = False) -> MetadataContainerType: - """Update a TextGrid object + def update_resource( + self, + sid: str, + textgrid_uri: str, + data, + metadata: MetadataContainerType, + create_revision: bool = False, + ) -> MetadataContainerType: + """Update a TextGrid object. Args: sid (str): Session ID diff --git a/src/tgclients/metadata.py b/src/tgclients/metadata.py index aaa50d503f37f05ae38dd4f5511c0eb5147171d5..7f7da54abc3c8bd2b6a7375fac6ac9aa9a055de5 100644 --- a/src/tgclients/metadata.py +++ b/src/tgclients/metadata.py @@ -2,10 +2,11 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -"""Helper functions to work with TextGrid metadata XML""" +"""Helper functions to work with TextGrid metadata XML.""" + +import logging import os import re -import logging from pathlib import Path from typing import Optional @@ -13,8 +14,8 @@ from jinja2 import Environment, FileSystemLoader from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.parsers import XmlParser -from tgclients.databinding.tgsearch import Response, ResultType from tgclients.databinding.textgrid_metadata_2010 import MetadataContainerType +from tgclients.databinding.tgsearch import Response, ResultType try: import icu @@ -23,11 +24,11 @@ except ImportError: logger = logging.getLogger(__name__) -__location__ = os.path.realpath( - os.path.join(os.getcwd(), os.path.dirname(__file__))) +__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) + class TextgridMetadata: - """Helper functions to work with TextGrid metadata XML""" + """Helper functions to work with TextGrid metadata XML.""" def __init__(self): context = XmlContext() @@ -38,7 +39,7 @@ class TextgridMetadata: @staticmethod def create(title: str, mimetype: str) -> str: - """Create XML metadata for an TextGrid Object + """Create XML metadata for an TextGrid Object. Args: title (str): title of the object @@ -48,14 +49,13 @@ class TextgridMetadata: str: XML metadata as string """ path = Path(__file__).parent / 'templates' - env = Environment( - loader=FileSystemLoader(Path(path)), autoescape=True) + env = Environment(loader=FileSystemLoader(Path(path)), autoescape=True) template = env.get_template('metadata.xml.jinja2') metadata = template.render(title=title, format=mimetype) return metadata def build(self, title: str, mimetype: str) -> MetadataContainerType: - """Build metadata for an TextGrid Object + """Build metadata for an TextGrid Object. Args: title (str): title of the object @@ -68,11 +68,20 @@ class TextgridMetadata: return self._parser.from_string(metadata, MetadataContainerType) def searchresponse2object(self, xml: str) -> Response: + """Build databinding for XML string returned from tgsearch. + + Args: + xml (str): xml string as returned from tgsearch + + Returns: + Response: tgsearch Response + """ return self._parser.from_string(xml, Response) def filename_from_metadata(self, metadata: ResultType) -> str: - """Generate a filename for a textgrid search metadata result - containg title, textfgrid-uri and extension + """Generate a filename for a textgrid search metadata result. + + This is made of title, textgrid-URI and extension. Args: metadata (ResultType): tgsearch metadata result @@ -90,8 +99,7 @@ class TextgridMetadata: return self.filename(title, uri, mimetype) def filename(self, title: str, tguri: str, mimetype: str) -> str: - """Generate a filename for the triple of - title, textfgrid-uri and extension + """Generate a filename for the triple of title, textfgrid-uri and extension. Args: title (str): the title @@ -113,8 +121,7 @@ class TextgridMetadata: # converted to python from info.textgrid.utils.export.filenames.FileExtensionMap # of link-rewriter (https://gitlab.gwdg.de/dariah-de/textgridrep/link-rewriter) extension_map = {} - map_line_pattern = re.compile( - '^[ \t]*([^# \t]+)[ \t]*([^#]+)[ \t]*(#.*)?$') + map_line_pattern = re.compile('^[ \t]*([^# \t]+)[ \t]*([^#]+)[ \t]*(#.*)?$') space_pattern = re.compile('[ \t]+') with open(os.path.join(__location__, 'mime.types'), encoding='utf8') as mimefile: @@ -132,6 +139,7 @@ class TextgridMetadata: def extension_for_format(self, mimetype: str) -> Optional[str]: """Find a matching extension for a textgrid mime type. + The first matching extension for a mime type is returned, so extensions defined first in mime.types will be used. @@ -149,12 +157,22 @@ class TextgridMetadata: @staticmethod def remove_tg_prefix(tguri: str) -> str: + """Remove the 'textgrid:' prefix from an textgrid URI. + + Args: + tguri (str): the textgrid URI + + Returns: + str: uri without the prefix + """ return tguri[9:] @staticmethod def id_from_filename(filename: str) -> str: - """extract the id from a filename which is named according to link rewriters - textgrid metadata to filename mapping + """Extract the id from a filename. + + This is named according to link rewriters + textgrid metadata to filename mapping. Args: filename (str): the filename @@ -166,7 +184,7 @@ class TextgridMetadata: next_to_last_dot = filename.rfind('.', 0, last_dot) # a textgrid uri has a revision number in the end. # if the chars after the last dot are not numeric, we have a filename extension - if not filename[last_dot+1:].isnumeric(): + if not filename[last_dot + 1 :].isnumeric(): # extension is there? we need the '.' before the dot separating the uri # from the revision component next_to_last_dot = filename.rfind('.', 0, next_to_last_dot) @@ -174,10 +192,10 @@ class TextgridMetadata: # there is no extension to cut of, we want the end of the string last_dot = None - return filename[next_to_last_dot+1:last_dot] + return filename[next_to_last_dot + 1 : last_dot] def transliterate(self, title: str) -> str: - """replace all chars which may be problematic in filenames from title string + """Replace all chars which may be problematic in filenames from title string. Args: title (str): a title from textgrid metadata @@ -198,4 +216,5 @@ class TextgridMetadata: with open(os.path.join(__location__, 'tgfilenames.rules'), encoding='utf8') as rulesfile: rules = rulesfile.read() return icu.Transliterator.createFromRules( - 'TgFilenames', rules, icu.UTransDirection.FORWARD) + 'TgFilenames', rules, icu.UTransDirection.FORWARD + ) diff --git a/src/tgclients/search.py b/src/tgclients/search.py index 2ed302eef9838193678e7e436d15b73de810a5c6..40ea32771ba64060d455b3f7f9ca627f9eb9e4c0 100644 --- a/src/tgclients/search.py +++ b/src/tgclients/search.py @@ -3,9 +3,10 @@ # SPDX-License-Identifier: LGPL-3.0-or-later """TextGrid Search API.""" + import logging from io import BytesIO -from typing import Optional, List +from typing import List, Optional import requests from requests.models import Response @@ -13,7 +14,8 @@ from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.parsers import XmlParser from tgclients.config import TextgridConfig -from tgclients.databinding.tgsearch import Response as SearchResponse, TextgridUris +from tgclients.databinding.tgsearch import Response as SearchResponse +from tgclients.databinding.tgsearch import TextgridUris logger = logging.getLogger(__name__) @@ -31,8 +33,7 @@ class TextgridSearchRequest: self._requests = requests.Session() def info(self, textgrid_uri: str, sid: Optional[str] = None) -> Response: - """Retrieve metadata for a textgrid object specified by its - textgrid-uri + """Retrieve metadata for a textgrid object specified by its textgrid-uri. Args: textgrid_uri (str): Textgrid URI @@ -46,12 +47,14 @@ class TextgridSearchRequest: """ url = self._url + '/info/' response = self._requests.get( - url + textgrid_uri, params={'sid': sid}, timeout=self._config.http_timeout) + url + textgrid_uri, params={'sid': sid}, timeout=self._config.http_timeout + ) return self._handle_response(response) def list_project_root(self, project_id: str, sid: Optional[str] = None) -> Response: - """Get objects belonging to a project, filtered by objects that are in - an aggregation in the same project. + """Get objects belonging to a project. + + These are filtered by objects that are in an aggregation in the same project. Args: project_id (str): the ID of the project to list @@ -64,8 +67,10 @@ class TextgridSearchRequest: Response: HTTP response from service, containing a list of textgrid metadata entries """ response = self._requests.get( - self._url + '/navigation/' + project_id, params={'sid': sid}, - timeout=self._config.http_timeout) + self._url + '/navigation/' + project_id, + params={'sid': sid}, + timeout=self._config.http_timeout, + ) return self._handle_response(response) def list_aggregation(self, textgrid_uri: str, sid: Optional[str] = None) -> Response: @@ -81,27 +86,31 @@ class TextgridSearchRequest: Returns: Response: HTTP response from service, containing a list of textgrid metadata entries """ - response = self._requests.get(self._url + '/navigation/agg/' + - textgrid_uri, params={'sid': sid}, - timeout=self._config.http_timeout) + response = self._requests.get( + self._url + '/navigation/agg/' + textgrid_uri, + params={'sid': sid}, + timeout=self._config.http_timeout, + ) return self._handle_response(response) # pylint: disable-msg=too-many-arguments,too-many-locals - def search(self, - query: Optional[str] = '*', - sid: Optional[str] = None, - target: Optional[str] = None, - order: Optional[str] = None, - start: Optional[int] = None, - limit: Optional[int] = None, - kwic_width: Optional[int] = None, - word_distance: Optional[int] = None, - path: Optional[bool] = None, - all_projects: Optional[bool] = None, - sandbox: Optional[bool] = None, - filters: Optional[List[str]] = None, - facet: Optional[List[str]] = None, - facet_limit: Optional[int] = None) -> Response: + def search( + self, + query: Optional[str] = '*', + sid: Optional[str] = None, + target: Optional[str] = None, + order: Optional[str] = None, + start: Optional[int] = None, + limit: Optional[int] = None, + kwic_width: Optional[int] = None, + word_distance: Optional[int] = None, + path: Optional[bool] = None, + all_projects: Optional[bool] = None, + sandbox: Optional[bool] = None, + filters: Optional[List[str]] = None, + facet: Optional[List[str]] = None, + facet_limit: Optional[int] = None, + ) -> Response: """Run fulltext queries or filters on TextGrid metadata and fulltext objects. Please note: as the defaults of this function are mostly set to None, the defaults from @@ -141,7 +150,6 @@ class TextgridSearchRequest: Response: HTTP response from service - a list of textgrid metadata entries, KWIC hits, paths and facets if requested """ - params = { 'q': query, 'sid': sid, @@ -156,15 +164,17 @@ class TextgridSearchRequest: 'sandbox': sandbox, 'filter': filters, 'facet': facet, - 'facetLimit': facet_limit + 'facetLimit': facet_limit, } - response = self._requests.get(self._url + '/search', params=params, - timeout=self._config.http_timeout) + response = self._requests.get( + self._url + '/search', params=params, timeout=self._config.http_timeout + ) return self._handle_response(response) + # pylint: enable-msg=too-many-arguments,too-many-locals def edition_work_metadata_for(self, textgrid_uri: str, sid: Optional[str] = None) -> Response: - """Find parent edition for an object and the edition and work metadata + """Find parent edition for an object and the edition and work metadata. Args: textgrid_uri (str): Textgrid URI @@ -178,13 +188,14 @@ class TextgridSearchRequest: from first matching parent edition """ response = self._requests.get( - self._url + '/info/' + textgrid_uri + - '/editionWorkMeta', params={'sid': sid}, - timeout=self._config.http_timeout) + self._url + '/info/' + textgrid_uri + '/editionWorkMeta', + params={'sid': sid}, + timeout=self._config.http_timeout, + ) return self._handle_response(response) def children(self, textgrid_uri: str, sid: Optional[str] = None) -> Response: - """List URIs for all children of this aggregation and its child aggregations + """List URIs for all children of this aggregation and its child aggregations. Args: textgrid_uri (str): Textgrid URI of an aggregation @@ -198,13 +209,15 @@ class TextgridSearchRequest: aggregation and its child aggregations """ response = self._requests.get( - self._url + '/info/' + textgrid_uri + '/children', params={'sid': sid}, - timeout=self._config.http_timeout) + self._url + '/info/' + textgrid_uri + '/children', + params={'sid': sid}, + timeout=self._config.http_timeout, + ) return self._handle_response(response) @staticmethod def _handle_response(response: Response) -> Response: - """Error handling for responses from tgsearch + """Error handling for responses from tgsearch. Args: response (Response): a response from tgsearch @@ -216,15 +229,16 @@ class TextgridSearchRequest: Response: the response """ if not response.ok: - message = '[Error] HTTP Code: ' + \ - str(response.status_code) + ' - ' + response.text[0:255] + message = ( + '[Error] HTTP Code: ' + str(response.status_code) + ' - ' + response.text[0:255] + ) logger.warning(message) raise TextgridSearchException(message) return response class TextgridSearch(TextgridSearchRequest): - """Provide access to the TextGrid search service using a XML data binding """ + """Provide access to the TextGrid search service using a XML data binding.""" def __init__(self, config: TextgridConfig = TextgridConfig(), nonpublic: bool = False) -> None: super().__init__(config, nonpublic) @@ -234,8 +248,7 @@ class TextgridSearch(TextgridSearchRequest): self._parser = XmlParser(context=context) def info(self, textgrid_uri: str, sid: Optional[str] = None) -> SearchResponse: - """Retrieve metadata for a textgrid object specified by its - textgrid-uri + """Retrieve metadata for a textgrid object specified by its textgrid-uri. Args: textgrid_uri (str): Textgrid URI @@ -251,8 +264,9 @@ class TextgridSearch(TextgridSearchRequest): return self._parser.parse(BytesIO(response.content), SearchResponse) def list_project_root(self, project_id: str, sid: Optional[str] = None) -> SearchResponse: - """Get objects belonging to a project, filtered by objects that are in - an aggregation in the same project. + """Get objects belonging to a project. + + These are filtered by objects that are in an aggregation in the same project. Args: project_id (str): the ID of the project to list @@ -285,21 +299,23 @@ class TextgridSearch(TextgridSearchRequest): # pylint: disable-msg=too-many-arguments,too-many-locals - def search(self, - query: Optional[str] = '*', - sid: Optional[str] = None, - target: Optional[str] = None, - order: Optional[str] = None, - start: Optional[int] = None, - limit: Optional[int] = None, - kwic_width: Optional[int] = None, - word_distance: Optional[int] = None, - path: Optional[bool] = None, - all_projects: Optional[bool] = None, - sandbox: Optional[bool] = None, - filters: Optional[List[str]] = None, - facet: Optional[List[str]] = None, - facet_limit: Optional[int] = None) -> SearchResponse: + def search( + self, + query: Optional[str] = '*', + sid: Optional[str] = None, + target: Optional[str] = None, + order: Optional[str] = None, + start: Optional[int] = None, + limit: Optional[int] = None, + kwic_width: Optional[int] = None, + word_distance: Optional[int] = None, + path: Optional[bool] = None, + all_projects: Optional[bool] = None, + sandbox: Optional[bool] = None, + filters: Optional[List[str]] = None, + facet: Optional[List[str]] = None, + facet_limit: Optional[int] = None, + ) -> SearchResponse: """Run fulltext queries or filters on TextGrid metadata and fulltext objects. Please note: as the defaults of this function are mostly set to None, the defaults from @@ -339,27 +355,30 @@ class TextgridSearch(TextgridSearchRequest): SearchResponse: a list of textgrid metadata entries, KWIC hits, paths and facets if requested """ - - response = super().search(query=query, - sid=sid, - target=target, - order=order, - start=start, - limit=limit, - kwic_width=kwic_width, - word_distance=word_distance, - path=path, - all_projects=all_projects, - sandbox=sandbox, - filters=filters, - facet=facet, - facet_limit=facet_limit) + response = super().search( + query=query, + sid=sid, + target=target, + order=order, + start=start, + limit=limit, + kwic_width=kwic_width, + word_distance=word_distance, + path=path, + all_projects=all_projects, + sandbox=sandbox, + filters=filters, + facet=facet, + facet_limit=facet_limit, + ) return self._parser.parse(BytesIO(response.content), SearchResponse) + # pylint: enable-msg=too-many-arguments,too-many-locals - def edition_work_metadata_for(self, textgrid_uri: str, - sid: Optional[str] = None) -> SearchResponse: - """Find parent edition for an object and the edition and work metadata + def edition_work_metadata_for( + self, textgrid_uri: str, sid: Optional[str] = None + ) -> SearchResponse: + """Find parent edition for an object and the edition and work metadata. Args: textgrid_uri (str): Textgrid URI @@ -376,7 +395,7 @@ class TextgridSearch(TextgridSearchRequest): return self._parser.parse(BytesIO(response.content), SearchResponse) def children(self, textgrid_uri: str, sid: Optional[str] = None) -> TextgridUris: - """List URIs for all children of this aggregation and its child aggregations + """List URIs for all children of this aggregation and its child aggregations. Args: textgrid_uri (str): Textgrid URI of an aggregation @@ -393,4 +412,4 @@ class TextgridSearch(TextgridSearchRequest): class TextgridSearchException(Exception): - """Exception communicating with tgsearch""" + """Exception communicating with tgsearch!""" diff --git a/src/tgclients/utils.py b/src/tgclients/utils.py index 904074827c9e3d47bc5596ee14adf2c977dd2e05..c8795141ca3d2472badbce4e3bcf4908371aa90c 100644 --- a/src/tgclients/utils.py +++ b/src/tgclients/utils.py @@ -2,22 +2,21 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -"""Utility functions for working with the TextGrid repository""" +"""Utility functions for working with the TextGrid repository.""" + from pathlib import Path from typing import List import defusedxml.ElementTree as ET from jinja2 import Environment, FileSystemLoader -from tgclients.databinding.tgsearch import Response as SearchResponse - class Utils: - """Utility functions for working with the TextGrid repository""" + """Utility functions for working with the TextGrid repository.""" @staticmethod def list_to_aggregation(textgrid_uri: str, members: List[str]) -> str: - """Create XML for a TextGrid aggregation from list + """Create XML for a TextGrid aggregation from list. Args: textgrid_uri (str): textgrid URI of the aggregation to create @@ -27,15 +26,14 @@ class Utils: str: XML for TextGrid Aggregation """ path = Path(__file__).parent / 'templates' - env = Environment( - loader=FileSystemLoader(Path(path)), autoescape=True) + env = Environment(loader=FileSystemLoader(Path(path)), autoescape=True) template = env.get_template('aggregation.xml.jinja2') aggregation = template.render(id=textgrid_uri, members=members) return aggregation @staticmethod def aggregation_to_list(xml: str) -> List[str]: - """Extract URIs from TextGrid aggregation into a list + """Extract URIs from TextGrid aggregation into a list. Args: xml (str): TextGrid aggregation XML diff --git a/tests/conftest.py b/tests/conftest.py index b58980c651c38375c22ea2518ee1e95fa2ff6712..f10d1b492fc93baaf7741b33fcfe8afbfcd5312b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,49 +17,58 @@ def pytest_collection_modifyitems(config, items): if config.getoption('--integration'): # --integration given in cli: do not skip integration tests return - skip_integration = pytest.mark.skip( - reason='need --integration option to run') + skip_integration = pytest.mark.skip(reason='need --integration option to run') for item in items: if 'integration' in item.keywords: item.add_marker(skip_integration) + if os.getenv('TEXTGRID_HOST') is not None: _tgconfig = TextgridConfig(os.getenv('TEXTGRID_HOST')) else: _tgconfig = TextgridConfig() + @pytest.fixture def tgconfig() -> TextgridConfig: return _tgconfig + @pytest.fixture def search_public() -> TextgridSearch: return TextgridSearch(_tgconfig) + @pytest.fixture def searchrequest_public() -> TextgridSearchRequest: return TextgridSearchRequest(_tgconfig) + @pytest.fixture def crud_public(): return TextgridCrud(_tgconfig, for_publication=True) + @pytest.fixture def crud(): return TextgridCrud(_tgconfig) + @pytest.fixture def crud_request_public(): return TextgridCrudRequest(_tgconfig, for_publication=True) + @pytest.fixture def crud_request(): return TextgridCrudRequest(_tgconfig) + @pytest.fixture def auth(): return TextgridAuth(_tgconfig) + @pytest.fixture def aggregator(): return Aggregator(_tgconfig) diff --git a/tests/integration/test_aggregatgor_integration.py b/tests/integration/test_aggregatgor_integration.py index 4849936077679108fd3b632c84667310dcba1bf0..3bbfa56785cad87d08bc5a34cb2d242cca06a0b3 100644 --- a/tests/integration/test_aggregatgor_integration.py +++ b/tests/integration/test_aggregatgor_integration.py @@ -4,40 +4,34 @@ import pytest - @pytest.mark.integration class TestAggregatorIntegration: - @staticmethod def test_zip_single_uri(aggregator): - """ get a zip file for a single uri should be succesfull # noqa: DAR101 - """ + """get a zip file for a single uri should be succesfull # noqa: DAR101""" uri = 'textgrid:kv2q.0' res = aggregator.zip(uri) assert res.status_code == 200 @staticmethod def test_zip_urilist(aggregator): - """ get a zip file for a single uri should be succesfull # noqa: DAR101 - """ + """get a zip file for a single uri should be succesfull # noqa: DAR101""" uris = ['textgrid:kv2v.0', 'textgrid:kv2q.0'] res = aggregator.zip(uris) assert res.status_code == 200 @staticmethod def test_single_text(aggregator): - """get plain text for single uri # noqa: DAR101 - """ + """get plain text for single uri # noqa: DAR101""" uri = 'textgrid:kv2q.0' res = aggregator.text(uri) assert res.status_code == 200 - assert res.text.startswith('Lewis Carroll\nAlice\'s Abenteuer im Wunderland') + assert res.text.startswith("Lewis Carroll\nAlice's Abenteuer im Wunderland") assert res.text.endswith('an ihr eigenes Kindesleben und die glücklichen Sommertage.\n') @staticmethod def test_more_text_uri_string(aggregator): - """get plain text for two uris (as string) # noqa: DAR101 - """ + """get plain text for two uris (as string) # noqa: DAR101""" uri = 'textgrid:kv2v.0,textgrid:kv2q.0' res = aggregator.text(uri) assert res.status_code == 200 @@ -46,18 +40,16 @@ class TestAggregatorIntegration: @staticmethod def test_more_text_uri_string_different_order(aggregator): - """get plain text for two uris (as string) in reverse order # noqa: DAR101 - """ + """get plain text for two uris (as string) in reverse order # noqa: DAR101""" uri = 'textgrid:kv2q.0,textgrid:kv2v.0' res = aggregator.text(uri) assert res.status_code == 200 - assert res.text.startswith('Lewis Carroll\nAlice\'s Abenteuer im Wunderland') + assert res.text.startswith("Lewis Carroll\nAlice's Abenteuer im Wunderland") assert res.text.endswith('im Haus seiner Schwester bei Guildford und wird dort beerdigt.\n') @staticmethod def test_more_text_uri_list(aggregator): - """get plain text for two uris (as list) # noqa: DAR101 - """ + """get plain text for two uris (as list) # noqa: DAR101""" uri = ['textgrid:kv2v.0', 'textgrid:kv2q.0'] res = aggregator.text(uri) assert res.status_code == 200 @@ -66,38 +58,34 @@ class TestAggregatorIntegration: @staticmethod def test_more_text_uri_list_different_order(aggregator): - """get plain text for two uris (as list) in reverse order # noqa: DAR101 - """ + """get plain text for two uris (as list) in reverse order # noqa: DAR101""" uri = ['textgrid:kv2q.0', 'textgrid:kv2v.0'] res = aggregator.text(uri) assert res.status_code == 200 - assert res.text.startswith('Lewis Carroll\nAlice\'s Abenteuer im Wunderland') + assert res.text.startswith("Lewis Carroll\nAlice's Abenteuer im Wunderland") assert res.text.endswith('im Haus seiner Schwester bei Guildford und wird dort beerdigt.\n') @staticmethod def test_single_teicorpus(aggregator): - """get teicorpus for single uri # noqa: DAR101 - """ + """get teicorpus for single uri # noqa: DAR101""" uri = 'textgrid:kv2q.0' res = aggregator.teicorpus(uri) assert res.status_code == 200 - assert res.text.startswith('<?xml version=\'1.0\' encoding=\'UTF-8\'?><TEI') + assert res.text.startswith("<?xml version='1.0' encoding='UTF-8'?><TEI") assert res.text.endswith('</TEI>') @staticmethod def test_teicorpus_uri_list(aggregator): - """get teicorpus for two uris (as list) # noqa: DAR101 - """ + """get teicorpus for two uris (as list) # noqa: DAR101""" uri = ['textgrid:kv2v.0', 'textgrid:kv2q.0'] res = aggregator.teicorpus(uri) assert res.status_code == 200 - assert res.text.startswith('<?xml version=\'1.0\' encoding=\'UTF-8\'?><teiCorpus') + assert res.text.startswith("<?xml version='1.0' encoding='UTF-8'?><teiCorpus") assert res.text.endswith('</TEI></teiCorpus>') @staticmethod def test_single_render(aggregator): - """get html for single uri # noqa: DAR101 - """ + """get html for single uri # noqa: DAR101""" uri = 'textgrid:kv2q.0' res = aggregator.render(uri) assert res.status_code == 200 @@ -106,8 +94,7 @@ class TestAggregatorIntegration: @staticmethod def test_render_urilist(aggregator): - """get html for two uris # noqa: DAR101 - """ + """get html for two uris # noqa: DAR101""" uri = ['textgrid:kv2v.0', 'textgrid:kv2q.0'] res = aggregator.render(uri) assert res.status_code == 200 @@ -117,8 +104,8 @@ class TestAggregatorIntegration: @staticmethod def test_single_render_stylesheet(aggregator): """test rendering for single uri - with own stylesheet. - textgrid:4228q.2 counts words # noqa: DAR101 + with own stylesheet. + textgrid:4228q.2 counts words # noqa: DAR101 """ uri = 'textgrid:kv2q.0' res = aggregator.render(uri, stylesheet_uri='textgrid:4228q.2', mediatype='text/plain') @@ -128,8 +115,8 @@ class TestAggregatorIntegration: @staticmethod def test_render_urilist_stylesheet(aggregator): """test rendering for two uris - with own stylesheet. - textgrid:4228q.2 counts words # noqa: DAR101 + with own stylesheet. + textgrid:4228q.2 counts words # noqa: DAR101 """ uri = ['textgrid:kv2v.0', 'textgrid:kv2q.0'] res = aggregator.render(uri, stylesheet_uri='textgrid:4228q.2', mediatype='text/plain') diff --git a/tests/integration/test_auth_integration.py b/tests/integration/test_auth_integration.py index e868463971f833a346c0dfbc139a40a26e8c30c0..c81e2089d0870c973ef4e37017e6959358bdb138 100644 --- a/tests/integration/test_auth_integration.py +++ b/tests/integration/test_auth_integration.py @@ -11,7 +11,6 @@ from tgclients.auth import TextgridAuthException @pytest.mark.integration class TestTextgridAuthIntegration: - @staticmethod def test_list_assigned_projects(auth): sid = os.getenv('SESSION_ID') @@ -37,8 +36,7 @@ class TestTextgridAuthIntegration: @staticmethod def test_get_project_description(auth): - project_info = auth.get_project_description( - 'TGPR-372fe6dc-57f2-6cd4-01b5-2c4bbefcfd3c') + project_info = auth.get_project_description('TGPR-372fe6dc-57f2-6cd4-01b5-2c4bbefcfd3c') assert project_info.name == 'Digitale Bibliothek' @staticmethod @@ -46,7 +44,10 @@ class TestTextgridAuthIntegration: sid = os.getenv('SESSION_ID') now = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') project_id = auth.create_project( - sid, 'Integration test project from tgclients ' + now, 'Integration test project from tgclients') + sid, + 'Integration test project from tgclients ' + now, + 'Integration test project from tgclients', + ) assert len(project_id) > 1 res = auth.delete_project(sid, project_id) assert res is True @@ -62,8 +63,12 @@ class TestTextgridAuthIntegration: def test_create_without_valid_sid(auth): sid = 'no-valid-sid' with pytest.raises(TextgridAuthException): - auth.create_project(sid, 'Integration test project from tgclients', - 'Integration test project from tgclients', False) + auth.create_project( + sid, + 'Integration test project from tgclients', + 'Integration test project from tgclients', + False, + ) @staticmethod def test_delete_without_valid_sid(auth): diff --git a/tests/integration/test_crud_integration.py b/tests/integration/test_crud_integration.py index a748172428053254b071c0e4e8bf6e222ba92782..0e1b39a35246fd7e44ba382bc4a5a81d038bec96 100644 --- a/tests/integration/test_crud_integration.py +++ b/tests/integration/test_crud_integration.py @@ -6,15 +6,14 @@ import os from datetime import datetime import pytest -from tgclients.metadata import TextgridMetadata from tgclients.crud import TextgridCrudException +from tgclients.metadata import TextgridMetadata FIXTURE_PATH = './tests/fixtures/' @pytest.mark.integration class TestTextgridCrudIntegration: - @staticmethod def test_read_public_data(crud): uri = 'textgrid:kv2q.0' @@ -46,8 +45,7 @@ class TestTextgridCrudIntegration: sid = os.getenv('SESSION_ID') project_id = os.getenv('PROJECT_ID') now = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') - metadata = TextgridMetadata().build( - title='test '+now, mimetype='text/xml') + metadata = TextgridMetadata().build(title='test ' + now, mimetype='text/xml') data = '<content>here</content>' res = crud.create_resource(sid, project_id, data, metadata) @@ -61,8 +59,7 @@ class TestTextgridCrudIntegration: sid = os.getenv('SESSION_ID') project_id = os.getenv('PROJECT_ID') now = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') - metadata = TextgridMetadata().build( - title='test '+now, mimetype='text/xml') + metadata = TextgridMetadata().build(title='test ' + now, mimetype='text/xml') data = '<content>here</content>' md_rev0 = crud.create_resource(sid, project_id, data, metadata) @@ -77,8 +74,7 @@ class TestTextgridCrudIntegration: assert textgrid_rev1_uri.endswith('.1') assert md_rev1.object_value.generic.generated.revision == 1 - md_rev2 = crud.update_resource( - sid, textgrid_rev1_uri, data, md_rev1, create_revision=True) + md_rev2 = crud.update_resource(sid, textgrid_rev1_uri, data, md_rev1, create_revision=True) textgrid_rev2_uri = md_rev2.object_value.generic.generated.textgrid_uri.value assert textgrid_rev2_uri.startswith('textgrid:') assert textgrid_rev2_uri.endswith('.2') @@ -99,8 +95,7 @@ class TestTextgridCrudIntegration: project_id = os.getenv('PROJECT_ID') now = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') title = 'test ' + now - metadata = TextgridMetadata().build( - title=title, mimetype='text/xml') + metadata = TextgridMetadata().build(title=title, mimetype='text/xml') data = '<content>here</content>' cmeta = crud.create_resource(sid, project_id, data, metadata) @@ -122,8 +117,7 @@ class TestTextgridCrudIntegration: sid = os.getenv('SESSION_ID') project_id = os.getenv('PROJECT_ID') now = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') - metadata = TextgridMetadata().build( - title='test '+now, mimetype='text/plain') + metadata = TextgridMetadata().build(title='test ' + now, mimetype='text/plain') # the content on creation cdata = 'created' @@ -138,8 +132,7 @@ class TestTextgridCrudIntegration: umetadata = crud.read_metadata(textgrid_uri, sid) # updated content udata = 'updated' - res = crud.update_resource( - sid, textgrid_uri, udata, umetadata) + res = crud.update_resource(sid, textgrid_uri, udata, umetadata) textgrid_uri = res.object_value.generic.generated.textgrid_uri.value assert textgrid_uri.startswith('textgrid:') @@ -155,8 +148,7 @@ class TestTextgridCrudIntegration: sid = os.getenv('SESSION_ID') project_id = os.getenv('PROJECT_ID') now = datetime.now().strftime('%Y-%m-%d-%H_M_%S') - metadata = TextgridMetadata().build( - title='stream-test '+now, mimetype='image/png') + metadata = TextgridMetadata().build(title='stream-test ' + now, mimetype='image/png') ### # the content on creation @@ -164,8 +156,7 @@ class TestTextgridCrudIntegration: cfile_loc = FIXTURE_PATH + 'eulen150dpi.png' csize = os.path.getsize(cfile_loc) with open(cfile_loc, 'rb') as cdata: - res = crud.create_resource( - sid, project_id, cdata, metadata) + res = crud.create_resource(sid, project_id, cdata, metadata) textgrid_uri = res.object_value.generic.generated.textgrid_uri.value assert textgrid_uri.startswith('textgrid:') @@ -188,8 +179,7 @@ class TestTextgridCrudIntegration: ufile_loc = FIXTURE_PATH + 'eulen300dpi.png' usize = os.path.getsize(ufile_loc) with open(ufile_loc, 'rb') as udata: - res = crud.update_resource( - sid, textgrid_uri, udata, umetadata) + res = crud.update_resource(sid, textgrid_uri, udata, umetadata) textgrid_uri = res.object_value.generic.generated.textgrid_uri.value assert textgrid_uri.startswith('textgrid:') diff --git a/tests/integration/test_crud_request_integration.py b/tests/integration/test_crud_request_integration.py index 5ca0a6d6ff25963b37a88570705c86e911865c15..4ca96dbb8af1326f4135961c2b9bab84ab8bafb7 100644 --- a/tests/integration/test_crud_request_integration.py +++ b/tests/integration/test_crud_request_integration.py @@ -13,7 +13,6 @@ FIXTURE_PATH = './tests/fixtures/' @pytest.mark.integration class TestTextgridCrudIntegration: - @staticmethod def test_read_public_data(crud_request): uri = 'textgrid:kv2q.0' @@ -37,8 +36,7 @@ class TestTextgridCrudIntegration: sid = os.getenv('SESSION_ID') project_id = os.getenv('PROJECT_ID') now = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') - metadata = TextgridMetadata.create( - title='test '+now, mimetype='text/xml') + metadata = TextgridMetadata.create(title='test ' + now, mimetype='text/xml') data = '<content>here</content>' res = crud_request.create_resource(sid, project_id, data, metadata) @@ -53,8 +51,7 @@ class TestTextgridCrudIntegration: sid = os.getenv('SESSION_ID') project_id = os.getenv('PROJECT_ID') now = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') - metadata = TextgridMetadata().create( - title='test '+now, mimetype='text/xml') + metadata = TextgridMetadata().create(title='test ' + now, mimetype='text/xml') data = '<content>here</content>' res_rev0 = crud_request.create_resource(sid, project_id, data, metadata) @@ -71,9 +68,9 @@ class TestTextgridCrudIntegration: assert textgrid_rev1_uri.startswith('textgrid:') assert textgrid_rev1_uri.endswith('.1') - res_rev2 = crud_request.update_resource( - sid, textgrid_rev1_uri, data, md_rev1, create_revision=True) + sid, textgrid_rev1_uri, data, md_rev1, create_revision=True + ) assert res_rev2.status_code == 200 textgrid_rev2_uri = res_rev2.headers['Location'] assert textgrid_rev2_uri.startswith('textgrid:') @@ -93,8 +90,7 @@ class TestTextgridCrudIntegration: sid = os.getenv('SESSION_ID') project_id = os.getenv('PROJECT_ID') now = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') - metadata = TextgridMetadata.create( - title='test '+now, mimetype='text/plain') + metadata = TextgridMetadata.create(title='test ' + now, mimetype='text/plain') # the content on creation cdata = 'created' @@ -126,13 +122,12 @@ class TestTextgridCrudIntegration: sid = os.getenv('SESSION_ID') project_id = os.getenv('PROJECT_ID') now = datetime.now().strftime('%Y-%m-%d-%H_M_%S') - metadata = TextgridMetadata.create( - title='stream-test '+now, mimetype='image/png') + metadata = TextgridMetadata.create(title='stream-test ' + now, mimetype='image/png') ### # the content on creation ### - cfile_loc = FIXTURE_PATH + 'eulen150dpi.png' + cfile_loc = FIXTURE_PATH + 'eulen150dpi.png' csize = os.path.getsize(cfile_loc) with open(cfile_loc, 'rb') as cdata: res = crud_request.create_resource(sid, project_id, cdata, metadata) @@ -141,7 +136,7 @@ class TestTextgridCrudIntegration: assert res.headers['Location'].startswith('textgrid:') textgrid_uri = res.headers['Location'] - dl1_png_loc = '/tmp/unit-'+now+'-1.png' + dl1_png_loc = '/tmp/unit-' + now + '-1.png' res = crud_request.read_data(textgrid_uri, sid) assert res.status_code == 200 @@ -156,7 +151,7 @@ class TestTextgridCrudIntegration: ### # metadata for update needs to have correct last-modified date umetadata = str(crud_request.read_metadata(textgrid_uri, sid).content, 'utf-8') - ufile_loc = FIXTURE_PATH + 'eulen300dpi.png' + ufile_loc = FIXTURE_PATH + 'eulen300dpi.png' usize = os.path.getsize(ufile_loc) with open(ufile_loc, 'rb') as udata: res = crud_request.update_resource(sid, textgrid_uri, udata, umetadata) @@ -164,7 +159,7 @@ class TestTextgridCrudIntegration: assert res.status_code == 200 assert res.headers['Location'].startswith('textgrid:') - dl2_png_loc = '/tmp/unit-'+now+'-2.png' + dl2_png_loc = '/tmp/unit-' + now + '-2.png' res = crud_request.read_data(textgrid_uri, sid) assert res.status_code == 200 diff --git a/tests/integration/test_notebooks.py b/tests/integration/test_notebooks.py index e946775f72566dec3898b7ec76f87c062b0c892a..81f6bf66ff5ba9bcbf7d1631d46da466516f69e2 100644 --- a/tests/integration/test_notebooks.py +++ b/tests/integration/test_notebooks.py @@ -9,7 +9,6 @@ from testbook import testbook @pytest.mark.integration class TestNotebooks: - @staticmethod @testbook('notebooks/Kallimachos Import.ipynb') def test_notebook(tb, crud, auth, tgconfig): @@ -19,13 +18,13 @@ class TestNotebooks: sid = os.getenv('SESSION_ID') host = tgconfig.host - tb.inject(f''' + tb.inject(f""" SID = "{sid}" TG_HOST = "{host}" from tgclients.config import TextgridConfig config = TextgridConfig(TG_HOST) - ''') + """) tb.execute_cell('create-project') project_id = tb.ref('project_id') assert project_id.startswith('TGPR-') @@ -35,8 +34,9 @@ class TestNotebooks: assert collection_uri.startswith('textgrid:') assert collection_uri.endswith('.0') - res = tb.execute_cell('list-collection') + tb.execute_cell('list-collection') # this test should work again when rdf4j federated repo is fixed + # res = tb.execute_cell('list-collection') # assert 'Die Rumplhanni' in res['outputs'][0]['text'] # cleanup diff --git a/tests/integration/test_textgrid_search_integration.py b/tests/integration/test_textgrid_search_integration.py index 216c6bcc18f135bcf64d1d67cd5e424635bad25b..c93b00f3b0bf3a08650e4e09b317999987aeb603 100644 --- a/tests/integration/test_textgrid_search_integration.py +++ b/tests/integration/test_textgrid_search_integration.py @@ -3,12 +3,12 @@ # SPDX-License-Identifier: LGPL-3.0-or-later import pytest -from tgclients.databinding.tgsearch import Response as SearchResponse, TextgridUris +from tgclients.databinding.tgsearch import Response as SearchResponse +from tgclients.databinding.tgsearch import TextgridUris @pytest.mark.integration class TestTextgridSearchIntegration: - @staticmethod def test_public_info(search_public): uri = 'textgrid:kv2q.0' @@ -38,8 +38,7 @@ class TestTextgridSearchIntegration: @staticmethod def test_public_search_filter_format_project(search_public): pid = 'TGPR-372fe6dc-57f2-6cd4-01b5-2c4bbefcfd3c' - filters = ['format:text/xml', - 'project.id:'+pid] + filters = ['format:text/xml', 'project.id:' + pid] res: SearchResponse = search_public.search(filters=filters) assert int(res.hits) > 100 for r in res.result: diff --git a/tests/integration/test_textgrid_search_request_integration.py b/tests/integration/test_textgrid_search_request_integration.py index 337efb0c470f4e9f547d3962768eddb4d0f1199a..e591893aff8d4585f39b6a3f4afa62a46912dc26 100644 --- a/tests/integration/test_textgrid_search_request_integration.py +++ b/tests/integration/test_textgrid_search_request_integration.py @@ -7,7 +7,6 @@ import pytest @pytest.mark.integration class TestTextgridSearchRequestIntegration: - @staticmethod def test_public_info(searchrequest_public): uri = 'textgrid:kv2q.0' diff --git a/tests/integration/test_textgrid_utils.py b/tests/integration/test_textgrid_utils.py index 79dc0e07ce64f1343e96e683562a7a98abf70891..a8a3775dc69828efe54b1b846147ce5e3be96ff5 100644 --- a/tests/integration/test_textgrid_utils.py +++ b/tests/integration/test_textgrid_utils.py @@ -2,12 +2,11 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from tgclients.utils import Utils def test_public_aggregation_to_list(search_public): uri = 'textgrid:kv2p.0' res = search_public.list_aggregation(uri) - #assert res.status_code == 200 + # assert res.status_code == 200 urilist = [res.textgrid_uri for res in res.result] print(urilist) - assert(urilist == ['textgrid:kv2x.0', 'textgrid:kv2t.0']) + assert urilist == ['textgrid:kv2x.0', 'textgrid:kv2t.0'] diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py index 694750595de71da0c6e63fc39e1b57ca6b456871..9033864d9dffd8b8f256716089b6a6e4db2a600f 100644 --- a/tests/unit/__init__.py +++ b/tests/unit/__init__.py @@ -4,6 +4,7 @@ FIXTURE_PATH = './tests/fixtures/' + def fileLoader(filename): def loader(req, context): with open(FIXTURE_PATH + filename, 'r') as f: @@ -11,6 +12,7 @@ def fileLoader(filename): return loader + def binaryFileLoader(filename): def loader(req, context): with open(FIXTURE_PATH + filename, 'rb') as f: diff --git a/tests/unit/test_aggregatgor.py b/tests/unit/test_aggregatgor.py index 788379d40e47a51fec60a15c85a02931fe6316e5..d4c05400273ca84420e2cac923c8a0cb83ce132d 100644 --- a/tests/unit/test_aggregatgor.py +++ b/tests/unit/test_aggregatgor.py @@ -1,55 +1,53 @@ # SPDX-FileCopyrightText: 2022 Georg-August-Universität Göttingen # # SPDX-License-Identifier: LGPL-3.0-or-later -import pytest -from . import fileLoader, binaryFileLoader +from . import binaryFileLoader, fileLoader class TestAggregator: - @staticmethod def test_zip_single_uri(requests_mock, aggregator, tgconfig): - """ get a zip file for a single uri should be succesfull # noqa: DAR101 - """ + """get a zip file for a single uri should be succesfull # noqa: DAR101""" requests_mock.get( tgconfig.host + '/1.0/aggregator/zip/textgrid:kv2q.0', - content=binaryFileLoader('aggregator/zip_kv2q.0')) + content=binaryFileLoader('aggregator/zip_kv2q.0'), + ) uri = 'textgrid:kv2q.0' res = aggregator.zip(uri) assert res.status_code == 200 @staticmethod def test_zip_urilist(requests_mock, aggregator, tgconfig): - """ get a zip file for a single uri should be succesfull # noqa: DAR101 - """ + """get a zip file for a single uri should be succesfull # noqa: DAR101""" requests_mock.get( tgconfig.host + '/1.0/aggregator/zip/textgrid:kv2v.0,textgrid:kv2q.0', - content=binaryFileLoader('aggregator/zip_kv2v.0_kv2q.0')) + content=binaryFileLoader('aggregator/zip_kv2v.0_kv2q.0'), + ) uris = ['textgrid:kv2v.0', 'textgrid:kv2q.0'] res = aggregator.zip(uris) assert res.status_code == 200 @staticmethod def test_single_text(requests_mock, aggregator, tgconfig): - """get plain text for single uri # noqa: DAR101 - """ + """get plain text for single uri # noqa: DAR101""" requests_mock.get( tgconfig.host + '/1.0/aggregator/text/textgrid:kv2q.0', - text=fileLoader('aggregator/text_kv2q.0')) + text=fileLoader('aggregator/text_kv2q.0'), + ) uri = 'textgrid:kv2q.0' res = aggregator.text(uri) assert res.status_code == 200 - assert res.text.startswith('Lewis Carroll\nAlice\'s Abenteuer im Wunderland') + assert res.text.startswith("Lewis Carroll\nAlice's Abenteuer im Wunderland") assert res.text.endswith('an ihr eigenes Kindesleben und die glücklichen Sommertage.\n') @staticmethod def test_more_text_uri_string(requests_mock, aggregator, tgconfig): - """get plain text for two uris (as string) # noqa: DAR101 - """ + """get plain text for two uris (as string) # noqa: DAR101""" requests_mock.get( tgconfig.host + '/1.0/aggregator/text/textgrid:kv2v.0,textgrid:kv2q.0', - text=fileLoader('aggregator/text_kv2v.0_kv2q.0')) + text=fileLoader('aggregator/text_kv2v.0_kv2q.0'), + ) uri = 'textgrid:kv2v.0,textgrid:kv2q.0' res = aggregator.text(uri) assert res.status_code == 200 @@ -58,24 +56,24 @@ class TestAggregator: @staticmethod def test_more_text_uri_string_different_order(requests_mock, aggregator, tgconfig): - """get plain text for two uris (as string) in reverse order # noqa: DAR101 - """ + """get plain text for two uris (as string) in reverse order # noqa: DAR101""" requests_mock.get( tgconfig.host + '/1.0/aggregator/text/textgrid:kv2q.0,textgrid:kv2v.0', - text=fileLoader('aggregator/text_kv2q.0_kv2v.0')) + text=fileLoader('aggregator/text_kv2q.0_kv2v.0'), + ) uri = 'textgrid:kv2q.0,textgrid:kv2v.0' res = aggregator.text(uri) assert res.status_code == 200 - assert res.text.startswith('Lewis Carroll\nAlice\'s Abenteuer im Wunderland') + assert res.text.startswith("Lewis Carroll\nAlice's Abenteuer im Wunderland") assert res.text.endswith('im Haus seiner Schwester bei Guildford und wird dort beerdigt.\n') @staticmethod def test_more_text_uri_list(requests_mock, aggregator, tgconfig): - """get plain text for two uris (as list) # noqa: DAR101 - """ + """get plain text for two uris (as list) # noqa: DAR101""" requests_mock.get( tgconfig.host + '/1.0/aggregator/text/textgrid:kv2v.0,textgrid:kv2q.0', - text=fileLoader('aggregator/text_kv2v.0_kv2q.0')) + text=fileLoader('aggregator/text_kv2v.0_kv2q.0'), + ) uri = ['textgrid:kv2v.0', 'textgrid:kv2q.0'] res = aggregator.text(uri) assert res.status_code == 200 @@ -84,52 +82,54 @@ class TestAggregator: @staticmethod def test_more_text_uri_list_different_order(requests_mock, aggregator, tgconfig): - """get plain text for two uris (as list) in reverse order # noqa: DAR101 - """ + """get plain text for two uris (as list) in reverse order # noqa: DAR101""" requests_mock.get( tgconfig.host + '/1.0/aggregator/text/textgrid:kv2q.0,textgrid:kv2v.0', - text=fileLoader('aggregator/text_kv2q.0_kv2v.0')) + text=fileLoader('aggregator/text_kv2q.0_kv2v.0'), + ) uri = ['textgrid:kv2q.0', 'textgrid:kv2v.0'] res = aggregator.text(uri) assert res.status_code == 200 - assert res.text.startswith('Lewis Carroll\nAlice\'s Abenteuer im Wunderland') + assert res.text.startswith("Lewis Carroll\nAlice's Abenteuer im Wunderland") assert res.text.endswith('im Haus seiner Schwester bei Guildford und wird dort beerdigt.\n') @staticmethod def test_single_teicorpus(requests_mock, aggregator, tgconfig): - """get teicorpus for single uri # noqa: DAR101 - """ + """get teicorpus for single uri # noqa: DAR101""" requests_mock.get( tgconfig.host + '/1.0/aggregator/teicorpus/textgrid:kv2q.0', - text=fileLoader('aggregator/teicorpus_kv2q.0')) + text=fileLoader('aggregator/teicorpus_kv2q.0'), + ) uri = 'textgrid:kv2q.0' res = aggregator.teicorpus(uri) assert res.status_code == 200 - assert res.text.startswith('<?xml version=\'1.0\' encoding=\'UTF-8\'?><TEI') + assert res.text.startswith("<?xml version='1.0' encoding='UTF-8'?><TEI") assert res.text.endswith('</TEI>') @staticmethod def test_teicorpus_uri_list(requests_mock, aggregator, tgconfig): - """get teicorpus for two uris (as list) # noqa: DAR101 - """ + """get teicorpus for two uris (as list) # noqa: DAR101""" requests_mock.get( tgconfig.host + '/1.0/aggregator/teicorpus/textgrid:kv2v.0,textgrid:kv2q.0', - text=fileLoader('aggregator/teicorpus_kv2v.0_kv2q.0')) + text=fileLoader('aggregator/teicorpus_kv2v.0_kv2q.0'), + ) uri = ['textgrid:kv2v.0', 'textgrid:kv2q.0'] res = aggregator.teicorpus(uri) assert res.status_code == 200 - assert res.text.startswith('<?xml version=\'1.0\' encoding=\'UTF-8\'?><teiCorpus') + assert res.text.startswith("<?xml version='1.0' encoding='UTF-8'?><teiCorpus") assert res.text.endswith('</TEI></teiCorpus>') @staticmethod def test_single_render_stylesheet(requests_mock, aggregator, tgconfig): """test rendering for single uri - with own stylesheet. - textgrid:4228q.2 counts words # noqa: DAR101 + with own stylesheet. + textgrid:4228q.2 counts words # noqa: DAR101 """ requests_mock.get( - tgconfig.host + '/1.0/aggregator/html/textgrid:kv2q.0?stylesheet=textgrid%3A4228q.2&mediatype=text%2Fplain', - text='25598') + tgconfig.host + + '/1.0/aggregator/html/textgrid:kv2q.0?stylesheet=textgrid%3A4228q.2&mediatype=text%2Fplain', + text='25598', + ) uri = 'textgrid:kv2q.0' res = aggregator.render(uri, stylesheet_uri='textgrid:4228q.2', mediatype='text/plain') assert res.status_code == 200 @@ -138,12 +138,14 @@ class TestAggregator: @staticmethod def test_render_urilist_stylesheet(requests_mock, aggregator, tgconfig): """test rendering for two uris - with own stylesheet. - textgrid:4228q.2 counts words # noqa: DAR101 + with own stylesheet. + textgrid:4228q.2 counts words # noqa: DAR101 """ requests_mock.get( - tgconfig.host + '/1.0/aggregator/html/textgrid:kv2v.0,textgrid:kv2q.0?stylesheet=textgrid%3A4228q.2&mediatype=text%2Fplain', - text='26311') + tgconfig.host + + '/1.0/aggregator/html/textgrid:kv2v.0,textgrid:kv2q.0?stylesheet=textgrid%3A4228q.2&mediatype=text%2Fplain', + text='26311', + ) uri = ['textgrid:kv2v.0', 'textgrid:kv2q.0'] res = aggregator.render(uri, stylesheet_uri='textgrid:4228q.2', mediatype='text/plain') assert res.status_code == 200 diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py index e12cb7af53c84cdbba64b119848dbc2cb3721c33..3fcb22090b1aefb67b4e01321ecc105ce7bcdfa2 100644 --- a/tests/unit/test_auth.py +++ b/tests/unit/test_auth.py @@ -4,8 +4,9 @@ from tgclients.auth import TextgridAuth + def test_empty_constructor(): """Not passing a config in constructor should provide a client for - the prodction system textgridlab.org""" + the prodction system textgridlab.org""" tga = TextgridAuth() - assert tga._config.auth_address.startswith("https://textgridlab.org/1.0/") + assert tga._config.auth_address.startswith('https://textgridlab.org/1.0/') diff --git a/tests/unit/test_crud_request.py b/tests/unit/test_crud_request.py index da706a4bc0c8f1a009d23a4fe49826cedc439e9d..82a2bee362b478913bf7dacd9cbeef6898d9365a 100644 --- a/tests/unit/test_crud_request.py +++ b/tests/unit/test_crud_request.py @@ -4,30 +4,29 @@ from datetime import datetime -import pytest from tgclients.metadata import TextgridMetadata -from tgclients.config import TextgridConfig -from tgclients.crud import TextgridCrud, TextgridCrudException -from . import fileLoader, binaryFileLoader +from . import binaryFileLoader class TestTextgridCrudRequest: - @staticmethod def test_create_revision_and_delete(requests_mock, crud_request, tgconfig): requests_mock.post( - tgconfig.host + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&projectId=PROJECT_ID&createRevision=false', + tgconfig.host + + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&projectId=PROJECT_ID&createRevision=false', headers={'Location': 'textgrid:kv2q.0'}, content=binaryFileLoader('crud/kv2q.0.metadata'), ) requests_mock.post( - tgconfig.host + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&uri=textgrid%3Akv2q.0&createRevision=true&projectId=PROJECT_ID', + tgconfig.host + + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&uri=textgrid%3Akv2q.0&createRevision=true&projectId=PROJECT_ID', headers={'Location': 'textgrid:kv2q.1'}, content=binaryFileLoader('crud/kv2q.1.metadata'), ) requests_mock.post( - tgconfig.host + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&uri=textgrid%3Akv2q.1&createRevision=true&projectId=PROJECT_ID', + tgconfig.host + + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&uri=textgrid%3Akv2q.1&createRevision=true&projectId=PROJECT_ID', headers={'Location': 'textgrid:kv2q.2'}, content=binaryFileLoader('crud/kv2q.2.metadata'), ) @@ -46,8 +45,7 @@ class TestTextgridCrudRequest: sid = 'SESSION_ID' # does not matter for mocked requests project_id = 'PROJECT_ID' # does not matter for mocked requests now = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') - metadata = TextgridMetadata().create( - title='test '+now, mimetype='text/xml') + metadata = TextgridMetadata().create(title='test ' + now, mimetype='text/xml') data = '<content>here</content>' res_rev0 = crud_request.create_resource(sid, project_id, data, metadata) @@ -65,7 +63,8 @@ class TestTextgridCrudRequest: assert textgrid_rev1_uri.endswith('.1') res_rev2 = crud_request.update_resource( - sid, textgrid_rev1_uri, data, md_rev1, create_revision=True) + sid, textgrid_rev1_uri, data, md_rev1, create_revision=True + ) assert res_rev2.status_code == 200 textgrid_rev2_uri = res_rev2.headers['Location'] assert textgrid_rev2_uri.startswith('textgrid:') @@ -82,25 +81,25 @@ class TestTextgridCrudRequest: @staticmethod def test_error_msg_from_html(crud_request): - html = ''' + html = """ <html> <head> <meta name="description" content="blubber"> </head> </html> - ''' + """ msg = crud_request._error_msg_from_html(html) - assert msg == "blubber" + assert msg == 'blubber' @staticmethod def test_error_msg_from_html_no_meta(crud_request): - html = '''<html> + html = """<html> <head> <title>look, error with no meta tag</title> </head> </html> - ''' + """ msg = crud_request._error_msg_from_html(html) assert msg.startswith('<html>') diff --git a/tests/unit/test_textgrid_config.py b/tests/unit/test_textgrid_config.py index 99e690334845d617cac253a3c54f75ead0a33636..90d412a1f5ba15e7b26dd7522a8aafcc97fe391d 100644 --- a/tests/unit/test_textgrid_config.py +++ b/tests/unit/test_textgrid_config.py @@ -2,57 +2,59 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from tgclients.config import TextgridConfig, DEFAULT_TIMEOUT, DEV_SERVER, TEST_SERVER +from tgclients.config import DEFAULT_TIMEOUT, DEV_SERVER, TEST_SERVER, TextgridConfig + def test_default_config(): - """ default empty constructor should be configured for textgrid production server - """ + """default empty constructor should be configured for textgrid production server""" config = TextgridConfig() assert config.host == 'https://textgridlab.org' assert config.search == 'https://textgridlab.org/1.0/tgsearch' + def test_dev_config(): - """ constructor should overwrite textgrid host - """ + """constructor should overwrite textgrid host""" config = TextgridConfig('https://dev.textgridlab.org') assert config.host == 'https://dev.textgridlab.org' assert config.search == 'https://dev.textgridlab.org/1.0/tgsearch' + def test_dev_config_from_const(): - """ constructor should overwrite textgrid host - """ + """constructor should overwrite textgrid host""" config = TextgridConfig(DEV_SERVER) assert config.host == 'https://dev.textgridlab.org' assert config.search == 'https://dev.textgridlab.org/1.0/tgsearch' + def test_test_config_from_const(): - """ constructor should overwrite textgrid host - """ + """constructor should overwrite textgrid host""" config = TextgridConfig(TEST_SERVER) assert config.host == 'https://test.textgridlab.org' assert config.search == 'https://test.textgridlab.org/1.0/tgsearch' + def test_host_url_trailing_slash(): - """ constructor should remove trailing slashes - """ + """constructor should remove trailing slashes""" config = TextgridConfig('https://dev.textgridlab.org/') assert config.host == 'https://dev.textgridlab.org' assert config.search == 'https://dev.textgridlab.org/1.0/tgsearch' + def test_get_set_http_timeout(): - """ default timeout should be overwritten by setter - """ + """default timeout should be overwritten by setter""" config = TextgridConfig() assert config.http_timeout == DEFAULT_TIMEOUT new_timeout: float = 30.2 config.http_timeout = new_timeout assert config.http_timeout is new_timeout + def test_default_for_none(): config = TextgridConfig(None) assert config.host == 'https://textgridlab.org' assert config.search == 'https://textgridlab.org/1.0/tgsearch' + def test_default_for_empty(): config = TextgridConfig('') assert config.host == 'https://textgridlab.org' diff --git a/tests/unit/test_textgrid_crud.py b/tests/unit/test_textgrid_crud.py index d6d6156e92b070774ac2242bd3f267b14b7af0bb..7192bd7f65bc895fe9b253562ae8828b339422ca 100644 --- a/tests/unit/test_textgrid_crud.py +++ b/tests/unit/test_textgrid_crud.py @@ -5,19 +5,18 @@ from datetime import datetime import pytest -from tgclients.metadata import TextgridMetadata from tgclients.config import TextgridConfig from tgclients.crud import TextgridCrud, TextgridCrudException +from tgclients.metadata import TextgridMetadata -from . import fileLoader, binaryFileLoader +from . import binaryFileLoader class TestTextgridCrud: - @staticmethod def test_empty_constructor(): """Not passing a config in constructor should provide a client for - tgcrud nonpublic on production system""" + tgcrud nonpublic on production system""" tgc = TextgridCrud() assert tgc._url == TextgridConfig().crud @@ -25,7 +24,8 @@ class TestTextgridCrud: def test_read_public_data(requests_mock, crud, tgconfig): requests_mock.get( tgconfig.host + '/1.0/tgcrud/rest/textgrid:kv2q.0/data', - content=binaryFileLoader('crud/kv2q.0.data')) + content=binaryFileLoader('crud/kv2q.0.data'), + ) uri = 'textgrid:kv2q.0' res = crud.read_data(uri) assert 'Alice fing an sich zu langweilen;' in str(res.content, 'utf-8') @@ -34,7 +34,8 @@ class TestTextgridCrud: def test_read_public_metadata(requests_mock, crud, tgconfig): requests_mock.get( tgconfig.host + '/1.0/tgcrud/rest/textgrid:kv2q.0/metadata', - content=binaryFileLoader('crud/kv2q.0.metadata')) + content=binaryFileLoader('crud/kv2q.0.metadata'), + ) uri = 'textgrid:kv2q.0' res = crud.read_metadata(uri) title = res.object_value.generic.provided.title[0] @@ -44,7 +45,8 @@ class TestTextgridCrud: def test_read_public_metadata_umlaut(requests_mock, crud, tgconfig): requests_mock.get( tgconfig.host + '/1.0/tgcrud/rest/textgrid:s60f.0/metadata', - content=binaryFileLoader('crud/s60f.0.metadata')) + content=binaryFileLoader('crud/s60f.0.metadata'), + ) uri = 'textgrid:s60f.0' res = crud.read_metadata(uri) title = res.object_value.generic.provided.title[0] @@ -55,7 +57,7 @@ class TestTextgridCrud: requests_mock.get( tgconfig.host + '/1.0/tgcrud/rest/textgrid:notexistent/metadata', status_code=404, - text='<meta name="description" content="wrong uri mock message">' + text='<meta name="description" content="wrong uri mock message">', ) uri = 'textgrid:notexistent' with pytest.raises(TextgridCrudException): @@ -64,7 +66,8 @@ class TestTextgridCrud: @staticmethod def test_create_and_delete(requests_mock, crud, tgconfig): requests_mock.post( - tgconfig.host + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&projectId=PROJECT_ID&createRevision=false', + tgconfig.host + + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&projectId=PROJECT_ID&createRevision=false', headers={'Location': 'textgrid:mocked'}, content=binaryFileLoader('crud/kv2q.0.metadata'), ) @@ -75,8 +78,7 @@ class TestTextgridCrud: sid = 'SESSION_ID' # does not matter for mocked requests project_id = 'PROJECT_ID' # does not matter for mocked requests now = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') - metadata = TextgridMetadata().build( - title='test '+now, mimetype='text/xml') + metadata = TextgridMetadata().build(title='test ' + now, mimetype='text/xml') data = '<content>here</content>' res = crud.create_resource(sid, project_id, data, metadata) @@ -88,17 +90,20 @@ class TestTextgridCrud: @staticmethod def test_create_revision_and_delete(requests_mock, crud, tgconfig): requests_mock.post( - tgconfig.host + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&projectId=PROJECT_ID&createRevision=false', + tgconfig.host + + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&projectId=PROJECT_ID&createRevision=false', headers={'Location': 'textgrid:mocked.0'}, content=binaryFileLoader('crud/kv2q.0.metadata'), ) requests_mock.post( - tgconfig.host + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&uri=textgrid%3Akv2q.0&createRevision=true&projectId=PROJECT_ID', + tgconfig.host + + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&uri=textgrid%3Akv2q.0&createRevision=true&projectId=PROJECT_ID', headers={'Location': 'textgrid:mocked.1'}, content=binaryFileLoader('crud/kv2q.1.metadata'), ) requests_mock.post( - tgconfig.host + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&uri=textgrid%3Akv2q.1&createRevision=true&projectId=PROJECT_ID', + tgconfig.host + + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&uri=textgrid%3Akv2q.1&createRevision=true&projectId=PROJECT_ID', headers={'Location': 'textgrid:mocked.2'}, content=binaryFileLoader('crud/kv2q.2.metadata'), ) @@ -117,8 +122,7 @@ class TestTextgridCrud: sid = 'SESSION_ID' # does not matter for mocked requests project_id = 'PROJECT_ID' # does not matter for mocked requests now = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') - metadata = TextgridMetadata().build( - title='test '+now, mimetype='text/xml') + metadata = TextgridMetadata().build(title='test ' + now, mimetype='text/xml') data = '<content>here</content>' md_rev0 = crud.create_resource(sid, project_id, data, metadata) @@ -133,8 +137,7 @@ class TestTextgridCrud: assert textgrid_rev1_uri.endswith('.1') assert md_rev1.object_value.generic.generated.revision == 1 - md_rev2 = crud.update_resource( - sid, textgrid_rev1_uri, data, md_rev1, create_revision=True) + md_rev2 = crud.update_resource(sid, textgrid_rev1_uri, data, md_rev1, create_revision=True) textgrid_rev2_uri = md_rev2.object_value.generic.generated.textgrid_uri.value assert textgrid_rev2_uri.startswith('textgrid:') assert textgrid_rev2_uri.endswith('.2') @@ -152,7 +155,8 @@ class TestTextgridCrud: @staticmethod def test_create_update_metadata_and_delete(requests_mock, crud, tgconfig): requests_mock.post( - tgconfig.host + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&projectId=PROJECT_ID&createRevision=false', + tgconfig.host + + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&projectId=PROJECT_ID&createRevision=false', headers={'Location': 'textgrid:mocked'}, content=binaryFileLoader('crud/kv2q.0.metadata'), ) @@ -162,18 +166,17 @@ class TestTextgridCrud: ) requests_mock.get( tgconfig.host + '/1.0/tgcrud/rest/textgrid:kv2q.0/data?sessionId=SESSION_ID', - content=binaryFileLoader('crud/kv2q.0.data') + content=binaryFileLoader('crud/kv2q.0.data'), ) requests_mock.post( tgconfig.host + '/1.0/tgcrud/rest/textgrid:kv2q.0/updateMetadata?sessionId=SESSION_ID', - content=binaryFileLoader('crud/kv2q.0.metadata') + content=binaryFileLoader('crud/kv2q.0.metadata'), ) sid = 'SESSION_ID' # does not matter for mocked requests project_id = 'PROJECT_ID' # does not matter for mocked requests now = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') title = 'Alice im Wunderland' - metadata = TextgridMetadata().build( - title=title, mimetype='text/xml') + metadata = TextgridMetadata().build(title=title, mimetype='text/xml') data = '<content>here</content>' cmeta = crud.create_resource(sid, project_id, data, metadata) @@ -186,7 +189,8 @@ class TestTextgridCrud: # we re-use fixtures in mocked response and do not test if the metadata # updates (hint: it does not) - cmeta2 = crud.update_metadata(sid, textgrid_uri, cmeta) + crud.update_metadata(sid, textgrid_uri, cmeta) + # cmeta2 = crud.update_metadata(sid, textgrid_uri, cmeta) # assert cmeta2.object_value.generic.provided.title[0] == newtitle res = crud.delete_resource(sid, textgrid_uri) @@ -195,7 +199,8 @@ class TestTextgridCrud: @staticmethod def test_create_update_read_and_delete(requests_mock, crud, tgconfig): requests_mock.post( - tgconfig.host + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&projectId=PROJECT_ID&createRevision=false', + tgconfig.host + + '/1.0/tgcrud/rest/create?sessionId=SESSION_ID&projectId=PROJECT_ID&createRevision=false', headers={'Location': 'textgrid:mocked'}, content=binaryFileLoader('crud/kv2q.0.metadata'), ) @@ -205,21 +210,20 @@ class TestTextgridCrud: ) requests_mock.get( tgconfig.host + '/1.0/tgcrud/rest/textgrid:kv2q.0/data?sessionId=SESSION_ID', - content=binaryFileLoader('crud/kv2q.0.data') + content=binaryFileLoader('crud/kv2q.0.data'), ) requests_mock.get( tgconfig.host + '/1.0/tgcrud/rest/textgrid:kv2q.0/metadata', - content=binaryFileLoader('crud/kv2q.0.metadata') + content=binaryFileLoader('crud/kv2q.0.metadata'), ) requests_mock.post( tgconfig.host + '/1.0/tgcrud/rest/textgrid:kv2q.0/update?sessionId=SESSION_ID', - content=binaryFileLoader('crud/kv2q.0.metadata') + content=binaryFileLoader('crud/kv2q.0.metadata'), ) sid = 'SESSION_ID' # does not matter for mocked requests project_id = 'PROJECT_ID' # does not matter for mocked requests now = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') - metadata = TextgridMetadata().build( - title='test '+now, mimetype='text/plain') + metadata = TextgridMetadata().build(title='test ' + now, mimetype='text/plain') # the content on creation cdata = 'created' @@ -234,8 +238,7 @@ class TestTextgridCrud: umetadata = crud.read_metadata(textgrid_uri, sid) # updated content udata = 'updated' - res = crud.update_resource( - sid, textgrid_uri, udata, umetadata) + res = crud.update_resource(sid, textgrid_uri, udata, umetadata) textgrid_uri = res.object_value.generic.generated.textgrid_uri.value assert textgrid_uri.startswith('textgrid:') diff --git a/tests/unit/test_textgrid_metadata.py b/tests/unit/test_textgrid_metadata.py index 05421f6dc51af92c419b7a3474a7c16ff434a20e..50034f450d94809583472d0834c7a3008c80c401 100644 --- a/tests/unit/test_textgrid_metadata.py +++ b/tests/unit/test_textgrid_metadata.py @@ -2,49 +2,52 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from tgclients.metadata import TextgridMetadata -from . import fileLoader, FIXTURE_PATH - from tgclients.databinding.tgsearch import Response +from tgclients.metadata import TextgridMetadata +from . import FIXTURE_PATH filenames = { - 'jftx.0' : 'Abraham_a_Sancta_Clara___Kein_Titel_.jftx.0.jpg', - 'jfsz.0': 'Biographie__Abraham_a_Sancta_Clara.jfsz.0.work', - 'jfsm.0': 'Abraham_a_Sancta_Clara.jfsm.0.collection', - 'jfsw.0' : 'Satirischer_Traktat.jfsw.0.aggregation', - 'jfsx.0' : 'Biographie__Abraham_a_Sancta_Clara.jfsx.0.xml', - 'noformat.1' : 'Alice_im_Wunderland.noformat.1', - 'kmwm.0' : 'An_Herren_J.F._Ratschky.kmwm.0.work', - 'noauth.0' : 'Restricted_TextGrid_Object.noauth.0', + 'jftx.0': 'Abraham_a_Sancta_Clara___Kein_Titel_.jftx.0.jpg', + 'jfsz.0': 'Biographie__Abraham_a_Sancta_Clara.jfsz.0.work', + 'jfsm.0': 'Abraham_a_Sancta_Clara.jfsm.0.collection', + 'jfsw.0': 'Satirischer_Traktat.jfsw.0.aggregation', + 'jfsx.0': 'Biographie__Abraham_a_Sancta_Clara.jfsx.0.xml', + 'noformat.1': 'Alice_im_Wunderland.noformat.1', + 'kmwm.0': 'An_Herren_J.F._Ratschky.kmwm.0.work', + 'noauth.0': 'Restricted_TextGrid_Object.noauth.0', } -def _fixture_2_filename(filename: str)-> str: - meta = TextgridMetadata() - metastring = open(FIXTURE_PATH + filename, encoding="utf8").read() - mdobj: Response = meta.searchresponse2object(metastring) - return meta.filename_from_metadata(mdobj.result[0]) +def _fixture_2_filename(filename: str) -> str: + meta = TextgridMetadata() + metastring = open(FIXTURE_PATH + filename, encoding='utf8').read() + mdobj: Response = meta.searchresponse2object(metastring) + return meta.filename_from_metadata(mdobj.result[0]) + def test_filename_from_metadata(): - for key in filenames: - print(_fixture_2_filename(key)) - assert _fixture_2_filename(key) == filenames[key] + for key in filenames: + print(_fixture_2_filename(key)) + assert _fixture_2_filename(key) == filenames[key] + def test_extension_from_format(): - mimetype = 'text/xml' - ext = TextgridMetadata().extension_for_format(mimetype) - assert ext == 'xml' + mimetype = 'text/xml' + ext = TextgridMetadata().extension_for_format(mimetype) + assert ext == 'xml' + + mimetype = 'image/jpeg' + ext = TextgridMetadata().extension_for_format(mimetype) + assert ext == 'jpg' - mimetype = 'image/jpeg' - ext = TextgridMetadata().extension_for_format(mimetype) - assert ext == 'jpg' def test_extension_from_unknown_format(): - mimetype = 'no/this.is.no+known+format' - ext = TextgridMetadata().extension_for_format(mimetype) - assert ext == None + mimetype = 'no/this.is.no+known+format' + ext = TextgridMetadata().extension_for_format(mimetype) + assert ext is None + def test_id_from_filename(): - for key in filenames: - assert TextgridMetadata().id_from_filename(filenames[key]) == key + for key in filenames: + assert TextgridMetadata().id_from_filename(filenames[key]) == key diff --git a/tests/unit/test_textgrid_search.py b/tests/unit/test_textgrid_search.py index 34204a7b960cbecf797ac02f9a4a6e388db37ed7..c62ffa6fdd9b7f1c403c761da4ee2adab3b7205b 100644 --- a/tests/unit/test_textgrid_search.py +++ b/tests/unit/test_textgrid_search.py @@ -3,26 +3,26 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from tgclients.config import TextgridConfig -from tgclients.databinding.tgsearch import Response as SearchResponse, TextgridUris +from tgclients.databinding.tgsearch import Response as SearchResponse +from tgclients.databinding.tgsearch import TextgridUris from tgclients.search import TextgridSearch from . import fileLoader class TestTextgridSearch: - @staticmethod def test_empty_constructor(): """Not passing a config in constructor should provide a client for - tgsearch public on production system""" + tgsearch public on production system""" tgs = TextgridSearch() assert tgs._url == TextgridConfig().search_public @staticmethod def test_public_info(requests_mock, search_public, tgconfig): requests_mock.get( - tgconfig.host + '/1.0/tgsearch-public/info/textgrid:kv2q.0', - text=fileLoader('kv2q.0')) + tgconfig.host + '/1.0/tgsearch-public/info/textgrid:kv2q.0', text=fileLoader('kv2q.0') + ) uri = 'textgrid:kv2q.0' res: SearchResponse = search_public.info(uri) @@ -38,7 +38,8 @@ class TestTextgridSearch: def test_public_aggregation_list(requests_mock, search_public, tgconfig): requests_mock.get( tgconfig.host + '/1.0/tgsearch-public/navigation/agg/textgrid:kv2p.0', - text=fileLoader('agg_kv2p.0')) + text=fileLoader('agg_kv2p.0'), + ) uri = 'textgrid:kv2p.0' res: SearchResponse = search_public.list_aggregation(uri) assert len(res.result) == 2 @@ -47,7 +48,8 @@ class TestTextgridSearch: def test_public_search_filter_format(requests_mock, search_public, tgconfig): requests_mock.get( tgconfig.host + '/1.0/tgsearch-public/search?q=%2A&filter=format%3Atext%2Fxml', - text=fileLoader('search/filter_format_xml.xml')) + text=fileLoader('search/filter_format_xml.xml'), + ) filters = ['format:text/xml'] res: SearchResponse = search_public.search(filters=filters) assert int(res.hits) > 100 @@ -57,11 +59,12 @@ class TestTextgridSearch: @staticmethod def test_public_search_filter_format_project(requests_mock, search_public, tgconfig): requests_mock.get( - tgconfig.host + '/1.0/tgsearch-public/search?q=%2A&filter=format%3Atext%2Fxml&filter=project.id%3ATGPR-372fe6dc-57f2-6cd4-01b5-2c4bbefcfd3c', - text=fileLoader('search/filter_format_project.xml')) + tgconfig.host + + '/1.0/tgsearch-public/search?q=%2A&filter=format%3Atext%2Fxml&filter=project.id%3ATGPR-372fe6dc-57f2-6cd4-01b5-2c4bbefcfd3c', + text=fileLoader('search/filter_format_project.xml'), + ) pid = 'TGPR-372fe6dc-57f2-6cd4-01b5-2c4bbefcfd3c' - filters = ['format:text/xml', - 'project.id:'+pid] + filters = ['format:text/xml', 'project.id:' + pid] res: SearchResponse = search_public.search(filters=filters) assert int(res.hits) > 100 for r in res.result: @@ -72,7 +75,8 @@ class TestTextgridSearch: def test_edition_work_metadata_for(requests_mock, search_public, tgconfig): requests_mock.get( tgconfig.host + '/1.0/tgsearch-public/info/textgrid:kv2q.0/editionWorkMeta', - text=fileLoader('search/kv2q.0.editionWorkMeta.xml')) + text=fileLoader('search/kv2q.0.editionWorkMeta.xml'), + ) uri = 'textgrid:kv2q.0' res: SearchResponse = search_public.edition_work_metadata_for(uri) print(res) @@ -86,7 +90,8 @@ class TestTextgridSearch: def test_children(requests_mock, search_public, tgconfig): requests_mock.get( tgconfig.host + '/1.0/tgsearch-public/info/textgrid:kv2t.0/children', - text=fileLoader('search/kv2t.0.children.xml')) + text=fileLoader('search/kv2t.0.children.xml'), + ) uri = 'textgrid:kv2t.0' res: TextgridUris = search_public.children(uri) assert 'textgrid:kv2q.0' in res.textgrid_uri diff --git a/tests/unit/test_textgrid_search_request.py b/tests/unit/test_textgrid_search_request.py index 3663310b11fd90452bfd5f0f2b537cc049ea1fa6..b407b14a74f809cf163db91c3705500e23344323 100644 --- a/tests/unit/test_textgrid_search_request.py +++ b/tests/unit/test_textgrid_search_request.py @@ -9,19 +9,18 @@ from . import fileLoader class TestTextgridSearchRequest: - @staticmethod def test_empty_constructor(): """Not passing a config in constructor should provide a client for - tgsearch public on production system""" + tgsearch public on production system""" tgs = TextgridSearchRequest() assert tgs._url == TextgridConfig().search_public @staticmethod def test_public_info(requests_mock, searchrequest_public, tgconfig): requests_mock.get( - tgconfig.host + '/1.0/tgsearch-public/info/textgrid:kv2q.0', - text=fileLoader('kv2q.0')) + tgconfig.host + '/1.0/tgsearch-public/info/textgrid:kv2q.0', text=fileLoader('kv2q.0') + ) uri = 'textgrid:kv2q.0' res = searchrequest_public.info(uri) @@ -37,7 +36,8 @@ class TestTextgridSearchRequest: def test_public_aggregation_list(requests_mock, searchrequest_public, tgconfig): requests_mock.get( tgconfig.host + '/1.0/tgsearch-public/navigation/agg/textgrid:kv2p.0', - text=fileLoader('agg_kv2p.0')) + text=fileLoader('agg_kv2p.0'), + ) uri = 'textgrid:kv2p.0' res = searchrequest_public.list_aggregation(uri) assert res.status_code == 200 diff --git a/tests/unit/test_textgrid_utils.py b/tests/unit/test_textgrid_utils.py index d3969040a2553c65799ada7c74e0f2dee5ea9884..de2d01ab983e3c5d19c520e8c8dea1a4dc423ab8 100644 --- a/tests/unit/test_textgrid_utils.py +++ b/tests/unit/test_textgrid_utils.py @@ -4,6 +4,7 @@ from tgclients.utils import Utils + def test_list_to_aggregation_and_back(): textgrid_uri = 'textgrid:xyz.1' members = ['textgrid:1234.0', 'textrid:abcd.0', 'textgrid:4321.0'] @@ -11,6 +12,3 @@ def test_list_to_aggregation_and_back(): assert agg.startswith('<?xml version="1.0" encoding="UTF-8"?>') extracted_list = Utils.aggregation_to_list(agg) assert extracted_list == members - - -