Skip to content

Commit 17b980a

Browse files
committed
fix write merge issue
1 parent d2a3308 commit 17b980a

4 files changed

Lines changed: 128 additions & 97 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ dependencies = [
1616
"structlog>=25.5.0",
1717
"rich>=14.2.0",
1818
"typer>=0.20.0",
19-
"requests>=2.32.5"
19+
"requests>=2.32.5",
20+
"sling>=1.5.1"
2021
]
2122
description = ""
2223
license = "GPL-3.0+"

src/datasync/pit_registering_salmon.py

Lines changed: 114 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
"""Biomark PIT registering salmon data synchronization."""
44

5+
import os
56
from datetime import datetime, timedelta
67

78
import dlt
89
import requests
910
import typer
10-
from dlt.destinations.impl.filesystem.factory import filesystem
11-
from dlt.sources.credentials import AwsCredentials
1211
from dlt.sources.helpers.rest_client import RESTClient
1312
from dlt.sources.helpers.rest_client.auth import BearerTokenAuth
13+
from dlt.sources.helpers.rest_client.paginators import SinglePagePaginator
14+
from sling import Replication
1415

1516
from .settings import (
1617
BIOMARK_ACCESS_KEY,
@@ -19,7 +20,6 @@
1920
BIOMARK_AWS_ENDPOINT,
2021
BIOMARK_BASE_URL,
2122
BIOMARK_BUCKET,
22-
BIOMARK_PREFIX,
2323
BIOMARK_REGION,
2424
BIOMARK_SECRET_KEY,
2525
log,
@@ -84,48 +84,59 @@ def get_bearer_token():
8484

8585

8686
def get_environmental_data(
87-
client: RESTClient, location_code: str, begin_date: str, end_date: str
87+
client: RESTClient, locations: list[str], begin_date: str, end_date: str
8888
):
8989
"""Fetch environmental data from Biomark API."""
90-
log.debug("Fetching environmental data for location", location_code=location_code)
91-
yield from client.paginate(
92-
f"enviro/{location_code}",
93-
method="get",
94-
params={
95-
"begin_dt": begin_date,
96-
"end_dt": end_date,
97-
},
98-
)
90+
for location_name in locations:
91+
location_code = SITES.get(location_name)
92+
log.debug(
93+
"Fetching environmental data for location", location_code=location_code
94+
)
95+
yield from client.paginate(
96+
f"enviro/{location_code}",
97+
method="get",
98+
params={
99+
"begin_dt": begin_date,
100+
"end_dt": end_date,
101+
},
102+
)
99103

100104

101105
def get_tags_data(
102-
client: RESTClient, location_code: str, begin_date: str, end_date: str
106+
client: RESTClient, locations: list[str], begin_date: str, end_date: str
103107
):
104108
"""Fetch tags data from Biomark API."""
105-
log.debug("Fetching tags data for location", location_code=location_code)
106-
yield from client.paginate(
107-
f"tags/{location_code}",
108-
method="get",
109-
params={
110-
"begin_dt": begin_date,
111-
"end_dt": end_date,
112-
},
113-
)
109+
for location_name in locations:
110+
location_code = SITES.get(location_name)
111+
log.debug("Fetching tags data for location", location_code=location_code)
112+
yield from client.paginate(
113+
f"tags/{location_code}",
114+
method="get",
115+
params={
116+
"begin_dt": begin_date,
117+
"end_dt": end_date,
118+
},
119+
)
114120

115121

116122
def get_readers_voltage_data(
117-
client: RESTClient, location_code: str, begin_date: str, end_date: str
123+
client: RESTClient, locations: list[str], begin_date: str, end_date: str
118124
):
119125
"""Fetch readers voltage data from Biomark API."""
120-
log.debug("Fetching readers voltage data for location", location_code=location_code)
121-
yield from client.paginate(
122-
f"reader/{location_code}",
123-
method="get",
124-
params={
125-
"begin_dt": begin_date,
126-
"end_dt": end_date,
127-
},
128-
)
126+
127+
for location_name in locations:
128+
location_code = SITES.get(location_name)
129+
log.debug(
130+
"Fetching readers voltage data for location", location_code=location_code
131+
)
132+
yield from client.paginate(
133+
f"reader/{location_code}",
134+
method="get",
135+
params={
136+
"begin_dt": begin_date,
137+
"end_dt": end_date,
138+
},
139+
)
129140

130141

131142
@dlt.transformer(primary_key=["tag", "detected_at"])
@@ -156,6 +167,7 @@ def biomark_pit_salmon(
156167
client = RESTClient(
157168
base_url=base_url,
158169
auth=BearerTokenAuth(token),
170+
paginator=SinglePagePaginator(),
159171
)
160172

161173
# set default dates if not provided
@@ -164,46 +176,36 @@ def biomark_pit_salmon(
164176
begin_date = (datetime.today() - timedelta(days=1)).strftime("%Y-%m-%d")
165177
end_date = datetime.today().strftime("%Y-%m-%d")
166178

167-
for location_name in locations:
168-
location_code = SITES.get(location_name)
169-
if not location_code:
170-
log.warning(f"Unknown location: {location_name}")
171-
continue
172-
173-
try:
174-
if tags:
175-
tags_resource = dlt.resource(
176-
get_tags_data(client, location_code, begin_date, end_date),
177-
name=f"tags_{location_name}",
178-
primary_key=["tag", "detected_at"],
179-
write_disposition="append",
180-
)
181-
# Apply decimal tag transformation
182-
yield tags_resource | add_decimal_tags.with_name(
183-
f"tags_{location_name}"
184-
)
185-
186-
if readers:
187-
yield dlt.resource(
188-
get_readers_voltage_data(
189-
client, location_code, begin_date, end_date
190-
),
191-
name=f"readers_voltage_{location_name}",
192-
primary_key=["read_at"],
193-
write_disposition="append",
194-
)
195-
196-
if environment:
197-
yield dlt.resource(
198-
get_environmental_data(client, location_code, begin_date, end_date),
199-
name=f"environment_data_{location_name}",
200-
primary_key=["read_at"],
201-
write_disposition="append",
202-
)
203-
204-
except Exception as e:
205-
log.error(f"Error processing {location_name}: {e}")
206-
# continue with next location
179+
try:
180+
if tags:
181+
tags_resource = dlt.resource(
182+
get_tags_data(client, locations, begin_date, end_date),
183+
name="tags",
184+
primary_key=["tag", "detected_at"],
185+
)
186+
yield tags_resource | add_decimal_tags.with_name("tags")
187+
188+
if readers:
189+
readers_resource = dlt.resource(
190+
get_readers_voltage_data(client, locations, begin_date, end_date),
191+
name="readers_voltage",
192+
primary_key=["reader__site__slug", "read_at"],
193+
)
194+
yield readers_resource
195+
196+
if environment:
197+
env_resource = dlt.resource(
198+
get_environmental_data(client, locations, begin_date, end_date),
199+
name="environment_data",
200+
primary_key=["read_at"],
201+
write_disposition="append",
202+
)
203+
env_resource.apply_hints(incremental=dlt.sources.incremental("read_at"))
204+
yield env_resource
205+
206+
except Exception as e:
207+
log.error(f"Error processing locations: {e}")
208+
# continue with next location
207209

208210

209211
@app.command()
@@ -224,12 +226,6 @@ def run(
224226
False, help="Download data from all accessible locations"
225227
),
226228
base_url: str = BIOMARK_BASE_URL,
227-
bucket: str = BIOMARK_BUCKET,
228-
prefix: str = BIOMARK_PREFIX,
229-
endpoint_url: str = BIOMARK_AWS_ENDPOINT,
230-
access_key: str = BIOMARK_ACCESS_KEY,
231-
secret_key: str = BIOMARK_SECRET_KEY,
232-
region: str = BIOMARK_REGION,
233229
):
234230
"""Biomark PIT registering salmon data synchronization."""
235231

@@ -255,22 +251,10 @@ def run(
255251
locations = [place]
256252
log.info("Processing single location", location=place)
257253

258-
credentials = AwsCredentials(
259-
s3_url_style="path",
260-
endpoint_url=endpoint_url,
261-
aws_secret_access_key=secret_key,
262-
aws_access_key_id=access_key,
263-
region_name=region,
264-
)
265-
266254
# Set up DLT pipeline
267255
pipeline = dlt.pipeline(
268-
pipeline_name="biomark_pit_registering_salmon",
269-
destination=filesystem(
270-
bucket_url=f"s3://{bucket}/" + prefix,
271-
credentials=credentials,
272-
layout="{table_name}.{ext}",
273-
),
256+
pipeline_name="biomark_pit",
257+
destination="duckdb",
274258
dataset_name="main",
275259
progress="log",
276260
)
@@ -287,11 +271,46 @@ def run(
287271
readers=readers,
288272
environment=environment,
289273
),
290-
write_disposition="append",
291-
loader_file_format="parquet",
292274
)
293275
)
294276

295277

278+
@app.command()
279+
def replicate(
280+
bucket: str = BIOMARK_BUCKET,
281+
endpoint_url: str = BIOMARK_AWS_ENDPOINT,
282+
access_key: str = BIOMARK_ACCESS_KEY,
283+
secret_key: str = BIOMARK_SECRET_KEY,
284+
region: str = BIOMARK_REGION,
285+
):
286+
os.environ["NINAS3"] = f"{{type: s3, bucket: {bucket}, use_environment: true }}"
287+
os.environ["AWS_ACCESS_KEY_ID"] = access_key
288+
os.environ["AWS_SECRET_ACCESS_KEY"] = secret_key
289+
os.environ["AWS_REGION"] = region
290+
os.environ["AWS_ENDPOINT"] = endpoint_url
291+
os.environ["DUCKDB"] = "{type: duckdb, instance: biomark_pit.duckdb}"
292+
293+
Replication(
294+
source="DUCKDB",
295+
target="NINAS3",
296+
defaults={
297+
"object": "tables/{stream_name}/{part_year}/{part_month}/{part_day}",
298+
"mode": "incremental",
299+
"target_options": {"format": "parquet"},
300+
},
301+
streams={
302+
"readers_voltage": {
303+
"primary_key": ["location_name", "read_at"],
304+
"update_key": "read_at",
305+
"object": "tables/{stream_name}/{part_year}/{part_month}/{part_day}",
306+
"mode": "incremental",
307+
"target_options": {"format": "parquet"},
308+
}
309+
},
310+
env={"SLING_STATE": "NINAS3/data/sling"},
311+
debug=True,
312+
).run()
313+
314+
296315
if __name__ == "__main__":
297316
app()

src/datasync/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
BIOMARK_API_PWD = env("BIOMARK_API_PWD", default="")
2020
BIOMARK_AWS_ENDPOINT = env("BIOMARK_AWS_ENDPOINT", default="")
2121
BIOMARK_BUCKET = env("BIOMARK_BUCKET", default="pit-registering-salmon")
22-
BIOMARK_PREFIX = env("BIOMARK_PREFIX", default="data")
22+
BIOMARK_PREFIX = env("BIOMARK_PREFIX", default="ducklake")
2323
BIOMARK_REGION = env("BIOMARK_REGION", default="us-east-1")
2424
BIOMARK_ACCESS_KEY = env("BIOMARK_ACCESS_KEY", default="")
2525
BIOMARK_SECRET_KEY = env("BIOMARK_SECRET_KEY", default="")

uv.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)