|
1
|
|
|
""" |
|
2
|
|
|
Usage: |
|
3
|
|
|
rapidpro-pull --flow-runs --api-token=<api-token> [--address=<address>] |
|
4
|
|
|
[--before=<before> --after=<after>] |
|
5
|
|
|
[--with-contacts --with-flows] |
|
6
|
|
|
[--cache=<database-url>] |
|
7
|
|
|
rapidpro-pull --flows --api-token=<api-token> [--address=<address>] |
|
8
|
|
|
[--before=<before> --after=<after>] |
|
9
|
|
|
[--uuid=<uuid> ...] |
|
10
|
|
|
[--cache=<database-url>] |
|
11
|
|
|
rapidpro-pull --contacts --api-token=<api-token> [--address=<address>] |
|
12
|
|
|
[--before=<before> --after=<after>] |
|
13
|
|
|
[--uuid=<uuid> ...] |
|
14
|
|
|
[--cache=<database-url>] |
|
15
|
|
|
rapidpro-pull --help |
|
16
|
|
|
|
|
17
|
|
|
Options: |
|
18
|
|
|
--flow-runs download flow runs |
|
19
|
|
|
--flows download flows |
|
20
|
|
|
--contacts download contacts |
|
21
|
|
|
-a, --address=<address> a RapidPro server [default: rapidpro.io] |
|
22
|
|
|
-t, --api-token=<api-token> a RapidPro API token |
|
23
|
|
|
|
|
24
|
|
|
-h, --help display this help and exit |
|
25
|
|
|
|
|
26
|
|
|
--before=<before> download all older than ISO 8601 date/time |
|
27
|
|
|
--after=<after> download all newer than ISO 8601 date/time |
|
28
|
|
|
|
|
29
|
|
|
--uuid=<uuid> fetch objects matching UUID(s) (repeatable) |
|
30
|
|
|
|
|
31
|
|
|
--with-flows download associated flows, too |
|
32
|
|
|
--with-contacts download associated contacts, too |
|
33
|
|
|
|
|
34
|
|
|
--cache=<database-url> use database-url as cache (store retrieved |
|
35
|
|
|
objects in cache; retrieve objects from |
|
36
|
|
|
cache instead of downloading from RapidPro |
|
37
|
|
|
when possible) |
|
38
|
|
|
""" |
|
39
|
|
|
from __future__ import print_function |
|
40
|
|
|
import sys |
|
41
|
|
|
import json |
|
42
|
|
|
|
|
43
|
|
|
import temba_client.v1 |
|
44
|
|
|
import temba_client.exceptions |
|
45
|
|
|
import docopt |
|
46
|
|
|
|
|
47
|
|
|
import rapidpropull.download |
|
48
|
|
|
|
|
49
|
|
|
__author__ = 'Tomasz J. Kotarba <[email protected]>' |
|
50
|
|
|
__copyright__ = 'Copyright (c) 2016, Tomasz J. Kotarba. All rights reserved.' |
|
51
|
|
|
__maintainer__ = 'Tomasz J. Kotarba' |
|
52
|
|
|
__email__ = '[email protected]' |
|
53
|
|
|
__version__ = 'rapidpro-pull 1.0.0' |
|
54
|
|
|
|
|
55
|
|
|
|
|
56
|
|
|
class ArgumentProcessor(object): |
|
57
|
|
|
""" |
|
58
|
|
|
A problem domain specific processor of command-line arguments for the |
|
59
|
|
|
rapidpro-pull program. Sanitises arguments and can be queried to provide |
|
60
|
|
|
domain relevant information to clients (e.g. to instances of the |
|
61
|
|
|
DownloadTask class). |
|
62
|
|
|
It depends on a definition in a format compatible with docopt and specified |
|
63
|
|
|
in __doc__. |
|
64
|
|
|
""" |
|
65
|
|
|
ENDPOINT_SELECTORS = [ |
|
66
|
|
|
'--flow-runs', |
|
67
|
|
|
'--flows', |
|
68
|
|
|
'--contacts' |
|
69
|
|
|
] |
|
70
|
|
|
|
|
71
|
|
|
def __init__(self, argv=None): |
|
72
|
|
|
""" |
|
73
|
|
|
Initialise and interact with docopt to process arguments given in argv |
|
74
|
|
|
(or sys.argv if argv not provided). |
|
75
|
|
|
""" |
|
76
|
|
|
if argv is None: |
|
77
|
|
|
argv = sys.argv[1:] |
|
78
|
|
|
self.arguments = docopt.docopt(__doc__, argv=argv, version=__version__) |
|
79
|
|
|
|
|
80
|
|
|
def get_address(self): |
|
81
|
|
|
"""Return the address of a RapidPro service to be used.""" |
|
82
|
|
|
return self.arguments['--address'] |
|
83
|
|
|
|
|
84
|
|
|
def get_api_token(self): |
|
85
|
|
|
"""Return a RapidPro API token provided by the user.""" |
|
86
|
|
|
return self.arguments['--api-token'] |
|
87
|
|
|
|
|
88
|
|
|
def get_endpoint_selector(self): |
|
89
|
|
|
""" |
|
90
|
|
|
Return an endpoint selector (see: the RapidPro API). The endpoint |
|
91
|
|
|
selector determines which of the endpoints publicly exposed by the |
|
92
|
|
|
RapidPro web API will be used to pull data. |
|
93
|
|
|
""" |
|
94
|
|
|
filtered = filter(lambda s: self.arguments.get(s), |
|
95
|
|
|
self.ENDPOINT_SELECTORS) |
|
96
|
|
|
if filtered: |
|
97
|
|
|
return filtered[0] |
|
98
|
|
|
else: |
|
99
|
|
|
return None |
|
100
|
|
|
|
|
101
|
|
|
def get_endpoint_kwargs(self): |
|
102
|
|
|
""" |
|
103
|
|
|
Return a dictionary of optional arguments the user has provided to |
|
104
|
|
|
modify a request to the user-selected endpoint. |
|
105
|
|
|
""" |
|
106
|
|
|
kwargs = {} |
|
107
|
|
|
if self.arguments['--before'] is not None: |
|
108
|
|
|
kwargs['before'] = self.arguments['--before'] |
|
109
|
|
|
if self.arguments['--after'] is not None: |
|
110
|
|
|
kwargs['after'] = self.arguments['--after'] |
|
111
|
|
|
if self.arguments['--uuid']: |
|
112
|
|
|
kwargs['uuids'] = self.arguments['--uuid'] |
|
113
|
|
|
return kwargs |
|
114
|
|
|
|
|
115
|
|
|
def get_selectors_of_requested_associations(self): |
|
116
|
|
|
""" |
|
117
|
|
|
Return optional endpoint selector[s] to be used to query RapidPro for |
|
118
|
|
|
objects associated to the objects downloaded as a result of a request |
|
119
|
|
|
sent to the main endpoint selector. Only the endpoint selectors |
|
120
|
|
|
explicitly requested by the user will be returned. |
|
121
|
|
|
""" |
|
122
|
|
|
selectors = [] |
|
123
|
|
|
if self.arguments['--with-contacts']: |
|
124
|
|
|
selectors.append('--contacts') |
|
125
|
|
|
if self.arguments['--with-flows']: |
|
126
|
|
|
selectors.append('--flows') |
|
127
|
|
|
return tuple(selectors) |
|
128
|
|
|
|
|
129
|
|
|
def get_cache_url(self): |
|
130
|
|
|
"""Return a URL to a database to be used as cache (if provided).""" |
|
131
|
|
|
return self.arguments['--cache'] |
|
132
|
|
|
|
|
133
|
|
|
|
|
134
|
|
|
def main(argv=None): |
|
135
|
|
|
""" |
|
136
|
|
|
This is an entry point used to automatically generate the rapidpro-pull |
|
137
|
|
|
command. |
|
138
|
|
|
""" |
|
139
|
|
|
arguments = ArgumentProcessor(argv) |
|
140
|
|
|
downloader = rapidpropull.download.DownloadTask(arguments) |
|
141
|
|
|
try: |
|
142
|
|
|
downloader.download() |
|
143
|
|
|
except temba_client.exceptions.TembaConnectionError: |
|
144
|
|
|
print('Unable to connect to host', file=sys.stderr) |
|
145
|
|
|
sys.exit(1) |
|
146
|
|
|
except temba_client.exceptions.TembaTokenError: |
|
147
|
|
|
print('Authentication with provided token failed', file=sys.stderr) |
|
148
|
|
|
sys.exit(1) |
|
149
|
|
|
else: |
|
150
|
|
|
print(json.dumps(downloader.get_downloaded_json_structure())) |
|
151
|
|
|
|