|
1 | 1 | #!/usr/bin/env python |
| 2 | +from __future__ import print_function |
| 3 | + |
2 | 4 | from getpass import getuser |
3 | 5 | from os import getcwd |
4 | 6 | import subprocess |
5 | 7 | from textwrap import dedent |
6 | 8 |
|
7 | 9 | import click |
8 | 10 |
|
| 11 | +import nbformat |
| 12 | +from nbconvert.preprocessors.clearoutput import ClearOutputPreprocessor |
9 | 13 | from pgcontents.constants import ( |
10 | 14 | ALEMBIC_DIR_LOCATION, |
11 | 15 | DB_URL_ENVVAR, |
12 | 16 | ) |
| 17 | +from pgcontents.crypto import single_password_crypto_factory |
| 18 | +from pgcontents.pgmanager import PostgresContentsManager |
13 | 19 |
|
14 | 20 | from pgcontents.utils.migrate import ( |
15 | 21 | temp_alembic_ini, |
16 | 22 | upgrade, |
17 | 23 | ) |
| 24 | +from tornado.web import HTTPError |
18 | 25 |
|
19 | 26 |
|
20 | 27 | @click.group(context_settings=dict(help_option_names=['-h', '--help'])) |
@@ -109,5 +116,86 @@ def gen_migration(db_url): |
109 | 116 | ) |
110 | 117 |
|
111 | 118 |
|
| 119 | +@main.command() |
| 120 | +@click.option( |
| 121 | + '-u', '--user', |
| 122 | + help='Owner of the notebook to be fetched.', |
| 123 | +) |
| 124 | +@click.option( |
| 125 | + '-f', '--filename', |
| 126 | + help='Name of the file to fetch in the DB.', |
| 127 | +) |
| 128 | +@click.option( |
| 129 | + '-o', '--output', |
| 130 | + help="Local filesystem destination.", |
| 131 | + type=click.Path( |
| 132 | + file_okay=True, |
| 133 | + dir_okay=False, |
| 134 | + writable=True, |
| 135 | + ), |
| 136 | +) |
| 137 | +@click.option( |
| 138 | + '-k', '--key', |
| 139 | + help="Decryption key.", |
| 140 | + type=click.STRING, |
| 141 | + envvar='PGCONTENTS_DECRYPTION_KEY', |
| 142 | +) |
| 143 | +@click.option( |
| 144 | + '-t', '--type', |
| 145 | + help="Type of file to fetch (notebook or file).", |
| 146 | + default='notebook', |
| 147 | + type=click.Choice(['file', 'notebook']), |
| 148 | + show_default=True, |
| 149 | +) |
| 150 | +@click.option( |
| 151 | + '--clear-output', |
| 152 | + help="Clear notebook output before writing?", |
| 153 | + default=False, |
| 154 | + is_flag=True, |
| 155 | +) |
| 156 | +@_db_url |
| 157 | +def fetch(db_url, user, filename, key, output, type, clear_output): |
| 158 | + """Fetch a notebook from the database to the local filesystem. |
| 159 | + """ |
| 160 | + if db_url is None: |
| 161 | + raise click.UsageError("-l/--db-url is required") |
| 162 | + if user is None: |
| 163 | + raise click.UsageError("-u/--user is required") |
| 164 | + if filename is None: |
| 165 | + raise click.UsageError("-f/--filename is required") |
| 166 | + if output is None: |
| 167 | + output = filename |
| 168 | + |
| 169 | + crypto = single_password_crypto_factory(key)(user) |
| 170 | + |
| 171 | + mgr = PostgresContentsManager( |
| 172 | + db_url=db_url, |
| 173 | + user_id=user, |
| 174 | + # User should already exist. |
| 175 | + create_directory_on_startup=False, |
| 176 | + create_user_on_startup=False, |
| 177 | + crypto=crypto, |
| 178 | + ) |
| 179 | + |
| 180 | + try: |
| 181 | + result = mgr.get(filename, content=True, type=type) |
| 182 | + except HTTPError as e: |
| 183 | + if e.status_code == 404: |
| 184 | + raise click.ClickException("No such file: {!r}".format(filename)) |
| 185 | + elif e.status_code == 500: |
| 186 | + raise click.ClickException( |
| 187 | + "Failed to load file: {!r}. Is the decryption key correct?" |
| 188 | + .format(filename) |
| 189 | + ) |
| 190 | + else: |
| 191 | + raise click.ClickException("Unknown error: %s" % e) |
| 192 | + |
| 193 | + nb = nbformat.from_dict(result['content']) |
| 194 | + if clear_output: |
| 195 | + ClearOutputPreprocessor().preprocess(nb, resources=None) |
| 196 | + |
| 197 | + nbformat.write(nb, open(output, 'w'), version=nbformat.NO_CONVERT) |
| 198 | + |
| 199 | + |
112 | 200 | if __name__ == "__main__": |
113 | 201 | main() |
0 commit comments