diff --git a/.github/workflows/atramhasis_backend.yaml b/.github/workflows/atramhasis_backend.yaml new file mode 100644 index 00000000..3da72d0a --- /dev/null +++ b/.github/workflows/atramhasis_backend.yaml @@ -0,0 +1,42 @@ +name: Atramhasis backend tests + +on: + push: + paths: + - atramhasis/** + - scripts/** + - tests/** + - '!atramhasis/static/**' + - .github/workflows/atramhasis_backend.yaml + - pyproject.toml + - requirements*.txt + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ "3.10", "3.11", "3.12" ] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + # You can test your matrix by printing the current Python version + - name: Display Python version + run: python -c "import sys; print(sys.version)" + + - name: Install python requirements + env: + HATCH_BUILD_NO_HOOKS: true + working-directory: ./ + run: | + pip --version + pip install pip-tools + pip-sync requirements-dev.txt + pip install -e . + + - name: Python tests + run: pytest tests --exitfirst --capture=no -vvv --full-trace diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 653b4d0d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -sudo: false -language: python -os: linux -dist: focal -python: - - 3.9 - - 3.10 - - 3.11 -before_install: - - pip install --upgrade pip - - pip install setuptools==59.6.0 #https://github.com/pypa/setuptools/issues/3293 -install: - - pip install -r requirements-dev.txt - - python setup.py develop -script: - - py.test --cov atramhasis --cov-report term-missing tests -after_success: - - coveralls diff --git a/CHANGES.rst b/CHANGES.rst index 9dd2201a..a5d3bc9d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +2.1.0 (20-06-2023) +------------------ + +Migrated from setup.py to the hatchling build tool for improved packaging and distribution. (#854) +Benefits include faster builds, modern configuration, and enhanced flexibility for future enhancements. + + 2.0.0 (22-12-2023) ------------------ diff --git a/CITATION.cff b/CITATION.cff index a5bc3a92..4997dc38 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -88,5 +88,5 @@ keywords: - vocabulary - python license: GPL-3.0 -version: 2.0.0 -date-released: '2023-12-22' +version: 2.1.0 +date-released: '2024-06-20' diff --git a/atramhasis/__init__.py b/atramhasis/__init__.py index 1a0a5ff3..09026f24 100644 --- a/atramhasis/__init__.py +++ b/atramhasis/__init__.py @@ -6,8 +6,29 @@ from atramhasis.renderers import json_renderer_verbose +DEFAULT_SETTINGS = { + "cache.tree.backend": "dogpile.cache.memory", + "cache.tree.arguments.cache_size": "5000", + "cache.tree.expiration_time": "7000", + "cache.list.backend": "dogpile.cache.memory", + "cache.list.arguments.cache_size": "5000", + "cache.list.expiration_time": "7000", + "jinja2.extensions": "jinja2.ext.do", + "jinja2.filters": "label_sort = atramhasis.utils.label_sort", + "dojo.mode": "dist", + "layout.focus_conceptschemes": [], + "skosprovider.skosregistry_factory": "atramhasis.skos.create_registry", + "skosprovider.skosregistry_location": "request", +} + + def includeme(config): """this function adds some configuration for the application""" + settings = config.registry.settings + for key, value in DEFAULT_SETTINGS.items(): + if key not in settings: + settings[key] = value + config.include('pyramid_jinja2') config.include('pyramid_tm') config.add_static_view('static', 'static', cache_max_age=3600) @@ -21,6 +42,8 @@ def includeme(config): config.include('atramhasis.cache') config.scan('pyramid_skosprovider') + config.add_translation_dirs('atramhasis:locale/') + config.scan() @@ -45,8 +68,6 @@ def load_app(config, settings): includeme(config) - config.add_translation_dirs('atramhasis:locale/') - config.include('atramhasis.data:db') return config.make_wsgi_app() diff --git a/atramhasis/data/datamanagers.py b/atramhasis/data/datamanagers.py index 270eadd3..52874be1 100644 --- a/atramhasis/data/datamanagers.py +++ b/atramhasis/data/datamanagers.py @@ -74,7 +74,7 @@ def find(self, conceptscheme_id, query): """ db_query = ( select(Thing) - .options(joinedload('labels')) + .options(joinedload(Thing.labels)) .filter(Thing.conceptscheme_id == conceptscheme_id) ) if 'type' in query and query['type'] in ['concept', 'collection']: @@ -127,7 +127,7 @@ def get_all(self, conceptscheme_id): """ all_results = self.session.execute( select(Thing) - .options(joinedload('labels')) + .options(joinedload(Thing.labels)) .filter(Thing.conceptscheme_id == conceptscheme_id) ).unique().scalars().all() return all_results diff --git a/atramhasis/openapi.yaml b/atramhasis/openapi.yaml index 38e84025..5bb4ef7b 100644 --- a/atramhasis/openapi.yaml +++ b/atramhasis/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: Atramhasis API - version: 2.0.0 + version: 2.1.0 servers: - url: '/' @@ -1138,9 +1138,6 @@ components: Provider: type: object - required: - - conceptscheme_uri - - uri_pattern properties: id: type: string @@ -1189,6 +1186,9 @@ components: type: object additionalProperties: true nullable: true + required: + - conceptscheme_uri + - uri_pattern Error: type: object required: diff --git a/atramhasis/static/admin/package-lock.json b/atramhasis/static/admin/package-lock.json index af68d998..8baa40cb 100644 --- a/atramhasis/static/admin/package-lock.json +++ b/atramhasis/static/admin/package-lock.json @@ -1,12 +1,12 @@ { "name": "atramhasis", - "version": "2.0.0", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "atramhasis", - "version": "2.0.0", + "version": "2.1.0", "dependencies": { "dgrid": "~1.2.1", "dGrowl": "https://github.com/stratease/dGrowl#0.1.3", diff --git a/atramhasis/static/admin/package.json b/atramhasis/static/admin/package.json index d0943de8..2ec1da6f 100644 --- a/atramhasis/static/admin/package.json +++ b/atramhasis/static/admin/package.json @@ -1,6 +1,6 @@ { "name": "atramhasis", - "version": "2.0.0", + "version": "2.1.0", "description": "Node packages for building Atramhasis.", "repository": { "type": "git", diff --git a/atramhasis/static/package-lock.json b/atramhasis/static/package-lock.json index e1966004..de5ce9de 100644 --- a/atramhasis/static/package-lock.json +++ b/atramhasis/static/package-lock.json @@ -1,12 +1,12 @@ { "name": "atramhasis", - "version": "2.0.0", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "atramhasis", - "version": "2.0.0", + "version": "2.1.0", "dependencies": { "d3": "~3.5.17", "font-awesome": "~4.7.0", diff --git a/atramhasis/static/package.json b/atramhasis/static/package.json index 59207bbe..da9683a4 100644 --- a/atramhasis/static/package.json +++ b/atramhasis/static/package.json @@ -1,6 +1,6 @@ { "name": "atramhasis", - "version": "2.0.0", + "version": "2.1.0", "description": "Node packages for public part of Atramhasis.", "repository": { "type": "git", diff --git a/atramhasis/views/exception_views.py b/atramhasis/views/exception_views.py index dbb8b027..59190fb6 100644 --- a/atramhasis/views/exception_views.py +++ b/atramhasis/views/exception_views.py @@ -5,12 +5,13 @@ import logging import sys -from openapi_core.unmarshalling.schemas.exceptions import InvalidSchemaValue +from openapi_core.validation.schemas.exceptions import InvalidSchemaValue from pyramid.httpexceptions import HTTPMethodNotAllowed from pyramid.view import notfound_view_config from pyramid.view import view_config from pyramid_openapi3 import RequestValidationError from pyramid_openapi3 import ResponseValidationError +from pyramid_openapi3 import extract_errors from pyramid_openapi3 import openapi_validation_error from skosprovider.exceptions import ProviderUnavailableException from sqlalchemy.exc import IntegrityError @@ -117,17 +118,13 @@ def failed_openapi_validation(exc, request): # noinspection PyTypeChecker errors.extend( [ - str(error) - for error in exc.errors - if not isinstance(error, InvalidSchemaValue) + f'{error.get("field")}: {error.get("message")}' + for error in + list(extract_errors(request, exc.errors)) ] ) request.response.status_int = 400 - if isinstance(exc, RequestValidationError): - subject = "Request" - else: - subject = "Response" - return {"message": f"{subject} was not valid for schema.", "errors": errors} + return {"message": "Request was not valid for schema.", "errors": errors} except Exception: log.exception("Issue with exception handling.") return openapi_validation_error(exc, request) diff --git a/build_hook.py b/build_hook.py new file mode 100644 index 00000000..d880fa06 --- /dev/null +++ b/build_hook.py @@ -0,0 +1,17 @@ +import subprocess +from pathlib import Path + +from hatchling.builders.hooks.plugin.interface import BuildHookInterface + + +class BuildHook(BuildHookInterface): + def initialize(self, version: str, build_data) -> None: + """Build frontend when building the wheel.""" + super().initialize(version, build_data) + + root_dir = Path(__file__).parent + static = root_dir / "atramhasis" / "static" + static_admin = static / "admin" + subprocess.run(["npm", "install"], cwd=static) + subprocess.run(["npm", "install"], cwd=static_admin) + subprocess.run(["grunt", "-v", "build"], cwd=static_admin, check=True) diff --git a/docs/source/conf.py b/docs/source/conf.py index 4a73a4f5..d9719bc0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -56,9 +56,9 @@ # built documents. # # The short X.Y version. -version = '2.0' +version = '2.1' # The full version, including alpha/beta/rc tags. -release = '2.0.0' +release = '2.1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/customisation.rst b/docs/source/customisation.rst index caec2fb0..500ca206 100644 --- a/docs/source/customisation.rst +++ b/docs/source/customisation.rst @@ -1044,3 +1044,72 @@ through code in this file. Your final code should looks somewhat like this: register_providers_from_db(registry, request.db) return registry + + +Application settings +==================== +These are settings you can use to change the atramhasis behaviour. +When a setting is marked with DEFAULT then writing the setting is optional. +When a setting is marked with REQUIRED then it is strongly advised to fill in the +setting. Skipping these may not prevent the application from starting, but some +functionality might fail to run properly. + +.. code-block:: ini + + [app:main] + use = egg:my_app + + pyramid.default_locale_name = nl + + # DEFAULT - If you wish to add your own values you should append to these values + jinja2.extensions = + jinja2.ext.do + # DEFAULT - If you wish to add your own values you should append to these values + jinja2.filters = + label_sort = atramhasis.utils.label_sort + + available_languages = en nl it + + # REQUIRED + sqlalchemy.url = sqlite:///test.db + # sqlalchemy.url = postgresql://postgres:postgres@localhost:5432/atramhasis_test + + # DEFAULT + skosprovider.skosregistry_location = request + # DEFAULT + skosprovider.skosregistry_factory = tests.create_registry + + # DEFAULT - cache which caches the data used for /conceptschemes/{scheme_id}/tree + cache.tree.backend = dogpile.cache.memory + cache.tree.arguments.cache_size = 5000 + cache.tree.expiration_time = 7000 + + # DEFAULT - cache which caches the data used for /labeltypes and /notetypes + cache.list.backend = dogpile.cache.memory + cache.list.arguments.cache_size = 5000 + cache.list.expiration_time = 7000 + + # REQUIRED - Filesystem location to dump exports + atramhasis.dump_location = path/to/folder + + # REQUIRED - Assume an LDF server is present? + atramhasis.ldf.enabled = True + + # External url of the LDF server + atramhasis.ldf.baseurl = http://demo.atramhasis.org/ldf + + # DEFAULT empty list + layout.focus_conceptschemes = + HERITAGETYPE + PERIOD + GEOGRAPHY + MATERIALS + + # DEFAULT - Run dojo from source (src) or distribution (dist) + dojo.mode = dist + + # The Twitter @username the card should be attributed to. + twitter.site = + + # Enter your tracking snippet + tracking_snippet = \ No newline at end of file diff --git a/docs/source/development.rst b/docs/source/development.rst index fd77a3f3..7631f20f 100644 --- a/docs/source/development.rst +++ b/docs/source/development.rst @@ -113,10 +113,24 @@ production environment. Admin development ================= -To work on the admin part, you'll need `npm`_ installed. Consult -your operating system documentation on how to install these. The following +To work on the admin part, you'll need `npm`_, `grunt`_ and `java`_ installed. +Consult your operating system documentation on how to install these. The following instructions will assume you're running a recent Debian based Linux distribution. +Confirmed known versions are as followed: + +.. code-block:: bash + + $ npm -v + 8.19.4 + + $ node -v + v16.20.2 + + $ grunt -V + grunt-cli v1.4.3 + + .. code-block:: bash # install npm and grunt-cli @@ -158,33 +172,31 @@ To update the message catalogs, do as follows: .. code-block:: bash - $ python setup.py extract_messages - $ python setup.py update_catalog -l fr -i atramhasis/locale/atramhasis.pot -o atramhasis/locale/fr/LC_MESSAGES/atramhasis.po - $ python setup.py update_catalog -l nl -i atramhasis/locale/atramhasis.pot -o atramhasis/locale/nl/LC_MESSAGES/atramhasis.po - $ python setup.py update_catalog -l en -i atramhasis/locale/atramhasis.pot -o atramhasis/locale/en/LC_MESSAGES/atramhasis.po + $ pybabel extract --add-comments 'TRANSLATORS:' --output-file 'atramhasis/locale/atramhasis.pot' --width 80 --mapping-file 'message-extraction.ini' atramhasis + $ pybabel update --input-file 'atramhasis/locale/atramhasis.pot' --output-dir 'atramhasis/locale' --previous true --domain atramhasis Update the catalogs accordingly and run: .. code-block:: bash - $ python setup.py compile_catalog + $ pybabel compile --directory 'atramhasis/locale' --domain atramhasis --statistics true You might also want to add a new translation. Suppose you want to add a German translation. .. code-block:: bash - $ python setup.py init_catalog -l de -i atramhasis/locale/atramhasis.pot -o atramhasis/locale/de/LC_MESSAGES/atramhasis.po + $ pybabel init --locale de --input-file 'atramhasis/locale/atramhasis.pot' --output-dir atramhasis/locale --domain atramhasis -Edit :file:`atramhasis/locale/do/LC_MESSAGES/atramhasis.po` and add the necessary +Edit :file:`atramhasis/locale/de/LC_MESSAGES/atramhasis.po` and add the necessary translations. Just as with updating the catalogs, you need to recompile them. .. code-block:: bash - $ python setup.py compile_catalog + $ pybabel compile --directory 'atramhasis/locale' --domain atramhasis --statistics true At this moment, Atramhasis will still only show the default languages in it's -language switcher. If you want to add you new language, you need to edit your +language switcher. If you want to add your new language, you need to edit your :file:`development.ini` (or similar file). Look for the line that says `available_languages` and add your locale identifier. @@ -320,3 +332,4 @@ This will build the dojo code in the static folder. .. _pyramid_skosprovider: http://pyramid-skosprovider.readthedocs.org .. _skosprovider_getty: http://skosprovider-getty.readthedocs.org .. _skosprovider_heritagedata: http://skosprovider-heritagedata.readthedocs.org +.. _java: https://www.java.com/en/download/manual.jsp diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..0c00bc77 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,126 @@ +[build-system] +requires = ["hatchling", "hatch-fancy-pypi-readme"] +build-backend = "hatchling.build" + +[project] +version = "2.1.0" +name = "atramhasis" +dynamic = ["readme"] +authors = [ + { name = "Flanders Heritage Agency", email = "ict@onroerenderfgoed.be" }, +] +#license = "GPL-3.0-or-later" +description = "A web based editor for thesauri adhering to the SKOS specification." +requires-python = ">=3.10,<3.13" +keywords = ["web", "wsgi", "pyramid", "SKOS", "thesaurus", "vocabulary"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python", + "Framework :: Pyramid", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] +dependencies = [ + "pyramid", + "pyramid_tm", + "SQLAlchemy", + "transaction", + "zope.sqlalchemy", + "skosprovider", + "skosprovider_sqlalchemy>=2.1.1", + "skosprovider_rdf", + "skosprovider_getty", + "pyramid_skosprovider", + "language_tags", + "jinja2 >= 3.0.0", + "markupsafe", + "pyramid_jinja2", + "alembic", + "babel", + "colander", + "requests", + "cachecontrol", + "dogpile.cache", + "pyramid_rewrite", + "python-dateutil", + "rdflib", + "bleach", + "pyramid_openapi3==0.19", +] + +[project.urls] +History = "https://github.com/OnroerendErfgoed/atramhasis/blob/master/CHANGES.rst" +Tracker = "https://github.com/OnroerendErfgoed/atramhasis/issues" +Source = "https://github.com/OnroerendErfgoed/atramhasis" +Documentation = "https://atramhasis.readthedocs.io/en/latest/" + +[project.optional-dependencies] +dev = [ + "pyramid-debugtoolbar==4.10", + "pytest==7.4.2", + "pytest-cov==4.1.0", + "coveralls==3.3.1", + "webtest==3.0.0", + "mock==5.1.0", + "testfixtures==7.2.2", + "Sphinx==6.2.1", + "sphinxcontrib-httpdomain==1.8.1", + "pygments==2.15.1", + "waitress==2.1.2", + "flake8==4.0.1", + "mccabe==0.6.1", + "pep8==1.7.1", + "pyflakes==2.4.0", +] + +[project.entry-points."paste.app_factory"] +main = "atramhasis:main" + +[project.scripts] +initialize_atramhasis_db = "atramhasis.scripts.initializedb:main" +import_file = "atramhasis.scripts.import_file:main" +dump_rdf = "atramhasis.scripts.dump_rdf:main" +generate_ldf_config = "atramhasis.scripts.generate_ldf_config:main" +sitemap_generator = "atramhasis.scripts.sitemap_generator:main" +delete_scheme = "atramhasis.scripts.delete_scheme:main" +migrate_sqlalchemy_providers = "atramhasis.scripts.migrate_sqlalchemy_providers:main" + + +## +# Build tool specific +## +[tool.hatch.build.targets.wheel] +# In the wheel we want to have atramhasis in the root as python module. +only-include = [ + "/atramhasis", + "/fixtures", +] +# In the wheel we do not need the frontend sources. We provide the dist. +exclude = [ + "/atramhasis/static/admin" +] +# these folders are (partially) in gitignore and would otherwise not be part of the builds +artifacts = [ + "/atramhasis/static/admin/dist", + # including node_modules bloats the build but we need refactoring + "/atramhasis/static/admin/node_modules", + "/atramhasis/static/node_modules", +] + +[tool.hatch.build.targets.wheel.hooks.custom] +path = "build_hook.py" + + +[tool.hatch.metadata] +# This allows us to use github links as dependencies +allow-direct-references = true + +[tool.hatch.metadata.hooks.fancy-pypi-readme] +content-type = "text/x-rst" +fragments = [ + { path = "README.rst" }, + { path = "CHANGES.rst" }, +] diff --git a/requirements-dev-base.txt b/requirements-dev-base.txt deleted file mode 100644 index 58c0f499..00000000 --- a/requirements-dev-base.txt +++ /dev/null @@ -1,26 +0,0 @@ -# Basic Atramhasis dev requirements - -# pyramid -pyramid-debugtoolbar==4.10 - -# Testing -pytest==7.4.2 -pytest-cov==4.1.0 -coveralls==3.3.1 -webtest==3.0.0 -mock==5.1.0 -testfixtures==7.2.2 - -# Documentation -Sphinx==6.2.1 -sphinxcontrib-httpdomain==1.8.1 -pygments==2.15.1 - -# waitress -waitress==2.1.2 - -# Linting -flake8==4.0.1 -mccabe==0.6.1 -pep8==1.7.1 -pyflakes==2.4.0 diff --git a/requirements-dev.txt b/requirements-dev.txt index 08975b23..a59f056d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,327 @@ -# Runtime requirements ---requirement requirements.txt - -# Basic atramhasis dev requirements ---requirement requirements-dev-base.txt \ No newline at end of file +alabaster==0.7.16 + # via sphinx +alembic==1.13.1 + # via atramhasis (pyproject.toml) +attrs==23.2.0 + # via + # jsonschema + # referencing +babel==2.15.0 + # via + # atramhasis (pyproject.toml) + # sphinx +beautifulsoup4==4.12.3 + # via webtest +bleach==6.1.0 + # via atramhasis (pyproject.toml) +cachecontrol==0.14.0 + # via atramhasis (pyproject.toml) +cachetools==5.3.3 + # via pyld +certifi==2024.6.2 + # via requests +charset-normalizer==3.3.2 + # via requests +colander==2.0 + # via atramhasis (pyproject.toml) +coverage==6.5.0 + # via + # coveralls + # pytest-cov +coveralls==3.3.1 + # via atramhasis (pyproject.toml) +decorator==5.1.1 + # via dogpile-cache +docopt==0.6.2 + # via coveralls +docutils==0.19 + # via sphinx +dogpile-cache==1.3.3 + # via atramhasis (pyproject.toml) +exceptiongroup==1.2.1 + # via pytest +flake8==4.0.1 + # via atramhasis (pyproject.toml) +frozendict==2.4.4 + # via pyld +greenlet==3.0.3 + # via sqlalchemy +html5lib==1.1 + # via + # skosprovider + # skosprovider-rdf +hupper==1.12.1 + # via pyramid +idna==3.7 + # via requests +imagesize==1.4.1 + # via sphinx +iniconfig==2.0.0 + # via pytest +iso8601==2.1.0 + # via colander +isodate==0.6.1 + # via + # openapi-core + # rdflib +jinja2==3.1.4 + # via + # atramhasis (pyproject.toml) + # pyramid-jinja2 + # sphinx +jsonschema==4.22.0 + # via + # openapi-core + # openapi-schema-validator + # openapi-spec-validator +jsonschema-path==0.3.2 + # via + # openapi-core + # openapi-spec-validator +jsonschema-specifications==2023.12.1 + # via + # jsonschema + # openapi-schema-validator +language-tags==1.2.0 + # via + # atramhasis (pyproject.toml) + # skosprovider +lazy-object-proxy==1.10.0 + # via openapi-spec-validator +lxml==5.2.2 + # via pyld +mako==1.3.5 + # via + # alembic + # pyramid-mako +markupsafe==2.1.5 + # via + # atramhasis (pyproject.toml) + # jinja2 + # mako + # pyramid-jinja2 + # werkzeug +mccabe==0.6.1 + # via + # atramhasis (pyproject.toml) + # flake8 +mock==5.1.0 + # via atramhasis (pyproject.toml) +more-itertools==10.2.0 + # via openapi-core +msgpack==1.0.8 + # via cachecontrol +openapi-core==0.19.1 + # via pyramid-openapi3 +openapi-schema-validator==0.6.2 + # via + # openapi-core + # openapi-spec-validator +openapi-spec-validator==0.7.1 + # via openapi-core +packaging==24.1 + # via + # pytest + # sphinx + # zope-sqlalchemy +parse==1.20.1 + # via openapi-core +pastedeploy==3.1.0 + # via plaster-pastedeploy +pathable==0.4.3 + # via jsonschema-path +pbr==6.0.0 + # via stevedore +pep8==1.7.1 + # via atramhasis (pyproject.toml) +plaster==1.1.2 + # via + # plaster-pastedeploy + # pyramid +plaster-pastedeploy==1.0.1 + # via pyramid +pluggy==1.5.0 + # via pytest +pycodestyle==2.8.0 + # via flake8 +pyflakes==2.4.0 + # via + # atramhasis (pyproject.toml) + # flake8 +pygments==2.15.1 + # via + # atramhasis (pyproject.toml) + # pyramid-debugtoolbar + # sphinx +pyld==2.0.4 + # via skosprovider +pyparsing==3.1.2 + # via rdflib +pyramid==2.0.2 + # via + # atramhasis (pyproject.toml) + # pyramid-debugtoolbar + # pyramid-jinja2 + # pyramid-mako + # pyramid-openapi3 + # pyramid-rewrite + # pyramid-skosprovider + # pyramid-tm +pyramid-debugtoolbar==4.10 + # via atramhasis (pyproject.toml) +pyramid-jinja2==2.10.1 + # via atramhasis (pyproject.toml) +pyramid-mako==1.1.0 + # via pyramid-debugtoolbar +pyramid-openapi3==0.19 + # via atramhasis (pyproject.toml) +pyramid-rewrite==0.2 + # via atramhasis (pyproject.toml) +pyramid-skosprovider==1.2.1 + # via atramhasis (pyproject.toml) +pyramid-tm==2.5 + # via atramhasis (pyproject.toml) +pytest==7.4.2 + # via + # atramhasis (pyproject.toml) + # pytest-cov +pytest-cov==4.1.0 + # via atramhasis (pyproject.toml) +python-dateutil==2.9.0.post0 + # via atramhasis (pyproject.toml) +pyyaml==6.0.1 + # via jsonschema-path +rdflib==7.0.0 + # via + # atramhasis (pyproject.toml) + # skosprovider-getty + # skosprovider-rdf +referencing==0.31.1 + # via + # jsonschema + # jsonschema-path + # jsonschema-specifications +requests==2.32.3 + # via + # atramhasis (pyproject.toml) + # cachecontrol + # coveralls + # jsonschema-path + # skosprovider-getty + # sphinx +rfc3339-validator==0.1.4 + # via openapi-schema-validator +rfc3987==1.3.8 + # via skosprovider +rpds-py==0.18.1 + # via + # jsonschema + # referencing +setuptools==70.0.0 + # via + # pyramid + # zope-deprecation + # zope-interface + # zope-sqlalchemy +six==1.16.0 + # via + # bleach + # html5lib + # isodate + # python-dateutil + # rfc3339-validator + # sphinxcontrib-httpdomain +skosprovider==1.2.0 + # via + # atramhasis (pyproject.toml) + # pyramid-skosprovider + # skosprovider-getty + # skosprovider-rdf + # skosprovider-sqlalchemy +skosprovider-getty==1.2.0 + # via atramhasis (pyproject.toml) +skosprovider-rdf==1.3.0 + # via atramhasis (pyproject.toml) +skosprovider-sqlalchemy==2.1.1 + # via atramhasis (pyproject.toml) +snowballstemmer==2.2.0 + # via sphinx +soupsieve==2.5 + # via beautifulsoup4 +sphinx==6.2.1 + # via + # atramhasis (pyproject.toml) + # sphinxcontrib-httpdomain +sphinxcontrib-applehelp==1.0.8 + # via sphinx +sphinxcontrib-devhelp==1.0.6 + # via sphinx +sphinxcontrib-htmlhelp==2.0.5 + # via sphinx +sphinxcontrib-httpdomain==1.8.1 + # via atramhasis (pyproject.toml) +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.7 + # via sphinx +sphinxcontrib-serializinghtml==1.1.10 + # via sphinx +sqlalchemy==2.0.30 + # via + # atramhasis (pyproject.toml) + # alembic + # skosprovider-sqlalchemy + # zope-sqlalchemy +stevedore==5.2.0 + # via dogpile-cache +testfixtures==7.2.2 + # via atramhasis (pyproject.toml) +tomli==2.0.1 + # via + # coverage + # pytest +transaction==4.0 + # via + # atramhasis (pyproject.toml) + # pyramid-tm + # zope-sqlalchemy +translationstring==1.4 + # via + # colander + # pyramid +typing-extensions==4.12.2 + # via + # alembic + # dogpile-cache + # sqlalchemy +urllib3==2.2.2 + # via requests +venusian==3.1.0 + # via pyramid +waitress==2.1.2 + # via + # atramhasis (pyproject.toml) + # webtest +webencodings==0.5.1 + # via + # bleach + # html5lib +webob==1.8.7 + # via + # pyramid + # webtest +webtest==3.0.0 + # via atramhasis (pyproject.toml) +werkzeug==3.0.3 + # via openapi-core +zope-deprecation==5.0 + # via + # pyramid + # pyramid-jinja2 +zope-interface==6.4.post2 + # via + # pyramid + # transaction + # zope-sqlalchemy +zope-sqlalchemy==3.1 + # via atramhasis (pyproject.toml) diff --git a/requirements.txt b/requirements.txt index 40ec801c..22d1b745 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,46 +1,225 @@ -# pyramid +alembic==1.13.1 + # via atramhasis (pyproject.toml) +attrs==23.2.0 + # via + # jsonschema + # referencing +babel==2.15.0 + # via atramhasis (pyproject.toml) +bleach==6.1.0 + # via atramhasis (pyproject.toml) +cachecontrol==0.14.0 + # via atramhasis (pyproject.toml) +cachetools==5.3.3 + # via pyld +certifi==2024.6.2 + # via requests +charset-normalizer==3.3.2 + # via requests +colander==2.0 + # via atramhasis (pyproject.toml) +decorator==5.1.1 + # via dogpile-cache +dogpile-cache==1.3.3 + # via atramhasis (pyproject.toml) +frozendict==2.4.4 + # via pyld +greenlet==3.0.3 + # via sqlalchemy +html5lib==1.1 + # via + # skosprovider + # skosprovider-rdf +hupper==1.12.1 + # via pyramid +idna==3.7 + # via requests +iso8601==2.1.0 + # via colander +isodate==0.6.1 + # via + # openapi-core + # rdflib +jinja2==3.1.4 + # via + # atramhasis (pyproject.toml) + # pyramid-jinja2 +jsonschema==4.22.0 + # via + # openapi-core + # openapi-schema-validator + # openapi-spec-validator +jsonschema-path==0.3.2 + # via + # openapi-core + # openapi-spec-validator +jsonschema-specifications==2023.12.1 + # via + # jsonschema + # openapi-schema-validator +language-tags==1.2.0 + # via + # atramhasis (pyproject.toml) + # skosprovider +lazy-object-proxy==1.10.0 + # via openapi-spec-validator +lxml==5.2.2 + # via pyld +mako==1.3.5 + # via alembic +markupsafe==2.1.5 + # via + # atramhasis (pyproject.toml) + # jinja2 + # mako + # pyramid-jinja2 + # werkzeug +more-itertools==10.2.0 + # via openapi-core +msgpack==1.0.8 + # via cachecontrol +openapi-core==0.19.1 + # via pyramid-openapi3 +openapi-schema-validator==0.6.2 + # via + # openapi-core + # openapi-spec-validator +openapi-spec-validator==0.7.1 + # via openapi-core +packaging==24.1 + # via zope-sqlalchemy +parse==1.20.1 + # via openapi-core +pastedeploy==3.1.0 + # via plaster-pastedeploy +pathable==0.4.3 + # via jsonschema-path +pbr==6.0.0 + # via stevedore +plaster==1.1.2 + # via + # plaster-pastedeploy + # pyramid +plaster-pastedeploy==1.0.1 + # via pyramid +pyld==2.0.4 + # via skosprovider +pyparsing==3.1.2 + # via rdflib pyramid==2.0.2 + # via + # atramhasis (pyproject.toml) + # pyramid-jinja2 + # pyramid-openapi3 + # pyramid-rewrite + # pyramid-skosprovider + # pyramid-tm +pyramid-jinja2==2.10.1 + # via atramhasis (pyproject.toml) +pyramid-openapi3==0.19 + # via atramhasis (pyproject.toml) +pyramid-rewrite==0.2 + # via atramhasis (pyproject.toml) +pyramid-skosprovider==1.2.1 + # via atramhasis (pyproject.toml) pyramid-tm==2.5 -pyramid_rewrite==0.2 -pyramid_openapi3==0.14.3 -openapi-spec-validator==0.4.0 # https://github.com/p1c2u/openapi-core/issues/442 -# need to limit jsonschema until openapi-spec-validator 0.6.0 exists -jsonschema==4.17.0 - -# skosprovider + # via atramhasis (pyproject.toml) +python-dateutil==2.9.0.post0 + # via atramhasis (pyproject.toml) +pyyaml==6.0.1 + # via jsonschema-path +rdflib==7.0.0 + # via + # atramhasis (pyproject.toml) + # skosprovider-getty + # skosprovider-rdf +referencing==0.31.1 + # via + # jsonschema + # jsonschema-path + # jsonschema-specifications +requests==2.32.3 + # via + # atramhasis (pyproject.toml) + # cachecontrol + # jsonschema-path + # skosprovider-getty +rfc3339-validator==0.1.4 + # via openapi-schema-validator +rfc3987==1.3.8 + # via skosprovider +rpds-py==0.18.1 + # via + # jsonschema + # referencing +setuptools==70.0.0 + # via + # pyramid + # zope-deprecation + # zope-interface + # zope-sqlalchemy +six==1.16.0 + # via + # bleach + # html5lib + # isodate + # python-dateutil + # rfc3339-validator skosprovider==1.2.0 -skosprovider_sqlalchemy==2.1.1 -pyramid_skosprovider==1.2.1 -skosprovider_rdf==1.3.0 -skosprovider_getty==1.2.0 - -language-tags==1.2.0 - -# database -sqlalchemy==1.4.48 -zope.sqlalchemy==2.0 -transaction==3.1.0 - -# jinja2 -jinja2==3.1.2 -markupsafe==2.1.2 -pyramid-jinja2==2.10 -Babel==2.12.1 - -# alembic -alembic==1.9.4 - -# validation -colander==2.0 - -# caching -dogpile.cache==1.1.8 - -# other -python-dateutil==2.8.2 -rdflib==6.3.1 -bleach==5.0.1 - -# requests -requests==2.31.0 -cachecontrol==0.13.1 + # via + # atramhasis (pyproject.toml) + # pyramid-skosprovider + # skosprovider-getty + # skosprovider-rdf + # skosprovider-sqlalchemy +skosprovider-getty==1.2.0 + # via atramhasis (pyproject.toml) +skosprovider-rdf==1.3.0 + # via atramhasis (pyproject.toml) +skosprovider-sqlalchemy==2.1.1 + # via atramhasis (pyproject.toml) +sqlalchemy==2.0.30 + # via + # atramhasis (pyproject.toml) + # alembic + # skosprovider-sqlalchemy + # zope-sqlalchemy +stevedore==5.2.0 + # via dogpile-cache +transaction==4.0 + # via + # atramhasis (pyproject.toml) + # pyramid-tm + # zope-sqlalchemy +translationstring==1.4 + # via + # colander + # pyramid +typing-extensions==4.12.2 + # via + # alembic + # dogpile-cache + # sqlalchemy +urllib3==2.2.2 + # via requests +venusian==3.1.0 + # via pyramid +webencodings==0.5.1 + # via + # bleach + # html5lib +webob==1.8.7 + # via pyramid +werkzeug==3.0.3 + # via openapi-core +zope-deprecation==5.0 + # via + # pyramid + # pyramid-jinja2 +zope-interface==6.4.post2 + # via + # pyramid + # transaction + # zope-sqlalchemy +zope-sqlalchemy==3.1 + # via atramhasis (pyproject.toml) diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 0ca734ff..00000000 --- a/setup.cfg +++ /dev/null @@ -1,21 +0,0 @@ -[compile_catalog] -directory = atramhasis/locale -domain = atramhasis -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = atramhasis/locale/atramhasis.pot -width = 80 -mapping_file = message-extraction.ini - -[init_catalog] -domain = atramhasis -input_file = atramhasis/locale/atramhasis.pot -output_dir = atramhasis/locale - -[update_catalog] -domain = atramhasis -input_file = atramhasis/locale/atramhasis.pot -output_dir = atramhasis/locale -previous = true diff --git a/setup.py b/setup.py deleted file mode 100644 index 1adc4a4d..00000000 --- a/setup.py +++ /dev/null @@ -1,114 +0,0 @@ -import os -import subprocess - -from setuptools import Command -from setuptools import find_packages -from setuptools import setup - -here = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(here, 'README.rst')) as f: - README = f.read() -with open(os.path.join(here, 'CHANGES.rst')) as f: - CHANGES = f.read() - - -def dojo_build(): - print('-' * 50) - print('==> check npm dependencies') - libs = str(subprocess.check_output(["npm", "list", "-g", "grunt-cli"])) - if 'grunt-cli' in libs: - gruntcli = True - print('grunt-cli OK') - else: - gruntcli = False - print('grunt-cli KO, use \'npm install -g grunt-cli\' to install') - if gruntcli: - print('==> running grunt build') - subprocess.call(["grunt", "-v", "build"], cwd="atramhasis/static/admin") - print('-' * 50) - - -class Prepare(Command): - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - dojo_build() - - -requires = [ - 'pyramid', - 'pyramid_tm', - 'SQLAlchemy<2.0.0,>=1.4.0', - 'transaction', - 'zope.sqlalchemy', - 'skosprovider', - 'skosprovider_sqlalchemy>=2.1.1', - 'skosprovider_rdf', - 'skosprovider_getty', - 'pyramid_skosprovider', - 'pyramid_openapi3', - 'openapi-spec-validator==0.4.0', # https://github.com/p1c2u/openapi-core/issues/442 - 'language_tags', - 'jinja2 >= 3.0.0', - 'markupsafe', - 'pyramid_jinja2', - 'alembic', - 'babel', - 'colander', - 'requests', - 'cachecontrol', - 'dogpile.cache', - 'pyramid_rewrite', - 'python-dateutil', - 'rdflib', - 'bleach', -] - -setup(name='atramhasis', - version='2.0.0', - description='A web based editor for thesauri adhering to the SKOS specification.', - long_description=README + '\n\n' + CHANGES, - long_description_content_type='text/x-rst', - classifiers=[ - "Development Status :: 5 - Production/Stable", - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - ], - author='Flanders Heritage Agency', - author_email='ict@onroerenderfgoed.be', - url='http://atramhasis.readthedocs.org', - keywords='web wsgi pyramid SKOS thesaurus vocabulary', - license='GPLv3', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - test_suite='atramhasis', - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = atramhasis:main - [console_scripts] - initialize_atramhasis_db = atramhasis.scripts.initializedb:main - import_file = atramhasis.scripts.import_file:main - dump_rdf = atramhasis.scripts.dump_rdf:main - generate_ldf_config = atramhasis.scripts.generate_ldf_config:main - sitemap_generator = atramhasis.scripts.sitemap_generator:main - delete_scheme = atramhasis.scripts.delete_scheme:main - migrate_sqlalchemy_providers = atramhasis.scripts.migrate_sqlalchemy_providers:main - """, - cmdclass={ - 'prepare': Prepare - } - ) diff --git a/tests/__init__.py b/tests/__init__.py index f49416d6..34dc185a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -13,6 +13,7 @@ from skosprovider_sqlalchemy.providers import SQLAlchemyProvider from skosprovider_sqlalchemy.utils import import_provider from sqlalchemy import engine_from_config +from sqlalchemy import text from sqlalchemy.exc import OperationalError from sqlalchemy.exc import ProgrammingError from sqlalchemy.orm import sessionmaker @@ -61,13 +62,16 @@ def setup_db(guarantee_empty=False): def _reset_db(): engine = engine_from_config(SETTINGS, prefix='sqlalchemy.') + session = sessionmaker(bind=engine)() try: - engine.execute("DELETE FROM concept_note") - engine.execute("DELETE FROM note") - engine.execute("DELETE FROM concept_label") - engine.execute("DELETE FROM label") + session.execute(text("DELETE FROM concept_note")) + session.execute(text("DELETE FROM note")) + session.execute(text("DELETE FROM concept_label")) + session.execute(text("DELETE FROM label")) except (ProgrammingError, OperationalError): """The tables may not exist if it's first time.""" + session.commit() + session.close() command.downgrade(ALEMBIC_CONFIG, 'base') engine.dispose() diff --git a/tests/conf_test.ini b/tests/conf_test.ini index 9089aa68..fe87b49b 100644 --- a/tests/conf_test.ini +++ b/tests/conf_test.ini @@ -6,7 +6,6 @@ [app:main] use = egg:atramhasis -atramhasis.test_mode = true pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false diff --git a/tests/test_functional.py b/tests/test_functional.py index c2dae763..5ea779c5 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -156,8 +156,9 @@ def setUp(self): def mock_event_handler(event): if event.uri == 'urn:x-vioe:geography:9': referenced_in = ['urn:someobject', 'http://test.test.org/object/2'] - raise ProtectedResourceException(f'resource {event.uri} is still in use, preventing operation', - referenced_in) + raise ProtectedResourceException( + f'resource {event.uri} is still in use, preventing operation', + referenced_in) @staticmethod def mock_event_handler_provider_unavailable(event): @@ -180,22 +181,26 @@ def test_get_csv(self): response = self.testapp.get('/conceptschemes/TREES/c.csv?type=collection&label=') self.assertEqual('200 OK', response.status) self.assertIn('text/csv', response.headers['Content-Type']) - self.assertIn('attachment;filename="atramhasis_export.csv"', response.headers['Content-Disposition']) + self.assertIn('attachment;filename="atramhasis_export.csv"', + response.headers['Content-Disposition']) def test_unicode_csv(self): - response = self.testapp.get('/conceptschemes/TREES/c.csv?label=Chestnut&_LOCALE_=fr') + response = self.testapp.get( + '/conceptschemes/TREES/c.csv?label=Chestnut&_LOCALE_=fr') data = response.body.decode('utf-8') self.assertIsInstance(data, str) self.assertEqual('200 OK', response.status) self.assertIn('text/csv', response.headers['Content-Type']) - self.assertIn('attachment;filename="atramhasis_export.csv"', response.headers['Content-Disposition']) + self.assertIn('attachment;filename="atramhasis_export.csv"', + response.headers['Content-Disposition']) self.assertIn('la châtaigne', data) def test_get_csv_all(self): response = self.testapp.get('/conceptschemes/TREES/c.csv') self.assertEqual('200 OK', response.status) self.assertIn('text/csv', response.headers['Content-Type']) - self.assertIn('attachment;filename="atramhasis_export.csv"', response.headers['Content-Disposition']) + self.assertIn('attachment;filename="atramhasis_export.csv"', + response.headers['Content-Disposition']) class RestFunctionalTests(FunctionalTests): @@ -203,7 +208,8 @@ def _get_default_headers(self): return {'Accept': 'application/json'} def test_get_concept(self): - res = self.testapp.get('/conceptschemes/TREES/c/1', headers=self._get_default_headers()) + res = self.testapp.get('/conceptschemes/TREES/c/1', + headers=self._get_default_headers()) self.assertEqual('200 OK', res.status) self.assertIn('application/json', res.headers['Content-Type']) self.assertIsNotNone(res.json['id']) @@ -212,32 +218,38 @@ def test_get_concept(self): self.assertIn('sortLabel', [label['type'] for label in res.json['labels']]) def test_get_conceptscheme(self): - res = self.testapp.get('/conceptschemes/TREES', headers=self._get_default_headers()) + res = self.testapp.get('/conceptschemes/TREES', + headers=self._get_default_headers()) self.assertEqual('200 OK', res.status) self.assertIn('application/json', res.headers['Content-Type']) self.assertIsNotNone(res.json['id']) def test_get_concept_dictprovider(self): - res = self.testapp.get('/conceptschemes/TEST/c/1', headers=self._get_default_headers()) + res = self.testapp.get('/conceptschemes/TEST/c/1', + headers=self._get_default_headers()) self.assertEqual('200 OK', res.status) self.assertIn('application/json', res.headers['Content-Type']) self.assertIsNotNone(res.json['id']) self.assertEqual(res.json['type'], 'concept') def test_get_concept_not_found(self): - res = self.testapp.get('/conceptschemes/TREES/c/89', headers=self._get_default_headers(), status=404, + res = self.testapp.get('/conceptschemes/TREES/c/89', + headers=self._get_default_headers(), status=404, expect_errors=True) self.assertEqual('404 Not Found', res.status) self.assertIn('application/json', res.headers['Content-Type']) def test_get_concept_dictprovider_not_found(self): - res = self.testapp.get('/conceptschemes/TEST/c/89', headers=self._get_default_headers(), status=404, + res = self.testapp.get('/conceptschemes/TEST/c/89', + headers=self._get_default_headers(), status=404, expect_errors=True) self.assertEqual('404 Not Found', res.status) self.assertIn('application/json', res.headers['Content-Type']) def test_add_concept(self): - res = self.testapp.post_json('/conceptschemes/TREES/c', headers=self._get_default_headers(), params=json_value) + res = self.testapp.post_json('/conceptschemes/TREES/c', + headers=self._get_default_headers(), + params=json_value) self.assertEqual('201 Created', res.status) self.assertIn('application/json', res.headers['Content-Type']) self.assertIsNotNone(res.json['id']) @@ -256,25 +268,25 @@ def test_add_update_concept_manual_id(self): self.assertDictEqual( { 'id': 'manual-3', - 'type': 'concept', + 'type': 'concept', 'uri': 'urn:x-skosprovider:manual-ids:manual-3', 'label': 'The Larch', 'concept_scheme': { 'uri': 'urn:x-vioe:manual', 'labels': [] - }, + }, 'labels': [ - {'label': 'The Larch', 'type': 'prefLabel', 'language': 'en'}, + {'label': 'The Larch', 'type': 'prefLabel', 'language': 'en'}, {'label': 'a', 'type': 'sortLabel', 'language': 'en'} - ], - 'notes': [], + ], + 'notes': [], 'sources': [ {'citation': 'short', 'markup': None} ], - 'narrower': [], + 'narrower': [], 'broader': [], - 'related': [], + 'related': [], 'member_of': [], - 'subordinate_arrays': [], + 'subordinate_arrays': [], 'matches': { 'close': [], 'exact': [], 'related': [], 'broad': [], 'narrow': [] } @@ -291,7 +303,8 @@ def test_add_update_concept_manual_id(self): self.assertEqual('updated', res.json['label']) def test_add_concept_empty_conceptscheme(self): - res = self.testapp.post_json('/conceptschemes/STYLES/c', headers=self._get_default_headers(), + res = self.testapp.post_json('/conceptschemes/STYLES/c', + headers=self._get_default_headers(), params=json_value) self.assertEqual('201 Created', res.status) self.assertIn('application/json', res.headers['Content-Type']) @@ -299,27 +312,31 @@ def test_add_concept_empty_conceptscheme(self): def test_add_concept_invalid_json(self): res = self.testapp.post_json( - '/conceptschemes/TREES/c', headers=self._get_default_headers(), params=json_value_invalid, status=400) + '/conceptschemes/TREES/c', headers=self._get_default_headers(), + params=json_value_invalid, status=400) self.assertEqual('400 Bad Request', res.status) self.assertIn('application/json', res.headers['Content-Type']) def test_add_concept_conceptscheme_not_found(self): res = self.testapp.post_json( - '/conceptschemes/GARDENNNN/c', headers=self._get_default_headers(), params=json_value, status=404, + '/conceptschemes/GARDENNNN/c', headers=self._get_default_headers(), + params=json_value, status=404, expect_errors=True) self.assertEqual('404 Not Found', res.status) self.assertIn('application/json', res.headers['Content-Type']) def test_edit_conceptscheme(self): res = self.testapp.put_json( - '/conceptschemes/TREES', headers=self._get_default_headers(), params=json_collection_value) + '/conceptschemes/TREES', headers=self._get_default_headers(), + params=json_collection_value) self.assertEqual('200 OK', res.status) self.assertIn('application/json', res.headers['Content-Type']) def test_edit_conceptscheme_invalid(self): json_collection_value.pop('labels') res = self.testapp.put_json( - '/conceptschemes/TREES', headers=self._get_default_headers(), params=json_collection_value, + '/conceptschemes/TREES', headers=self._get_default_headers(), + params=json_collection_value, expect_errors=True) self.assertEqual('400 Bad Request', res.status) self.assertIn('application/json', res.headers['Content-Type']) @@ -335,27 +352,31 @@ def test_edit_conceptscheme_invalid(self): def test_edit_concept(self): res = self.testapp.put_json( - '/conceptschemes/TREES/c/1', headers=self._get_default_headers(), params=json_value) + '/conceptschemes/TREES/c/1', headers=self._get_default_headers(), + params=json_value) self.assertEqual('200 OK', res.status) self.assertIn('application/json', res.headers['Content-Type']) def test_edit_concept_has_relations(self): res = self.testapp.put_json( - '/conceptschemes/MATERIALS/c/13', headers=self._get_default_headers(), params=json_value_relations) + '/conceptschemes/MATERIALS/c/13', headers=self._get_default_headers(), + params=json_value_relations) self.assertEqual('200 OK', res.status) self.assertIn('application/json', res.headers['Content-Type']) self.assertEqual(2, len(res.json['narrower'])) def test_edit_concept_not_found(self): res = self.testapp.put_json( - '/conceptschemes/TREES/c/89', headers=self._get_default_headers(), params=json_value, status=404, + '/conceptschemes/TREES/c/89', headers=self._get_default_headers(), + params=json_value, status=404, expect_errors=True) self.assertEqual('404 Not Found', res.status) self.assertIn('application/json', res.headers['Content-Type']) def test_delete_concept(self): new_id = '1' - res = self.testapp.delete(f'/conceptschemes/TREES/c/{new_id}', headers=self._get_default_headers()) + res = self.testapp.delete(f'/conceptschemes/TREES/c/{new_id}', + headers=self._get_default_headers()) self.assertEqual('200 OK', res.status) self.assertIsNotNone(res.json['id']) self.assertEqual(new_id, res.json['id']) @@ -364,12 +385,14 @@ def test_delete_concept(self): print() def test_delete_concept_not_found(self): - res = self.testapp.delete('/conceptschemes/TREES/c/7895', headers=self._get_default_headers(), + res = self.testapp.delete('/conceptschemes/TREES/c/7895', + headers=self._get_default_headers(), expect_errors=True) self.assertEqual('404 Not Found', res.status) def test_add_collection(self): - res = self.testapp.post_json('/conceptschemes/GEOGRAPHY/c', headers=self._get_default_headers(), + res = self.testapp.post_json('/conceptschemes/GEOGRAPHY/c', + headers=self._get_default_headers(), params=json_collection_value, expect_errors=True) self.assertEqual('201 Created', res.status) self.assertIn('application/json', res.headers['Content-Type']) @@ -379,7 +402,8 @@ def test_add_collection(self): def test_edit_collection(self): json_collection_value['members'] = [{"id": 7}, {"id": 8}] json_collection_value['infer_concept_relations'] = False - res = self.testapp.put_json('/conceptschemes/GEOGRAPHY/c/333', headers=self._get_default_headers(), + res = self.testapp.put_json('/conceptschemes/GEOGRAPHY/c/333', + headers=self._get_default_headers(), params=json_collection_value) self.assertEqual('200 OK', res.status) self.assertIn('application/json', res.headers['Content-Type']) @@ -389,12 +413,14 @@ def test_edit_collection(self): self.assertFalse(res.json['infer_concept_relations']) def test_delete_collection(self): - res = self.testapp.delete('/conceptschemes/GEOGRAPHY/c/333', headers=self._get_default_headers()) + res = self.testapp.delete('/conceptschemes/GEOGRAPHY/c/333', + headers=self._get_default_headers()) self.assertEqual('200 OK', res.status) self.assertIn('application/json', res.headers['Content-Type']) def test_uri(self): - res = self.testapp.post_json('/conceptschemes/MATERIALS/c', headers=self._get_default_headers(), + res = self.testapp.post_json('/conceptschemes/MATERIALS/c', + headers=self._get_default_headers(), params=json_value) self.assertEqual('201 Created', res.status) self.assertIn('application/json', res.headers['Content-Type']) @@ -403,6 +429,7 @@ def test_uri(self): def test_provider_unavailable_view(self): def raise_provider_unavailable_exception(): raise ProviderUnavailableException('test msg') + with patch('atramhasis.views.crud.AtramhasisCrud.delete_concept', Mock(side_effect=raise_provider_unavailable_exception)): res = self.testapp.delete('/conceptschemes/GEOGRAPHY/c/55', @@ -442,7 +469,8 @@ def test_get_language(self): self.assertEqual('German', res.json['name']) def test_get_language_not_found(self): - res = self.testapp.get('/languages/jos', headers=self._get_default_headers(), expect_errors=True) + res = self.testapp.get('/languages/jos', headers=self._get_default_headers(), + expect_errors=True) self.assertEqual('404 Not Found', res.status) self.assertIn('application/json', res.headers['Content-Type']) self.assertIsNotNone(res.json) @@ -457,13 +485,16 @@ def test_add_language(self): self.assertEqual(res.json['name'], 'Afrikaans') def test_add_language_non_valid(self): - res = self.testapp.put_json('/languages/flup', headers=self._get_default_headers(), - params={"id": "flup", "name": "flup"}, expect_errors=True) + res = self.testapp.put_json('/languages/flup', + headers=self._get_default_headers(), + params={"id": "flup", "name": "flup"}, + expect_errors=True) self.assertEqual('400 Bad Request', res.status) self.assertIn('application/json', res.headers['Content-Type']) self.assertIsNotNone(res.json) self.assertEqual(res.json, { - "errors": [{"id": "Invalid language tag: Unknown code 'flup', Missing language tag in 'flup'."}], + "errors": [{ + "id": "Invalid language tag: Unknown code 'flup', Missing language tag in 'flup'."}], "message": "Language could not be validated"}) def test_add_language_non_valid_json(self): @@ -472,7 +503,8 @@ def test_add_language_non_valid_json(self): self.assertEqual('400 Bad Request', res.status) self.assertIn('application/json', res.headers['Content-Type']) self.assertIsNotNone(res.json) - self.assertEqual(res.json, {'errors': {'name': 'Required'}, 'message': 'Language could not be validated'}) + self.assertEqual(res.json, {'errors': {'name': 'Required'}, + 'message': 'Language could not be validated'}) def test_edit_language(self): res = self.testapp.put_json('/languages/de', headers=self._get_default_headers(), @@ -483,13 +515,16 @@ def test_edit_language(self): self.assertEqual(res.json['name'], 'Duits') def test_edit_language_invalid_language_tag(self): - res = self.testapp.put_json('/languages/joss', headers=self._get_default_headers(), - params={"id": "joss", "name": "Duits"}, expect_errors=True) + res = self.testapp.put_json('/languages/joss', + headers=self._get_default_headers(), + params={"id": "joss", "name": "Duits"}, + expect_errors=True) self.assertEqual('400 Bad Request', res.status) self.assertIn('application/json', res.headers['Content-Type']) self.assertIsNotNone(res.json) self.assertEqual(res.json, { - 'errors': [{'id': "Invalid language tag: Unknown code 'joss', Missing language tag in 'joss'."}] + 'errors': [{ + 'id': "Invalid language tag: Unknown code 'joss', Missing language tag in 'joss'."}] , "message": "Language could not be validated"}) def test_edit_language_no_id(self): @@ -506,7 +541,8 @@ def test_delete_language(self): self.assertIn('application/json', res.headers['Content-Type']) def test_delete_language_not_found(self): - res = self.testapp.delete('/languages/jos', headers=self._get_default_headers(), expect_errors=True) + res = self.testapp.delete('/languages/jos', headers=self._get_default_headers(), + expect_errors=True) self.assertEqual('404 Not Found', res.status) self.assertIn('application/json', res.headers['Content-Type']) self.assertIsNotNone(res.json) @@ -514,7 +550,7 @@ def test_delete_language_not_found(self): def test_delete_protected_resource(self): def mock_event_handler(event): - if isinstance(event, ProtectedResourceEvent): + if isinstance(event, ProtectedResourceEvent): referenced_in = ['urn:someobject', 'http://test.test.org/object/2'] raise ProtectedResourceException( 'resource {} is still in use, preventing operation' @@ -537,11 +573,14 @@ def mock_event_handler(event): }) def test_method_not_allowed(self): - self.testapp.delete('/conceptschemes/TREES', headers=self._get_default_headers(), status=405) - self.testapp.post('/conceptschemes', headers=self._get_default_headers(), status=405) + self.testapp.delete('/conceptschemes/TREES', headers=self._get_default_headers(), + status=405) + self.testapp.post('/conceptschemes', headers=self._get_default_headers(), + status=405) def test_get_conceptschemes(self): - self.testapp.get('/conceptschemes', headers=self._get_default_headers(), status=200) + self.testapp.get('/conceptschemes', headers=self._get_default_headers(), + status=200) def test_create_provider_openapi_validation(self): response = self.testapp.post_json( @@ -555,12 +594,28 @@ def test_create_provider_openapi_validation(self): ) self.assertEqual( { - 'message': 'Request was not valid for schema.', + 'errors': ['None: Failed to cast value to array type: wrong'], + 'message': 'Request was not valid for schema.' + }, + response.json + ) + response = self.testapp.post_json( + url='/providers', + params={ + 'uri_pattern': 'invalid', + 'subject': ['right'] + }, + headers=self._get_default_headers(), + expect_errors=True + ) + self.assertEqual( + { 'errors': [ - ": 'conceptscheme_uri' is a required property", "uri_pattern: 'invalid' does not match '.*%s.*'", - "subject: 'wrong' is not of type array" - ]}, + "uri_pattern: 'conceptscheme_uri' is a required property" + ], + 'message': 'Request was not valid for schema.' + }, response.json ) @@ -657,7 +712,8 @@ def test_create_full_provider_via_put(self): ) def test_update_provider(self): - conceptscheme = ConceptScheme(uri='https://id.erfgoed.net/thesauri/conceptschemes') + conceptscheme = ConceptScheme( + uri='https://id.erfgoed.net/thesauri/conceptschemes') provider = Provider( id='ERFGOEDTYPES', uri_pattern='https://id.erfgoed.net/thesauri/erfgoedtypes/%s', @@ -786,15 +842,18 @@ def _get_default_headers(self): return {'Accept': 'text/html'} def test_cookie(self): - response = self.testapp.get('/locale?language=nl', headers=self._get_default_headers()) + response = self.testapp.get('/locale?language=nl', + headers=self._get_default_headers()) self.assertIsNotNone(response.headers['Set-Cookie']) self.assertEqual(response.status, '302 Found') self.assertTrue((response.headers.get('Set-Cookie')).startswith('_LOCALE_=nl')) def test_unsupported_language(self): config_default_lang = settings.get('pyramid.default_locale_name') - response = self.testapp.get('/locale?language=fr', headers=self._get_default_headers()) - self.assertTrue((response.headers.get('Set-Cookie')).startswith('_LOCALE_=' + config_default_lang)) + response = self.testapp.get('/locale?language=fr', + headers=self._get_default_headers()) + self.assertTrue((response.headers.get('Set-Cookie')).startswith( + '_LOCALE_=' + config_default_lang)) class JsonTreeFunctionalTests(FunctionalTests): @@ -802,7 +861,8 @@ def _get_default_headers(self): return {'Accept': 'application/json'} def test_tree(self): - response = self.testapp.get('/conceptschemes/GEOGRAPHY/tree?_LOCALE_=nl', headers=self._get_default_headers()) + response = self.testapp.get('/conceptschemes/GEOGRAPHY/tree?_LOCALE_=nl', + headers=self._get_default_headers()) self.assertEqual('200 OK', response.status) self.assertIn('application/json', response.headers['Content-Type']) self.assertIsNotNone(response.json) @@ -810,7 +870,8 @@ def test_tree(self): self.assertEqual('World', response.json[0]['label']) def test_missing_labels(self): - response = self.testapp.get('/conceptschemes/MISSING_LABEL/tree?_LOCALE_=nl', headers=self._get_default_headers()) + response = self.testapp.get('/conceptschemes/MISSING_LABEL/tree?_LOCALE_=nl', + headers=self._get_default_headers()) self.assertEqual('200 OK', response.status) self.assertIsNotNone(response.json) self.assertEqual(2, len(response.json)) @@ -834,7 +895,8 @@ def test_tree_language(self): ) def test_no_tree(self): - response = self.testapp.get('/conceptschemes/FOO/tree?_LOCALE_=nl', headers=self._get_default_headers(), + response = self.testapp.get('/conceptschemes/FOO/tree?_LOCALE_=nl', + headers=self._get_default_headers(), status=404, expect_errors=True) self.assertEqual('404 Not Found', response.status) @@ -844,15 +906,18 @@ def _get_default_headers(self): return {'Accept': 'text/html'} def test_tree(self): - response = self.testapp.get('/conceptschemes/GEOGRAPHY/tree?_LOCALE_=nl', headers=self._get_default_headers()) + response = self.testapp.get('/conceptschemes/GEOGRAPHY/tree?_LOCALE_=nl', + headers=self._get_default_headers()) self.assertEqual('200 OK', response.status) self.assertIn('text/html', response.headers['Content-Type']) def test_no_tree(self): - response = self.testapp.get('/conceptschemes/FOO/tree?_LOCALE_=nl', headers=self._get_default_headers(), + response = self.testapp.get('/conceptschemes/FOO/tree?_LOCALE_=nl', + headers=self._get_default_headers(), status=404, expect_errors=True) self.assertEqual('404 Not Found', response.status) + class SkosFunctionalTests(FunctionalTests): def _get_default_headers(self): @@ -864,19 +929,23 @@ def _get_json_headers(self): def test_admin_no_skos_provider(self): with patch.dict(self.app.request_extensions.descriptors): del self.app.request_extensions.descriptors['skos_registry'] - res = self.testapp.get('/admin', headers=self._get_default_headers(), expect_errors=True) + res = self.testapp.get('/admin', headers=self._get_default_headers(), + expect_errors=True) self.assertEqual('500 Internal Server Error', res.status) self.assertTrue('message' in res) - self.assertTrue('No SKOS registry found, please check your application setup' in res) + self.assertTrue( + 'No SKOS registry found, please check your application setup' in res) def test_crud_no_skos_provider(self): with patch.dict(self.app.request_extensions.descriptors): del self.app.request_extensions.descriptors['skos_registry'] - res = self.testapp.post_json('/conceptschemes/GEOGRAPHY/c', headers=self._get_json_headers(), + res = self.testapp.post_json('/conceptschemes/GEOGRAPHY/c', + headers=self._get_json_headers(), params=json_collection_value, expect_errors=True) self.assertEqual('500 Internal Server Error', res.status) self.assertTrue('message' in res) - self.assertTrue('No SKOS registry found, please check your application setup' in res) + self.assertTrue( + 'No SKOS registry found, please check your application setup' in res) def test_match_filter(self): response = self.testapp.get( @@ -919,8 +988,9 @@ def test_create_cache(self): self.assertEqual('200 OK', tree_response.status) self.assertIsNotNone(tree_response.json) - cached_tree_response = self.testapp.get('/conceptschemes/MATERIALS/tree?_LOCALE_=nl', - headers=self._get_default_headers()) + cached_tree_response = self.testapp.get( + '/conceptschemes/MATERIALS/tree?_LOCALE_=nl', + headers=self._get_default_headers()) self.assertEqual('200 OK', cached_tree_response.status) self.assertIsNotNone(cached_tree_response.json) @@ -941,8 +1011,9 @@ def test_auto_invalidate_cache(self): tree_response = self.testapp.get('/conceptschemes/MATERIALS/tree?_LOCALE_=nl', headers=self._get_default_headers()) - cached_tree_response = self.testapp.get('/conceptschemes/MATERIALS/tree?_LOCALE_=nl', - headers=self._get_default_headers()) + cached_tree_response = self.testapp.get( + '/conceptschemes/MATERIALS/tree?_LOCALE_=nl', + headers=self._get_default_headers()) self.assertEqual(tree_response.json, cached_tree_response.json) delete_response = self.testapp.delete('/conceptschemes/MATERIALS/c/31', @@ -954,8 +1025,9 @@ def test_auto_invalidate_cache(self): headers=self._get_default_headers()) self.assertNotEqual(tree_response.json, tree_response2.json) - cached_tree_response2 = self.testapp.get('/conceptschemes/MATERIALS/tree?_LOCALE_=nl', - headers=self._get_default_headers()) + cached_tree_response2 = self.testapp.get( + '/conceptschemes/MATERIALS/tree?_LOCALE_=nl', + headers=self._get_default_headers()) self.assertEqual(tree_response2.json, cached_tree_response2.json) tree_region.configure('dogpile.cache.null', replace_existing_backend=True) @@ -970,7 +1042,8 @@ def test_void(self): self.assertEqual('text/turtle', rdf_response.content_type) def test_rdf_full_xml(self): - rdf_response = self.testapp.get('/conceptschemes/MATERIALS/c', headers={'Accept': 'application/rdf+xml'}) + rdf_response = self.testapp.get('/conceptschemes/MATERIALS/c', + headers={'Accept': 'application/rdf+xml'}) self.assertEqual('200 OK', rdf_response.status) self.assertEqual('application/rdf+xml', rdf_response.content_type) @@ -980,7 +1053,8 @@ def test_rdf_full_xml_ext(self): self.assertEqual('application/rdf+xml', rdf_response.content_type) def test_rdf_full_turtle(self): - ttl_response = self.testapp.get('/conceptschemes/MATERIALS/c', headers={'Accept': 'text/turtle'}) + ttl_response = self.testapp.get('/conceptschemes/MATERIALS/c', + headers={'Accept': 'text/turtle'}) self.assertEqual('200 OK', ttl_response.status) self.assertEqual('text/turtle', ttl_response.content_type) @@ -990,7 +1064,8 @@ def test_rdf_full_turtle_ext(self): self.assertEqual('text/turtle', ttl_response.content_type) def test_rdf_conceptscheme_xml(self): - rdf_response = self.testapp.get('/conceptschemes/MATERIALS', headers={'Accept': 'application/rdf+xml'}) + rdf_response = self.testapp.get('/conceptschemes/MATERIALS', + headers={'Accept': 'application/rdf+xml'}) self.assertEqual('200 OK', rdf_response.status) self.assertEqual('application/rdf+xml', rdf_response.content_type) @@ -1000,7 +1075,8 @@ def test_rdf_conceptscheme_xml_ext(self): self.assertEqual('application/rdf+xml', rdf_response.content_type) def test_rdf_conceptscheme_turtle(self): - ttl_response = self.testapp.get('/conceptschemes/MATERIALS', headers={'Accept': 'text/turtle'}) + ttl_response = self.testapp.get('/conceptschemes/MATERIALS', + headers={'Accept': 'text/turtle'}) self.assertEqual('200 OK', ttl_response.status) self.assertEqual('text/turtle', ttl_response.content_type) @@ -1010,7 +1086,8 @@ def test_rdf_conceptscheme_turtle_ext(self): self.assertEqual('text/turtle', ttl_response.content_type) def test_rdf_conceptscheme_jsonld(self): - res = self.testapp.get('/conceptschemes/MATERIALS', headers={'Accept': 'application/ld+json'}) + res = self.testapp.get('/conceptschemes/MATERIALS', + headers={'Accept': 'application/ld+json'}) self.assertEqual('200 OK', res.status) self.assertEqual('application/ld+json', res.content_type) @@ -1020,7 +1097,8 @@ def test_rdf_conceptscheme_jsonld_ext(self): self.assertEqual('application/ld+json', res.content_type) def test_rdf_individual_jsonld(self): - res = self.testapp.get('/conceptschemes/MATERIALS/c/1', headers={'Accept': 'application/ld+json'}) + res = self.testapp.get('/conceptschemes/MATERIALS/c/1', + headers={'Accept': 'application/ld+json'}) self.assertEqual('200 OK', res.status) self.assertEqual('application/ld+json', res.content_type) @@ -1030,7 +1108,8 @@ def test_rdf_individual_jsonld_ext(self): self.assertEqual('application/ld+json', res.content_type) def test_rdf_individual_xml(self): - rdf_response = self.testapp.get('/conceptschemes/MATERIALS/c/1', headers={'Accept': 'application/rdf+xml'}) + rdf_response = self.testapp.get('/conceptschemes/MATERIALS/c/1', + headers={'Accept': 'application/rdf+xml'}) self.assertEqual('200 OK', rdf_response.status) self.assertEqual('application/rdf+xml', rdf_response.content_type) @@ -1040,7 +1119,8 @@ def test_rdf_individual_xml_ext(self): self.assertEqual('application/rdf+xml', rdf_response.content_type) def test_rdf_individual_turtle(self): - ttl_response = self.testapp.get('/conceptschemes/MATERIALS/c/1', headers={'Accept': 'text/turtle'}) + ttl_response = self.testapp.get('/conceptschemes/MATERIALS/c/1', + headers={'Accept': 'text/turtle'}) self.assertEqual('200 OK', ttl_response.status) self.assertEqual('text/turtle', ttl_response.content_type) @@ -1065,12 +1145,14 @@ def test_rdf_individual_turtle_manual(self): self.assertEqual('text/turtle', ttl_response.content_type) def test_rdf_individual_turtle_manual_uri(self): - ttl_response = self.testapp.get('/conceptschemes/manual-ids/c/http://id.manual.org/manual/68.ttl') + ttl_response = self.testapp.get( + '/conceptschemes/manual-ids/c/http://id.manual.org/manual/68.ttl') self.assertEqual('200 OK', ttl_response.status) self.assertEqual('text/turtle', ttl_response.content_type) def test_rdf_individual_not_found(self): - res = self.testapp.get('/conceptschemes/TREES/c/test.ttl', headers={'Accept': 'text/turtle'}, status=404, + res = self.testapp.get('/conceptschemes/TREES/c/test.ttl', + headers={'Accept': 'text/turtle'}, status=404, expect_errors=True) self.assertEqual('404 Not Found', res.status) diff --git a/tests/test_views.py b/tests/test_views.py index 6a250dd9..ee41cb70 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -4,7 +4,6 @@ import mock import pytest -from openapi_core.validation.request.datatypes import RequestValidationResult from paste.deploy.loadwsgi import appconfig from pyramid import testing from pyramid.config.settings import Settings @@ -730,7 +729,7 @@ def test_add_concept_manual_id_strategy(self): self.view.edit_concept() def test_add_provider(self): - self.request.openapi_validated = RequestValidationResult(body={}) + self.request.openapi_validated = Mock() self.request.skos_registry = Registry() view = 'atramhasis.views.crud' @@ -745,7 +744,7 @@ def test_add_provider(self): self.assertEqual(response, renderer.return_value) def test_update_provider(self): - self.request.openapi_validated = RequestValidationResult(body={}) + self.request.openapi_validated = Mock() self.request.matchdict = {"id": 1} view = 'atramhasis.views.crud' diff --git a/tox.ini b/tox.ini deleted file mode 100644 index df3789cf..00000000 --- a/tox.ini +++ /dev/null @@ -1,26 +0,0 @@ -[tox] -envlist = py39, py310, py311, cover - -[testenv] -passenv = 3.9 -commands = - pip install -r requirements-dev.txt - python setup.py develop - py.test tests -deps = - pytest - webtest - mock - testfixtures - -[testenv:cover] -basepython = - python3.9.0 -commands = - pip install -U setuptools - pip install -r requirements-dev.txt - python setup.py develop - py.test --cov-report term-missing --cov skosprovider tests -deps = - pytest - pytest-cov