libraries.py 10.9 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
80
import oset

from beat.core import library
81
82

from . import common
83
84
from . import commands

85
from .decorators import raise_on_error
86
87
from .click_helper import AliasedGroup

88

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


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

  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
103
104
105
106
107
    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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

    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).

  """

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

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

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

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

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

Samuel GAIST's avatar
Samuel GAIST committed
149
150
151
152
153
154
155
    # 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
156

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

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

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


168
@click.group(cls=AliasedGroup)
169
170
171
172
@click.pass_context
def libraries(ctx):
    """Configuration and manipulation of libraries"""

173
    ctx.meta["asset_type"] = "library"
174
175


176
libraries.command(name="list")(commands.command("list"))
177
178
179


@libraries.command()
Samuel GAIST's avatar
Samuel GAIST committed
180
@click.argument("names", nargs=-1)
181
@click.pass_context
182
@raise_on_error
183
def path(ctx, names):
Samuel GAIST's avatar
Samuel GAIST committed
184
    """Displays local path of libraries files
185
186
187

  Example:
    $ beat libraries path xxx
Samuel GAIST's avatar
Samuel GAIST committed
188
189
  """
    return common.display_local_path(ctx.meta["config"].path, "library", names)
190
191


192
@libraries.command()
Samuel GAIST's avatar
Samuel GAIST committed
193
@click.argument("name", nargs=1)
194
@click.pass_context
195
@raise_on_error
196
def edit(ctx, name):
Samuel GAIST's avatar
Samuel GAIST committed
197
    """Edit local library file
198
199
200

  Example:
    $ beat libraries edit xxx
Samuel GAIST's avatar
Samuel GAIST committed
201
202
203
204
  """
    return common.edit_local_file(
        ctx.meta["config"].path, ctx.meta["config"].editor, "library", name
    )
205
206
207


@libraries.command()
Samuel GAIST's avatar
Samuel GAIST committed
208
@click.argument("name", nargs=1)
209
@click.pass_context
210
@raise_on_error
211
def check(ctx, names):
Samuel GAIST's avatar
Samuel GAIST committed
212
    """Checks a local library for validity
213
214
215

  Example:
    $ beat libraries check xxx
Samuel GAIST's avatar
Samuel GAIST committed
216
217
  """
    return common.check(ctx.meta["config"].path, "library", names)
218
219
220


@libraries.command()
Samuel GAIST's avatar
Samuel GAIST committed
221
222
223
224
@click.argument("names", nargs=-1)
@click.option(
    "--force", help="Performs operation regardless of conflicts", is_flag=True
)
225
@click.pass_context
226
@raise_on_error
227
def pull(ctx, names, force):
Samuel GAIST's avatar
Samuel GAIST committed
228
    """Downloads the specified libraries from the server
229
230
231

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


@libraries.command()
Samuel GAIST's avatar
Samuel GAIST committed
238
239
240
241
242
243
244
245
246
@click.argument("names", nargs=-1)
@click.option(
    "--force", help="Performs operation regardless of conflicts", is_flag=True
)
@click.option(
    "--dry-run",
    help="Doesn't really perform the task, just " "comments what would do",
    is_flag=True,
)
247
@click.pass_context
248
@raise_on_error
249
def push(ctx, names, force, dry_run):
Samuel GAIST's avatar
Samuel GAIST committed
250
    """Uploads libraries to the server
251
252
253

  Example:
    $ beat libraries push --dry-run yyy
Samuel GAIST's avatar
Samuel GAIST committed
254
255
256
257
258
259
260
261
262
263
264
265
266
  """
    with common.make_webapi(ctx.meta["config"]) as webapi:
        return common.push(
            webapi,
            ctx.meta["config"].path,
            "library",
            names,
            ["names", "declaration", "code", "description"],
            {},
            force,
            dry_run,
            0,
        )
267
268
269


@libraries.command()
Samuel GAIST's avatar
Samuel GAIST committed
270
@click.argument("name", nargs=1)
271
@click.pass_context
272
@raise_on_error
273
def diff(ctx, name):
Samuel GAIST's avatar
Samuel GAIST committed
274
    """Shows changes between the local library and the remote version
275
276
277

  Example:
    $ beat libraries diff xxx
Samuel GAIST's avatar
Samuel GAIST committed
278
279
280
281
282
283
284
285
286
  """
    with common.make_webapi(ctx.meta["config"]) as webapi:
        return common.diff(
            webapi,
            ctx.meta["config"].path,
            "library",
            name,
            ["declaration", "code", "description"],
        )
287
288
289
290


@libraries.command()
@click.pass_context
291
@raise_on_error
292
def status(ctx):
Samuel GAIST's avatar
Samuel GAIST committed
293
    """Shows (editing) status for all available libraries
294
295
296

  Example:
    $ beat libraries status
Samuel GAIST's avatar
Samuel GAIST committed
297
298
299
  """
    with common.make_webapi(ctx.meta["config"]) as webapi:
        return common.status(webapi, ctx.meta["config"].path, "library")[0]
300
301
302


@libraries.command()
Samuel GAIST's avatar
Samuel GAIST committed
303
@click.argument("names", nargs=-1)
304
@click.pass_context
305
@raise_on_error
306
def create(ctx, names):
Samuel GAIST's avatar
Samuel GAIST committed
307
    """Creates a new local library
308
309
310

  Example:
    $ beat libraries create xxx
Samuel GAIST's avatar
Samuel GAIST committed
311
312
  """
    return common.create(ctx.meta["config"].path, "library", names)
313
314
315


@libraries.command()
Samuel GAIST's avatar
Samuel GAIST committed
316
@click.argument("name", nargs=1)
317
@click.pass_context
318
@raise_on_error
319
def version(ctx, name):
Samuel GAIST's avatar
Samuel GAIST committed
320
    """Creates a new version of an existing library
321
322
323

  Example:
    $ beat libraries version xxx
Samuel GAIST's avatar
Samuel GAIST committed
324
325
326
  """
    return common.new_version(ctx.meta["config"].path, "library", name)

327
328

@libraries.command()
Samuel GAIST's avatar
Samuel GAIST committed
329
330
@click.argument("src", nargs=1)
@click.argument("dst", nargs=1)
331
@click.pass_context
332
@raise_on_error
333
def fork(ctx, src, dst):
Samuel GAIST's avatar
Samuel GAIST committed
334
    """Forks a local library
335
336
337

  Example:
      $ beat libraries fork xxx yyy
Samuel GAIST's avatar
Samuel GAIST committed
338
339
  """
    return common.fork(ctx.meta["config"].path, "library", src, dst)
340
341
342


@libraries.command()
Samuel GAIST's avatar
Samuel GAIST committed
343
344
345
346
@click.argument("names", nargs=1)
@click.option(
    "--remote", help="Only acts on the remote copy of the library", is_flag=True
)
347
@click.pass_context
348
@raise_on_error
349
def rm(ctx, names, remote):
Samuel GAIST's avatar
Samuel GAIST committed
350
    """Deletes a local library (unless --remote is specified)
351
352
353

  Example:
      $ beat libraries rm xxx
Samuel GAIST's avatar
Samuel GAIST committed
354
355
356
357
358
359
  """
    if remote:
        with common.make_webapi(ctx.meta["config"]) as webapi:
            return common.delete_remote(webapi, "library", names)
    else:
        return common.delete_local(ctx.meta["config"].path, "library", names)