Skip to content

Commit

Permalink
feat(core): add project description (#2235)
Browse files Browse the repository at this point in the history
  • Loading branch information
m-alisafaee committed Sep 3, 2021
1 parent e8f9ebf commit 109a3db
Show file tree
Hide file tree
Showing 25 changed files with 568 additions and 54 deletions.
2 changes: 2 additions & 0 deletions renku/cli/__init__.py
Expand Up @@ -82,6 +82,7 @@
from renku.cli.login import login, logout, token
from renku.cli.migrate import check_immutable_template_files, migrate, migrationscheck
from renku.cli.move import move
from renku.cli.project import project
from renku.cli.remove import remove
from renku.cli.rerun import rerun
from renku.cli.run import run
Expand Down Expand Up @@ -203,6 +204,7 @@ def help(ctx):
cli.add_command(migrationscheck)
cli.add_command(check_immutable_template_files)
cli.add_command(move)
cli.add_command(project)
cli.add_command(remove)
cli.add_command(rerun)
cli.add_command(run)
Expand Down
17 changes: 13 additions & 4 deletions renku/cli/init.py
Expand Up @@ -37,6 +37,11 @@
This creates a new subdirectory named ``.renku`` that contains all the
necessary files for managing the project configuration.
Every project requires a ``name`` that can either be provided using
``--name`` or automatically taken from the target folder.
You can also provide a description for a project using ``--description``.
If provided directory does not exist, it will be created.
Use a different template
Expand Down Expand Up @@ -102,10 +107,11 @@
``-parameter``, you will be asked to provide them. Empty values are allowed
and passed to the template initialization function.
.. note:: Every project requires a ``name`` that can either be provided using
``--name`` or automatically taken from the target folder. This is
also considered as a special parameter, therefore it's automatically added
to the list of parameters forwarded to the ``init`` command.
.. note::
Project's ``name`` is considered as a special parameter and it's
automatically added to the list of parameters forwarded to the ``init``
command.
Update an existing project
~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -240,6 +246,7 @@ def check_git_user_config():
@click.command()
@click.argument("path", default=".", type=click.Path(writable=True, file_okay=False, resolve_path=True))
@click.option("-n", "--name", callback=validate_name, help="Provide a custom project name.")
@click.option("--description", help="Provide a description for the project.")
@click.option(
"--data-dir",
default=None,
Expand Down Expand Up @@ -275,6 +282,7 @@ def init(
external_storage_requested,
path,
name,
description,
template_id,
template_index,
template_source,
Expand Down Expand Up @@ -307,6 +315,7 @@ def init(
external_storage_requested=external_storage_requested,
path=path,
name=name,
description=description,
template_id=template_id,
template_index=template_index,
template_source=template_source,
Expand Down
71 changes: 71 additions & 0 deletions renku/cli/project.py
@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
#
# Copyright 2017-2021 - Swiss Data Science Center (SDSC)
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
# Eidgenössische Technische Hochschule Zürich (ETHZ).
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""Renku CLI commands for handling of projects.
Editing projects
~~~~~~~~~~~~~~~~
Users can edit some project's metadata using by using ``renku project edit``
command.
The following options can be passed to this command to set various metadata
for a project.
+-------------------+------------------------------------------------------+
| Option | Description |
+===================+======================================================+
| -d, --description | Project's description. |
+-------------------+------------------------------------------------------+
| -c, --creator | Creator's name, email, and an optional affiliation. |
| | Accepted format is |
| | 'Forename Surname <email> [affiliation]'. |
+-------------------+------------------------------------------------------+
"""

import click

from renku.cli.utils.callback import ClickCallback
from renku.core.commands.project import edit_project_command


@click.group()
def project():
"""Project commands."""


@project.command()
@click.option("-d", "--description", default=None, type=click.STRING, help="Project's description.")
@click.option(
"-c",
"--creator",
default=None,
type=click.STRING,
help="Creator's name, email, and affiliation. Accepted format is 'Forename Surname <email> [affiliation]'.",
)
def edit(description, creator):
"""Edit project metadata."""
result = edit_project_command().build().execute(description=description, creator=creator)

updated, no_email_warning = result.output

if not updated:
click.echo("Nothing to update. Check available fields with `renku project edit --help`\n")
else:
click.echo("Successfully updated: {}.".format(", ".join(updated.keys())))
if no_email_warning:
click.echo(ClickCallback.WARNING + f"No email or wrong format for: {no_email_warning}")
39 changes: 3 additions & 36 deletions renku/core/commands/dataset.py
Expand Up @@ -56,6 +56,7 @@
from renku.core.models.tabulate import tabulate
from renku.core.utils import communication
from renku.core.utils.doi import is_doi
from renku.core.utils.metadata import construct_creators
from renku.core.utils.urls import remove_credentials


Expand Down Expand Up @@ -99,7 +100,7 @@ def create_dataset_helper(
if not creators:
creators = [Person.from_git(client.repo)]
else:
creators, _ = _construct_creators(creators)
creators, _ = construct_creators(creators)

dataset = client.create_dataset(
name=name,
Expand Down Expand Up @@ -142,7 +143,7 @@ def _edit_dataset(
"title": title,
}

creators, no_email_warnings = _construct_creators(creators, ignore_email=True)
creators, no_email_warnings = construct_creators(creators, ignore_email=True)
title = title.strip() if isinstance(title, str) else ""

dataset = client.get_dataset(name=name)
Expand Down Expand Up @@ -188,40 +189,6 @@ def show_dataset():
return Command().command(_show_dataset).with_database().require_migration()


def _construct_creators(creators, ignore_email=False):
from collections.abc import Iterable

creators = creators or ()

if not isinstance(creators, Iterable) or isinstance(creators, str):
raise errors.ParameterError("Invalid type")

people = []
no_email_warnings = []
for creator in creators:
if isinstance(creator, str):
person = Person.from_string(creator)
elif isinstance(creator, dict):
person = Person.from_dict(creator)
else:
raise errors.ParameterError("Invalid type")

message = 'A valid format is "Name <email> [affiliation]"'

if not person.name: # pragma: no cover
raise errors.ParameterError(f'Name is invalid: "{creator}".\n{message}')

if not person.email:
if not ignore_email: # pragma: no cover
raise errors.ParameterError(f'Email is invalid: "{creator}".\n{message}')
else:
no_email_warnings.append(creator)

people.append(person)

return people, no_email_warnings


@inject.autoparams()
def _add_to_dataset(
urls,
Expand Down
14 changes: 10 additions & 4 deletions renku/core/commands/init.py
Expand Up @@ -126,7 +126,7 @@ def select_template_from_manifest(
repeat = True

if template_index is not None:
if template_index > 0 and template_index <= len(template_manifest):
if 0 < template_index <= len(template_manifest):
template_data = template_manifest[template_index - 1]
else:
communication.echo(f"The template at index {template_index} is not available.")
Expand All @@ -152,13 +152,13 @@ def select_template_from_manifest(


def verify_template_variables(template_data, metadata):
"""Verifies that template variables are correcly set."""
"""Verifies that template variables are correctly set."""
template_variables = template_data.get("variables", {})
template_variables_keys = set(template_variables.keys())
input_parameters_keys = set(metadata.keys())
for key in template_variables_keys - input_parameters_keys:
value = communication.prompt(
msg=(f'The template requires a value for "{key}" ' f"({template_variables[key]})"),
msg=f'The template requires a value for "{key}" ({template_variables[key]})',
default="",
show_default=False,
)
Expand Down Expand Up @@ -235,6 +235,7 @@ def _init(
external_storage_requested,
path,
name,
description,
template_id,
template_index,
template_source,
Expand Down Expand Up @@ -291,6 +292,7 @@ def _init(
metadata["__sanitized_project_name__"] = ""
metadata["__repository__"] = ""
metadata["__project_slug__"] = ""
metadata["__project_description__"] = description
if is_release() and "__renku_version__" not in metadata:
metadata["__renku_version__"] = __version__
metadata["name"] = name
Expand Down Expand Up @@ -337,6 +339,7 @@ def _init(
automated_update=template_data.get("allow_template_update", False),
force=force,
data_dir=data_dir,
description=description,
)
except FileExistsError as e:
raise errors.InvalidFileOperation(e)
Expand Down Expand Up @@ -503,6 +506,7 @@ def create_from_template(
data_dir=None,
user=None,
commit_message=None,
description=None,
):
"""Initialize a new project from a template."""

Expand All @@ -514,7 +518,7 @@ def create_from_template(
metadata["name"] = name

with client.commit(commit_message=commit_message, commit_only=commit_only, skip_dirty_checks=True):
with client.with_metadata(name=name) as project:
with client.with_metadata(name=name, description=description) as project:
project.template_source = metadata["__template_source__"]
project.template_ref = metadata["__template_ref__"]
project.template_id = metadata["__template_id__"]
Expand Down Expand Up @@ -548,6 +552,7 @@ def _create_from_template_local(
invoked_from=None,
initial_branch=None,
commit_message=None,
description=None,
):
"""Initialize a new project from a template."""

Expand All @@ -572,6 +577,7 @@ def _create_from_template_local(
force=False,
user=user,
commit_message=commit_message,
description=description,
)


Expand Down
47 changes: 47 additions & 0 deletions renku/core/commands/project.py
@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
#
# Copyright 2017-2021 - Swiss Data Science Center (SDSC)
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
# Eidgenössische Technische Hochschule Zürich (ETHZ).
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Project management."""

from renku.core.management.command_builder import inject
from renku.core.management.command_builder.command import Command
from renku.core.management.interface.project_gateway import IProjectGateway
from renku.core.management.repository import DATABASE_METADATA_PATH
from renku.core.utils.metadata import construct_creator


@inject.autoparams()
def _edit_project(description, creator, project_gateway: IProjectGateway):
"""Edit dataset metadata."""
possible_updates = {"creator": creator, "description": description}

creator, no_email_warnings = construct_creator(creator, ignore_email=True)

updated = {k: v for k, v in possible_updates.items() if v}

if updated:
project = project_gateway.get_project()
project.update_metadata(creator=creator, description=description)
project_gateway.update_project(project)

return updated, no_email_warnings


def edit_project_command():
"""Command for editing project metadata."""
command = Command().command(_edit_project).lock_project().with_database(write=True)
return command.require_migration().with_commit(commit_only=DATABASE_METADATA_PATH)
8 changes: 7 additions & 1 deletion renku/core/management/repository.py
Expand Up @@ -429,13 +429,14 @@ def with_metadata(
database_gateway: IDatabaseGateway,
read_only=False,
name=None,
description=None,
):
"""Yield an editable metadata object."""

try:
project = project_gateway.get_project()
except ValueError:
project = Project.from_client(name=name, client=self)
project = Project.from_client(name=name, description=description, client=self)

yield project

Expand Down Expand Up @@ -633,3 +634,8 @@ def _content_hash(self, path):
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()


DATABASE_METADATA_PATH = [
Path(RENKU_HOME) / RepositoryApiMixin.DATABASE_PATH,
]
2 changes: 2 additions & 0 deletions renku/core/metadata/immutable.py
Expand Up @@ -42,6 +42,8 @@ def make_instance(cls, **kwargs):
return cls(**kwargs)

def __getstate__(self):
if not self.__class__.__all_slots__:
self.__class__.__all_slots__ = self._get_all_slots()
return {name: getattr(self, name, None) for name in self.__class__.__all_slots__ if name != "__weakref__"}

def __setstate__(self, state):
Expand Down

0 comments on commit 109a3db

Please sign in to comment.