11import datetime
22
33import dlt
4- import duckdb
54import typer
6- from dlt .destinations .impl .filesystem .factory import filesystem
7- from dlt .sources .credentials import AwsCredentials
85from dlt .sources .helpers .rest_client import RESTClient
96from dlt .sources .helpers .rest_client .paginators import JSONLinkPaginator
107
11- from ..settings import (
12- env ,
13- log ,
8+ from ..settings import env , log
9+ from .settings import (
10+ NVA_BASE_URL ,
11+ NVA_INSTITUTION_CODE ,
12+ NVA_S3_ACCESS_KEY ,
13+ NVA_S3_BUCKET ,
14+ NVA_S3_ENDPOINT_URL ,
15+ NVA_S3_PREFIX ,
16+ NVA_S3_SECRET_KEY ,
17+ )
18+ from .utils import (
19+ create_pipeline ,
20+ create_s3_credentials ,
21+ setup_duckdb_s3_connection ,
22+ write_timestamp ,
1423)
1524
16- NVA_BASE_URL = env ("NVA_BASE_URL" , default = "https://api.nva.unit.no/" )
1725NVA_DUCKDB_NAME = env ("NVA_DUCKDB_FILE_NAME" , default = "nva_sync" )
18- NVA_INSTITUTION_CODE = env ("NVA_INSTITUTION_CODE" , default = "7511.0.0.0" )
19-
20- NVA_ACCESS_KEY = env ("NVA_ACCESS_KEY" , default = "" )
21- NVA_SECRET_KEY = env ("NVA_SECRET_KEY" , default = "" )
22- NVA_ENDPOINT = env ("NVA_ENDPOINT" , default = "" )
23- NVA_BUCKET = env ("NVA_BUCKET" , default = "" )
24-
25- NVA_PREFIX = env ("NVA_PREFIX" , default = "nva" )
26- NVA_REGION = env ("NVA_REGION" , default = "us-east-1" )
2726
2827app = typer .Typer (help = "Export NVA APIs to Parquet on a S3 Bucket" )
2928
@@ -95,7 +94,7 @@ def nva(
9594 get_resources (client , institution_code ),
9695 name = "resources" ,
9796 write_disposition = "replace" ,
98- primary_key = "identifier " ,
97+ primary_key = "id " ,
9998 max_table_nesting = 1 ,
10099 )
101100
@@ -104,6 +103,7 @@ def nva(
104103 get_projects (client , institution_code ),
105104 name = "projects" ,
106105 write_disposition = "replace" ,
106+ primary_key = "id" ,
107107 max_table_nesting = 1 ,
108108 )
109109
@@ -112,20 +112,23 @@ def nva(
112112 get_persons (client , institution_code ),
113113 name = "persons" ,
114114 write_disposition = "replace" ,
115+ primary_key = "id" ,
115116 max_table_nesting = 1 ,
116117 )
117118 if categories :
118119 yield dlt .resource (
119120 get_categories (client ),
120121 name = "categories" ,
121122 write_disposition = "replace" ,
123+ primary_key = "_dlt_id" ,
122124 max_table_nesting = 1 ,
123125 )
124126
125127 if funding_sources :
126128 yield dlt .resource (
127129 get_funding_sources (client ),
128130 name = "funding_sources" ,
131+ primary_key = "identifier" ,
129132 write_disposition = "replace" ,
130133 max_table_nesting = 1 ,
131134 )
@@ -143,12 +146,12 @@ def run(
143146 base_url : str = NVA_BASE_URL ,
144147 duckdb_name : str = NVA_DUCKDB_NAME ,
145148 institution_code : str = NVA_INSTITUTION_CODE ,
146- endpoint_url : str = NVA_ENDPOINT ,
147- access_key : str = NVA_ACCESS_KEY ,
148- secret_key : str = NVA_SECRET_KEY ,
149- bucket : str = NVA_BUCKET ,
150- prefix : str = NVA_PREFIX ,
151- region : str = NVA_REGION ,
149+ endpoint_url : str = NVA_S3_ENDPOINT_URL ,
150+ access_key : str = NVA_S3_ACCESS_KEY ,
151+ secret_key : str = NVA_S3_SECRET_KEY ,
152+ bucket : str = NVA_S3_BUCKET ,
153+ prefix : str = NVA_S3_PREFIX ,
154+ region : str = "us-east-1" ,
152155):
153156 if not endpoint_url :
154157 log .error ("AWS S3 endpoint URL is not provided" )
@@ -162,24 +165,20 @@ def run(
162165
163166 log .info ("Starting NVA data sync" )
164167 log .info (f"Data will be available at: { endpoint_url } /{ bucket } /{ prefix } " )
165- credentials = AwsCredentials (
166- s3_url_style = "path" ,
168+
169+ credentials = create_s3_credentials (
167170 endpoint_url = endpoint_url ,
168- aws_secret_access_key = secret_key ,
169- aws_access_key_id = access_key ,
170- region_name = region ,
171+ access_key = access_key ,
172+ secret_key = secret_key ,
173+ region = region ,
171174 )
172175
173- pipeline = dlt . pipeline (
176+ pipeline = create_pipeline (
174177 pipeline_name = duckdb_name ,
175- destination = filesystem (
176- region_name = region ,
177- bucket_url = f"s3://{ bucket } /" + prefix ,
178- credentials = credentials ,
179- layout = "{table_name}.{ext}" ,
180- ),
181- dataset_name = "main" ,
182- progress = "log" ,
178+ bucket = bucket ,
179+ prefix = prefix ,
180+ credentials = credentials ,
181+ region = region ,
183182 )
184183
185184 log .info (
@@ -201,28 +200,15 @@ def run(
201200 log .info ("NVA data sync completed" )
202201 log .info (f"Data available at: { endpoint_url } /{ bucket } /{ prefix } " )
203202
204- con = duckdb .connect ()
205- con .execute ("INSTALL httpfs;" )
206- con .execute ("LOAD httpfs;" )
207- con .execute (f"""
208- CREATE OR REPLACE SECRET (
209- TYPE S3,
210- KEY_ID '{ access_key } ',
211- SECRET '{ secret_key } ',
212- ENDPOINT '{ endpoint_url .replace ("https://" , "" ).replace ("http://" , "" )} ',
213- REGION '{ region } ',
214- URL_STYLE 'path'
215- );
216- """ )
217-
218- timestamp = datetime .datetime .now ().isoformat ()
219- con .execute (f"""
220- COPY (SELECT '{ timestamp } ' as last_successful_run)
221- TO 's3://{ bucket } /{ prefix } /last_successful_run.parquet'
222- (FORMAT PARQUET, COMPRESSION ZSTD)
223- """ )
203+ con = setup_duckdb_s3_connection (
204+ endpoint_url = endpoint_url ,
205+ access_key = access_key ,
206+ secret_key = secret_key ,
207+ region = region ,
208+ )
209+
210+ write_timestamp (con , bucket , prefix )
224211 con .close ()
225- log .info (f"Last successful run timestamp written: { timestamp } " )
226212
227213
228214if __name__ == "__main__" :
0 commit comments