• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SwissDataScienceCenter / renku-python / 9058668052

13 May 2024 07:05AM UTC coverage: 77.713% (-8.4%) from 86.115%
9058668052

Pull #3727

github

web-flow
Merge 128d38387 into 050ed61bf
Pull Request #3727: fix: don't fail session launch when gitlab couldn't be reached

15 of 29 new or added lines in 3 files covered. (51.72%)

2594 existing lines in 125 files now uncovered.

23893 of 30745 relevant lines covered (77.71%)

3.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.74
/renku/core/project.py
1
# Copyright Swiss Data Science Center (SDSC). A partnership between
2
# École Polytechnique Fédérale de Lausanne (EPFL) and
3
# Eidgenössische Technische Hochschule Zürich (ETHZ).
4
#
5
# Licensed under the Apache License, Version 2.0 (the "License");
6
# you may not use this file except in compliance with the License.
7
# You may obtain a copy of the License at
8
#
9
#     http://www.apache.org/licenses/LICENSE-2.0
10
#
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS,
13
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
# See the License for the specific language governing permissions and
15
# limitations under the License.
16
"""Project business logic."""
6✔
17

18
import os
6✔
19
import shutil
6✔
20
from typing import Dict, List, Optional, Union, cast
6✔
21

22
from pydantic import ConfigDict, validate_call
6✔
23

24
from renku.command.command_builder import inject
6✔
25
from renku.command.view_model.project import ProjectViewModel
6✔
26
from renku.core import errors
6✔
27
from renku.core.image import ImageObjectRequest
6✔
28
from renku.core.interface.project_gateway import IProjectGateway
6✔
29
from renku.core.util.metadata import construct_creator
6✔
30
from renku.core.util.os import get_relative_path
6✔
31
from renku.domain_model.constant import NO_VALUE, NoValueType
6✔
32
from renku.domain_model.dataset import ImageObjectRequestJson
6✔
33
from renku.domain_model.project_context import project_context
6✔
34
from renku.domain_model.provenance.agent import Person
6✔
35

36

37
@inject.autoparams()
6✔
38
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
6✔
39
def edit_project(
6✔
40
    description: Optional[Union[str, NoValueType]],
41
    creator: Union[Dict, str, NoValueType],
42
    keywords: Optional[Union[List[str], NoValueType]],
43
    custom_metadata: Optional[Union[Dict, List[Dict], NoValueType]],
44
    custom_metadata_source: Optional[Union[str, NoValueType]],
45
    image_request: Optional[Union[ImageObjectRequest, NoValueType]],
46
    project_gateway: IProjectGateway,
47
):
48
    """Edit dataset metadata.
49

50
    Args:
51
        description(Union[Optional[str], NoValueType]): New description.
52
        creator(Union[Dict, str, NoValueType]): New creators.
53
        keywords(Union[Optional[List[str]]): New keywords.
54
        custom_metadata(Union[Optional[Dict, List[Dict]]): Custom JSON-LD metadata.
55
        custom_metadata_source(Optional[str]): Custom metadata source.
56
        project_gateway(IProjectGateway): Injected project gateway.
57
        image_request(Optional[ImageObjectRequest]): Project's image.
58

59
    Returns:
60
        Tuple of fields that were updated and dictionary of warnings.
61
    """
62
    possible_updates = {
1✔
63
        "creator": creator,
64
        "description": description,
65
        "keywords": keywords,
66
        "custom_metadata": custom_metadata,
67
        "image": (
68
            image_request
69
            if image_request is NO_VALUE or image_request is None
70
            else ImageObjectRequestJson().dump(image_request)
71
        ),
72
    }
73

74
    no_email_warnings: Optional[Union[Dict, str]] = None
1✔
75
    parsed_creator: Optional[Union[NoValueType, Person]] = NO_VALUE
1✔
76

77
    if creator is not NO_VALUE:
1✔
78
        parsed_creator, no_email_warnings = construct_creator(cast(Union[Dict, str], creator), ignore_email=True)
1✔
79

80
    if image_request is None:
1✔
81
        delete_project_image()
1✔
82
    elif image_request is not NO_VALUE:
1✔
83
        set_project_image(image_request=image_request)  # type: ignore
1✔
84

85
    updated = {k: v for k, v in possible_updates.items() if v is not NO_VALUE}
1✔
86

87
    if updated:
1✔
88
        project = project_gateway.get_project()
1✔
89
        # NOTE: No need to pass ``image`` here since we already copied/deleted the file and updated the project
90
        project.update_metadata(
1✔
91
            creator=parsed_creator,
92
            description=description,
93
            keywords=keywords,
94
            custom_metadata=custom_metadata,
95
            custom_metadata_source=custom_metadata_source,
96
        )
97
        project_gateway.update_project(project)
1✔
98

99
    return updated, no_email_warnings
1✔
100

101

102
def show_project() -> ProjectViewModel:
6✔
103
    """Show project metadata.
104

105
    Returns:
106
        Project view model.
107
    """
108
    return ProjectViewModel.from_project(project_context.project)
1✔
109

110

111
def set_project_image(image_request: Optional[ImageObjectRequest]) -> None:
6✔
112
    """Download and set a project's images.
113

114
    Args:
115
        image_request(Optional[ImageObjectRequest]): The image to set.
116
    """
117
    if image_request is None:
6✔
118
        return
6✔
119

120
    # NOTE: Projects can have maximum one image
121
    image_request.position = 0
1✔
122

123
    image_object = image_request.to_image_object(owner_id=project_context.project.id)
1✔
124

125
    project_image = project_context.project_image_pathname
1✔
126

127
    # NOTE: Do nothing if the new path is the same as the old one
128
    if project_image.resolve() != image_object.content_url:
1✔
129
        # NOTE: Always delete the old image in case the image wasn't mirrored in the project
130
        delete_project_image()
1✔
131

132
        if not image_object.is_remote:
1✔
133
            project_image.parent.mkdir(parents=True, exist_ok=True)
1✔
134
            shutil.copy(image_object.content_url, project_context.project_image_pathname)
1✔
135

136
            image_object.content_url = get_relative_path(project_image, base=project_context.path)  # type: ignore
1✔
137

138
    project_context.project.image = image_object
1✔
139

140

141
def delete_project_image() -> None:
6✔
142
    """Delete project image in a project."""
143
    try:
1✔
144
        os.remove(project_context.project_image_pathname)
1✔
145
    except FileNotFoundError:
1✔
146
        pass
1✔
147
    except OSError as e:
×
148
        raise errors.ImageError(f"Cannot delete project image '{project_context.project_image_pathname}': {e}") from e
×
149
    else:
UNCOV
150
        project_context.project.image = None
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc