libraries.py 7.84 KB
Newer Older
André Anjos's avatar
André Anjos committed
1
2
3
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

Samuel GAIST's avatar
Samuel GAIST committed
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
###################################################################################
#                                                                                 #
# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/               #
# Contact: beat.support@idiap.ch                                                  #
#                                                                                 #
# Redistribution and use in source and binary forms, with or without              #
# modification, are permitted provided that the following conditions are met:     #
#                                                                                 #
# 1. Redistributions of source code must retain the above copyright notice, this  #
# list of conditions and the following disclaimer.                                #
#                                                                                 #
# 2. Redistributions in binary form must reproduce the above copyright notice,    #
# this list of conditions and the following disclaimer in the documentation       #
# and/or other materials provided with the distribution.                          #
#                                                                                 #
# 3. Neither the name of the copyright holder nor the names of its contributors   #
# may be used to endorse or promote products derived from this software without   #
# specific prior written permission.                                              #
#                                                                                 #
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND #
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   #
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE          #
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE    #
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL      #
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR      #
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER      #
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,   #
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   #
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.            #
#                                                                                 #
###################################################################################
André Anjos's avatar
André Anjos committed
35
36
37
38


"""Usage:
  %(prog)s libraries list [--remote]
39
  %(prog)s libraries path [<name>]...
40
  %(prog)s libraries edit <name>...
André Anjos's avatar
André Anjos committed
41
42
43
44
45
46
47
48
49
50
51
52
53
54
  %(prog)s libraries check [<name>]...
  %(prog)s libraries pull [--force] [<name>]...
  %(prog)s libraries push [--force] [--dry-run] [<name>]...
  %(prog)s libraries diff <name>
  %(prog)s libraries status
  %(prog)s libraries create <name>...
  %(prog)s libraries version <name>
  %(prog)s libraries fork <src> <dst>
  %(prog)s libraries rm [--remote] <name>...
  %(prog)s libraries --help


Commands:
  list      Lists all the libraries available on the platform
55
  path      Displays local path of libraries files
56
  edit      Edit local library file
André Anjos's avatar
André Anjos committed
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
  check     Checks a local library for validity
  pull      Downloads the specified libraries from the server
  push      Uploads libraries to the server
  diff      Shows changes between the local library and the remote version
  status    Shows (editing) status for all available libraries
  create    Creates a new local library
  version   Creates a new version of an existing library
  fork      Forks a local library
  rm        Deletes a local library (unless --remote is specified)


Options:
  --force    Performs operation regardless of conflicts
  --dry-run  Doesn't really perform the task, just comments what would do
  --remote   Only acts on the remote copy of the library
  --help     Display this screen

"""

import logging
77
import click
André Anjos's avatar
André Anjos committed
78
79

from beat.core import library
80
81

from . import common
82
83
from . import commands

84
from .decorators import raise_on_error
85
from .click_helper import AliasedGroup
86
87
from .click_helper import AssetCommand
from .click_helper import AssetInfo
88

89

90
logger = logging.getLogger(__name__)
André Anjos's avatar
André Anjos committed
91
92


93
def pull_impl(webapi, prefix, names, force, indentation, cache):
Samuel GAIST's avatar
Samuel GAIST committed
94
    """Copies libraries (and dependent libraries) from the server.
André Anjos's avatar
André Anjos committed
95
96
97
98
99
100
101
102
103

  Parameters:

    webapi (object): An instance of our WebAPI class, prepared to access the
      BEAT server of interest

    prefix (str): A string representing the root of the path in which the user
      objects are stored

André Anjos's avatar
André Anjos committed
104
105
106
107
108
    names (:py:class:`list`): A list of strings, each representing the unique
      relative path of the objects to retrieve or a list of usernames from
      which to retrieve objects. If the list is empty, then we pull all
      available objects of a given type. If no user is set, then pull all
      public objects of a given type.
André Anjos's avatar
André Anjos committed
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

    force (bool): If set to ``True``, then overwrites local changes with the
      remotely retrieved copies.

    indentation (int): The indentation level, useful if this function is called
      recursively while downloading different object types. This is normally
      set to ``0`` (zero).

    cache (dict): A dictionary containing all libraries already downloaded.


  Returns:

    int: Indicating the exit status of the command, to be reported back to the
      calling process. This value should be zero if everything works OK,
      otherwise, different than zero (POSIX compliance).

  """

128
129
    libraries = set(names)  # what is being request
    download = libraries - set(cache.keys())  # what we actually need
André Anjos's avatar
André Anjos committed
130

Samuel GAIST's avatar
Samuel GAIST committed
131
132
    if not download:
        return 0
André Anjos's avatar
André Anjos committed
133

Samuel GAIST's avatar
Samuel GAIST committed
134
135
    if indentation == 0:
        indentation = 4
André Anjos's avatar
André Anjos committed
136

Samuel GAIST's avatar
Samuel GAIST committed
137
138
139
140
141
142
143
144
145
    status, downloaded = common.pull(
        webapi,
        prefix,
        "library",
        download,
        ["declaration", "code", "description"],
        force,
        indentation,
    )
André Anjos's avatar
André Anjos committed
146

Samuel GAIST's avatar
Samuel GAIST committed
147
148
    if status != 0:
        return status
André Anjos's avatar
André Anjos committed
149

Samuel GAIST's avatar
Samuel GAIST committed
150
151
152
153
154
155
156
    # see what else one needs to pull
    for name in downloaded:
        try:
            obj = library.Library(prefix, name)
            cache[name] = obj
            if not obj.valid:
                cache[name] = None
André Anjos's avatar
André Anjos committed
157

Samuel GAIST's avatar
Samuel GAIST committed
158
159
            # downloads any dependencies
            libraries |= obj.libraries.keys()
André Anjos's avatar
André Anjos committed
160

Samuel GAIST's avatar
Samuel GAIST committed
161
162
163
        except Exception as e:
            logger.error("loading `%s': %s...", name, str(e))
            cache[name] = None
André Anjos's avatar
André Anjos committed
164

Samuel GAIST's avatar
Samuel GAIST committed
165
166
    # recurse until done
    return pull_impl(webapi, prefix, libraries, force, indentation, cache)
167
168


169
170
def get_dependencies(ctx, asset_name):
    prefix = ctx.meta["config"].path
171
    lib = library.Library(prefix, asset_name)
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

    dependencies = {}

    libraries = list(lib.uses.values())
    if libraries:
        dependencies["libraries"] = libraries

    return dependencies


class LibraryCommand(AssetCommand):
    asset_info = AssetInfo(
        asset_type="library",
        diff_fields=["declaration", "code", "description"],
        push_fields=["name", "declaration", "code", "description"],
        get_dependencies=get_dependencies,
    )


191
@click.group(cls=AliasedGroup)
192
193
194
195
196
@click.pass_context
def libraries(ctx):
    """Configuration and manipulation of libraries"""


197
198
199
200
201
202
203
204
205
206
207
CMD_LIST = [
    "list",
    "path",
    "edit",
    "check",
    "status",
    "create",
    "version",
    "fork",
    "rm",
    "diff",
208
    "push",
209
210
]

211
commands.initialise_asset_commands(libraries, CMD_LIST, LibraryCommand)
212
213
214


@libraries.command()
Samuel GAIST's avatar
Samuel GAIST committed
215
216
217
218
@click.argument("names", nargs=-1)
@click.option(
    "--force", help="Performs operation regardless of conflicts", is_flag=True
)
219
@click.pass_context
220
@raise_on_error
221
def pull(ctx, names, force):
Samuel GAIST's avatar
Samuel GAIST committed
222
    """Downloads the specified libraries from the server
223
224
225

  Example:
    $ beat libraries pull --force yyy
Samuel GAIST's avatar
Samuel GAIST committed
226
227
228
  """
    with common.make_webapi(ctx.meta["config"]) as webapi:
        return pull_impl(webapi, ctx.meta["config"].path, names, force, 0, {})