Skip to content

Comments

fix: graceful shutdown and lazy-load heavy SDK dependencies#1064

Open
fyzanshaik-atlan wants to merge 4 commits intomainfrom
fix/local-sigint-graceful-shutdown
Open

fix: graceful shutdown and lazy-load heavy SDK dependencies#1064
fyzanshaik-atlan wants to merge 4 commits intomainfrom
fix/local-sigint-graceful-shutdown

Conversation

@fyzanshaik-atlan
Copy link
Contributor

Summary

  • fix APIServer.start() shutdown path so Ctrl+C / task cancellation triggers explicit uvicorn shutdown (server.should_exit + await server.shutdown())
  • update BaseApplication.start() local-mode lifecycle to always stop the worker when server exits/fails
  • add Worker.stop() support for both same-loop and cross-thread (daemon) worker shutdown
  • lazy-load heavy dependencies to reduce eager memory/runtime overhead: pandas, duckdb, OpenTelemetry modules, boto3, and daft
  • replace eager module-level observability singleton initialization with lazy proxies (get_metrics() / get_traces() only when used)

Why

  • Ctrl+C previously left local runs hanging because embedded uvicorn/worker shutdown was not coordinated.
  • several heavyweight imports were loaded at startup even on code paths that never used them, increasing baseline memory and startup cost.

Validation

  • uv run pytest tests/unit/observability tests/unit/application/test_application.py tests/unit/application/metadata_extraction/test_sql.py tests/unit/common/test_aws_utils.py tests/unit/common/test_file_converter.py tests/unit/transformers -q
  • uv run pre-commit run --files application_sdk/application/__init__.py application_sdk/application/metadata_extraction/sql.py application_sdk/common/aws_utils.py application_sdk/common/file_converter.py application_sdk/observability/logger_adaptor.py application_sdk/observability/metrics_adaptor.py application_sdk/observability/observability.py application_sdk/observability/traces_adaptor.py application_sdk/server/fastapi/middleware/metrics.py application_sdk/transformers/atlas/__init__.py application_sdk/transformers/query/__init__.py

@snykgituser
Copy link

snykgituser commented Feb 18, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link

github-actions bot commented Feb 18, 2026

📜 Docstring Coverage Report

RESULT: PASSED (minimum: 30.0%, actual: 77.1%)

Detailed Coverage Report
======= Coverage for /home/runner/work/application-sdk/application-sdk/ ========
----------------------------------- Summary ------------------------------------
| Name                                                                              | Total | Miss | Cover | Cover% |
|-----------------------------------------------------------------------------------|-------|------|-------|--------|
| application_sdk/__init__.py                                                       |     1 |    0 |     1 |   100% |
| application_sdk/constants.py                                                      |     2 |    0 |     2 |   100% |
| application_sdk/version.py                                                        |     1 |    0 |     1 |   100% |
| application_sdk/worker.py                                                         |     8 |    0 |     8 |   100% |
| application_sdk/activities/__init__.py                                            |    10 |    0 |    10 |   100% |
| application_sdk/activities/lock_management.py                                     |     3 |    0 |     3 |   100% |
| application_sdk/activities/common/__init__.py                                     |     1 |    1 |     0 |     0% |
| application_sdk/activities/common/models.py                                       |     3 |    1 |     2 |    67% |
| application_sdk/activities/common/sql_utils.py                                    |     6 |    1 |     5 |    83% |
| application_sdk/activities/common/utils.py                                        |     8 |    1 |     7 |    88% |
| application_sdk/activities/metadata_extraction/__init__.py                        |     1 |    1 |     0 |     0% |
| application_sdk/activities/metadata_extraction/base.py                            |     6 |    1 |     5 |    83% |
| application_sdk/activities/metadata_extraction/rest.py                            |     1 |    1 |     0 |     0% |
| application_sdk/activities/metadata_extraction/sql.py                             |    20 |    3 |    17 |    85% |
| application_sdk/activities/query_extraction/__init__.py                           |     1 |    1 |     0 |     0% |
| application_sdk/activities/query_extraction/sql.py                                |    13 |    1 |    12 |    92% |
| application_sdk/application/__init__.py                                           |    15 |    6 |     9 |    60% |
| application_sdk/application/metadata_extraction/sql.py                            |    10 |    3 |     7 |    70% |
| application_sdk/clients/__init__.py                                               |     4 |    0 |     4 |   100% |
| application_sdk/clients/atlan.py                                                  |     5 |    3 |     2 |    40% |
| application_sdk/clients/atlan_auth.py                                             |    10 |    0 |    10 |   100% |
| application_sdk/clients/base.py                                                   |     6 |    1 |     5 |    83% |
| application_sdk/clients/models.py                                                 |     3 |    0 |     3 |   100% |
| application_sdk/clients/redis.py                                                  |    27 |    0 |    27 |   100% |
| application_sdk/clients/sql.py                                                    |    23 |    0 |    23 |   100% |
| application_sdk/clients/temporal.py                                               |    14 |    1 |    13 |    93% |
| application_sdk/clients/utils.py                                                  |     2 |    1 |     1 |    50% |
| application_sdk/clients/workflow.py                                               |     9 |    2 |     7 |    78% |
| application_sdk/clients/azure/__init__.py                                         |     1 |    0 |     1 |   100% |
| application_sdk/clients/azure/auth.py                                             |     7 |    0 |     7 |   100% |
| application_sdk/clients/azure/client.py                                           |    13 |    0 |    13 |   100% |
| application_sdk/common/__init__.py                                                |     1 |    1 |     0 |     0% |
| application_sdk/common/aws_utils.py                                               |    11 |    2 |     9 |    82% |
| application_sdk/common/error_codes.py                                             |    14 |    2 |    12 |    86% |
| application_sdk/common/file_converter.py                                          |    11 |    6 |     5 |    45% |
| application_sdk/common/file_ops.py                                                |    16 |    1 |    15 |    94% |
| application_sdk/common/path.py                                                    |     2 |    1 |     1 |    50% |
| application_sdk/common/types.py                                                   |     2 |    1 |     1 |    50% |
| application_sdk/common/utils.py                                                   |    17 |    2 |    15 |    88% |
| application_sdk/decorators/__init__.py                                            |     1 |    1 |     0 |     0% |
| application_sdk/decorators/locks.py                                               |     3 |    2 |     1 |    33% |
| application_sdk/decorators/mcp_tool.py                                            |     3 |    1 |     2 |    67% |
| application_sdk/docgen/__init__.py                                                |     5 |    2 |     3 |    60% |
| application_sdk/docgen/exporters/__init__.py                                      |     1 |    1 |     0 |     0% |
| application_sdk/docgen/exporters/mkdocs.py                                        |     7 |    3 |     4 |    57% |
| application_sdk/docgen/models/__init__.py                                         |     1 |    1 |     0 |     0% |
| application_sdk/docgen/models/export/__init__.py                                  |     1 |    1 |     0 |     0% |
| application_sdk/docgen/models/export/page.py                                      |     2 |    1 |     1 |    50% |
| application_sdk/docgen/models/manifest/__init__.py                                |     2 |    1 |     1 |    50% |
| application_sdk/docgen/models/manifest/customer.py                                |     3 |    1 |     2 |    67% |
| application_sdk/docgen/models/manifest/internal.py                                |     2 |    1 |     1 |    50% |
| application_sdk/docgen/models/manifest/metadata.py                                |     2 |    1 |     1 |    50% |
| application_sdk/docgen/models/manifest/page.py                                    |     2 |    1 |     1 |    50% |
| application_sdk/docgen/models/manifest/section.py                                 |     2 |    1 |     1 |    50% |
| application_sdk/docgen/parsers/__init__.py                                        |     1 |    1 |     0 |     0% |
| application_sdk/docgen/parsers/directory.py                                       |    13 |    2 |    11 |    85% |
| application_sdk/docgen/parsers/manifest.py                                        |     6 |    1 |     5 |    83% |
| application_sdk/handlers/__init__.py                                              |     7 |    1 |     6 |    86% |
| application_sdk/handlers/base.py                                                  |     7 |    1 |     6 |    86% |
| application_sdk/handlers/sql.py                                                   |    19 |    6 |    13 |    68% |
| application_sdk/interceptors/__init__.py                                          |     1 |    1 |     0 |     0% |
| application_sdk/interceptors/cleanup.py                                           |     7 |    1 |     6 |    86% |
| application_sdk/interceptors/correlation_context.py                               |    13 |    0 |    13 |   100% |
| application_sdk/interceptors/events.py                                            |     9 |    1 |     8 |    89% |
| application_sdk/interceptors/lock.py                                              |    10 |    2 |     8 |    80% |
| application_sdk/interceptors/models.py                                            |    13 |    1 |    12 |    92% |
| application_sdk/io/__init__.py                                                    |    25 |    0 |    25 |   100% |
| application_sdk/io/json.py                                                        |    15 |    1 |    14 |    93% |
| application_sdk/io/parquet.py                                                     |    22 |    1 |    21 |    95% |
| application_sdk/io/utils.py                                                       |     8 |    1 |     7 |    88% |
| application_sdk/observability/__init__.py                                         |     1 |    1 |     0 |     0% |
| application_sdk/observability/context.py                                          |     1 |    0 |     1 |   100% |
| application_sdk/observability/lazy_proxies.py                                     |     5 |    3 |     2 |    40% |
| application_sdk/observability/logger_adaptor.py                                   |    32 |    3 |    29 |    91% |
| application_sdk/observability/metrics_adaptor.py                                  |    12 |    1 |    11 |    92% |
| application_sdk/observability/models.py                                           |     5 |    1 |     4 |    80% |
| application_sdk/observability/observability.py                                    |    24 |    1 |    23 |    96% |
| application_sdk/observability/segment_client.py                                   |    14 |    2 |    12 |    86% |
| application_sdk/observability/traces_adaptor.py                                   |    16 |    1 |    15 |    94% |
| application_sdk/observability/utils.py                                            |     4 |    1 |     3 |    75% |
| application_sdk/observability/decorators/observability_decorator.py               |     7 |    4 |     3 |    43% |
| application_sdk/server/__init__.py                                                |     4 |    0 |     4 |   100% |
| application_sdk/server/fastapi/__init__.py                                        |    25 |    5 |    20 |    80% |
| application_sdk/server/fastapi/models.py                                          |    32 |   28 |     4 |    12% |
| application_sdk/server/fastapi/utils.py                                           |     5 |    0 |     5 |   100% |
| application_sdk/server/fastapi/middleware/logmiddleware.py                        |     4 |    4 |     0 |     0% |
| application_sdk/server/fastapi/middleware/metrics.py                              |     3 |    3 |     0 |     0% |
| application_sdk/server/fastapi/routers/__init__.py                                |     1 |    1 |     0 |     0% |
| application_sdk/server/fastapi/routers/server.py                                  |     8 |    2 |     6 |    75% |
| application_sdk/server/mcp/__init__.py                                            |     1 |    1 |     0 |     0% |
| application_sdk/server/mcp/models.py                                              |     2 |    2 |     0 |     0% |
| application_sdk/server/mcp/server.py                                              |     5 |    0 |     5 |   100% |
| application_sdk/services/__init__.py                                              |     1 |    0 |     1 |   100% |
| application_sdk/services/_utils.py                                                |     2 |    1 |     1 |    50% |
| application_sdk/services/atlan_storage.py                                         |     5 |    0 |     5 |   100% |
| application_sdk/services/eventstore.py                                            |     5 |    0 |     5 |   100% |
| application_sdk/services/objectstore.py                                           |    17 |    0 |    17 |   100% |
| application_sdk/services/secretstore.py                                           |    14 |    0 |    14 |   100% |
| application_sdk/services/statestore.py                                            |     9 |    1 |     8 |    89% |
| application_sdk/test_utils/__init__.py                                            |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/workflow_monitoring.py                                 |     3 |    0 |     3 |   100% |
| application_sdk/test_utils/e2e/__init__.py                                        |    14 |    2 |    12 |    86% |
| application_sdk/test_utils/e2e/base.py                                            |    16 |    2 |    14 |    88% |
| application_sdk/test_utils/e2e/client.py                                          |    10 |    2 |     8 |    80% |
| application_sdk/test_utils/e2e/conftest.py                                        |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/e2e/utils.py                                           |     3 |    1 |     2 |    67% |
| application_sdk/test_utils/hypothesis/__init__.py                                 |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/__init__.py                      |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/sql_client.py                    |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/temporal.py                      |     6 |    1 |     5 |    83% |
| application_sdk/test_utils/hypothesis/strategies/clients/__init__.py              |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/clients/sql.py                   |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/common/__init__.py               |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/common/logger.py                 |     3 |    0 |     3 |   100% |
| application_sdk/test_utils/hypothesis/strategies/handlers/__init__.py             |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/handlers/sql/__init__.py         |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/handlers/sql/sql_metadata.py     |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/handlers/sql/sql_preflight.py    |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/inputs/__init__.py               |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/inputs/json_input.py             |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/inputs/parquet_input.py          |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/outputs/__init__.py              |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/outputs/json_output.py           |     2 |    1 |     1 |    50% |
| application_sdk/test_utils/hypothesis/strategies/outputs/statestore.py            |     3 |    1 |     2 |    67% |
| application_sdk/test_utils/hypothesis/strategies/server/__init__.py               |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/hypothesis/strategies/server/fastapi/__init__.py       |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/scale_data_generator/__init__.py                       |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/scale_data_generator/config_loader.py                  |    10 |    4 |     6 |    60% |
| application_sdk/test_utils/scale_data_generator/data_generator.py                 |    10 |    3 |     7 |    70% |
| application_sdk/test_utils/scale_data_generator/driver.py                         |     3 |    3 |     0 |     0% |
| application_sdk/test_utils/scale_data_generator/output_handler/__init__.py        |     1 |    1 |     0 |     0% |
| application_sdk/test_utils/scale_data_generator/output_handler/base.py            |     7 |    3 |     4 |    57% |
| application_sdk/test_utils/scale_data_generator/output_handler/csv_handler.py     |     5 |    5 |     0 |     0% |
| application_sdk/test_utils/scale_data_generator/output_handler/json_handler.py    |     5 |    5 |     0 |     0% |
| application_sdk/test_utils/scale_data_generator/output_handler/parquet_handler.py |     6 |    6 |     0 |     0% |
| application_sdk/transformers/__init__.py                                          |     3 |    1 |     2 |    67% |
| application_sdk/transformers/atlas/__init__.py                                    |     7 |    2 |     5 |    71% |
| application_sdk/transformers/atlas/sql.py                                         |    25 |    4 |    21 |    84% |
| application_sdk/transformers/common/__init__.py                                   |     1 |    1 |     0 |     0% |
| application_sdk/transformers/common/utils.py                                      |     6 |    0 |     6 |   100% |
| application_sdk/transformers/query/__init__.py                                    |    14 |    5 |     9 |    64% |
| application_sdk/workflows/__init__.py                                             |     4 |    0 |     4 |   100% |
| application_sdk/workflows/metadata_extraction/__init__.py                         |     3 |    1 |     2 |    67% |
| application_sdk/workflows/metadata_extraction/sql.py                              |     7 |    0 |     7 |   100% |
| application_sdk/workflows/query_extraction/__init__.py                            |     2 |    2 |     0 |     0% |
| application_sdk/workflows/query_extraction/sql.py                                 |     4 |    0 |     4 |   100% |
| examples/application_custom_fastapi.py                                            |    14 |   14 |     0 |     0% |
| examples/application_fastapi.py                                                   |     9 |    9 |     0 |     0% |
| examples/application_hello_world.py                                               |     7 |    7 |     0 |     0% |
| examples/application_sql.py                                                       |     5 |    4 |     1 |    20% |
| examples/application_sql_miner.py                                                 |     5 |    4 |     1 |    20% |
| examples/application_sql_with_custom_pyatlan_transformer.py                       |    11 |    9 |     2 |    18% |
| examples/application_sql_with_custom_transformer.py                               |     9 |    8 |     1 |    11% |
| examples/run_examples.py                                                          |     2 |    1 |     1 |    50% |
| tests/__init__.py                                                                 |     1 |    1 |     0 |     0% |
| tests/conftest.py                                                                 |     4 |    0 |     4 |   100% |
| tests/unit/__init__.py                                                            |     1 |    1 |     0 |     0% |
| tests/unit/test_worker.py                                                         |    24 |    7 |    17 |    71% |
| tests/unit/activities/__init__.py                                                 |     1 |    1 |     0 |     0% |
| tests/unit/activities/test_activities.py                                          |    41 |    3 |    38 |    93% |
| tests/unit/activities/test_base_metadata_extraction_activities.py                 |     7 |    0 |     7 |   100% |
| tests/unit/activities/test_lock_management.py                                     |    12 |    0 |    12 |   100% |
| tests/unit/activities/common/__init__.py                                          |     1 |    1 |     0 |     0% |
| tests/unit/activities/common/test_sql_utils.py                                    |     4 |    1 |     3 |    75% |
| tests/unit/activities/common/test_utils.py                                        |    28 |   10 |    18 |    64% |
| tests/unit/activities/metadata_extraction/__init__.py                             |     1 |    1 |     0 |     0% |
| tests/unit/activities/metadata_extraction/test_sql.py                             |    56 |   38 |    18 |    32% |
| tests/unit/activities/query_extraction/__init__.py                                |     1 |    1 |     0 |     0% |
| tests/unit/application/__init__.py                                                |     1 |    1 |     0 |     0% |
| tests/unit/application/test_application.py                                        |    45 |    3 |    42 |    93% |
| tests/unit/application/metadata_extraction/test_sql.py                            |    36 |    6 |    30 |    83% |
| tests/unit/clients/__init__.py                                                    |     1 |    1 |     0 |     0% |
| tests/unit/clients/test_async_sql_client.py                                       |    15 |   14 |     1 |     7% |
| tests/unit/clients/test_atlan_auth.py                                             |    11 |    0 |    11 |   100% |
| tests/unit/clients/test_atlan_client.py                                           |     7 |    7 |     0 |     0% |
| tests/unit/clients/test_atlanauth.py                                              |    11 |    1 |    10 |    91% |
| tests/unit/clients/test_azure_auth.py                                             |    14 |    0 |    14 |   100% |
| tests/unit/clients/test_azure_client.py                                           |    19 |    0 |    19 |   100% |
| tests/unit/clients/test_base_client.py                                            |    23 |    1 |    22 |    96% |
| tests/unit/clients/test_redis_client.py                                           |    40 |    0 |    40 |   100% |
| tests/unit/clients/test_sql_client.py                                             |    28 |    6 |    22 |    79% |
| tests/unit/clients/test_temporal_client.py                                        |    20 |    4 |    16 |    80% |
| tests/unit/common/test_aws_utils.py                                               |    30 |    1 |    29 |    97% |
| tests/unit/common/test_credential_utils.py                                        |    30 |    1 |    29 |    97% |
| tests/unit/common/test_file_converter.py                                          |    29 |    0 |    29 |   100% |
| tests/unit/common/test_file_ops.py                                                |    21 |    0 |    21 |   100% |
| tests/unit/common/test_path.py                                                    |     6 |    0 |     6 |   100% |
| tests/unit/common/test_utils.py                                                   |    74 |    6 |    68 |    92% |
| tests/unit/common/test_utils_file_discovery.py                                    |    11 |    0 |    11 |   100% |
| tests/unit/decorators/__init__.py                                                 |     1 |    1 |     0 |     0% |
| tests/unit/decorators/test_mcp_tool.py                                            |    56 |    4 |    52 |    93% |
| tests/unit/docgen/parsers/test_directory_parser.py                                |    14 |    3 |    11 |    79% |
| tests/unit/docgen/parsers/test_manifest_parser.py                                 |    12 |   12 |     0 |     0% |
| tests/unit/handlers/__init__.py                                                   |     1 |    1 |     0 |     0% |
| tests/unit/handlers/test_base_handler.py                                          |    26 |    2 |    24 |    92% |
| tests/unit/handlers/sql/test_auth.py                                              |    10 |    4 |     6 |    60% |
| tests/unit/handlers/sql/test_check_schemas_and_databases.py                       |    14 |    4 |    10 |    71% |
| tests/unit/handlers/sql/test_extract_allowed_schemas.py                           |    11 |    3 |     8 |    73% |
| tests/unit/handlers/sql/test_metadata.py                                          |    27 |   10 |    17 |    63% |
| tests/unit/handlers/sql/test_preflight_check.py                                   |    16 |   15 |     1 |     6% |
| tests/unit/handlers/sql/test_prepare_metadata.py                                  |    14 |    4 |    10 |    71% |
| tests/unit/handlers/sql/test_tables_check.py                                      |     9 |    6 |     3 |    33% |
| tests/unit/handlers/sql/test_validate_filters.py                                  |    12 |    4 |     8 |    67% |
| tests/unit/interceptors/__init__.py                                               |     1 |    1 |     0 |     0% |
| tests/unit/interceptors/test_correlation_context.py                               |    37 |    0 |    37 |   100% |
| tests/unit/io/test_base_io.py                                                     |    29 |    4 |    25 |    86% |
| tests/unit/io/test_writer_data_integrity.py                                       |    12 |    5 |     7 |    58% |
| tests/unit/io/readers/test_json_reader.py                                         |    38 |   19 |    19 |    50% |
| tests/unit/io/readers/test_parquet_reader.py                                      |    60 |   38 |    22 |    37% |
| tests/unit/io/writers/test_json_writer.py                                         |     7 |    6 |     1 |    14% |
| tests/unit/io/writers/test_parquet_writer.py                                      |    57 |   10 |    47 |    82% |
| tests/unit/observability/__init__.py                                              |     1 |    1 |     0 |     0% |
| tests/unit/observability/test_logger_adaptor.py                                   |    35 |    2 |    33 |    94% |
| tests/unit/observability/test_metrics_adaptor.py                                  |    17 |    1 |    16 |    94% |
| tests/unit/observability/test_traces_adaptor.py                                   |    10 |    1 |     9 |    90% |
| tests/unit/server/__init__.py                                                     |     1 |    1 |     0 |     0% |
| tests/unit/server/fastapi/test_fastapi.py                                         |    76 |   28 |    48 |    63% |
| tests/unit/server/fastapi/test_fastapi_utils.py                                   |    34 |    0 |    34 |   100% |
| tests/unit/server/fastapi/routers/__init__.py                                     |     1 |    1 |     0 |     0% |
| tests/unit/server/fastapi/routers/server.py                                       |     1 |    1 |     0 |     0% |
| tests/unit/server/mcp/__init__.py                                                 |     1 |    1 |     0 |     0% |
| tests/unit/server/mcp/test_mcp_server.py                                          |    24 |    1 |    23 |    96% |
| tests/unit/services/test_atlan_storage.py                                         |    10 |    0 |    10 |   100% |
| tests/unit/services/test_eventstore.py                                            |    18 |    0 |    18 |   100% |
| tests/unit/services/test_objectstore.py                                           |    36 |    5 |    31 |    86% |
| tests/unit/services/test_statestore.py                                            |    14 |    0 |    14 |   100% |
| tests/unit/services/test_statestore_path_traversal.py                             |    23 |   17 |     6 |    26% |
| tests/unit/transformers/__init__.py                                               |     1 |    1 |     0 |     0% |
| tests/unit/transformers/atlas/__init__.py                                         |     1 |    1 |     0 |     0% |
| tests/unit/transformers/atlas/test_column.py                                      |    17 |    6 |    11 |    65% |
| tests/unit/transformers/atlas/test_database.py                                    |     8 |    6 |     2 |    25% |
| tests/unit/transformers/atlas/test_function.py                                    |     9 |    5 |     4 |    44% |
| tests/unit/transformers/atlas/test_procedure.py                                   |     7 |    6 |     1 |    14% |
| tests/unit/transformers/atlas/test_schema.py                                      |     8 |    6 |     2 |    25% |
| tests/unit/transformers/atlas/test_table.py                                       |    13 |    6 |     7 |    54% |
| tests/unit/transformers/query/test_sql_transformer.py                             |    16 |    4 |    12 |    75% |
| tests/unit/transformers/query/test_sql_transformer_output_validation.py           |     5 |    2 |     3 |    60% |
| tests/unit/workflows/metadata_extraction/test_base_workflow.py                    |     7 |    0 |     7 |   100% |
| tests/unit/workflows/metadata_extraction/test_sql_workflow.py                     |     9 |    4 |     5 |    56% |
| tests/unit/workflows/query_extraction/__init__.py                                 |     1 |    1 |     0 |     0% |
| tests/unit/workflows/query_extraction/test_sql.py                                 |     8 |    3 |     5 |    62% |
|-----------------------------------------------------------------------------------|-------|------|-------|--------|
| TOTAL                                                                             |  2579 |  666 |  1913 |  74.2% |
---------------- RESULT: PASSED (minimum: 30.0%, actual: 74.2%) ----------------

@fyzanshaik-atlan fyzanshaik-atlan added e2e-test run-examples Run examples on the Pull Request labels Feb 18, 2026
@github-actions
Copy link

github-actions bot commented Feb 18, 2026

📦 Trivy Vulnerability Scan Results

Schema Version Created At Artifact Type
2 2026-02-19T09:44:30.857411205Z . filesystem

Report Summary

Could not generate summary table (data length mismatch: 9 vs 8).

Scan Result Details

requirements.txt
uv.lock

@github-actions
Copy link

github-actions bot commented Feb 18, 2026

📦 Trivy Secret Scan Results

Schema Version Created At Artifact Type
2 2026-02-19T09:44:41.052590144Z . filesystem

Report Summary

Target Type Secrets . filesystem ✅ None found

Scan Result Details

✅ No secrets found during the scan for ..

@github-actions
Copy link

github-actions bot commented Feb 18, 2026

@atlan-ci
Copy link
Collaborator

atlan-ci commented Feb 18, 2026

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
7581 5520 73% 0% 🟢

New Files

No new covered files...

Modified Files

No covered modified files...

updated for commit: df8ec14 by action🐍

@github-actions
Copy link

github-actions bot commented Feb 18, 2026

🛠 Full Test Coverage Report: https://k.atlan.dev/coverage/application-sdk/pr/1064

@github-actions
Copy link

github-actions bot commented Feb 18, 2026

📦 Example workflows test results

  • This workflow runs all the examples in the examples directory.

Example Status Time Taken
application_sql COMPLETED 🟢 10.55 seconds
application_sql_with_custom_transformer COMPLETED 🟢 8.47 seconds
application_sql_miner COMPLETED 🟢 101.24 seconds
application_hello_world COMPLETED 🟢 5.27 seconds

This is an automatically generated file. Please do not edit directly.
Operating system: ubuntu-22.04

@github-actions
Copy link

github-actions bot commented Feb 18, 2026

📦 Example workflows test results

  • This workflow runs all the examples in the examples directory.

Example Status Time Taken
application_sql COMPLETED 🟢 10.83 seconds
application_sql_with_custom_transformer COMPLETED 🟢 8.61 seconds
application_sql_miner COMPLETED 🟢 79.35 seconds
application_hello_world COMPLETED 🟢 5.64 seconds

This is an automatically generated file. Please do not edit directly.
Operating system: windows-latest

@github-actions
Copy link

github-actions bot commented Feb 18, 2026

📦 Example workflows test results

  • This workflow runs all the examples in the examples directory.

Example Status Time Taken
application_sql COMPLETED 🟢 15.71 seconds
application_sql_with_custom_transformer COMPLETED 🟢 8.37 seconds
application_sql_miner COMPLETED 🟢 95.49 seconds
application_hello_world COMPLETED 🟢 5.26 seconds

This is an automatically generated file. Please do not edit directly.
Operating system: macOS-latest

@greptile-apps
Copy link

greptile-apps bot commented Feb 18, 2026

Greptile Summary

This PR addresses two concerns: graceful shutdown coordination for local-mode runs and lazy-loading of heavy dependencies to reduce startup cost.

  • Graceful shutdown: APIServer.start() now catches KeyboardInterrupt and asyncio.CancelledError to explicitly trigger uvicorn shutdown. BaseApplication.start() uses a try/finally block in LOCAL mode to always stop the worker when the server exits. A new Worker.stop() method supports both same-loop and cross-thread (daemon) worker shutdown via run_coroutine_threadsafe.
  • Lazy imports: Heavy dependencies (pandas, duckdb, boto3, daft, OpenTelemetry modules) are moved from module-level to function-level or behind lazy proxy classes, reducing baseline memory and startup time for code paths that don't use them.
  • Lazy observability singletons: Module-level get_metrics() / get_traces() calls replaced with _LazyMetricsProxy / _LazyTracesProxy classes that defer initialization. Similarly, default_logger becomes a lazy proxy.
  • The cross-thread Worker.stop() fallback path (line 332 in worker.py) may incorrectly attempt to call _shutdown_worker() in the wrong event loop when the worker loop is unavailable — worth reviewing.
  • The _LazyMetricsProxy and _LazyTracesProxy classes are duplicated across application/__init__.py and metadata_extraction/sql.py rather than being shared.

Confidence Score: 4/5

  • This PR is safe to merge with low risk; the core shutdown and lazy-loading changes are well-structured and tested.
  • The graceful shutdown logic is correct for the primary code paths (LOCAL, SERVER, WORKER modes) and is backed by tests. Lazy-loading changes are straightforward import deferrals. One potential logic concern exists in the cross-thread fallback path of Worker.stop(), but it only executes in a degraded state. Code duplication of proxy classes is a style concern, not a correctness issue.
  • application_sdk/worker.py — the cross-thread shutdown fallback path deserves attention. application_sdk/application/__init__.py and application_sdk/application/metadata_extraction/sql.py — duplicate proxy classes.

Important Files Changed

Filename Overview
application_sdk/application/init.py Adds lazy proxy classes for metrics/traces, wraps server lifecycle in try/finally to stop worker on exit in LOCAL mode, and adds _stop_worker() method. Duplicate proxy classes with sql.py.
application_sdk/application/metadata_extraction/sql.py Adds identical lazy proxy classes for metrics/traces. Duplicates the pattern from application/__init__.py instead of sharing a common implementation.
application_sdk/worker.py Adds stop() method with cross-thread shutdown support and early-exit check for pre-startup shutdown requests. Race conditions between daemon thread and stop() are handled via _shutdown_initiated flag.
application_sdk/server/fastapi/init.py Adds KeyboardInterrupt and CancelledError handling around server.serve() to ensure uvicorn shutdown. CancelledError is correctly re-raised to preserve cancellation semantics.
application_sdk/server/fastapi/middleware/metrics.py Removes module-level eager get_metrics() call, moving it inside the dispatch method. Clean change that defers metrics initialization to first request.
application_sdk/observability/logger_adaptor.py Lazy-loads OpenTelemetry imports inside conditional blocks, adds _DefaultLoggerProxy for backward-compatible default_logger symbol. TYPE_CHECKING import for OTelLogRecord type alias is correct.
application_sdk/observability/metrics_adaptor.py Moves OpenTelemetry imports inside _setup_otel_metrics() method. Renames metrics to otel_metrics to avoid shadowing the local import. Clean change.
application_sdk/observability/traces_adaptor.py Moves OpenTelemetry imports to function-level for lazy loading. Duplicates imports across _setup_otel_traces, _setup_console_only_traces, _str_to_span_kind, and _send_to_otel. Return type of _str_to_span_kind changed to Any.
application_sdk/observability/observability.py Moves pandas and duckdb imports to function-level for lazy loading. Clean change that only imports these heavy dependencies when actually needed.
application_sdk/common/aws_utils.py Adds _get_boto3() helper for lazy boto3 import. Uses from __future__ import annotations with TYPE_CHECKING for type hints. Clean approach.
application_sdk/common/file_converter.py Replaces top-level import pandas as pd with a _PandasProxy class. Every pd.* access now goes through __getattr__, meaning repeated accesses have proxy overhead. The proxy never caches the pandas module reference.
application_sdk/transformers/atlas/init.py Adds _import_daft() helper for lazy daft import. Correctly uses TYPE_CHECKING and from __future__ import annotations for type annotations.
application_sdk/transformers/query/init.py Replaces top-level daft imports with lazy _import_daft(), to_struct(), and when() wrapper functions. Uses TYPE_CHECKING for annotations. Thorough approach.

Sequence Diagram

sequenceDiagram
    participant User as User (Ctrl+C)
    participant App as BaseApplication.start()
    participant Server as APIServer.start()
    participant Uvicorn as uvicorn Server
    participant Worker as Worker (daemon thread)

    App->>Worker: start(daemon=True)
    Worker-->>Worker: Thread: asyncio.run(start(daemon=False))
    Worker->>Worker: _worker_loop = get_running_loop()
    Worker->>Worker: worker.run()
    App->>Server: _start_server()
    Server->>Uvicorn: server.serve()

    User->>Uvicorn: SIGINT / KeyboardInterrupt
    Uvicorn-->>Server: KeyboardInterrupt raised
    Server->>Uvicorn: server.should_exit = True
    Server->>Uvicorn: await server.shutdown()

    Note over App: finally block (LOCAL mode)
    App->>Worker: _stop_worker() → worker.stop()
    Worker->>Worker: _shutdown_initiated = True

    alt Worker loop is different loop (daemon)
        Worker->>Worker: run_coroutine_threadsafe(_shutdown_worker(), worker_loop)
        Worker->>Worker: workflow_worker.shutdown()
    else Worker loop same or unavailable
        Worker->>Worker: await _shutdown_worker()
    end

    Worker-->>App: Worker stopped
Loading

Last reviewed commit: 83b06b6

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

17 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

@fyzanshaik-atlan
Copy link
Contributor Author

@greptile re-review

@greptile-apps
Copy link

greptile-apps bot commented Feb 18, 2026

Greptile Summary

This PR fixes graceful shutdown coordination for local-mode runs and lazy-loads several heavyweight dependencies (pandas, duckdb, boto3, daft, and OpenTelemetry SDK modules) to reduce startup memory and import time.

  • Graceful shutdown: APIServer.start() now catches KeyboardInterrupt and asyncio.CancelledError to trigger explicit uvicorn shutdown. BaseApplication.start() wraps the server lifecycle in a try/finally to always stop the daemon worker in LOCAL mode. A new Worker.stop() method supports both same-loop and cross-thread (daemon) shutdown via run_coroutine_threadsafe with a safe fallback.
  • Lazy-loading: Heavy imports (opentelemetry.*, pandas, duckdb, boto3, daft) are moved from module-level to function/method scope. Module-level singletons (metrics, traces, default_logger) are replaced with lazy proxy objects that defer initialization until first attribute access.
  • New shared module: application_sdk/observability/lazy_proxies.py centralizes LazyMetricsProxy and LazyTracesProxy to avoid duplication across application modules.
  • Test coverage: New tests verify worker stop on server exit/failure, KeyboardInterrupt/CancelledError handling in the server, and same-loop/cross-loop/no-op paths in Worker.stop().

Confidence Score: 4/5

  • This PR is safe to merge; changes are well-tested and the shutdown coordination logic handles edge cases correctly.
  • Score of 4 reflects solid implementation with good test coverage across all new code paths. The lazy-loading changes are straightforward and low-risk. The shutdown coordination is the most complex part but handles race conditions through the _shutdown_initiated flag and RuntimeError fallback. Minor deduction for cross-thread coordination relying on CPython GIL semantics rather than explicit synchronization primitives.
  • application_sdk/worker.py has the most complex cross-thread shutdown logic and deserves careful review of the race condition handling between daemon thread startup and stop() calls.

Important Files Changed

Filename Overview
application_sdk/application/init.py Adds graceful worker shutdown in LOCAL mode via try/finally around server lifecycle, replaces eager metrics/traces singletons with lazy proxies.
application_sdk/common/file_converter.py Introduces _PandasProxy for lazy pandas import. Each attribute access re-imports (Python caches in sys.modules, so minimal overhead).
application_sdk/observability/lazy_proxies.py New shared module providing LazyMetricsProxy and LazyTracesProxy, centralizing the lazy proxy pattern previously duplicated inline.
application_sdk/observability/logger_adaptor.py Moves OpenTelemetry imports to method scope, replaces eager default_logger initialization with _DefaultLoggerProxy for lazy creation.
application_sdk/observability/metrics_adaptor.py Defers all OpenTelemetry imports to _setup_otel_metrics method. Renames local import to otel_metrics to avoid shadowing.
application_sdk/observability/traces_adaptor.py Defers OpenTelemetry imports to method scope across _setup_otel_traces, _setup_console_only_traces, _str_to_span_kind, and _send_to_otel.
application_sdk/server/fastapi/init.py Adds KeyboardInterrupt and CancelledError handling in start() to trigger explicit uvicorn shutdown, correctly re-raising CancelledError.
application_sdk/transformers/query/init.py Defers daft and daft.functions imports; wraps to_struct and when in lazy helper functions that preserve the call-site API.
application_sdk/worker.py Adds stop() method with same-loop and cross-thread shutdown support. Stores _worker_loop and _worker_thread references for coordinated shutdown. Includes early-exit check for _shutdown_initiated before worker.run().

Sequence Diagram

sequenceDiagram
    participant User as User (Ctrl+C)
    participant App as BaseApplication.start()
    participant Server as APIServer.start()
    participant Uvicorn as uvicorn.Server
    participant Worker as Worker (daemon thread)

    App->>Worker: start(daemon=True)
    Worker-->>Worker: Thread: asyncio.run(start(daemon=False))
    Worker->>Worker: _worker_loop = get_running_loop()
    Worker->>Worker: workflow_worker = create_worker()
    Worker->>Worker: worker.run()

    App->>Server: _start_server()
    Server->>Uvicorn: await server.serve()

    User->>Uvicorn: KeyboardInterrupt / CancelledError
    Uvicorn-->>Server: exception raised
    Server->>Uvicorn: server.should_exit = True
    Server->>Uvicorn: await server.shutdown()
    Server-->>App: returns (finally block)

    App->>Worker: _stop_worker() → worker.stop()
    alt Same loop
        Worker->>Worker: await _shutdown_worker()
    else Cross-thread (daemon)
        Worker->>Worker: run_coroutine_threadsafe(_shutdown_worker, worker_loop)
        Worker->>Worker: await wrap_future(future)
    end
    Worker-->>App: shutdown complete
Loading

Last reviewed commit: d8c6750

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

18 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile


await EventStore.publish_event(worker_creation_event)

self._worker_loop = asyncio.get_running_loop()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing memory barrier for cross-thread field visibility

self._worker_loop is written here in the daemon thread and read by stop() in the main thread (line 317). While CPython's GIL makes individual attribute writes atomic, there is no guaranteed ordering between _worker_loop and workflow_worker assignments across threads. In practice this works because:

  1. The _shutdown_initiated flag covers the early window.
  2. Python's GIL ensures visibility after the next bytecode boundary.

However, if you ever need stronger guarantees (or run on an alternative Python runtime), consider using a threading.Event or threading.Lock to coordinate the handoff. This is not a blocking issue for CPython but worth noting for future maintainability.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

e2e-test run-examples Run examples on the Pull Request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants