|
2 | 2 |
|
3 | 3 | """Biomark PIT registering salmon data synchronization.""" |
4 | 4 |
|
5 | | -import os |
6 | 5 | from datetime import datetime, timedelta |
7 | 6 | from pathlib import Path |
8 | 7 |
|
@@ -361,106 +360,12 @@ def run( |
361 | 360 | ) |
362 | 361 | ) |
363 | 362 |
|
364 | | - |
365 | | -def create_stream_config(config: dict, location: str) -> dict: |
366 | | - """Create a stream configuration for a specific data type and location.""" |
367 | | - return { |
368 | | - "primary_key": config["primary_key"], |
369 | | - "update_key": config["update_key"], |
370 | | - "object": ( |
371 | | - f"tables/{config['table_name']}/location={location}/" |
372 | | - f"{{part_year}}/{{part_month}}/{{part_day}}" |
373 | | - ), |
374 | | - "mode": "incremental", |
375 | | - "target_options": {"format": "parquet"}, |
376 | | - "sql": ( |
377 | | - f"select * replace (" # noqa: S608 |
378 | | - f"{config['time_column']} at time zone 'UTC' as " |
379 | | - f"{config['time_column']}) " |
380 | | - f"from {config['table_name']} where " |
381 | | - f"{config['location']} = '{location}' " |
382 | | - f"and {{incremental_where_cond}}" |
383 | | - ), |
384 | | - } |
385 | | - |
386 | | - |
387 | | -def check_table_has_data(db_path: str, table_name: str, dataset_name: str) -> bool: |
388 | | - """ |
389 | | - Check if a table exists and has data in the DuckDB database. |
390 | | -
|
391 | | - Args: |
392 | | - db_path: Path to the DuckDB database file |
393 | | - table_name: Name of the table to check |
394 | | - dataset_name: Name of the schema/dataset |
395 | | -
|
396 | | - Returns: |
397 | | - bool: True if table exists and has at least one row, False otherwise |
398 | | - """ |
399 | | - try: |
400 | | - conn = duckdb.connect(db_path, read_only=True) |
401 | | - try: |
402 | | - log.info(f"Checking table {table_name} existence in schema {dataset_name}") |
403 | | - |
404 | | - check_table_exists = """ |
405 | | - SELECT table_name |
406 | | - FROM information_schema.tables |
407 | | - WHERE table_schema = ? AND table_name = ? |
408 | | - """ |
409 | | - result = conn.execute( |
410 | | - check_table_exists, [dataset_name, table_name] |
411 | | - ).fetchone() |
412 | | - |
413 | | - if result is None: |
414 | | - log.info(f"Table {table_name} does not exist in schema {dataset_name}") |
415 | | - return False |
416 | | - |
417 | | - qualified_table = f"{dataset_name}.{table_name}" |
418 | | - table_relation = conn.table(qualified_table) |
419 | | - count_result = table_relation.count("*").fetchone() |
420 | | - |
421 | | - row_count = count_result[0] if count_result else 0 |
422 | | - |
423 | | - log.info(f"Table {table_name} has {row_count} rows") |
424 | | - return row_count > 0 |
425 | | - |
426 | | - finally: |
427 | | - conn.close() |
428 | | - |
429 | | - except Exception as e: |
430 | | - log.error(f"Error checking table {table_name}: {e}") |
431 | | - return False |
432 | | - |
433 | | - |
434 | | -@app.command() |
435 | | -def replicate( |
436 | | - bucket: str = BIOMARK_BUCKET, |
437 | | - endpoint_url: str = BIOMARK_AWS_ENDPOINT, |
438 | | - access_key: str = BIOMARK_ACCESS_KEY, |
439 | | - secret_key: str = BIOMARK_SECRET_KEY, |
440 | | - duckdb_path: str = BIOMARK_DUCKDB_PATH, |
441 | | - region: str = BIOMARK_REGION, |
442 | | - dataset_name: str = BIOMARK_DATASET_NAME, |
443 | | - tags: bool = typer.Option(False, help="Add tags data to S3"), |
444 | | - readers: bool = typer.Option(False, help="Add readers voltage data to S3"), |
445 | | - environment: bool = typer.Option(False, help="Add environment data to S3"), |
446 | | -): |
447 | | - """Upload data from .duckdb to S3 bucket.""" |
448 | | - os.environ["NINAS3"] = f"{{type: s3, bucket: {bucket}, use_environment: true }}" |
449 | | - os.environ["AWS_ACCESS_KEY_ID"] = access_key |
450 | | - os.environ["AWS_SECRET_ACCESS_KEY"] = secret_key |
451 | | - os.environ["AWS_REGION"] = region |
452 | | - os.environ["AWS_ENDPOINT"] = endpoint_url |
453 | | - os.environ["DUCKDB"] = f"{{type: duckdb, instance: {duckdb_path}}}" |
454 | | - |
455 | | - if not any([readers, tags, environment]): |
| 363 | + if not Path(duckdb_path).exists(): |
456 | 364 | raise typer.BadParameter( |
457 | | - "Error: At least one data type must be selected " |
458 | | - "(--readers, --tags, or --environment)" |
| 365 | + f"Error: DuckDB file not found at {Path(duckdb_path).resolve()}" |
459 | 366 | ) |
460 | 367 |
|
461 | | - if not Path(duckdb_path).exists(): |
462 | | - raise typer.BadParameter(f"Error: DuckDB file not found at {duckdb_path}") |
463 | | - |
| 368 | + ## REPLICATE WITH SLING |
464 | 369 | # only include configs for enabled data types that have data |
465 | 370 | stream_configs = {} |
466 | 371 | if readers and check_table_has_data(duckdb_path, "readers_voltage", dataset_name): |
@@ -525,10 +430,87 @@ def replicate( |
525 | 430 | "target_options": {"format": "parquet"}, |
526 | 431 | }, |
527 | 432 | streams=streams, |
528 | | - env={"SLING_STATE": "NINAS3/data/sling", "SLING_DIRECT_INSERT": "True"}, |
| 433 | + env={ |
| 434 | + "SLING_STATE": "NINAS3/data/sling", |
| 435 | + "SLING_DIRECT_INSERT": "True", |
| 436 | + "NINAS3": f"{{type: s3, bucket: {BIOMARK_BUCKET}, use_environment: true }}", |
| 437 | + "AWS_ACCESS_KEY_ID": BIOMARK_ACCESS_KEY, |
| 438 | + "AWS_SECRET_ACCESS_KEY": BIOMARK_SECRET_KEY, |
| 439 | + "AWS_REGION": BIOMARK_REGION, |
| 440 | + "AWS_ENDPOINT": BIOMARK_AWS_ENDPOINT, |
| 441 | + }, |
529 | 442 | debug=True, |
530 | 443 | ).run() |
531 | 444 |
|
532 | 445 |
|
| 446 | +def create_stream_config(config: dict, location: str) -> dict: |
| 447 | + """Create a stream configuration for a specific data type and location.""" |
| 448 | + return { |
| 449 | + "primary_key": config["primary_key"], |
| 450 | + "update_key": config["update_key"], |
| 451 | + "object": ( |
| 452 | + f"tables/{config['table_name']}/location={location}/" |
| 453 | + f"{{part_year}}/{{part_month}}/{{part_day}}" |
| 454 | + ), |
| 455 | + "mode": "incremental", |
| 456 | + "target_options": {"format": "parquet"}, |
| 457 | + "sql": ( |
| 458 | + f"select * replace (" # noqa: S608 |
| 459 | + f"{config['time_column']} at time zone 'UTC' as " |
| 460 | + f"{config['time_column']}) " |
| 461 | + f"from {config['table_name']} where " |
| 462 | + f"{config['location']} = '{location}' " |
| 463 | + f"and {{incremental_where_cond}}" |
| 464 | + ), |
| 465 | + } |
| 466 | + |
| 467 | + |
| 468 | +def check_table_has_data(db_path: str, table_name: str, dataset_name: str) -> bool: |
| 469 | + """ |
| 470 | + Check if a table exists and has data in the DuckDB database. |
| 471 | +
|
| 472 | + Args: |
| 473 | + db_path: Path to the DuckDB database file |
| 474 | + table_name: Name of the table to check |
| 475 | + dataset_name: Name of the schema/dataset |
| 476 | +
|
| 477 | + Returns: |
| 478 | + bool: True if table exists and has at least one row, False otherwise |
| 479 | + """ |
| 480 | + try: |
| 481 | + conn = duckdb.connect(db_path, read_only=True) |
| 482 | + try: |
| 483 | + log.info(f"Checking table {table_name} existence in schema {dataset_name}") |
| 484 | + |
| 485 | + check_table_exists = """ |
| 486 | + SELECT table_name |
| 487 | + FROM information_schema.tables |
| 488 | + WHERE table_schema = ? AND table_name = ? |
| 489 | + """ |
| 490 | + result = conn.execute( |
| 491 | + check_table_exists, [dataset_name, table_name] |
| 492 | + ).fetchone() |
| 493 | + |
| 494 | + if result is None: |
| 495 | + log.info(f"Table {table_name} does not exist in schema {dataset_name}") |
| 496 | + return False |
| 497 | + |
| 498 | + qualified_table = f"{dataset_name}.{table_name}" |
| 499 | + table_relation = conn.table(qualified_table) |
| 500 | + count_result = table_relation.count("*").fetchone() |
| 501 | + |
| 502 | + row_count = count_result[0] if count_result else 0 |
| 503 | + |
| 504 | + log.info(f"Table {table_name} has {row_count} rows") |
| 505 | + return row_count > 0 |
| 506 | + |
| 507 | + finally: |
| 508 | + conn.close() |
| 509 | + |
| 510 | + except Exception as e: |
| 511 | + log.error(f"Error checking table {table_name}: {e}") |
| 512 | + return False |
| 513 | + |
| 514 | + |
533 | 515 | if __name__ == "__main__": |
534 | 516 | app() |
0 commit comments