Skip to content

Commit 6029ad2

Browse files
authored
fix(core): python 3.11 compatiblity and fix tests(#3386)
1 parent 35f9d22 commit 6029ad2

16 files changed

+340
-291
lines changed

poetry.lock

Lines changed: 213 additions & 204 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ classifiers = [
3636
"Programming Language :: Python :: 3",
3737
"Programming Language :: Python :: 3.8",
3838
"Programming Language :: Python :: 3.9",
39+
"Programming Language :: Python :: 3.10",
40+
"Programming Language :: Python :: 3.11",
3941
"Development Status :: 4 - Beta",
4042
]
4143
homepage = "https://github.com/swissdatasciencecenter/renku-python"
@@ -73,7 +75,7 @@ importlib-resources = ">=5.12.0,<5.13.0"
7375
inject = "<4.4.0,>=4.3.0"
7476
jinja2 = { version = ">=2.11.3,<3.1.3" }
7577
networkx = "<2.7,>=2.6.0"
76-
numpy = ">=1.20.0,<1.22.0"
78+
numpy = ">=1.24.0,<1.25.0"
7779
packaging = "<24.0,>=23.0"
7880
pathspec = "<1.0.0,>=0.8.0"
7981
patool = "==1.12"
@@ -255,6 +257,7 @@ markers = [
255257
"serial: mark a test that can not be run in parallel",
256258
"service: mark a test as service test.",
257259
"shelled: mark a test as a shelled test.",
260+
"remote_repo: used to specify which remote to use in test fixtures."
258261
]
259262
filterwarnings = [
260263
"ignore:<class 'pytest_black.BlackItem'> is not using a cooperative constructor:pytest.PytestDeprecationWarning",

renku/domain_model/template.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ def from_project(cls, project: Optional["Project"]) -> "TemplateMetadata":
530530
# NOTE: Always set __renku_version__ to the value read from the Dockerfile (if available) since setting/updating
531531
# the template doesn't change project's metadata version and shouldn't update the Renku version either
532532
renku_version = metadata.get("__renku_version__")
533-
metadata["__renku_version__"] = str(read_renku_version_from_dockerfile()) or renku_version or __version__
533+
metadata["__renku_version__"] = str(read_renku_version_from_dockerfile() or renku_version or __version__)
534534

535535
return cls(metadata=metadata, immutable_files=immutable_files)
536536

renku/infrastructure/database.py

Lines changed: 44 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -870,87 +870,77 @@ def serialize(self, object: persistent.Persistent):
870870

871871
return data
872872

873-
def _serialize_helper(self, object):
873+
def _serialize_helper(self, obj):
874874
# TODO: Raise an error if an unsupported object is being serialized
875-
if object is None:
875+
if obj is None:
876876
return None
877-
elif isinstance(object, (int, float, str, bool)):
878-
return object
879-
elif isinstance(object, list):
880-
return [self._serialize_helper(value) for value in object]
881-
elif isinstance(object, set):
877+
elif isinstance(obj, (int, float, str, bool)):
878+
return obj
879+
elif isinstance(obj, list):
880+
return [self._serialize_helper(value) for value in obj]
881+
elif isinstance(obj, set):
882882
return {
883883
"@renku_data_type": SET_TYPE,
884-
"@renku_data_value": [self._serialize_helper(value) for value in object],
884+
"@renku_data_value": [self._serialize_helper(value) for value in obj],
885885
}
886-
elif isinstance(object, frozenset):
886+
elif isinstance(obj, frozenset):
887887
return {
888888
"@renku_data_type": FROZEN_SET_TYPE,
889-
"@renku_data_value": [self._serialize_helper(value) for value in object],
889+
"@renku_data_value": [self._serialize_helper(value) for value in obj],
890890
}
891-
elif isinstance(object, dict):
891+
elif isinstance(obj, dict):
892892
result = dict()
893-
items = sorted(object.items(), key=lambda x: x[0])
893+
items = sorted(obj.items(), key=lambda x: x[0])
894894
for key, value in items:
895895
result[key] = self._serialize_helper(value)
896896
return result
897-
elif isinstance(object, Index):
897+
elif isinstance(obj, Index):
898898
# NOTE: Index objects are not stored as references and are included in their parent object (i.e. root)
899-
state = object.__getstate__()
899+
state = obj.__getstate__()
900900
state = self._serialize_helper(state)
901-
return {"@renku_data_type": get_type_name(object), "@renku_oid": object._p_oid, **state}
902-
elif isinstance(object, (OOTreeSet, Length, OOSet)):
903-
state = object.__getstate__()
901+
return {"@renku_data_type": get_type_name(obj), "@renku_oid": obj._p_oid, **state}
902+
elif isinstance(obj, (OOTreeSet, Length, OOSet)):
903+
state = obj.__getstate__()
904904
state = self._serialize_helper(state)
905-
return {"@renku_data_type": get_type_name(object), "@renku_data_value": state}
906-
elif isinstance(object, persistent.Persistent):
907-
if not object._p_oid:
908-
object._p_oid = Database.generate_oid(object)
909-
if object._p_state not in [GHOST, UPTODATE] or (object._p_state == UPTODATE and object._p_serial == NEW):
910-
self._database.register(object)
911-
return {"@renku_data_type": get_type_name(object), "@renku_oid": object._p_oid, "@renku_reference": True}
912-
elif isinstance(object, datetime.datetime):
913-
value = object.isoformat()
914-
elif isinstance(object, tuple):
915-
value = tuple(self._serialize_helper(value) for value in object)
916-
elif isinstance(object, (InterfaceClass)):
905+
return {"@renku_data_type": get_type_name(obj), "@renku_data_value": state}
906+
elif isinstance(obj, persistent.Persistent):
907+
if not obj._p_oid:
908+
obj._p_oid = Database.generate_oid(obj)
909+
if obj._p_state not in [GHOST, UPTODATE] or (obj._p_state == UPTODATE and obj._p_serial == NEW):
910+
self._database.register(obj)
911+
return {"@renku_data_type": get_type_name(obj), "@renku_oid": obj._p_oid, "@renku_reference": True}
912+
elif isinstance(obj, datetime.datetime):
913+
value = obj.isoformat()
914+
elif isinstance(obj, tuple):
915+
value = tuple(self._serialize_helper(value) for value in obj)
916+
elif isinstance(obj, (InterfaceClass)):
917917
# NOTE: Zope interfaces are weird, they're a class with type InterfaceClass, but need to be deserialized
918918
# as the class (without instantiation)
919-
return {"@renku_data_type": TYPE_TYPE, "@renku_data_value": f"{object.__module__}.{object.__name__}"}
920-
elif isinstance(object, type):
919+
return {"@renku_data_type": TYPE_TYPE, "@renku_data_value": f"{obj.__module__}.{obj.__name__}"}
920+
elif isinstance(obj, type):
921921
# NOTE: We're storing a type, not an instance
922-
return {"@renku_data_type": TYPE_TYPE, "@renku_data_value": get_type_name(object)}
923-
elif isinstance(object, (FunctionType, BuiltinFunctionType)):
924-
name = object.__name__
925-
module = getattr(object, "__module__", None)
922+
return {"@renku_data_type": TYPE_TYPE, "@renku_data_value": get_type_name(obj)}
923+
elif isinstance(obj, (FunctionType, BuiltinFunctionType)):
924+
name = obj.__name__
925+
module = getattr(obj, "__module__", None)
926926
return {"@renku_data_type": FUNCTION_TYPE, "@renku_data_value": f"{module}.{name}"}
927-
elif hasattr(object, "__getstate__"):
928-
if id(object) in self._serialization_cache:
929-
# NOTE: We already serialized this -> circular/repeat reference.
930-
return {"@renku_data_type": REFERENCE_TYPE, "@renku_data_value": self._serialization_cache[id(object)]}
931-
932-
# NOTE: The reference used for circular reference is just the position in the serialization cache,
933-
# as the order is deterministic. So the order in which objects are encountered is their id for referencing.
934-
self._serialization_cache[id(object)] = len(self._serialization_cache)
935-
936-
value = object.__getstate__().copy()
937-
value = {k: v for k, v in value.items() if not k.startswith("_v_")}
938-
value = self._serialize_helper(value)
939-
assert not isinstance(value, dict) or "id" in value, f"Invalid object state: {value} for {object}"
940927
else:
941-
if id(object) in self._serialization_cache:
928+
if id(obj) in self._serialization_cache:
942929
# NOTE: We already serialized this -> circular/repeat reference
943-
return {"@renku_data_type": REFERENCE_TYPE, "@renku_data_value": self._serialization_cache[id(object)]}
930+
return {"@renku_data_type": REFERENCE_TYPE, "@renku_data_value": self._serialization_cache[id(obj)]}
944931

945932
# NOTE: The reference used for circular reference is just the position in the serialization cache,
946933
# as the order is deterministic So the order in which objects are encoutered is their id for referencing.
947-
self._serialization_cache[id(object)] = len(self._serialization_cache)
948-
949-
value = object.__dict__.copy()
934+
self._serialization_cache[id(obj)] = len(self._serialization_cache)
935+
if hasattr(obj, "__getstate__"):
936+
# NOTE: On Python 3.11+ this just returns __dict__ if __getstate__ isn't implemented.
937+
value = obj.__getstate__().copy()
938+
else:
939+
value = obj.__dict__.copy()
950940
value = {k: v for k, v in value.items() if not k.startswith("_v_")}
951941
value = self._serialize_helper(value)
952942

953-
return {"@renku_data_type": get_type_name(object), "@renku_data_value": value}
943+
return {"@renku_data_type": get_type_name(obj), "@renku_data_value": value}
954944

955945

956946
class ObjectReader:

tests/fixtures/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
IT_REMOTE_REPO_URL = os.getenv(
2525
"IT_REMOTE_REPOSITORY", "https://gitlab.dev.renku.ch/renku-python-integration-tests/core-integration-test"
2626
)
27+
IT_REMOTE_OLD_REPO_URL = os.getenv(
28+
"IT_REMOTE_REPOSITORY", "https://gitlab.dev.renku.ch/renku-python-integration-tests/core-integration-test-old"
29+
)
2730
IT_PROTECTED_REMOTE_REPO_URL = os.getenv(
2831
"IT_PROTECTED_REMOTE_REPO", "https://gitlab.dev.renku.ch/renku-python-integration-tests/core-it-protected.git"
2932
)

tests/service/fixtures/service_integration.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,14 @@ def integration_repo(headers, project_id, url_components) -> Generator[Repositor
9191

9292
@pytest.fixture()
9393
def integration_lifecycle(
94-
svc_client, mock_redis, identity_headers, it_remote_repo_url, it_protected_repo_url, it_workflow_repo_url, request
94+
svc_client,
95+
mock_redis,
96+
identity_headers,
97+
it_remote_repo_url,
98+
it_protected_repo_url,
99+
it_workflow_repo_url,
100+
it_remote_old_repo_url,
101+
request,
95102
):
96103
"""Setup and teardown steps for integration tests."""
97104
from renku.domain_model.git import GitURL
@@ -104,6 +111,8 @@ def integration_lifecycle(
104111
remote_repo = it_protected_repo_url
105112
elif marker.args[0] == "workflow":
106113
remote_repo = it_workflow_repo_url
114+
elif marker.args[0] == "old":
115+
remote_repo = it_remote_old_repo_url
107116
else:
108117
raise ValueError(f"Couldn't get remote repo for marker {marker.args[0]}")
109118

tests/service/fixtures/service_projects.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ def it_remote_repo_url():
5656
return IT_REMOTE_REPO_URL
5757

5858

59+
@pytest.fixture(scope="module")
60+
def it_remote_old_repo_url():
61+
"""Returns a remote path to integration test repository."""
62+
from tests.fixtures.config import IT_REMOTE_OLD_REPO_URL
63+
64+
return IT_REMOTE_OLD_REPO_URL
65+
66+
5967
@pytest.fixture(scope="module")
6068
def it_remote_public_renku_repo_url():
6169
"""Returns a remote path to a public integration test repository."""
@@ -93,6 +101,29 @@ def it_remote_repo_url_temp_branch(it_remote_repo_url):
93101
repo.branches.remove(branch)
94102

95103

104+
@pytest.fixture(scope="function")
105+
def it_remote_old_repo_url_temp_branch(it_remote_old_repo_url):
106+
"""Returns a remote path to integration test repository."""
107+
108+
with tempfile.TemporaryDirectory() as tempdir:
109+
# NOTE: create temporary branch and push it
110+
git_url = urlparse(it_remote_old_repo_url)
111+
112+
url = "oauth2:{}@{}".format(os.getenv("IT_OAUTH_GIT_TOKEN"), git_url.netloc)
113+
git_url = git_url._replace(netloc=url).geturl()
114+
repo = Repository.clone_from(url=git_url, path=tempdir)
115+
origin = repo.remotes["origin"]
116+
branch_name = uuid.uuid4().hex
117+
branch = repo.branches.add(branch_name)
118+
repo.push(origin, branch, set_upstream=True)
119+
try:
120+
yield it_remote_old_repo_url, branch_name
121+
finally:
122+
# NOTE: delete remote branch
123+
repo.push(origin, branch, delete=True)
124+
repo.branches.remove(branch)
125+
126+
96127
@pytest.fixture(scope="module")
97128
def it_non_renku_repo_url():
98129
"""Returns a remote path to integration test repository."""

tests/service/jobs/test_config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
"""Test config jobs."""
1818
import pytest
1919

20-
from renku.core.errors import MigrationRequired
2120
from tests.utils import retry_failed
2221

2322

@@ -67,5 +66,6 @@ def test_delay_config_set_failure(svc_client_cache, it_remote_repo_url, view_use
6766

6867
from renku.ui.service.jobs.delayed_ctrl import delayed_ctrl_job
6968

70-
with pytest.raises(MigrationRequired):
71-
delayed_ctrl_job(context, view_user_data, job.job_id, renku_module, renku_ctrl)
69+
updated_job = delayed_ctrl_job(context, view_user_data, job.job_id, renku_module, renku_ctrl)
70+
71+
assert updated_job

tests/service/jobs/test_datasets.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import pytest
2424
from werkzeug.utils import secure_filename
2525

26-
from renku.core.errors import DatasetExistsError, MigrationRequired, ParameterError
26+
from renku.core.errors import DatasetExistsError, DatasetNotFound, ParameterError
2727
from renku.infrastructure.repository import Repository
2828
from renku.ui.service.jobs.cleanup import cache_project_cleanup
2929
from renku.ui.service.jobs.datasets import dataset_add_remote_file, dataset_import
@@ -362,8 +362,7 @@ def test_delay_add_file_job_failure(svc_client_cache, it_remote_repo_url_temp_br
362362

363363
from renku.ui.service.jobs.delayed_ctrl import delayed_ctrl_job
364364

365-
with pytest.raises(MigrationRequired):
366-
delayed_ctrl_job(context, view_user_data, job.job_id, renku_module, renku_ctrl)
365+
delayed_ctrl_job(context, view_user_data, job.job_id, renku_module, renku_ctrl)
367366

368367

369368
@pytest.mark.parametrize("doi", ["10.5281/zenodo.3761586"])
@@ -471,15 +470,14 @@ def test_delay_create_dataset_failure(svc_client_cache, it_remote_repo_url_temp_
471470

472471
from renku.ui.service.jobs.delayed_ctrl import delayed_ctrl_job
473472

474-
with pytest.raises(MigrationRequired):
475-
delayed_ctrl_job(context, view_user_data, job.job_id, renku_module, renku_ctrl)
473+
delayed_ctrl_job(context, view_user_data, job.job_id, renku_module, renku_ctrl)
476474

477475

478476
@pytest.mark.service
479477
@pytest.mark.integration
480478
@retry_failed
481479
def test_delay_remove_dataset_job(svc_client_cache, it_remote_repo_url_temp_branch, view_user_data):
482-
"""Create a dataset was removed successfully."""
480+
"""Check a dataset was removed successfully."""
483481
from renku.ui.service.jobs.delayed_ctrl import delayed_ctrl_job
484482
from renku.ui.service.serializers.datasets import DatasetRemoveRequest
485483

@@ -512,7 +510,7 @@ def test_delay_remove_dataset_job(svc_client_cache, it_remote_repo_url_temp_bran
512510
@pytest.mark.integration
513511
@retry_failed
514512
def test_delay_remove_dataset_job_failure(svc_client_cache, it_remote_repo_url_temp_branch, view_user_data):
515-
"""Create a dataset was removed successfully."""
513+
"""Check removing missing dataset fails."""
516514
from renku.ui.service.jobs.delayed_ctrl import delayed_ctrl_job
517515
from renku.ui.service.serializers.datasets import DatasetRemoveRequest
518516

@@ -535,7 +533,7 @@ def test_delay_remove_dataset_job_failure(svc_client_cache, it_remote_repo_url_t
535533
user, job_data={"ctrl_context": {**context, "renku_module": renku_module, "renku_ctrl": renku_ctrl}}
536534
)
537535

538-
with pytest.raises(MigrationRequired):
536+
with pytest.raises(DatasetNotFound):
539537
delayed_ctrl_job(context, view_user_data, delete_job.job_id, renku_module, renku_ctrl)
540538

541539

@@ -608,8 +606,7 @@ def test_delay_edit_dataset_job_failure(svc_client_cache, it_remote_repo_url_tem
608606

609607
from renku.ui.service.jobs.delayed_ctrl import delayed_ctrl_job
610608

611-
with pytest.raises(MigrationRequired):
612-
delayed_ctrl_job(context, view_user_data, job.job_id, renku_module, renku_ctrl)
609+
delayed_ctrl_job(context, view_user_data, job.job_id, renku_module, renku_ctrl)
613610

614611

615612
@pytest.mark.service
@@ -675,8 +672,7 @@ def test_delay_unlink_dataset_job_failure(svc_client_cache, it_remote_repo_url_t
675672

676673
from renku.ui.service.jobs.delayed_ctrl import delayed_ctrl_job
677674

678-
with pytest.raises(MigrationRequired):
679-
delayed_ctrl_job(context, view_user_data, job.job_id, renku_module, renku_ctrl)
675+
delayed_ctrl_job(context, view_user_data, job.job_id, renku_module, renku_ctrl)
680676

681677

682678
@pytest.mark.service

tests/service/jobs/test_project.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
2525
@pytest.mark.service
2626
@pytest.mark.integration
2727
@retry_failed
28-
def test_delay_migration_job(svc_client_cache, it_remote_repo_url_temp_branch, view_user_data):
28+
def test_delay_migration_job(svc_client_cache, it_remote_old_repo_url_temp_branch, view_user_data):
2929
"""Verify delayed project migration."""
3030

31-
it_remote_repo_url, branch = it_remote_repo_url_temp_branch
31+
it_remote_repo_url, branch = it_remote_old_repo_url_temp_branch
3232

3333
context = ProjectMigrateRequest().load({"git_url": it_remote_repo_url, "ref": branch, "skip_docker_update": True})
3434

0 commit comments

Comments
 (0)