I'm attempting to use createReplica from the ArcGIS Rest API to download a copy of a hosted feature service, but the results never include records from both a parent layer and its children -- it's either one or the other. The feature service was created automatically when I created a Survey123 survey, and the survey includes a repeat group, hence the parent-child relationship. I'm making calls to the API via the Python script shown below.
If I don't include the layerQuery parameter, all records in the parent layer and attachments are returned in the result and the child layer exists, but it's completely empty. If I do include the layerQuery parameter with "includeRelated": true and "queryOption": "none", all records in the child layer are returned, but the parent layer is empty. I've tried several different iterations of other parameters, but none of them yield records from both layers. Those iterations include
- "queryOption": "all" - only the parent layer records are returned
- "dataFormat" of "sqlite", "json", and "filegdb" - behavior is the same (i.e., depends on layerQuery params)
- "where" and "useFilter" with a query that should evaluate to return everything - only parent layer records are returned
Also of note, if I download the data manually from the Data tab/page of the survey, all of the records are there. I assume this means there's nothing wrong with the feature service itself and there aren't any settings of the feature service I've left unchecked.
UPDATE: If I use query instead of createReplica with layerDefs that should definitely return all records from both layers (i.e., {'0': "CreationDate > TIMESTAMP '1970-1-1 00:00:00'", '1': "CreationDate > TIMESTAMP '1970-1-1 00:00:00'"}), all the features of the parent layer are returned and the child layer is empty. So the problem is not with createReplica. Is there something wrong with the way Survey123 automatically creates relationships such that the REST API ignores related layers/tables?
I've searched for quite a while, and I can't find anything mentioning a similar problem. Usually when that happens it means I'm doing something wrong. The Python script is below. Any help or suggestions would be appreciated.
<SPAN class="keyword token">import</SPAN> os<SPAN class="punctuation token">,</SPAN> sys
<SPAN class="keyword token">import</SPAN> requests
<SPAN class="keyword token">import</SPAN> time
<SPAN class="keyword token">import</SPAN> json
<SPAN class="keyword token">import</SPAN> pandas <SPAN class="keyword token">as</SPAN> pd
<SPAN class="keyword token">from</SPAN> sqlalchemy <SPAN class="keyword token">import</SPAN> create_engine
<SPAN class="keyword token">from</SPAN> datetime <SPAN class="keyword token">import</SPAN> datetime
RESULT_TIMEOUT <SPAN class="operator token">=</SPAN> <SPAN class="number token">900</SPAN> <SPAN class="comment token"># seconds in 15 minutes</SPAN>
CHECK_STATUS_INTERVAL <SPAN class="operator token">=</SPAN> <SPAN class="number token">5</SPAN>
<SPAN class="keyword token">def</SPAN> <SPAN class="token function">check_http_error</SPAN><SPAN class="punctuation token">(</SPAN>attempted_action<SPAN class="punctuation token">,</SPAN> response<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">:</SPAN>
<SPAN class="keyword token">try</SPAN><SPAN class="punctuation token">:</SPAN>
response<SPAN class="punctuation token">.</SPAN>raise_for_status<SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="keyword token">except</SPAN> Exception <SPAN class="keyword token">as</SPAN> e<SPAN class="punctuation token">:</SPAN>
<SPAN class="keyword token">raise</SPAN> requests<SPAN class="punctuation token">.</SPAN>HTTPError<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'failed to {action} because {error}'</SPAN><SPAN class="punctuation token">.</SPAN>format<SPAN class="punctuation token">(</SPAN>action<SPAN class="operator token">=</SPAN>attempted_action<SPAN class="punctuation token">,</SPAN> error<SPAN class="operator token">=</SPAN>e<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token"># status codes are often valid even if there was an error, so check the response json</SPAN>
response_json <SPAN class="operator token">=</SPAN> response<SPAN class="punctuation token">.</SPAN>json<SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="keyword token">if</SPAN> <SPAN class="string token">'error'</SPAN> <SPAN class="keyword token">in</SPAN> response_json<SPAN class="punctuation token">:</SPAN>
<SPAN class="keyword token">raise</SPAN> requests<SPAN class="punctuation token">.</SPAN>HTTPError<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'failed to {action} because {error}'</SPAN><SPAN class="punctuation token">.</SPAN>format<SPAN class="punctuation token">(</SPAN>action<SPAN class="operator token">=</SPAN>attempted_action<SPAN class="punctuation token">,</SPAN> error<SPAN class="operator token">=</SPAN>response_json<SPAN class="punctuation token">[</SPAN><SPAN class="string token">'error'</SPAN><SPAN class="punctuation token">]</SPAN><SPAN class="punctuation token">[</SPAN><SPAN class="string token">'details'</SPAN><SPAN class="punctuation token">]</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="keyword token">def</SPAN> <SPAN class="token function">download_data</SPAN><SPAN class="punctuation token">(</SPAN>portal_url<SPAN class="punctuation token">,</SPAN> service_url<SPAN class="punctuation token">,</SPAN> client_id<SPAN class="punctuation token">,</SPAN> client_secret<SPAN class="punctuation token">,</SPAN> ssl_cert<SPAN class="punctuation token">,</SPAN> out_dir<SPAN class="punctuation token">,</SPAN> last_poll_time<SPAN class="operator token">=</SPAN>None<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">:</SPAN>
<SPAN class="comment token"># Get token for REST API</SPAN>
token_params <SPAN class="operator token">=</SPAN> <SPAN class="punctuation token">{</SPAN><SPAN class="string token">'client_id'</SPAN><SPAN class="punctuation token">:</SPAN> client_id<SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'client_secret'</SPAN><SPAN class="punctuation token">:</SPAN> client_secret<SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'grant_type'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="string token">'client_credentials'</SPAN><SPAN class="punctuation token">}</SPAN>
token_response <SPAN class="operator token">=</SPAN> requests<SPAN class="punctuation token">.</SPAN>get<SPAN class="punctuation token">(</SPAN>portal_url <SPAN class="operator token">+</SPAN> <SPAN class="string token">'/sharing/rest/oauth2/token/'</SPAN><SPAN class="punctuation token">,</SPAN> params<SPAN class="operator token">=</SPAN>token_params<SPAN class="punctuation token">,</SPAN> verify<SPAN class="operator token">=</SPAN>ssl_cert<SPAN class="punctuation token">)</SPAN>
check_http_error<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'get token'</SPAN><SPAN class="punctuation token">,</SPAN> token_response<SPAN class="punctuation token">)</SPAN>
token <SPAN class="operator token">=</SPAN> token_response<SPAN class="punctuation token">.</SPAN>json<SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">[</SPAN><SPAN class="string token">'access_token'</SPAN><SPAN class="punctuation token">]</SPAN>
<SPAN class="comment token"># Get feature service info</SPAN>
info_response <SPAN class="operator token">=</SPAN> requests<SPAN class="punctuation token">.</SPAN>get<SPAN class="punctuation token">(</SPAN>service_url<SPAN class="punctuation token">,</SPAN> params<SPAN class="operator token">=</SPAN><SPAN class="punctuation token">{</SPAN><SPAN class="string token">'f'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="string token">'json'</SPAN><SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'token'</SPAN><SPAN class="punctuation token">:</SPAN> token<SPAN class="punctuation token">}</SPAN><SPAN class="punctuation token">,</SPAN> verify<SPAN class="operator token">=</SPAN>ssl_cert<SPAN class="punctuation token">)</SPAN>
check_http_error<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'get service info'</SPAN><SPAN class="punctuation token">,</SPAN> info_response<SPAN class="punctuation token">)</SPAN>
service_info <SPAN class="operator token">=</SPAN> info_response<SPAN class="punctuation token">.</SPAN>json<SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token"># Submit POST request to get data</SPAN>
<SPAN class="comment token"># Make sure that the layerQueries parameter is given with 'includeRelated' = true. Otherwise, related records will</SPAN>
<SPAN class="comment token"># not be included</SPAN>
layers <SPAN class="operator token">=</SPAN> <SPAN class="punctuation token">[</SPAN>layer_info<SPAN class="punctuation token">[</SPAN><SPAN class="string token">'id'</SPAN><SPAN class="punctuation token">]</SPAN> <SPAN class="keyword token">for</SPAN> layer_info <SPAN class="keyword token">in</SPAN> service_info<SPAN class="punctuation token">[</SPAN><SPAN class="string token">'layers'</SPAN><SPAN class="punctuation token">]</SPAN><SPAN class="punctuation token">]</SPAN>
layer_queries <SPAN class="operator token">=</SPAN> <SPAN class="punctuation token">{</SPAN><SPAN class="punctuation token">}</SPAN>
<SPAN class="keyword token">for</SPAN> layer_id <SPAN class="keyword token">in</SPAN> layers<SPAN class="punctuation token">:</SPAN>
str_id <SPAN class="operator token">=</SPAN> str<SPAN class="punctuation token">(</SPAN>layer_id<SPAN class="punctuation token">)</SPAN>
layer_queries<SPAN class="punctuation token">[</SPAN>str_id<SPAN class="punctuation token">]</SPAN> <SPAN class="operator token">=</SPAN> <SPAN class="punctuation token">{</SPAN>
<SPAN class="string token">'includeRelated'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="token boolean">True</SPAN><SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'queryOption'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="string token">'none'</SPAN>
<SPAN class="punctuation token">}</SPAN>
<SPAN class="keyword token">if</SPAN> last_poll_time<SPAN class="punctuation token">:</SPAN>
layer_queries<SPAN class="punctuation token">[</SPAN>str_id<SPAN class="punctuation token">]</SPAN><SPAN class="punctuation token">[</SPAN><SPAN class="string token">'queryOption'</SPAN><SPAN class="punctuation token">]</SPAN> <SPAN class="operator token">=</SPAN> <SPAN class="string token">'useFilter'</SPAN>
layer_queries<SPAN class="punctuation token">[</SPAN>str_id<SPAN class="punctuation token">]</SPAN><SPAN class="punctuation token">[</SPAN><SPAN class="string token">'where'</SPAN><SPAN class="punctuation token">]</SPAN> <SPAN class="operator token">=</SPAN> <SPAN class="string token">"CreationDate > TIMESTAMP '%s'"</SPAN> <SPAN class="operator token">%</SPAN> last_poll_time
create_replica_params <SPAN class="operator token">=</SPAN> <SPAN class="punctuation token">{</SPAN>
<SPAN class="string token">'f'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="string token">'json'</SPAN><SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'token'</SPAN><SPAN class="punctuation token">:</SPAN> token<SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'layers'</SPAN><SPAN class="punctuation token">:</SPAN> layers<SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'geometry'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="string token">'-180,-90,180,90'</SPAN><SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'geometryType'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="string token">'esriGeometryEnvelope'</SPAN><SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'inSR'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="number token">4326</SPAN><SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'dataFormat'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="string token">'sqlite'</SPAN><SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'returnAttachments'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="token boolean">True</SPAN><SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'returnAttachmentsDatabyURL'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="token boolean">False</SPAN><SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'async'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="token boolean">True</SPAN><SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'syncModel'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="string token">'none'</SPAN><SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'layerQueries'</SPAN><SPAN class="punctuation token">:</SPAN> json<SPAN class="punctuation token">.</SPAN>dumps<SPAN class="punctuation token">(</SPAN>layer_queries<SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">}</SPAN>
replica_response <SPAN class="operator token">=</SPAN> requests<SPAN class="punctuation token">.</SPAN>post<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'%s/createReplica'</SPAN> <SPAN class="operator token">%</SPAN> service_url<SPAN class="punctuation token">,</SPAN> params<SPAN class="operator token">=</SPAN>create_replica_params<SPAN class="punctuation token">,</SPAN> verify<SPAN class="operator token">=</SPAN>ssl_cert<SPAN class="punctuation token">)</SPAN>
check_http_error<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'create replica'</SPAN><SPAN class="punctuation token">,</SPAN> replica_response<SPAN class="punctuation token">)</SPAN>
status_url <SPAN class="operator token">=</SPAN> replica_response<SPAN class="punctuation token">.</SPAN>json<SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">[</SPAN><SPAN class="string token">'statusUrl'</SPAN><SPAN class="punctuation token">]</SPAN>
<SPAN class="comment token"># Query was set as asynchronous, so check the status at a set interval</SPAN>
<SPAN class="keyword token">for</SPAN> i <SPAN class="keyword token">in</SPAN> range<SPAN class="punctuation token">(</SPAN><SPAN class="number token">0</SPAN><SPAN class="punctuation token">,</SPAN> RESULT_TIMEOUT<SPAN class="punctuation token">,</SPAN> CHECK_STATUS_INTERVAL<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">:</SPAN>
status_response <SPAN class="operator token">=</SPAN> requests<SPAN class="punctuation token">.</SPAN>get<SPAN class="punctuation token">(</SPAN>status_url<SPAN class="punctuation token">,</SPAN> params<SPAN class="operator token">=</SPAN><SPAN class="punctuation token">{</SPAN><SPAN class="string token">'f'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="string token">'json'</SPAN><SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'token'</SPAN><SPAN class="punctuation token">:</SPAN> token<SPAN class="punctuation token">}</SPAN><SPAN class="punctuation token">,</SPAN> verify<SPAN class="operator token">=</SPAN>ssl_cert<SPAN class="punctuation token">)</SPAN>
check_http_error<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'check status after %s iterations'</SPAN><SPAN class="punctuation token">,</SPAN> status_response<SPAN class="punctuation token">)</SPAN>
status_json <SPAN class="operator token">=</SPAN> status_response<SPAN class="punctuation token">.</SPAN>json<SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token"># If the resultUrl isn't empty, that means the result is ready to download</SPAN>
result_url <SPAN class="operator token">=</SPAN> status_json<SPAN class="punctuation token">[</SPAN><SPAN class="string token">'resultUrl'</SPAN><SPAN class="punctuation token">]</SPAN>
<SPAN class="keyword token">if</SPAN> result_url <SPAN class="operator token">!=</SPAN> <SPAN class="string token">''</SPAN><SPAN class="punctuation token">:</SPAN>
<SPAN class="keyword token">break</SPAN>
<SPAN class="keyword token">elif</SPAN> status_json<SPAN class="punctuation token">[</SPAN><SPAN class="string token">'status'</SPAN><SPAN class="punctuation token">]</SPAN> <SPAN class="keyword token">in</SPAN> <SPAN class="punctuation token">(</SPAN><SPAN class="string token">'CompletedWithErrors'</SPAN><SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'Failed'</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">:</SPAN>
<SPAN class="keyword token">raise</SPAN> requests<SPAN class="punctuation token">.</SPAN>HTTPError<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'the query failed for an unspecified reason'</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="keyword token">else</SPAN><SPAN class="punctuation token">:</SPAN>
<SPAN class="keyword token">if</SPAN> i <SPAN class="operator token">+</SPAN> CHECK_STATUS_INTERVAL <SPAN class="operator token">>=</SPAN> RESULT_TIMEOUT<SPAN class="punctuation token">:</SPAN> <SPAN class="comment token"># timeout exceeded</SPAN>
<SPAN class="keyword token">raise</SPAN> requests<SPAN class="punctuation token">.</SPAN>exceptions<SPAN class="punctuation token">.</SPAN>ConnectTimeout<SPAN class="punctuation token">(</SPAN>
<SPAN class="string token">'Asynchronous query exceeded RESULT_TIMEOUT of %.1f minutes'</SPAN> <SPAN class="operator token">%</SPAN> <SPAN class="punctuation token">(</SPAN>RESULT_TIMEOUT<SPAN class="operator token">/</SPAN><SPAN class="number token">60.0</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">)</SPAN>
time<SPAN class="punctuation token">.</SPAN>sleep<SPAN class="punctuation token">(</SPAN>CHECK_STATUS_INTERVAL<SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token"># Write the result to disk</SPAN>
result_response <SPAN class="operator token">=</SPAN> requests<SPAN class="punctuation token">.</SPAN>get<SPAN class="punctuation token">(</SPAN>result_url<SPAN class="punctuation token">,</SPAN> params<SPAN class="operator token">=</SPAN><SPAN class="punctuation token">{</SPAN><SPAN class="string token">'f'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="string token">'json'</SPAN><SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'token'</SPAN><SPAN class="punctuation token">:</SPAN> token<SPAN class="punctuation token">}</SPAN><SPAN class="punctuation token">,</SPAN> verify<SPAN class="operator token">=</SPAN>ssl_cert<SPAN class="punctuation token">)</SPAN>
check_http_error<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'get createReplica result'</SPAN><SPAN class="punctuation token">,</SPAN> result_response<SPAN class="punctuation token">)</SPAN>
service_name <SPAN class="operator token">=</SPAN> service_info<SPAN class="punctuation token">[</SPAN><SPAN class="string token">'serviceDescription'</SPAN><SPAN class="punctuation token">]</SPAN>
sqlite_path <SPAN class="operator token">=</SPAN> os<SPAN class="punctuation token">.</SPAN>path<SPAN class="punctuation token">.</SPAN>join<SPAN class="punctuation token">(</SPAN>out_dir<SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'{0}_{1}.db'</SPAN><SPAN class="punctuation token">.</SPAN>format<SPAN class="punctuation token">(</SPAN>service_name<SPAN class="punctuation token">,</SPAN> datetime<SPAN class="punctuation token">.</SPAN>now<SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">.</SPAN>strftime<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'%Y%m%d-%H%M%S'</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="keyword token">with</SPAN> open<SPAN class="punctuation token">(</SPAN>sqlite_path<SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'wb'</SPAN><SPAN class="punctuation token">)</SPAN> <SPAN class="keyword token">as</SPAN> f<SPAN class="punctuation token">:</SPAN>
f<SPAN class="punctuation token">.</SPAN>write<SPAN class="punctuation token">(</SPAN>result_response<SPAN class="punctuation token">.</SPAN>content<SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token"># Get attachments (stored as bytes in Blob dtype column of the sqlite DB)</SPAN>
engine <SPAN class="operator token">=</SPAN> create_engine<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'sqlite:///'</SPAN> <SPAN class="operator token">+</SPAN> sqlite_path<SPAN class="punctuation token">)</SPAN>
<SPAN class="keyword token">with</SPAN> engine<SPAN class="punctuation token">.</SPAN>connect<SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN> <SPAN class="keyword token">as</SPAN> conn<SPAN class="punctuation token">,</SPAN> conn<SPAN class="punctuation token">.</SPAN>begin<SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">:</SPAN>
attachment_tables <SPAN class="operator token">=</SPAN> pd<SPAN class="punctuation token">.</SPAN>read_sql<SPAN class="punctuation token">(</SPAN><SPAN class="string token">"SELECT name FROM sqlite_master WHERE name LIKE('%__ATTACH');"</SPAN><SPAN class="punctuation token">,</SPAN> conn<SPAN class="punctuation token">)</SPAN>\
<SPAN class="punctuation token">.</SPAN>squeeze<SPAN class="punctuation token">(</SPAN>axis<SPAN class="operator token">=</SPAN><SPAN class="number token">1</SPAN><SPAN class="punctuation token">)</SPAN>
attachments_dir <SPAN class="operator token">=</SPAN> os<SPAN class="punctuation token">.</SPAN>path<SPAN class="punctuation token">.</SPAN>join<SPAN class="punctuation token">(</SPAN>out_dir<SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'attachments'</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="keyword token">for</SPAN> table <SPAN class="keyword token">in</SPAN> attachment_tables<SPAN class="punctuation token">:</SPAN>
df <SPAN class="operator token">=</SPAN> pd<SPAN class="punctuation token">.</SPAN>read_sql_table<SPAN class="punctuation token">(</SPAN>table<SPAN class="punctuation token">,</SPAN> conn<SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token"># Create a separate dir for saving attachments (if it doesn't already exist). Check if it should be created</SPAN>
<SPAN class="comment token"># here because the attachment table might exist but the table might be empty</SPAN>
<SPAN class="keyword token">if</SPAN> len<SPAN class="punctuation token">(</SPAN>df<SPAN class="punctuation token">)</SPAN> <SPAN class="operator token">and</SPAN> <SPAN class="operator token">not</SPAN> os<SPAN class="punctuation token">.</SPAN>path<SPAN class="punctuation token">.</SPAN>isdir<SPAN class="punctuation token">(</SPAN>attachments_dir<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">:</SPAN>
os<SPAN class="punctuation token">.</SPAN>mkdir<SPAN class="punctuation token">(</SPAN>attachments_dir<SPAN class="punctuation token">)</SPAN>
<SPAN class="keyword token">for</SPAN> _<SPAN class="punctuation token">,</SPAN> row <SPAN class="keyword token">in</SPAN> df<SPAN class="punctuation token">.</SPAN>iterrows<SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">:</SPAN>
<SPAN class="comment token"># Write the attachment with a name that is unique so if another submission comes in with an attachment</SPAN>
<SPAN class="comment token"># that has the same name, the original doesn't get written over</SPAN>
name<SPAN class="punctuation token">,</SPAN> extension <SPAN class="operator token">=</SPAN> os<SPAN class="punctuation token">.</SPAN>path<SPAN class="punctuation token">.</SPAN>splitext<SPAN class="punctuation token">(</SPAN>row<SPAN class="punctuation token">.</SPAN>ATT_NAME<SPAN class="punctuation token">)</SPAN>
attachment_id <SPAN class="operator token">=</SPAN> row<SPAN class="punctuation token">.</SPAN>GLOBALID<SPAN class="punctuation token">.</SPAN>replace<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'{'</SPAN><SPAN class="punctuation token">,</SPAN> <SPAN class="string token">''</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">.</SPAN>replace<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'}'</SPAN><SPAN class="punctuation token">,</SPAN> <SPAN class="string token">''</SPAN><SPAN class="punctuation token">)</SPAN> <SPAN class="comment token">#for some annoying reason, ids have braces</SPAN>
attachment_path <SPAN class="operator token">=</SPAN> os<SPAN class="punctuation token">.</SPAN>path<SPAN class="punctuation token">.</SPAN>join<SPAN class="punctuation token">(</SPAN>attachments_dir<SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'{name}_{id}{ext}'</SPAN>
<SPAN class="punctuation token">.</SPAN>format<SPAN class="punctuation token">(</SPAN>name<SPAN class="operator token">=</SPAN>name<SPAN class="punctuation token">,</SPAN> id<SPAN class="operator token">=</SPAN>attachment_id<SPAN class="punctuation token">,</SPAN> ext<SPAN class="operator token">=</SPAN>extension<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="keyword token">with</SPAN> open<SPAN class="punctuation token">(</SPAN>attachment_path<SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'wb'</SPAN><SPAN class="punctuation token">)</SPAN> <SPAN class="keyword token">as</SPAN> attachment_pointer<SPAN class="punctuation token">:</SPAN>
attachment_pointer<SPAN class="punctuation token">.</SPAN>write<SPAN class="punctuation token">(</SPAN>row<SPAN class="punctuation token">.</SPAN>DATA<SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token"># write a JSON file with some metadata so the attachment can be related back to this DB file</SPAN>
row_dict <SPAN class="operator token">=</SPAN> row<SPAN class="punctuation token">.</SPAN>drop<SPAN class="punctuation token">(</SPAN><SPAN class="string token">'DATA'</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">.</SPAN>to_dict<SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN> <SPAN class="comment token"># drop 'DATA' field because it contains non-serializable bytes</SPAN>
row_dict<SPAN class="punctuation token">[</SPAN><SPAN class="string token">'sqlite_path'</SPAN><SPAN class="punctuation token">]</SPAN> <SPAN class="operator token">=</SPAN> sqlite_path
json_path <SPAN class="operator token">=</SPAN> attachment_path<SPAN class="punctuation token">.</SPAN>rstrip<SPAN class="punctuation token">(</SPAN>extension<SPAN class="punctuation token">)</SPAN> <SPAN class="operator token">+</SPAN> <SPAN class="string token">'.json'</SPAN>
<SPAN class="keyword token">with</SPAN> open<SPAN class="punctuation token">(</SPAN>json_path<SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'w'</SPAN><SPAN class="punctuation token">)</SPAN> <SPAN class="keyword token">as</SPAN> json_pointer<SPAN class="punctuation token">:</SPAN>
json<SPAN class="punctuation token">.</SPAN>dump<SPAN class="punctuation token">(</SPAN>row_dict<SPAN class="punctuation token">,</SPAN> json_pointer<SPAN class="punctuation token">,</SPAN> indent<SPAN class="operator token">=</SPAN><SPAN class="number token">4</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="keyword token">if</SPAN> __name__ <SPAN class="operator token">==</SPAN> <SPAN class="string token">'__main__'</SPAN><SPAN class="punctuation token">:</SPAN>
sys<SPAN class="punctuation token">.</SPAN>exit<SPAN class="punctuation token">(</SPAN>download_data<SPAN class="punctuation token">(</SPAN><SPAN class="operator token">*</SPAN>sys<SPAN class="punctuation token">.</SPAN>argv<SPAN class="punctuation token">[</SPAN><SPAN class="number token">1</SPAN><SPAN class="punctuation token">:</SPAN><SPAN class="punctuation token">]</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="line-numbers-rows"><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN></SPAN>