From 90efe95143146d297eebc0353c85d4ac151707d0 Mon Sep 17 00:00:00 2001 From: vuillaut Date: Thu, 30 Nov 2023 18:13:11 +0100 Subject: [PATCH 1/4] replace setup.py and pytest.ini with pyproject.toml --- pyproject.toml | 50 +++++++++++++++++++++++++++++++++++++++++ pytest.ini | 3 --- setup.py | 60 -------------------------------------------------- 3 files changed, 50 insertions(+), 63 deletions(-) create mode 100644 pyproject.toml delete mode 100644 pytest.ini delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..781dca4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +[build-system] +requires = ["setuptools", "wheel", "setuptools_scm<8.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "eossr" +description = "ESCAPE OSSR library" +readme = "README.md" +authors = [ + { name = "Thomas Vuillaume", email = "vuillaume@lapp.in2p3.fr" }, + { name = "Enrique Garcia" } +] +license = "MIT" +dependencies = [ + "requests>=2.25.0,<3.0", + "markdown>=3.3.6,<4.0", + "pandas", + "remotezip==0.9.3", + "semver>=2,<3", + "jsonschema", + "jsonref", + "setuptools_scm<8.0", +] +requires-python = ">=3.6" + +[project.urls] +homepage = "https://gitlab.com/escape-ossr/eossr" + +[project.scripts] +eossr-zip-repository = "eossr.scripts.zip_repository:main" +eossr-codemeta2zenodo = "eossr.scripts.eossr_codemeta2zenodo:main" +eossr-upload-repository = "eossr.scripts.eossr_upload_repository:main" +eossr-check-connection-zenodo = "eossr.scripts.check_connection_zenodo:main" +eossr-metadata-validator = "eossr.scripts.eossr_metadata_validator:main" +eossr-zenodo-metadata-validator = "eossr.scripts.eossr_zenodo_validator:main" + +[project.extras] +tests = ["pytest", "pytest-cov", "python-dotenv"] +extras = ["pre-commit"] + +[tool.setuptools_scm] +write_to = "eossr/_version.py" +relative_to = "file:__file__" +write_to_template = "__version__ = '{version}'" + +[tool.setuptools.dynamic] +package_data = { "eossr" = ["metadata/schema/.zenodo.json", "metadata/schema/codemeta.json", "metadata/schema/escape_codemeta_crosswalk.csv"] } + +[tool.pytest.ini_options] +xfail_strict = true diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 4c52e41..0000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -# pytest.ini -[pytest] -xfail_strict=true diff --git a/setup.py b/setup.py deleted file mode 100644 index 3d35d77..0000000 --- a/setup.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python - -from pathlib import Path - -from setuptools import find_packages, setup - -entry_points = { - 'console_scripts': [ - 'eossr-zip-repository = eossr.scripts.zip_repository:main', - 'eossr-codemeta2zenodo = eossr.scripts.eossr_codemeta2zenodo:main', - 'eossr-upload-repository = eossr.scripts.eossr_upload_repository:main', - 'eossr-check-connection-zenodo = eossr.scripts.check_connection_zenodo:main', - 'eossr-metadata-validator = eossr.scripts.eossr_metadata_validator:main', - 'eossr-zenodo-metadata-validator = eossr.scripts.eossr_zenodo_validator:main', - ] -} - - -this_directory = Path(__file__).parent -long_description = (this_directory / "README.md").read_text() - - -setup( - name='eossr', - description="ESCAPE OSSR library", - long_description=long_description, - long_description_content_type='text/markdown', - setup_requires="setuptools_scm<8.0", - install_requires=[ - "requests>=2.25.0,<3.0", - "markdown>=3.3.6,<4.0", - "pandas", - "remotezip==0.9.3", - "semver>=2,<3", - "jsonschema", - "jsonref", - "setuptools_scm<8.0", - ], - extras_require={'tests': ['pytest', 'pytest-cov', 'python-dotenv'], 'extras': ['pre-commit']}, - packages=find_packages(exclude="eossr._dev_version"), - scripts=[], - tests_require=['pytest'], - author='Thomas Vuillaume, Enrique Garcia', - author_email='vuillaume@lapp.in2p3.fr', - url='https://gitlab.com/escape-ossr/eossr', - license='MIT', - entry_points=entry_points, - package_data={ - 'eossr': [ - 'metadata/schema/.zenodo.json', - 'metadata/schema/codemeta.json', - 'metadata/schema/escape_codemeta_crosswalk.csv', - ] - }, - use_scm_version={ - "write_to":"eossr/_version.py", - "relative_to": __file__, - "write_to_template": "__version__ = '{version}'", - }, -) -- GitLab From 533ebd6030e126eafe6e96d7a1604ed4d0728b04 Mon Sep 17 00:00:00 2001 From: vuillaut Date: Thu, 30 Nov 2023 18:28:36 +0100 Subject: [PATCH 2/4] fix pyproject.toml --- eossr/tests/conftest.py | 16 +--------------- pyproject.toml | 10 ++++++---- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/eossr/tests/conftest.py b/eossr/tests/conftest.py index 86fc38e..24ba7db 100644 --- a/eossr/tests/conftest.py +++ b/eossr/tests/conftest.py @@ -11,19 +11,5 @@ def pytest_configure(): load_dotenv(zenodo_tokens_path, override=True) print(os.getenv('ZENODO_TOKEN')) -pytest_configure() - -print(os.getenv('SANDBOX_ZENODO_TOKEN')) - -# In conftest.py -import pytest - -@pytest.fixture -def sample_fixture(): - return "Fixture loaded" - -# In your test file -def test_sample(sample_fixture): - print(os.getenv('SANDBOX_ZENODO_TOKEN')) - assert sample_fixture == "Fixture loaded" +pytest_configure() diff --git a/pyproject.toml b/pyproject.toml index 781dca4..73b6e99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,11 +6,12 @@ build-backend = "setuptools.build_meta" name = "eossr" description = "ESCAPE OSSR library" readme = "README.md" +dynamic = ["version"] authors = [ { name = "Thomas Vuillaume", email = "vuillaume@lapp.in2p3.fr" }, { name = "Enrique Garcia" } ] -license = "MIT" +license = { file = "LICENSE" } dependencies = [ "requests>=2.25.0,<3.0", "markdown>=3.3.6,<4.0", @@ -34,7 +35,7 @@ eossr-check-connection-zenodo = "eossr.scripts.check_connection_zenodo:main" eossr-metadata-validator = "eossr.scripts.eossr_metadata_validator:main" eossr-zenodo-metadata-validator = "eossr.scripts.eossr_zenodo_validator:main" -[project.extras] +[project.optional-dependencies] tests = ["pytest", "pytest-cov", "python-dotenv"] extras = ["pre-commit"] @@ -43,8 +44,9 @@ write_to = "eossr/_version.py" relative_to = "file:__file__" write_to_template = "__version__ = '{version}'" -[tool.setuptools.dynamic] -package_data = { "eossr" = ["metadata/schema/.zenodo.json", "metadata/schema/codemeta.json", "metadata/schema/escape_codemeta_crosswalk.csv"] } +[tool.setuptools.packages.find] +where = ["eossr/metadata/schema/"] + [tool.pytest.ini_options] xfail_strict = true -- GitLab From 089030b1c148b2db627fe4b7eb600250e5ac0e40 Mon Sep 17 00:00:00 2001 From: vuillaut Date: Fri, 1 Dec 2023 00:28:20 +0100 Subject: [PATCH 3/4] working pyproject --- .gitignore | 2 +- README.md | 2 +- docs/Makefile | 2 +- docs/_static/custom.css | 1 - docs/_templates/layout.html | 2 +- eossr/metadata/codemeta2zenodo/__init__.py | 1 - pyproject.toml | 5 +- {eossr => src/eossr}/__init__.py | 0 .../eossr}/_dev_version/_dev_version.py | 0 {eossr => src/eossr}/api/__init__.py | 0 {eossr => src/eossr}/api/ossr.py | 2 +- {eossr => src/eossr}/api/tests/test_api.py | 6 +- {eossr => src/eossr}/api/zenodo/__init__.py | 0 .../eossr}/api/zenodo/http_status.py | 1 - .../api/zenodo/tests/test_http_status.py | 8 +- .../eossr}/api/zenodo/tests/test_zenodo.py | 57 +++++---- {eossr => src/eossr}/api/zenodo/zenodo.py | 116 +++++++----------- {eossr => src/eossr}/metadata/__init__.py | 0 {eossr => src/eossr}/metadata/codemeta.py | 20 +-- .../metadata/codemeta2zenodo/__init__.py | 1 + .../metadata/codemeta2zenodo/converters.py | 92 ++++++-------- .../eossr}/metadata/codemeta2zenodo/core.py | 38 +++--- .../tests/test_codemeta2zenodo.py | 7 +- .../tests/test_codemeta2zenodo_utils.py | 5 +- .../codemeta2zenodo/tests/test_converters.py | 18 +-- .../eossr}/metadata/codemeta2zenodo/utils.py | 4 +- .../eossr}/metadata/schema/__init__.py | 0 .../eossr}/metadata/schema/codemeta.json | 0 .../schema/escape_codemeta_crosswalk.csv | 2 +- .../eossr}/metadata/tests/__init__.py | 2 +- .../samples/codemeta_contributors_sample.json | 0 .../tests/samples/codemeta_not_valid.json | 0 .../tests/samples/codemeta_sample1.json | 0 .../metadata/tests/samples/codemeta_test.json | 2 +- .../tests/samples/codemeta_valid.json | 0 .../metadata/tests/samples/zenodo_test.json | 2 +- .../eossr}/metadata/tests/test_codemeta.py | 3 +- .../metadata/tests/test_zenodo_metadata.py | 0 {eossr => src/eossr}/metadata/zenodo.py | 5 +- {eossr => src/eossr}/scripts/__init__.py | 0 .../eossr}/scripts/check_connection_zenodo.py | 2 +- .../eossr}/scripts/eossr_codemeta2zenodo.py | 0 .../scripts/eossr_metadata_validator.py | 0 .../eossr}/scripts/eossr_upload_repository.py | 2 +- .../eossr}/scripts/eossr_zenodo_validator.py | 0 .../eossr}/scripts/tests/test_scripts.py | 4 +- .../eossr}/scripts/update_codemeta_eossr.py | 0 .../scripts/zenodo_user_entries_cleanup.py | 1 + .../eossr}/scripts/zip_repository.py | 0 {eossr => src/eossr}/tests/conftest.py | 2 + {eossr => src/eossr}/tests/test_globals.py | 4 +- {eossr => src/eossr}/tests/test_utils.py | 0 {eossr => src/eossr}/utils.py | 0 {eossr => src/eossr}/version.py | 0 54 files changed, 198 insertions(+), 223 deletions(-) delete mode 100644 eossr/metadata/codemeta2zenodo/__init__.py rename {eossr => src/eossr}/__init__.py (100%) rename {eossr => src/eossr}/_dev_version/_dev_version.py (100%) rename {eossr => src/eossr}/api/__init__.py (100%) rename {eossr => src/eossr}/api/ossr.py (98%) rename {eossr => src/eossr}/api/tests/test_api.py (99%) rename {eossr => src/eossr}/api/zenodo/__init__.py (100%) rename {eossr => src/eossr}/api/zenodo/http_status.py (99%) rename {eossr => src/eossr}/api/zenodo/tests/test_http_status.py (99%) rename {eossr => src/eossr}/api/zenodo/tests/test_zenodo.py (90%) rename {eossr => src/eossr}/api/zenodo/zenodo.py (93%) rename {eossr => src/eossr}/metadata/__init__.py (100%) rename {eossr => src/eossr}/metadata/codemeta.py (92%) create mode 100644 src/eossr/metadata/codemeta2zenodo/__init__.py rename {eossr => src/eossr}/metadata/codemeta2zenodo/converters.py (89%) rename {eossr => src/eossr}/metadata/codemeta2zenodo/core.py (88%) rename {eossr => src/eossr}/metadata/codemeta2zenodo/tests/test_codemeta2zenodo.py (99%) rename {eossr => src/eossr}/metadata/codemeta2zenodo/tests/test_codemeta2zenodo_utils.py (93%) rename {eossr => src/eossr}/metadata/codemeta2zenodo/tests/test_converters.py (97%) rename {eossr => src/eossr}/metadata/codemeta2zenodo/utils.py (85%) rename {eossr => src/eossr}/metadata/schema/__init__.py (100%) rename {eossr => src/eossr}/metadata/schema/codemeta.json (100%) rename {eossr => src/eossr}/metadata/schema/escape_codemeta_crosswalk.csv (99%) rename {eossr => src/eossr}/metadata/tests/__init__.py (100%) rename {eossr => src/eossr}/metadata/tests/samples/codemeta_contributors_sample.json (100%) rename {eossr => src/eossr}/metadata/tests/samples/codemeta_not_valid.json (100%) rename {eossr => src/eossr}/metadata/tests/samples/codemeta_sample1.json (100%) rename {eossr => src/eossr}/metadata/tests/samples/codemeta_test.json (99%) rename {eossr => src/eossr}/metadata/tests/samples/codemeta_valid.json (100%) rename {eossr => src/eossr}/metadata/tests/samples/zenodo_test.json (99%) rename {eossr => src/eossr}/metadata/tests/test_codemeta.py (94%) rename {eossr => src/eossr}/metadata/tests/test_zenodo_metadata.py (100%) rename {eossr => src/eossr}/metadata/zenodo.py (99%) rename {eossr => src/eossr}/scripts/__init__.py (100%) rename {eossr => src/eossr}/scripts/check_connection_zenodo.py (100%) rename {eossr => src/eossr}/scripts/eossr_codemeta2zenodo.py (100%) rename {eossr => src/eossr}/scripts/eossr_metadata_validator.py (100%) rename {eossr => src/eossr}/scripts/eossr_upload_repository.py (100%) rename {eossr => src/eossr}/scripts/eossr_zenodo_validator.py (100%) rename {eossr => src/eossr}/scripts/tests/test_scripts.py (99%) rename {eossr => src/eossr}/scripts/update_codemeta_eossr.py (100%) rename {eossr => src/eossr}/scripts/zenodo_user_entries_cleanup.py (99%) rename {eossr => src/eossr}/scripts/zip_repository.py (100%) rename {eossr => src/eossr}/tests/conftest.py (99%) rename {eossr => src/eossr}/tests/test_globals.py (100%) rename {eossr => src/eossr}/tests/test_utils.py (100%) rename {eossr => src/eossr}/utils.py (100%) rename {eossr => src/eossr}/version.py (100%) diff --git a/.gitignore b/.gitignore index 03166c4..352dd4a 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,4 @@ default.profraw eossr.code-workspace # tokens -zenodo.token \ No newline at end of file +zenodo.token diff --git a/README.md b/README.md index 5665e45..a4eb674 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ In particular, it includes: ![eossr schema](https://s3-eu-west-1.amazonaws.com/openreseurope/manuscripts/16954/8df35fb4-5da0-472b-b560-c410ecf56296_figure1.gif) -Code: [https://gitlab.com/escape-ossr/eossr](https://gitlab.com/escape-ossr/eossr) +Code: [https://gitlab.com/escape-ossr/eossr](https://gitlab.com/escape-ossr/eossr) Documentation: [https://escape-ossr.gitlab.io/eossr/](https://escape2020.pages.in2p3.fr/wp3/eossr/) [![pipeline_badge](https://gitlab.com/escape-ossr/eossr/badges/master/pipeline.svg)]( diff --git a/docs/Makefile b/docs/Makefile index 0e282a4..0f96f89 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -35,4 +35,4 @@ doc: source-token # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 8be537a..b07bdb1 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -1,4 +1,3 @@ .wy-nav-content { max-width: none; } - diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index ca03e16..2cbe9e8 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -1,4 +1,4 @@ {% extends "!layout.html" %} {% block extrahead %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/eossr/metadata/codemeta2zenodo/__init__.py b/eossr/metadata/codemeta2zenodo/__init__.py deleted file mode 100644 index a8ce586..0000000 --- a/eossr/metadata/codemeta2zenodo/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .core import * \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 73b6e99..8189506 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,13 +39,12 @@ eossr-zenodo-metadata-validator = "eossr.scripts.eossr_zenodo_validator:main" tests = ["pytest", "pytest-cov", "python-dotenv"] extras = ["pre-commit"] + [tool.setuptools_scm] -write_to = "eossr/_version.py" +write_to = "src/eossr/_version.py" relative_to = "file:__file__" write_to_template = "__version__ = '{version}'" -[tool.setuptools.packages.find] -where = ["eossr/metadata/schema/"] [tool.pytest.ini_options] diff --git a/eossr/__init__.py b/src/eossr/__init__.py similarity index 100% rename from eossr/__init__.py rename to src/eossr/__init__.py diff --git a/eossr/_dev_version/_dev_version.py b/src/eossr/_dev_version/_dev_version.py similarity index 100% rename from eossr/_dev_version/_dev_version.py rename to src/eossr/_dev_version/_dev_version.py diff --git a/eossr/api/__init__.py b/src/eossr/api/__init__.py similarity index 100% rename from eossr/api/__init__.py rename to src/eossr/api/__init__.py diff --git a/eossr/api/ossr.py b/src/eossr/api/ossr.py similarity index 98% rename from eossr/api/ossr.py rename to src/eossr/api/ossr.py index 65cae8e..aa3e28a 100644 --- a/eossr/api/ossr.py +++ b/src/eossr/api/ossr.py @@ -2,7 +2,7 @@ import requests -from .zenodo import ZenodoAPI, search_records, query_records +from .zenodo import ZenodoAPI, query_records, search_records __all__ = [ 'search_ossr_records', diff --git a/eossr/api/tests/test_api.py b/src/eossr/api/tests/test_api.py similarity index 99% rename from eossr/api/tests/test_api.py rename to src/eossr/api/tests/test_api.py index 1051983..8221f6c 100644 --- a/eossr/api/tests/test_api.py +++ b/src/eossr/api/tests/test_api.py @@ -1,7 +1,9 @@ -from eossr import api -import requests import warnings +import requests + +from eossr import api + def test_search_ossr_records(): try: diff --git a/eossr/api/zenodo/__init__.py b/src/eossr/api/zenodo/__init__.py similarity index 100% rename from eossr/api/zenodo/__init__.py rename to src/eossr/api/zenodo/__init__.py diff --git a/eossr/api/zenodo/http_status.py b/src/eossr/api/zenodo/http_status.py similarity index 99% rename from eossr/api/zenodo/http_status.py rename to src/eossr/api/zenodo/http_status.py index 08d3aae..d691a23 100644 --- a/eossr/api/zenodo/http_status.py +++ b/src/eossr/api/zenodo/http_status.py @@ -1,7 +1,6 @@ #!/usr/bin/env python import requests - __all__ = ['ZenodoHTTPStatus'] diff --git a/eossr/api/zenodo/tests/test_http_status.py b/src/eossr/api/zenodo/tests/test_http_status.py similarity index 99% rename from eossr/api/zenodo/tests/test_http_status.py rename to src/eossr/api/zenodo/tests/test_http_status.py index 1a0ce3e..5f876df 100644 --- a/eossr/api/zenodo/tests/test_http_status.py +++ b/src/eossr/api/zenodo/tests/test_http_status.py @@ -1,15 +1,15 @@ -import pytest +from unittest.mock import MagicMock -from eossr.api.zenodo import http_status +import pytest import requests -from unittest.mock import MagicMock +from eossr.api.zenodo import http_status def test_ZenodoHTTPStatus(): # good status, no error raised - + response = MagicMock(status_code=200) status = http_status.ZenodoHTTPStatus(response) assert status.code == 200 diff --git a/eossr/api/zenodo/tests/test_zenodo.py b/src/eossr/api/zenodo/tests/test_zenodo.py similarity index 90% rename from eossr/api/zenodo/tests/test_zenodo.py rename to src/eossr/api/zenodo/tests/test_zenodo.py index 74e3776..9715b25 100644 --- a/eossr/api/zenodo/tests/test_zenodo.py +++ b/src/eossr/api/zenodo/tests/test_zenodo.py @@ -10,11 +10,19 @@ from pathlib import Path import pytest import requests -from eossr.api.zenodo import zenodo -from eossr.api.zenodo import query_deposits -from eossr.api.zenodo import Record, ZenodoAPI, get_record, search_records, get_deposit -from eossr.api.zenodo import get_funder, get_license, get_record, get_community from eossr.api.ossr import sandbox_escape_community +from eossr.api.zenodo import ( + Record, + ZenodoAPI, + get_community, + get_deposit, + get_funder, + get_license, + get_record, + query_deposits, + search_records, + zenodo, +) from eossr.api.zenodo.http_status import HTTPStatusError from eossr.api.zenodo.zenodo import is_live, query_records from eossr.metadata.tests import ZENODO_TEST_FILE @@ -41,7 +49,7 @@ def temp_dir_with_file(): _, filename = tempfile.mkstemp(dir=tmpdirname) Path(filename).write_text('Hello from eossr unit tests') - yield tmpdirname, filename + yield tmpdirname, filename class TestZenodoApiSandbox(unittest.TestCase): @@ -56,6 +64,7 @@ class TestZenodoApiSandbox(unittest.TestCase): def test_initialization_sandbox(self): from eossr.api.zenodo import zenodo_sandbox_api_base_url + assert isinstance(self.zenodo, ZenodoAPI) assert self.zenodo.api_base_url == zenodo_sandbox_api_base_url assert self.zenodo.access_token == self.token @@ -73,6 +82,7 @@ class TestZenodoAPINoToken(unittest.TestCase): def test_initialization(self): from eossr.api.zenodo import zenodo_api_base_url + assert isinstance(self.zenodo, ZenodoAPI) assert self.zenodo.api_base_url == zenodo_api_base_url assert self.zenodo.access_token == self.token @@ -129,7 +139,7 @@ class TestZenodoAPITokenSandbox(unittest.TestCase): @pytest.mark.skipif(os.getenv('SANDBOX_ZENODO_TOKEN') is None, reason="SANDBOX_ZENODO_TOKEN not defined") def test_upload_package(temp_dir_with_file): - tmpdirname, _ = temp_dir_with_file + tmpdirname, _ = temp_dir_with_file zenodo = ZenodoAPI(access_token=os.getenv('SANDBOX_ZENODO_TOKEN'), sandbox=True) try: # create new record @@ -165,15 +175,14 @@ def test_check_upload_to_zenodo(temp_dir_with_file): test_deposit_id = new_deposit.json()['id'] with open(zenodo.path_zenodo_file(tmpdirname)) as file: metadata_entry = json.load(file) - updated_metadata = zenodo.set_deposit_metadata(test_deposit_id, - json_metadata=metadata_entry) + updated_metadata = zenodo.set_deposit_metadata(test_deposit_id, json_metadata=metadata_entry) assert updated_metadata.status_code == 200 # 4 - Test delete entry delete_test_entry = zenodo.erase_deposit(test_deposit_id) assert delete_test_entry.status_code == 204 - # 2023-15-11 - not working at the moment with Zenodo API + # 2023-15-11 - not working at the moment with Zenodo API # def test_accept_community_request(self): # zen = self.zenodo # record_id = test_record_sandbox @@ -217,10 +226,11 @@ class TestZenodoAPIToken(unittest.TestCase): def test_search_records(): try: - zenodo_records = search_records('ESCAPE template project', - all_versions=True, - timeout=tests_default_timeout, - ) + zenodo_records = search_records( + 'ESCAPE template project', + all_versions=True, + timeout=tests_default_timeout, + ) assert len(zenodo_records) > 1 all_dois = [r.data['doi'] for r in zenodo_records] assert '10.5281/zenodo.4923992' in all_dois @@ -259,8 +269,7 @@ def test_get_record_sandbox(): def test_write_record_zenodo(record_4923992, tmpdir): - record_4923992._write_zenodo_deposit( - filename=tmpdir / '.zenodo.json', validate=False) + record_4923992._write_zenodo_deposit(filename=tmpdir / '.zenodo.json', validate=False) with open(tmpdir / '.zenodo.json') as file: json_dict = json.load(file) assert json_dict['title'] == 'ESCAPE template project' @@ -268,8 +277,7 @@ def test_write_record_zenodo(record_4923992, tmpdir): def test_search_funders(): - funders = zenodo.search_funders('name:European+Commission', - timeout=tests_default_timeout) + funders = zenodo.search_funders('name:European+Commission', timeout=tests_default_timeout) assert len(funders) > 1 @@ -280,15 +288,15 @@ def test_search_funders(): def test_search_license(): - licenses = zenodo.search_licenses('MIT', - timeout=tests_default_timeout) + licenses = zenodo.search_licenses('MIT', timeout=tests_default_timeout) assert licenses[0]['title'] == {'en': 'MIT License'} def test_search_communities(): - communities = zenodo.search_communities('id:8b951469-55d0-44f2-bb91-b541501c9c8e', - timeout=tests_default_timeout, - ) + communities = zenodo.search_communities( + 'id:8b951469-55d0-44f2-bb91-b541501c9c8e', + timeout=tests_default_timeout, + ) assert communities[0]['slug'] == 'escape2020' @@ -303,8 +311,7 @@ def test_get_associated_versions(): # Seven versions, to date 21/03/2022 assert len(eossr_record_versions) >= 7 for recid in eossr_record_versions.keys(): - assert eossr_record.data['conceptrecid'] == Record.from_id( - recid).data['conceptrecid'] + assert eossr_record.data['conceptrecid'] == Record.from_id(recid).data['conceptrecid'] assert eossr_record_versions[5524913] == 'v0.2' # ID of eOSSR version v0.2 @@ -367,5 +374,3 @@ def test_get_deposit(): deposit = get_deposit(test_record_sandbox, sandbox=True, access_token=os.getenv('SANDBOX_ZENODO_TOKEN')) assert isinstance(deposit, dict) assert deposit['conceptdoi'] == f'10.5072/zenodo.{test_record_sandbox-1}' - - \ No newline at end of file diff --git a/eossr/api/zenodo/zenodo.py b/src/eossr/api/zenodo/zenodo.py similarity index 93% rename from eossr/api/zenodo/zenodo.py rename to src/eossr/api/zenodo/zenodo.py index 2696d80..94505a0 100644 --- a/eossr/api/zenodo/zenodo.py +++ b/src/eossr/api/zenodo/zenodo.py @@ -11,7 +11,7 @@ The main class is `ZenodoAPI`, which allows to perform tasks within the (sandbox Note the following nomeclature: - queries: GET methods to {api_url}, returns requests.response objects - search: general text-based searches. Returns JSON dicts or custom classes with the information of the query -- get: targeted query. returns JSON dicts or custom classes with the information of the query +- get: targeted query. returns JSON dicts or custom classes with the information of the query - deposit: a deposit is a single entry, published or not published, in Zenodo. - record: a record is a published entry in Zenodo. It can be a software, a dataset, a publication, etc. @@ -26,11 +26,12 @@ import re import sys import textwrap import warnings -import requests from copy import deepcopy from pathlib import Path from urllib.request import urlopen +import requests + from ...metadata.zenodo import write_zenodo_metadata, zenodo_filepath from ...utils import get_codemeta_from_zipurl, write_json from . import http_status @@ -56,7 +57,7 @@ __all__ = [ 'get_funder', 'query_deposits', 'query_deposit', - 'get_deposit' + 'get_deposit', ] @@ -116,8 +117,7 @@ class ZenodoAPI: :return: """ if self.access_token is None or self.access_token == '': - raise ValueError( - "No access token was provided. This method requires one.") + raise ValueError("No access token was provided. This method requires one.") def query_user_deposits(self): """ @@ -151,7 +151,6 @@ class ZenodoAPI: """ self._raise_token_status() return query_deposit(deposit_id, self.access_token, sandbox=self.sandbox) - def create_new_deposit(self): """ @@ -190,11 +189,7 @@ class ZenodoAPI: self._raise_token_status() url = f"{self.api_base_url}/deposit/depositions" headers = {"Content-Type": "application/json"} - req = requests.post(url, - json={}, - headers=headers, - params=self.parameters, - timeout=_default_timeout) + req = requests.post(url, json={}, headers=headers, params=self.parameters, timeout=_default_timeout) http_status.ZenodoHTTPStatus(req) return req @@ -226,10 +221,7 @@ class ZenodoAPI: url = f"{bucket_url}/{name_file}" with open(path_file, 'rb') as upload_file: - upload = requests.put(url, - data=upload_file, - params=self.parameters, - timeout=_default_timeout) + upload = requests.put(url, data=upload_file, params=self.parameters, timeout=_default_timeout) http_status.ZenodoHTTPStatus(upload) return upload @@ -257,11 +249,9 @@ class ZenodoAPI: # The metadata field is already created, just need to be updated. # Thus, the root 'metadata' key need to be kept, to indicate the field to be updated. data = {"metadata": json_metadata} - req = requests.put(url, - data=json.dumps(data), - headers=headers, - params=self.parameters, - timeout=_default_timeout) + req = requests.put( + url, data=json.dumps(data), headers=headers, params=self.parameters, timeout=_default_timeout + ) http_status.ZenodoHTTPStatus(req) return req @@ -303,9 +293,7 @@ class ZenodoAPI: """ self._raise_token_status() url = f"{self.api_base_url}/deposit/depositions/{deposit_id}" - req = requests.delete(url, - params=self.parameters, - timeout=_default_timeout) + req = requests.delete(url, params=self.parameters, timeout=_default_timeout) if req.status_code == 204: print("The deposit has been deleted") return req @@ -338,9 +326,7 @@ class ZenodoAPI: """ self._raise_token_status() url = f"{self.api_base_url}/deposit/depositions/{deposit_id}/files/{file_id}" - req = requests.delete(url, - params=self.parameters, - timeout=_default_timeout) + req = requests.delete(url, params=self.parameters, timeout=_default_timeout) http_status.ZenodoHTTPStatus(req) return req @@ -420,30 +406,30 @@ class ZenodoAPI: def upload_dir_content(self, directory, record_id=None, metadata=None, erase_previous_files=True, publish=True): """ Package the project root directory as a zip archive and upload it to Zenodo. - + Parameters ---------- directory : str or Path Path to the directory to upload. record_id : str or int or None, optional - If a record_id is provided, a new version of the record will be created. + If a record_id is provided, a new version of the record will be created. Otherwise, a new record is created. metadata : dict or None, optional - Dictionary of Zenodo metadata. If None, the metadata will be read from a `.zenodo.json` file + Dictionary of Zenodo metadata. If None, the metadata will be read from a `.zenodo.json` file or a `codemeta.json` file in `self.root_dir`. erase_previous_files : bool, optional In case of making a new version of an existing record (`record_id` not None), erase files from the previous version. publish : bool, optional - If True, publish the record. Otherwise, the record is prepared but publication must be done manually. + If True, publish the record. Otherwise, the record is prepared but publication must be done manually. This is useful to check or discard the record before publication. - + Returns ------- str The ID of the newly created or updated record. """ self._raise_token_status() - + # prepare new record version if record_id is not None: new_deposit = self.new_version_deposit(record_id) @@ -515,8 +501,7 @@ class ZenodoAPI: if not path_zenodo_file.exists(): raise FileNotFoundError(f"No {path_zenodo_file} file.") - print( - f"\n * Using {path_zenodo_file} file to simulate a new upload to Zenodo... \n") + print(f"\n * Using {path_zenodo_file} file to simulate a new upload to Zenodo... \n") # 1 - Test connection print("1 --> Testing communication with Zenodo...") @@ -547,8 +532,7 @@ class ZenodoAPI: print(" * Metadata deposit status OK !") pprint.pprint(metadata_entry) except http_status.HTTPStatusError: - print(" ! ERROR while testing update of metadata\n", - updated_metadata.json()) + print(" ! ERROR while testing update of metadata\n", updated_metadata.json()) print(" ! The deposit will be deleted") # 4 - Test delete entry @@ -557,10 +541,8 @@ class ZenodoAPI: try: http_status.ZenodoHTTPStatus(delete_test_entry) except http_status.HTTPStatusError: - print( - f" !! ERROR erasing dummy test entry: {delete_test_entry.json()}") - print( - f"Please erase it manually at {self.api_base_url[:-4]}/deposit") + print(f" !! ERROR erasing dummy test entry: {delete_test_entry.json()}") + print(f"Please erase it manually at {self.api_base_url[:-4]}/deposit") sys.exit(-1) print(" * Delete test entry status OK !") @@ -603,10 +585,8 @@ class ZenodoAPI: if 'related_identifiers' in user_rec.data['metadata'] and 'related_identifiers' in record.data['metadata']: - relid1 = [r['identifier'] - for r in user_rec.data['metadata']['related_identifiers']] - relid2 = [r['identifier'] - for r in record.data['metadata']['related_identifiers']] + relid1 = [r['identifier'] for r in user_rec.data['metadata']['related_identifiers']] + relid2 = [r['identifier'] for r in record.data['metadata']['related_identifiers']] if set(relid1).intersection(relid2): similar_records.append(user_rec) @@ -631,8 +611,7 @@ class ZenodoAPI: """ self._raise_token_status() - community_json = get_community( - community, sandbox=self.sandbox, token=self.access_token) + community_json = get_community(community, sandbox=self.sandbox, token=self.access_token) community_requests_url = community_json['links']['requests'] parameters = deepcopy(self.parameters) parameters.update(params) @@ -668,12 +647,10 @@ class ZenodoAPI: pending_record = req break if pending_record is None: - raise ValueError( - f"Record {record_id} is not in the pending requests of community {community}") + raise ValueError(f"Record {record_id} is not in the pending requests of community {community}") # get url in links if action not in pending_record['links']['actions'].keys(): - raise KeyError( - f"Action {action} is not available for record {record_id}") + raise KeyError(f"Action {action} is not available for record {record_id}") else: url = pending_record['links']['actions'][action] # send request @@ -701,7 +678,7 @@ class ZenodoAPI: req = requests.post( f"{self.api_base_url}/deposit/depositions/{record_id}/actions/edit", params={"access_token": self.access_token}, - timeout=_default_timeout + timeout=_default_timeout, ) if req.status_code == 403: # In this case it is fine to continue editing the record metadata @@ -741,8 +718,7 @@ class Record: # Other keys are either optional, or can be hidden in case of Closed Access entries. for meta_key in ['title', 'doi']: if meta_key not in data['metadata'].keys(): - raise ValueError( - f"Mandatory key {meta_key} not in data['metadata']") + raise ValueError(f"Mandatory key {meta_key} not in data['metadata']") self.data = data def __str__(self): @@ -774,14 +750,12 @@ class Record: if 'relations' in metadata: metadata.pop('relations') if 'communities' in metadata: - metadata['communities'] = [{'identifier': c['id']} - for c in metadata['communities']] + metadata['communities'] = [{'identifier': c['id']} for c in metadata['communities']] if 'zenodo' in metadata['doi']: metadata.pop('doi') metadata['license'] = metadata['license']['id'] - write_zenodo_metadata(metadata, filename=filename, - overwrite=overwrite, validate=validate) + write_zenodo_metadata(metadata, filename=filename, overwrite=overwrite, validate=validate) def write_metadata(self, filename, overwrite=False): """ @@ -793,8 +767,7 @@ class Record: overwrite: bool True to overwrite existing file """ - write_json(self.data['metadata'], - filename=filename, overwrite=overwrite) + write_json(self.data['metadata'], filename=filename, overwrite=overwrite) @property def id(self): @@ -906,8 +879,7 @@ class Record: description = self.metadata.get('description', '') # Replace paragraph tags with newlines - description = re.sub('

', linebreak, re.sub( - '

', linebreak, description)) + description = re.sub('

', linebreak, re.sub('

', linebreak, description)) # Then strip the remaining HTML tags stripped_description = re.sub('<[^<]+?>', '', description) @@ -975,11 +947,9 @@ class Record: If no `codemeta.json` file is found in the record. """ if 'files' not in self.data: - raise FileNotFoundError( - f'The record {self.id} does not contain any file') + raise FileNotFoundError(f'The record {self.id} does not contain any file') - codemeta_paths = [s for s in self.filelist - if Path(s.rsplit('/content', maxsplit=1)[0]).name == 'codemeta.json'] + codemeta_paths = [s for s in self.filelist if Path(s.rsplit('/content', maxsplit=1)[0]).name == 'codemeta.json'] ziparchives = [s for s in self.filelist if s.endswith('.zip/content')] if len(codemeta_paths) >= 1: # if there are more than one codemeta file in the repository, we consider the one in the root directory, @@ -992,11 +962,9 @@ class Record: return get_codemeta_from_zipurl(zipurl, **zipurl_kwargs) except FileNotFoundError: pass - raise FileNotFoundError( - f"No `codemeta.json` file found in record {self.id}") + raise FileNotFoundError(f"No `codemeta.json` file found in record {self.id}") else: - raise FileNotFoundError( - f"No `codemeta.json` file found in record {self.id}") + raise FileNotFoundError(f"No `codemeta.json` file found in record {self.id}") @property def doi(self): @@ -1042,9 +1010,10 @@ class Record: with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: future_to_url = { - executor.submit(download_file, - url, - f'{directory}/{os.path.basename(remove_trailing_content(url))}'): url for url in self.filelist + executor.submit( + download_file, url, f'{directory}/{os.path.basename(remove_trailing_content(url))}' + ): url + for url in self.filelist } for future in concurrent.futures.as_completed(future_to_url): url = future_to_url[future] @@ -1123,6 +1092,7 @@ def _zenodo_get_factory(endpoint): This will retrieve the Zenodo record with ID 1234. """ + def api_function(id, sandbox=False, token=None): base_url = zenodo_sandbox_api_base_url if sandbox else zenodo_api_base_url url = f"{base_url}{endpoint}/{id}" @@ -1645,7 +1615,7 @@ def get_deposit(deposit_id, access_token, sandbox=False): ---------- deposit_id : str or int The ID of the deposit to retrieve. - + access_token : str The access token for authentication. diff --git a/eossr/metadata/__init__.py b/src/eossr/metadata/__init__.py similarity index 100% rename from eossr/metadata/__init__.py rename to src/eossr/metadata/__init__.py diff --git a/eossr/metadata/codemeta.py b/src/eossr/metadata/codemeta.py similarity index 92% rename from eossr/metadata/codemeta.py rename to src/eossr/metadata/codemeta.py index c564a87..a95fb2c 100644 --- a/eossr/metadata/codemeta.py +++ b/src/eossr/metadata/codemeta.py @@ -1,11 +1,11 @@ import json +from pathlib import Path from warnings import warn import numpy as np import pandas as pd import pkg_resources import requests -from pathlib import Path from ..utils import write_json from . import valid_semver @@ -20,7 +20,7 @@ __all__ = [ CODEMETA_VERSIONS_CONTEXTS = { "https://raw.githubusercontent.com/codemeta/codemeta/3.0/codemeta.jsonld": "3.0", "https://raw.githubusercontent.com/codemeta/codemeta/2.0/codemeta.jsonld": "2.0", - "https://raw.githubusercontent.com/codemeta/codemeta/1.0/codemeta.jsonld":"1.0", + "https://raw.githubusercontent.com/codemeta/codemeta/1.0/codemeta.jsonld": "1.0", "https://doi.org/10.5063/schema/codemeta-2.0": "2.0", "https://doi.org/10.5063/schema/codemeta-1.0": "1.0", # "https://doi.org/10.5063/schema/codemeta-3.0": "3.0", @@ -62,13 +62,17 @@ def schema(codemeta_version='2.0'): def codemeta_version_from_context(context): if context not in CODEMETA_VERSIONS_CONTEXTS: - raise ValueError(f"Unknown @context {context}, supported contexts are {list(CODEMETA_VERSIONS_CONTEXTS.keys())}") + raise ValueError( + f"Unknown @context {context}, supported contexts are {list(CODEMETA_VERSIONS_CONTEXTS.keys())}" + ) return CODEMETA_VERSIONS_CONTEXTS[context] def codemeta_crosswalk(codemeta_version='2.0'): if codemeta_version not in CODEMETA_VERSIONS_CONTEXTS.values(): - raise ValueError(f"CodeMeta version {codemeta_version} not supported. Supported versions are {CODEMETA_VERSIONS_CONTEXTS.values()}") + raise ValueError( + f"CodeMeta version {codemeta_version} not supported. Supported versions are {CODEMETA_VERSIONS_CONTEXTS.values()}" + ) df = pd.read_csv( pkg_resources.resource_stream(__name__, f'schema/escape_codemeta_crosswalk.csv'), comment='#', @@ -155,12 +159,14 @@ class Codemeta: codemeta_dict = self.metadata.copy() if codemeta_dict["@type"] != "SoftwareSourceCode": raise ValueError(f"Invalid @type {codemeta_dict['@type']}") - + if "@context" not in codemeta_dict: raise ValueError("No @context key in provided codemeta") - + if codemeta_dict["@context"] not in CODEMETA_VERSIONS_CONTEXTS: - raise ValueError(f"Invalid @context {codemeta_dict['@context']}, must be one of {list(CODEMETA_VERSIONS_CONTEXTS.keys())}") + raise ValueError( + f"Invalid @context {codemeta_dict['@context']}, must be one of {list(CODEMETA_VERSIONS_CONTEXTS.keys())}" + ) self._codemeta_version = codemeta_version_from_context(codemeta_dict["@context"]) codemeta_dict.pop("@context") diff --git a/src/eossr/metadata/codemeta2zenodo/__init__.py b/src/eossr/metadata/codemeta2zenodo/__init__.py new file mode 100644 index 0000000..bb67a43 --- /dev/null +++ b/src/eossr/metadata/codemeta2zenodo/__init__.py @@ -0,0 +1 @@ +from .core import * diff --git a/eossr/metadata/codemeta2zenodo/converters.py b/src/eossr/metadata/codemeta2zenodo/converters.py similarity index 89% rename from eossr/metadata/codemeta2zenodo/converters.py rename to src/eossr/metadata/codemeta2zenodo/converters.py index 44b4e33..18f3c76 100644 --- a/eossr/metadata/codemeta2zenodo/converters.py +++ b/src/eossr/metadata/codemeta2zenodo/converters.py @@ -1,22 +1,20 @@ -import re -import numpy as np import datetime +import re import warnings +from typing import Any, Dict, List from urllib.parse import urlparse -from typing import Any, List, Dict -from ...api.zenodo import search_licenses, search_communities + +import numpy as np + +from ...api.zenodo import search_communities, search_licenses from ...utils import spdx_licenses from ..codemeta import codemeta_crosswalk - CROSSWALK_TABLE = codemeta_crosswalk() -__filtered_table__ = CROSSWALK_TABLE.query( - "Type.str.contains('Organization') or Type.str.contains('Person')") +__filtered_table__ = CROSSWALK_TABLE.query("Type.str.contains('Organization') or Type.str.contains('Person')") CODEMETA_ROLES = __filtered_table__['Property'].values -CODEMETA_CONTRIBUTORS_ROLES = __filtered_table__.loc[__filtered_table__[ - 'Zenodo'] == 'contributors', 'Property'].values -CODEMETA_CREATORS_ROLES = __filtered_table__.loc[__filtered_table__[ - 'Zenodo'] == 'creators', 'Property'].values +CODEMETA_CONTRIBUTORS_ROLES = __filtered_table__.loc[__filtered_table__['Zenodo'] == 'contributors', 'Property'].values +CODEMETA_CREATORS_ROLES = __filtered_table__.loc[__filtered_table__['Zenodo'] == 'creators', 'Property'].values __CONVERTER_MAPPING__ = { @@ -64,7 +62,8 @@ def MasterConverter(codemeta_value: Any, codemeta_type: str, codemeta_key=None, converted_values = [] for item in codemeta_value: converted_value = MasterConverter( - item, codemeta_type, codemeta_key=codemeta_key, zenodo_contributor_type=zenodo_contributor_type) + item, codemeta_type, codemeta_key=codemeta_key, zenodo_contributor_type=zenodo_contributor_type + ) if converted_value is not None: if isinstance(converted_value, list): converted_values.extend(converted_value) @@ -144,6 +143,7 @@ class BaseConverter: is_valid_type(valid_type) -> bool: Checks if the type of the value is valid. """ + def __init__(self, value): self.value = value self.check_type() @@ -155,9 +155,7 @@ class BaseConverter: raise NotImplementedError def is_valid_type(self, valid_type): - return isinstance(self.value, valid_type) or all( - isinstance(x, valid_type) for x in self.value - ) + return isinstance(self.value, valid_type) or all(isinstance(x, valid_type) for x in self.value) class DummyConverter(BaseConverter): @@ -174,6 +172,7 @@ class ReferencesConverter(ListConverter): """ Converter for the 'references' field of CodeMeta to the 'references' field of Zenodo. """ + def check_type(self) -> None: """ Check if the value is a valid URL. @@ -194,8 +193,7 @@ class TextConverter(BaseConverter): def check_type(self) -> None: if not isinstance(self.value, str): - raise ValueError( - f"{self.value} must be a string, not {type(self.value)}") + raise ValueError(f"{self.value} must be a string, not {type(self.value)}") def convert(self) -> str: """ @@ -266,6 +264,8 @@ class PersonConverter(BaseConverter): If the Person object does not have either givenName or familyName. If the affiliation is not a dict or list of dicts. """ + + class PersonConverter(BaseConverter): def check_type(self): if not isinstance(self.value, dict): @@ -284,8 +284,7 @@ class PersonConverter(BaseConverter): elif family_name: complete_name = family_name else: - raise ValueError( - f"Person must have either givenName or familyName") + raise ValueError(f"Person must have either givenName or familyName") zenodo_person["name"] = complete_name @@ -294,15 +293,14 @@ class PersonConverter(BaseConverter): pass elif isinstance(affiliation, dict): # OrganizationConverter always return a list of dicts (because that's what Zenodo expects) - zenodo_person['affiliation'] = OrganizationConverter(affiliation).convert()[ - 0]['name'] + zenodo_person['affiliation'] = OrganizationConverter(affiliation).convert()[0]['name'] elif isinstance(affiliation, list): # if they are several affiliations, we join them in a single string zenodo_person['affiliation'] = "; ".join( - [OrganizationConverter(org).convert()[0]['name'] for org in affiliation]) + [OrganizationConverter(org).convert()[0]['name'] for org in affiliation] + ) else: - raise ValueError( - f"Affiliation must be a dict or list of dicts, not {type(affiliation)}") + raise ValueError(f"Affiliation must be a dict or list of dicts, not {type(affiliation)}") identifier = self.value.get("identifier", None) if identifier and IDConverter(identifier).convert().get('orcid', None): @@ -318,8 +316,7 @@ class IDConverter(BaseConverter): def check_type(self): if not isinstance(self.value, str): - raise ValueError( - f"{self.value} must be a string, not {type(self.value)}") + raise ValueError(f"{self.value} must be a string, not {type(self.value)}") @staticmethod def _check_orcid_number(number): @@ -352,8 +349,7 @@ class OrganizationConverter(BaseConverter): # take dict as entry and return a list of dict (because that's what Zenodo expect, even for a single organization) def check_type(self): if not isinstance(self.value, dict): - raise ValueError( - f"Organization must be a dict, not {type(self.value)}") + raise ValueError(f"Organization must be a dict, not {type(self.value)}") def _convert_dict(self): if "name" not in self.value: @@ -374,6 +370,7 @@ class TextOrUrlConverter(TextConverter): """ Treat as text """ + pass @@ -393,8 +390,7 @@ class NumberOrTextConverter(BaseConverter): class BooleanConverter(BaseConverter): def check_type(self): if not isinstance(self.value, bool): - raise ValueError( - f"{self.value} must be a bool, not {type(self.value)}") + raise ValueError(f"{self.value} must be a bool, not {type(self.value)}") def convert(self): return self.value @@ -451,13 +447,9 @@ class DateConverter(BaseConverter): def check_type(self): # check that date is in ISO 8601 format try: - datetime.datetime.fromisoformat( - self.value - ) + datetime.datetime.fromisoformat(self.value) except ValueError as exc: - raise ValueError( - f"Incorrect date format, should be ISO 8601 format, but is {self.value}" - ) from exc + raise ValueError(f"Incorrect date format, should be ISO 8601 format, but is {self.value}") from exc def convert(self): return self.value @@ -488,9 +480,10 @@ class CommunityConverter(TextConverter): A list of matching communities found in Zenodo. """ if self._matching_communities is None: - self._matching_communities = search_communities(self.value, - size=100, - ) + self._matching_communities = search_communities( + self.value, + size=100, + ) return self._matching_communities def convert(self): @@ -512,20 +505,19 @@ class CommunityConverter(TextConverter): class ReviewConverter(BaseConverter): def convert(self) -> Any: - raise NotImplementedError( - "Should be implemented to support CodeMeta v3.0") + raise NotImplementedError("Should be implemented to support CodeMeta v3.0") class OrganizationOrPersonConverter(BaseConverter): def check_type(self): if not isinstance(self.value, dict): - raise ValueError( - f"Organization or Person must be a dict, not {type(self.value)}") + raise ValueError(f"Organization or Person must be a dict, not {type(self.value)}") if "@type" not in self.value: raise ValueError("Organization or Person must have a @type") if not self.value["@type"] in ["Organization", "Person"]: raise ValueError( - f"Organization or Person must be of type Organization or Person, not {self.value['@type']}") + f"Organization or Person must be of type Organization or Person, not {self.value['@type']}" + ) def convert(self): if self.value.get("@type", None) == "Person": @@ -561,8 +553,7 @@ class ContributorConverter(OrganizationOrPersonConverter): # raise ValueError(f"Contributors roles are {CODEMETA_CONTRIBUTORS_ROLES}, {self.contributor_type} is not one of them") def convert(self): - zenodo_contributor = OrganizationOrPersonConverter( - self.value).convert() + zenodo_contributor = OrganizationOrPersonConverter(self.value).convert() # OrganizationOrPersonConverter always returns a list of dicts (because that's what Zenodo expects) zenodo_contributor[0]["type"] = self.contributor_type @@ -626,8 +617,7 @@ class LicenseConverter(BaseConverter): :rtype: np.ndarray """ if cls._all_zenodo_license_ids is None: - cls._all_zenodo_license_ids = np.array( - [lic['id'] for lic in cls.all_zenodo_licenses()]) + cls._all_zenodo_license_ids = np.array([lic['id'] for lic in cls.all_zenodo_licenses()]) return cls._all_zenodo_license_ids @classmethod @@ -639,8 +629,7 @@ class LicenseConverter(BaseConverter): :rtype: np.ndarray """ if cls._all_zenodo_license_titles is None: - cls._all_zenodo_license_titles = np.array( - [lic['title']['en'] for lic in cls.all_zenodo_licenses()]) + cls._all_zenodo_license_titles = np.array([lic['title']['en'] for lic in cls.all_zenodo_licenses()]) return cls._all_zenodo_license_titles def _convert_string(self, license): @@ -694,5 +683,4 @@ class LicenseConverter(BaseConverter): return self._convert_list(self.value) else: # check type should have raised an error here - raise ValueError( - f"License must be a string or a list of strings, not {type(self.value)}") + raise ValueError(f"License must be a string or a list of strings, not {type(self.value)}") diff --git a/eossr/metadata/codemeta2zenodo/core.py b/src/eossr/metadata/codemeta2zenodo/core.py similarity index 88% rename from eossr/metadata/codemeta2zenodo/core.py rename to src/eossr/metadata/codemeta2zenodo/core.py index a65e147..bacd1d5 100644 --- a/eossr/metadata/codemeta2zenodo/core.py +++ b/src/eossr/metadata/codemeta2zenodo/core.py @@ -6,19 +6,20 @@ The logic is the following: - in general, the choice of which converter class should be used is based on the codemeta Type """ -import json import datetime +import json import warnings from pathlib import Path -from .utils import remove_duplicates, handle_version_type + from ..codemeta import Codemeta -from .converters import MasterConverter from ..zenodo import ( - validate_zenodo_metadata_deposit, add_escape2020_community, add_escape2020_grant, + validate_zenodo_metadata_deposit, write_zenodo_metadata, ) +from .converters import MasterConverter +from .utils import handle_version_type, remove_duplicates def add_upload_type(codemeta_dict, zenodo_dict): @@ -57,9 +58,7 @@ def codemeta2zenodo(codemeta_dict, zenodo_access_right='open'): except IndexError: # if the key is not found in the crosswalk table, just don't convert it # note: the codemeta schema should have been validated before - warnings.warn( - f"Key '{codemeta_key}' not found in crosswalk table, ignored by converter" - ) + warnings.warn(f"Key '{codemeta_key}' not found in crosswalk table, ignored by converter") continue zenodo_key = crosswalk_table.loc[index, "Zenodo"] @@ -83,10 +82,12 @@ def codemeta2zenodo(codemeta_dict, zenodo_access_right='open'): if not isinstance(sub_zenodo_dict[zenodo_key], list): zenodo_dict[zenodo_key] = [sub_zenodo_dict[zenodo_key]] - converted_value = MasterConverter(codemeta_value, - codemeta_type, - codemeta_key=codemeta_key, - zenodo_contributor_type=zenodo_contributor_type) + converted_value = MasterConverter( + codemeta_value, + codemeta_type, + codemeta_key=codemeta_key, + zenodo_contributor_type=zenodo_contributor_type, + ) if converted_value is None: pass elif isinstance(converted_value, list): @@ -94,18 +95,21 @@ def codemeta2zenodo(codemeta_dict, zenodo_access_right='open'): else: sub_zenodo_dict[zenodo_key].append(converted_value) else: - converted_value = MasterConverter(codemeta_value, - codemeta_type, - codemeta_key=codemeta_key, - zenodo_contributor_type=zenodo_contributor_type, - ) + converted_value = MasterConverter( + codemeta_value, + codemeta_type, + codemeta_key=codemeta_key, + zenodo_contributor_type=zenodo_contributor_type, + ) if converted_value is None: pass else: sub_zenodo_dict[zenodo_key] = converted_value if zenodo_access_right not in ['open', 'closed', 'embargoed', 'restricted']: - raise ValueError(f"Invalid access_right {zenodo_access_right}, must be one of 'open', 'closed', 'embargoed', 'restricted'") + raise ValueError( + f"Invalid access_right {zenodo_access_right}, must be one of 'open', 'closed', 'embargoed', 'restricted'" + ) zenodo_dict['access_right'] = zenodo_access_right # Handle the case where version is provided in two places in codemeta, leading to a list of versions, but zenodo only accepts one diff --git a/eossr/metadata/codemeta2zenodo/tests/test_codemeta2zenodo.py b/src/eossr/metadata/codemeta2zenodo/tests/test_codemeta2zenodo.py similarity index 99% rename from eossr/metadata/codemeta2zenodo/tests/test_codemeta2zenodo.py rename to src/eossr/metadata/codemeta2zenodo/tests/test_codemeta2zenodo.py index 3c9a142..7130b62 100644 --- a/eossr/metadata/codemeta2zenodo/tests/test_codemeta2zenodo.py +++ b/src/eossr/metadata/codemeta2zenodo/tests/test_codemeta2zenodo.py @@ -1,10 +1,12 @@ import json +import warnings + import pytest import requests -import warnings + from eossr.metadata.codemeta2zenodo import ( - codemeta2ossr, CodeMeta2ZenodoController, + codemeta2ossr, parse_codemeta_and_write_zenodo_metadata_file, ) from eossr.metadata.tests import CODEMETA_TEST_FILE @@ -67,6 +69,7 @@ def test_sample_file_conversion(tmp_dir): except requests.exceptions.ReadTimeout as e: warnings.warn(f"ReadTimeout: {e}") + def test_root_codemeta_conversion(tmp_dir): try: parse_codemeta_and_write_zenodo_metadata_file(CODEMETA_TEST_FILE, tmp_dir) diff --git a/eossr/metadata/codemeta2zenodo/tests/test_codemeta2zenodo_utils.py b/src/eossr/metadata/codemeta2zenodo/tests/test_codemeta2zenodo_utils.py similarity index 93% rename from eossr/metadata/codemeta2zenodo/tests/test_codemeta2zenodo_utils.py rename to src/eossr/metadata/codemeta2zenodo/tests/test_codemeta2zenodo_utils.py index a27ac38..a1632ef 100644 --- a/eossr/metadata/codemeta2zenodo/tests/test_codemeta2zenodo_utils.py +++ b/src/eossr/metadata/codemeta2zenodo/tests/test_codemeta2zenodo_utils.py @@ -1,7 +1,4 @@ -from eossr.metadata.codemeta2zenodo.utils import ( - handle_version_type, - remove_duplicates, -) +from eossr.metadata.codemeta2zenodo.utils import handle_version_type, remove_duplicates def test_handle_version_type(): diff --git a/eossr/metadata/codemeta2zenodo/tests/test_converters.py b/src/eossr/metadata/codemeta2zenodo/tests/test_converters.py similarity index 97% rename from eossr/metadata/codemeta2zenodo/tests/test_converters.py rename to src/eossr/metadata/codemeta2zenodo/tests/test_converters.py index 5a091a6..7503040 100644 --- a/eossr/metadata/codemeta2zenodo/tests/test_converters.py +++ b/src/eossr/metadata/codemeta2zenodo/tests/test_converters.py @@ -1,15 +1,17 @@ import json +import warnings + import pytest import requests -import warnings -from eossr.metadata.codemeta2zenodo.converters import CodeRepositoryConverter + from eossr.metadata.codemeta2zenodo.converters import ( - OrganizationConverter, - IDConverter, - PersonConverter, - DateConverter, + CodeRepositoryConverter, CommunityConverter, + DateConverter, + IDConverter, LicenseConverter, + OrganizationConverter, + PersonConverter, ) @@ -212,9 +214,7 @@ def test_license_converter(): converter = LicenseConverter("https://spdx.org/licenses/MIT") assert converter.convert() == "mit" - converter = LicenseConverter( - ["https://spdx.org/licenses/MIT", "https://spdx.org/licenses/BSD-3-Clause"] - ) + converter = LicenseConverter(["https://spdx.org/licenses/MIT", "https://spdx.org/licenses/BSD-3-Clause"]) assert converter.convert() == "other-open" diff --git a/eossr/metadata/codemeta2zenodo/utils.py b/src/eossr/metadata/codemeta2zenodo/utils.py similarity index 85% rename from eossr/metadata/codemeta2zenodo/utils.py rename to src/eossr/metadata/codemeta2zenodo/utils.py index 2f68785..6dbeefe 100644 --- a/eossr/metadata/codemeta2zenodo/utils.py +++ b/src/eossr/metadata/codemeta2zenodo/utils.py @@ -4,9 +4,7 @@ def handle_version_type(zenodo_dict): """ if "version" in zenodo_dict and isinstance(zenodo_dict["version"], list): if not all(x == zenodo_dict["version"][0] for x in zenodo_dict["version"]): - raise ValueError( - f"Multiple versions provided: {zenodo_dict['version']}" - ) + raise ValueError(f"Multiple versions provided: {zenodo_dict['version']}") else: zenodo_dict["version"] = zenodo_dict["version"][0] diff --git a/eossr/metadata/schema/__init__.py b/src/eossr/metadata/schema/__init__.py similarity index 100% rename from eossr/metadata/schema/__init__.py rename to src/eossr/metadata/schema/__init__.py diff --git a/eossr/metadata/schema/codemeta.json b/src/eossr/metadata/schema/codemeta.json similarity index 100% rename from eossr/metadata/schema/codemeta.json rename to src/eossr/metadata/schema/codemeta.json diff --git a/eossr/metadata/schema/escape_codemeta_crosswalk.csv b/src/eossr/metadata/schema/escape_codemeta_crosswalk.csv similarity index 99% rename from eossr/metadata/schema/escape_codemeta_crosswalk.csv rename to src/eossr/metadata/schema/escape_codemeta_crosswalk.csv index 7b394fe..d097541 100644 --- a/eossr/metadata/schema/escape_codemeta_crosswalk.csv +++ b/src/eossr/metadata/schema/escape_codemeta_crosswalk.csv @@ -72,4 +72,4 @@ schema:Review;;reviewAspect;;;;;;;; schema:Review;;reviewBody;;;;;;;; schema:Role;;endDate;;endDate;;;;;; schema:Role;;roleName;;roleName;;;;;; -schema:Role;;startDate;;startDate;;;;;; \ No newline at end of file +schema:Role;;startDate;;startDate;;;;;; diff --git a/eossr/metadata/tests/__init__.py b/src/eossr/metadata/tests/__init__.py similarity index 100% rename from eossr/metadata/tests/__init__.py rename to src/eossr/metadata/tests/__init__.py index 0e77df4..cacfab4 100644 --- a/eossr/metadata/tests/__init__.py +++ b/src/eossr/metadata/tests/__init__.py @@ -1,7 +1,7 @@ -import pytest import json from pathlib import Path +import pytest SAMPLES_DIR = Path(__file__).parent.joinpath("samples") ROOT_DIR = Path("codemeta.json").parent.resolve() diff --git a/eossr/metadata/tests/samples/codemeta_contributors_sample.json b/src/eossr/metadata/tests/samples/codemeta_contributors_sample.json similarity index 100% rename from eossr/metadata/tests/samples/codemeta_contributors_sample.json rename to src/eossr/metadata/tests/samples/codemeta_contributors_sample.json diff --git a/eossr/metadata/tests/samples/codemeta_not_valid.json b/src/eossr/metadata/tests/samples/codemeta_not_valid.json similarity index 100% rename from eossr/metadata/tests/samples/codemeta_not_valid.json rename to src/eossr/metadata/tests/samples/codemeta_not_valid.json diff --git a/eossr/metadata/tests/samples/codemeta_sample1.json b/src/eossr/metadata/tests/samples/codemeta_sample1.json similarity index 100% rename from eossr/metadata/tests/samples/codemeta_sample1.json rename to src/eossr/metadata/tests/samples/codemeta_sample1.json diff --git a/eossr/metadata/tests/samples/codemeta_test.json b/src/eossr/metadata/tests/samples/codemeta_test.json similarity index 99% rename from eossr/metadata/tests/samples/codemeta_test.json rename to src/eossr/metadata/tests/samples/codemeta_test.json index 63259a5..ad18721 100644 --- a/eossr/metadata/tests/samples/codemeta_test.json +++ b/src/eossr/metadata/tests/samples/codemeta_test.json @@ -112,4 +112,4 @@ "sameAs": "https://en.wikipedia.org/wiki/Software_Name", "url": "https://example.com", "relatedLink": "https://example.com/related" -} \ No newline at end of file +} diff --git a/eossr/metadata/tests/samples/codemeta_valid.json b/src/eossr/metadata/tests/samples/codemeta_valid.json similarity index 100% rename from eossr/metadata/tests/samples/codemeta_valid.json rename to src/eossr/metadata/tests/samples/codemeta_valid.json diff --git a/eossr/metadata/tests/samples/zenodo_test.json b/src/eossr/metadata/tests/samples/zenodo_test.json similarity index 99% rename from eossr/metadata/tests/samples/zenodo_test.json rename to src/eossr/metadata/tests/samples/zenodo_test.json index 6ae862b..da278aa 100644 --- a/eossr/metadata/tests/samples/zenodo_test.json +++ b/src/eossr/metadata/tests/samples/zenodo_test.json @@ -58,4 +58,4 @@ "description": "Description of software", "title": "eOSSR test", "access_right": "open" -} \ No newline at end of file +} diff --git a/eossr/metadata/tests/test_codemeta.py b/src/eossr/metadata/tests/test_codemeta.py similarity index 94% rename from eossr/metadata/tests/test_codemeta.py rename to src/eossr/metadata/tests/test_codemeta.py index e7e9062..3cf4e5b 100644 --- a/eossr/metadata/tests/test_codemeta.py +++ b/src/eossr/metadata/tests/test_codemeta.py @@ -1,10 +1,11 @@ import tempfile from os import path from pathlib import Path + import pytest from eossr.metadata import codemeta -from eossr.metadata.tests import codemeta_test, codemeta_not_valid +from eossr.metadata.tests import codemeta_not_valid, codemeta_test SAMPLES_DIR = Path(path.join(path.dirname(path.realpath(__file__)), "samples")) diff --git a/eossr/metadata/tests/test_zenodo_metadata.py b/src/eossr/metadata/tests/test_zenodo_metadata.py similarity index 100% rename from eossr/metadata/tests/test_zenodo_metadata.py rename to src/eossr/metadata/tests/test_zenodo_metadata.py diff --git a/eossr/metadata/zenodo.py b/src/eossr/metadata/zenodo.py similarity index 99% rename from eossr/metadata/zenodo.py rename to src/eossr/metadata/zenodo.py index 2adc544..f10d277 100644 --- a/eossr/metadata/zenodo.py +++ b/src/eossr/metadata/zenodo.py @@ -1,9 +1,10 @@ import warnings +from datetime import date +from pathlib import Path + import jsonschema import requests -from datetime import date from jsonref import replace_refs -from pathlib import Path from ..utils import write_json from . import valid_semver diff --git a/eossr/scripts/__init__.py b/src/eossr/scripts/__init__.py similarity index 100% rename from eossr/scripts/__init__.py rename to src/eossr/scripts/__init__.py diff --git a/eossr/scripts/check_connection_zenodo.py b/src/eossr/scripts/check_connection_zenodo.py similarity index 100% rename from eossr/scripts/check_connection_zenodo.py rename to src/eossr/scripts/check_connection_zenodo.py index 5b83868..1342d53 100644 --- a/eossr/scripts/check_connection_zenodo.py +++ b/src/eossr/scripts/check_connection_zenodo.py @@ -3,9 +3,9 @@ import argparse from eossr.api.zenodo import ZenodoAPI +from eossr.metadata.codemeta import codemeta_filepath from eossr.metadata.codemeta2zenodo import parse_codemeta_and_write_zenodo_metadata_file from eossr.metadata.zenodo import zenodo_filepath -from eossr.metadata.codemeta import codemeta_filepath def build_argparser(): diff --git a/eossr/scripts/eossr_codemeta2zenodo.py b/src/eossr/scripts/eossr_codemeta2zenodo.py similarity index 100% rename from eossr/scripts/eossr_codemeta2zenodo.py rename to src/eossr/scripts/eossr_codemeta2zenodo.py diff --git a/eossr/scripts/eossr_metadata_validator.py b/src/eossr/scripts/eossr_metadata_validator.py similarity index 100% rename from eossr/scripts/eossr_metadata_validator.py rename to src/eossr/scripts/eossr_metadata_validator.py diff --git a/eossr/scripts/eossr_upload_repository.py b/src/eossr/scripts/eossr_upload_repository.py similarity index 100% rename from eossr/scripts/eossr_upload_repository.py rename to src/eossr/scripts/eossr_upload_repository.py index b6edac9..aa506fc 100644 --- a/eossr/scripts/eossr_upload_repository.py +++ b/src/eossr/scripts/eossr_upload_repository.py @@ -9,9 +9,9 @@ from copy import deepcopy from pathlib import Path from eossr.api.zenodo import Record, SimilarRecordError, ZenodoAPI +from eossr.metadata.codemeta import codemeta_filepath from eossr.metadata.codemeta2zenodo import codemeta2zenodo from eossr.metadata.zenodo import add_escape2020_community, add_escape2020_grant, zenodo_filepath -from eossr.metadata.codemeta import codemeta_filepath from eossr.utils import zip_repository diff --git a/eossr/scripts/eossr_zenodo_validator.py b/src/eossr/scripts/eossr_zenodo_validator.py similarity index 100% rename from eossr/scripts/eossr_zenodo_validator.py rename to src/eossr/scripts/eossr_zenodo_validator.py diff --git a/eossr/scripts/tests/test_scripts.py b/src/eossr/scripts/tests/test_scripts.py similarity index 99% rename from eossr/scripts/tests/test_scripts.py rename to src/eossr/scripts/tests/test_scripts.py index 86c906e..6a0ea84 100644 --- a/eossr/scripts/tests/test_scripts.py +++ b/src/eossr/scripts/tests/test_scripts.py @@ -4,13 +4,13 @@ import os import shutil import subprocess -import requests import warnings from os.path import dirname, join, realpath from pathlib import Path import pkg_resources import pytest +import requests from eossr.scripts import eossr_upload_repository @@ -64,7 +64,7 @@ def test_eossr_upload_repository(tmpdir): upload_directory=tmpdir, force_new_record=True, publish=False, - ) + ) except requests.exceptions.ReadTimeout as e: warnings.warn(f"ReadTimeout: {e}") diff --git a/eossr/scripts/update_codemeta_eossr.py b/src/eossr/scripts/update_codemeta_eossr.py similarity index 100% rename from eossr/scripts/update_codemeta_eossr.py rename to src/eossr/scripts/update_codemeta_eossr.py diff --git a/eossr/scripts/zenodo_user_entries_cleanup.py b/src/eossr/scripts/zenodo_user_entries_cleanup.py similarity index 99% rename from eossr/scripts/zenodo_user_entries_cleanup.py rename to src/eossr/scripts/zenodo_user_entries_cleanup.py index 6f76ddf..03544e8 100644 --- a/eossr/scripts/zenodo_user_entries_cleanup.py +++ b/src/eossr/scripts/zenodo_user_entries_cleanup.py @@ -5,6 +5,7 @@ Simple code to delete all user entries that have not been published import argparse import os import warnings + import requests from eossr.api.zenodo import ZenodoAPI diff --git a/eossr/scripts/zip_repository.py b/src/eossr/scripts/zip_repository.py similarity index 100% rename from eossr/scripts/zip_repository.py rename to src/eossr/scripts/zip_repository.py diff --git a/eossr/tests/conftest.py b/src/eossr/tests/conftest.py similarity index 99% rename from eossr/tests/conftest.py rename to src/eossr/tests/conftest.py index 24ba7db..e7bf6db 100644 --- a/eossr/tests/conftest.py +++ b/src/eossr/tests/conftest.py @@ -1,5 +1,7 @@ import os + from dotenv import load_dotenv + from eossr import ROOT_DIR diff --git a/eossr/tests/test_globals.py b/src/eossr/tests/test_globals.py similarity index 100% rename from eossr/tests/test_globals.py rename to src/eossr/tests/test_globals.py index 8fa1d2f..dee47f1 100644 --- a/eossr/tests/test_globals.py +++ b/src/eossr/tests/test_globals.py @@ -1,5 +1,6 @@ -from pathlib import Path import os +from pathlib import Path + import eossr from eossr.metadata import codemeta @@ -13,4 +14,3 @@ def test_eossr_codemeta(): def test_version(): print("HEREEEEEE", os.getenv('SANDBOX_ZENODO_TOKEN')) assert eossr.__version__ != "0.0.0" - diff --git a/eossr/tests/test_utils.py b/src/eossr/tests/test_utils.py similarity index 100% rename from eossr/tests/test_utils.py rename to src/eossr/tests/test_utils.py diff --git a/eossr/utils.py b/src/eossr/utils.py similarity index 100% rename from eossr/utils.py rename to src/eossr/utils.py diff --git a/eossr/version.py b/src/eossr/version.py similarity index 100% rename from eossr/version.py rename to src/eossr/version.py -- GitLab From d9f83f5763ce44b2eae2c1bf8fd775c46c022cb7 Mon Sep 17 00:00:00 2001 From: vuillaut Date: Fri, 1 Dec 2023 00:32:34 +0100 Subject: [PATCH 4/4] fix pytest --- .gitlab-ci.yml | 2 +- CONTRIBUTING.md | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 58ba44c..e72dd68 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -37,7 +37,7 @@ test: - apt-get -y update && apt-get install git gcc -y - pip install --upgrade setuptools pip wheel - pip install -e ".[tests]" - - pytest eossr/ + - pytest src/eossr/ --junitxml=junit_py37.xml --color=yes --verbose diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1687dac..9a8d283 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ You may use the script `eossr/script/update_codemeta.py`. - If you are adding a new function / method, please add the corresponding unit tests. - - Run `pytest eossr`. + - Run `pytest src/eossr`. - Note that some tests will not run if you don't setup a zenodo token in your env (see README). diff --git a/README.md b/README.md index a4eb674..fbd16c7 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ To run tests locally, run: ```bash pip install -e ".[tests]" -pytest eossr +pytest src/eossr ``` Some tests will be skiped if `SANDBOX_ZENODO_TOKEN` is not defined in your environment variables. -- GitLab