instructure-dap-client 2.2.0b2__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/LICENSE +22 -1
- instructure_dap_client-2.3.0/MANIFEST.in +11 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/PKG-INFO +20 -20
- instructure_dap_client-2.3.0/README.md +73 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/__init__.py +3 -3
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/actions/init_db.py +3 -1
- instructure_dap_client-2.3.0/dap/actions/list_db.py +176 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/actions/sync_db.py +3 -1
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/api.py +112 -51
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/commands/command_group.py +3 -2
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/commands/commands.py +48 -7
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/concurrency.py +9 -13
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/dap_error.py +1 -1
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/dap_types.py +47 -15
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/integration/base_connection.py +3 -1
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/integration/database.py +7 -4
- instructure_dap_client-2.3.0/dap/integration/database_errors.py +116 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/log.py +45 -11
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/replicator/meta_schema.py +1 -0
- instructure_dap_client-2.3.0/dap/replicator/record_validation.py +274 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/replicator/sql.py +3 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/replicator/sql_metatable_handler.py +4 -4
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/replicator/sql_op.py +9 -4
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/replicator/sql_op_init.py +32 -13
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/replicator/sql_op_sync.py +54 -15
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/retry_options.py +10 -3
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/tracking.py +36 -16
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/ui.py +90 -24
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/instructure_dap_client.egg-info/PKG-INFO +20 -20
- instructure_dap_client-2.3.0/instructure_dap_client.egg-info/SOURCES.txt +172 -0
- instructure_dap_client-2.3.0/instructure_dap_client.egg-info/requires.txt +23 -0
- instructure_dap_client-2.3.0/instructure_dap_client.egg-info/top_level.txt +3 -0
- instructure_dap_client-2.3.0/pyproject.toml +134 -0
- instructure_dap_client-2.3.0/pysqlsync/__init__.py +17 -0
- instructure_dap_client-2.3.0/pysqlsync/base.py +1392 -0
- instructure_dap_client-2.3.0/pysqlsync/connection.py +93 -0
- instructure_dap_client-2.3.0/pysqlsync/data/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/data/exchange.py +192 -0
- instructure_dap_client-2.3.0/pysqlsync/data/generator.py +355 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/README.md +35 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/delta/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/delta/data_types.py +59 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/delta/dependency.py +9 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/delta/engine.py +26 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/delta/generator.py +82 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/delta/object_types.py +95 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mssql/README.md +28 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mssql/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mssql/connection.py +154 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mssql/data_types.py +127 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mssql/dependency.py +11 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mssql/discovery.py +107 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mssql/engine.py +28 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mssql/generator.py +96 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mssql/mutation.py +54 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mssql/object_types.py +143 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mysql/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mysql/connection.py +136 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mysql/data_types.py +48 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mysql/dependency.py +12 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mysql/discovery.py +153 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mysql/engine.py +28 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mysql/generator.py +152 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mysql/mutation.py +73 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/mysql/object_types.py +83 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/oracle/README.md +22 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/oracle/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/oracle/connection.py +149 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/oracle/data_types.py +112 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/oracle/dependency.py +11 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/oracle/discovery.py +239 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/oracle/engine.py +28 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/oracle/generator.py +101 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/oracle/mutation.py +45 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/oracle/object_types.py +67 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/postgresql/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/postgresql/connection.py +141 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/postgresql/data_types.py +14 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/postgresql/dependency.py +11 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/postgresql/discovery.py +400 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/postgresql/engine.py +28 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/postgresql/generator.py +107 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/postgresql/mutation.py +90 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/postgresql/object_types.py +107 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/redshift/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/redshift/connection.py +164 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/redshift/data_types.py +18 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/redshift/dependency.py +11 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/redshift/engine.py +28 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/redshift/generator.py +90 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/snowflake/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/snowflake/data_types.py +26 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/snowflake/dependency.py +11 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/snowflake/engine.py +26 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/snowflake/generator.py +72 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/snowflake/object_types.py +92 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/test/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/test/dependency.py +12 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/trino/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/trino/connection.py +94 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/trino/dependency.py +11 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/trino/discovery.py +29 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/trino/engine.py +28 -0
- instructure_dap_client-2.3.0/pysqlsync/dialect/trino/generator.py +39 -0
- instructure_dap_client-2.3.0/pysqlsync/factory.py +171 -0
- instructure_dap_client-2.3.0/pysqlsync/formation/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/formation/constraints.py +86 -0
- instructure_dap_client-2.3.0/pysqlsync/formation/data_types.py +200 -0
- instructure_dap_client-2.3.0/pysqlsync/formation/discovery.py +398 -0
- instructure_dap_client-2.3.0/pysqlsync/formation/inspection.py +186 -0
- instructure_dap_client-2.3.0/pysqlsync/formation/mutation.py +328 -0
- instructure_dap_client-2.3.0/pysqlsync/formation/object_dict.py +87 -0
- instructure_dap_client-2.3.0/pysqlsync/formation/object_types.py +885 -0
- instructure_dap_client-2.3.0/pysqlsync/formation/py_to_sql.py +1142 -0
- instructure_dap_client-2.3.0/pysqlsync/formation/sql_to_py.py +202 -0
- instructure_dap_client-2.3.0/pysqlsync/model/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/model/data_types.py +405 -0
- instructure_dap_client-2.3.0/pysqlsync/model/entity_types.py +76 -0
- instructure_dap_client-2.3.0/pysqlsync/model/id_types.py +201 -0
- instructure_dap_client-2.3.0/pysqlsync/model/key_types.py +46 -0
- instructure_dap_client-2.3.0/pysqlsync/model/properties.py +242 -0
- instructure_dap_client-2.3.0/pysqlsync/py.typed +0 -0
- instructure_dap_client-2.3.0/pysqlsync/python_types.py +202 -0
- instructure_dap_client-2.3.0/pysqlsync/resultset.py +107 -0
- instructure_dap_client-2.3.0/pysqlsync/util/__init__.py +0 -0
- instructure_dap_client-2.3.0/pysqlsync/util/dataclasses.py +80 -0
- instructure_dap_client-2.3.0/pysqlsync/util/dispatch.py +26 -0
- instructure_dap_client-2.3.0/pysqlsync/util/typing.py +6 -0
- instructure_dap_client-2.3.0/setup.cfg +4 -0
- instructure_dap_client-2.3.0/strong_typing/__init__.py +13 -0
- instructure_dap_client-2.3.0/strong_typing/auxiliary.py +214 -0
- instructure_dap_client-2.3.0/strong_typing/classdef.py +425 -0
- instructure_dap_client-2.3.0/strong_typing/core.py +40 -0
- instructure_dap_client-2.3.0/strong_typing/deserializer.py +974 -0
- instructure_dap_client-2.3.0/strong_typing/docstring.py +431 -0
- instructure_dap_client-2.3.0/strong_typing/exception.py +17 -0
- instructure_dap_client-2.3.0/strong_typing/inspection.py +1029 -0
- instructure_dap_client-2.3.0/strong_typing/mapping.py +34 -0
- instructure_dap_client-2.3.0/strong_typing/name.py +292 -0
- instructure_dap_client-2.3.0/strong_typing/py.typed +0 -0
- instructure_dap_client-2.3.0/strong_typing/schema.py +702 -0
- instructure_dap_client-2.3.0/strong_typing/serialization.py +116 -0
- instructure_dap_client-2.3.0/strong_typing/serializer.py +535 -0
- instructure_dap_client-2.3.0/strong_typing/topological.py +84 -0
- instructure_dap_client-2.2.0b2/MANIFEST.in +0 -1
- instructure_dap_client-2.2.0b2/README.md +0 -9
- instructure_dap_client-2.2.0b2/dap/counted.py +0 -60
- instructure_dap_client-2.2.0b2/dap/integration/database_errors.py +0 -43
- instructure_dap_client-2.2.0b2/dap/payload.py +0 -44
- instructure_dap_client-2.2.0b2/dap/timer.py +0 -50
- instructure_dap_client-2.2.0b2/instructure_dap_client.egg-info/SOURCES.txt +0 -64
- instructure_dap_client-2.2.0b2/instructure_dap_client.egg-info/requires.txt +0 -19
- instructure_dap_client-2.2.0b2/instructure_dap_client.egg-info/top_level.txt +0 -1
- instructure_dap_client-2.2.0b2/pyproject.toml +0 -5
- instructure_dap_client-2.2.0b2/setup.cfg +0 -67
- instructure_dap_client-2.2.0b2/setup.py +0 -4
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/__main__.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/actions/__init__.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/actions/download_incremental.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/actions/download_schema.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/actions/download_snapshot.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/actions/drop_db.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/actions/list_tables.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/commands/__init__.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/commands/global_options.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/integration/__init__.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/integration/connection.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/networking.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/py.typed +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/replicator/__init__.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/replicator/canvas.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/replicator/canvas_logs.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/replicator/catalog.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/replicator/new_quizzes.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/replicator/sql_op_drop.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/timestamp.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/version_upgrade/__init__.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/version_upgrade/db_version_upgrader.py +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/version_upgrade/mssql_0_to_1.sql +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/version_upgrade/mysql_0_to_1.sql +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/dap/version_upgrade/postgresql_0_to_1.sql +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/instructure_dap_client.egg-info/dependency_links.txt +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/instructure_dap_client.egg-info/entry_points.txt +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/instructure_dap_client.egg-info/zip-safe +0 -0
- {instructure_dap_client-2.2.0b2 → instructure_dap_client-2.3.0}/pypi_index.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2022-
|
|
3
|
+
Copyright (c) 2022-2026 Instructure, Inc.
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -19,3 +19,24 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
===============================================================================
|
|
25
|
+
Third-party vendored components
|
|
26
|
+
===============================================================================
|
|
27
|
+
|
|
28
|
+
This distribution bundles the following third-party packages as top-level
|
|
29
|
+
modules. Both are licensed under the MIT License, whose terms are reproduced
|
|
30
|
+
above, with the copyright notices below. The versions bundled are recorded in
|
|
31
|
+
`vendor/VENDORED.md`.
|
|
32
|
+
|
|
33
|
+
`strong_typing` -- derived from https://github.com/hunyadi/strong_typing
|
|
34
|
+
|
|
35
|
+
Copyright (c) 2021-2026 Levente Hunyadi
|
|
36
|
+
Copyright (c) 2021-2026 Instructure Inc.
|
|
37
|
+
Copyright (c) 2021 Kheiron Medical Technologies Ltd.
|
|
38
|
+
|
|
39
|
+
`pysqlsync` -- derived from https://github.com/hunyadi/pysqlsync
|
|
40
|
+
|
|
41
|
+
Copyright (c) 2023-2025 Levente Hunyadi
|
|
42
|
+
Copyright (c) 2026 Instructure, Inc.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
include dap/version_upgrade/*.sql
|
|
2
|
+
|
|
3
|
+
# Vendored packages -- see vendor/VENDORED.md. `recursive-include` mirrors the
|
|
4
|
+
# forks' own MANIFEST files, so package data added by a future re-vendor (e.g. a
|
|
5
|
+
# new dialect README) is picked up without editing this file.
|
|
6
|
+
include strong_typing/py.typed
|
|
7
|
+
include pysqlsync/py.typed
|
|
8
|
+
recursive-include pysqlsync *.md
|
|
9
|
+
|
|
10
|
+
global-exclude *.pyc
|
|
11
|
+
global-exclude __pycache__/*
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: instructure-dap-client
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Data Access Platform client library
|
|
5
|
-
Maintainer:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Classifier: Development Status :: 4 - Beta
|
|
5
|
+
Maintainer-email: "Instructure, Inc." <dapclient@instructure.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Changelog, https://community.instructure.com/en/discussion/664378/2026-api-and-cli-change-log
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
9
|
Classifier: Environment :: Console
|
|
11
10
|
Classifier: Intended Audience :: Developers
|
|
12
11
|
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
12
|
Classifier: Operating System :: OS Independent
|
|
15
13
|
Classifier: Programming Language :: Python :: 3
|
|
16
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -21,22 +19,24 @@ Classifier: Typing :: Typed
|
|
|
21
19
|
Requires-Python: >=3.11
|
|
22
20
|
Description-Content-Type: text/markdown
|
|
23
21
|
License-File: LICENSE
|
|
24
|
-
Requires-Dist: aiohttp
|
|
25
|
-
Requires-Dist: aiohttp-retry
|
|
26
|
-
Requires-Dist: aiofiles
|
|
27
|
-
Requires-Dist: asyncclick==8.1.8
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
32
|
-
Requires-Dist:
|
|
33
|
-
Requires-Dist:
|
|
22
|
+
Requires-Dist: aiohttp==3.13.4
|
|
23
|
+
Requires-Dist: aiohttp-retry==2.9.1
|
|
24
|
+
Requires-Dist: aiofiles==25.1.0
|
|
25
|
+
Requires-Dist: asyncclick==8.1.8.0
|
|
26
|
+
Requires-Dist: PyJWT==2.13.0
|
|
27
|
+
Requires-Dist: tsv2py==0.7.1
|
|
28
|
+
Requires-Dist: rich==14.3.3
|
|
29
|
+
Requires-Dist: jsonschema==4.26.0
|
|
30
|
+
Requires-Dist: truststore==0.10.4
|
|
31
|
+
Requires-Dist: typing_extensions==4.16.0; python_version < "3.12"
|
|
34
32
|
Provides-Extra: postgresql
|
|
35
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: asyncpg==0.31.0; extra == "postgresql"
|
|
36
34
|
Provides-Extra: mysql
|
|
37
|
-
Requires-Dist:
|
|
35
|
+
Requires-Dist: aiomysql==0.3.2; extra == "mysql"
|
|
36
|
+
Requires-Dist: PyMySQL[rsa]==1.2.0; extra == "mysql"
|
|
37
|
+
Requires-Dist: cryptography==50.0.0; extra == "mysql"
|
|
38
38
|
Provides-Extra: mssql
|
|
39
|
-
Requires-Dist:
|
|
39
|
+
Requires-Dist: pyodbc==5.3.0; extra == "mssql"
|
|
40
40
|
Dynamic: license-file
|
|
41
41
|
|
|
42
42
|
# Data Access Platform Client Library
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Data Access Platform CLI & Client Library
|
|
2
|
+
|
|
3
|
+
Data Access Platform (DAP) acts as a single source of data for analytics at Instructure. It provides efficient access to data collected across various educational products in bulk with high fidelity and low latency, adhering to a canonical data model.
|
|
4
|
+
|
|
5
|
+
The DAP CLI and Client Library is a Python wrapper around the [DAP Query API](https://developerdocs.instructure.com/services/dap/query-api), allowing you to fetch an initial snapshot and incremental changes, or initialize a supported database and keep it synchronized with [data in DAP](https://developerdocs.instructure.com/services/dap/dataset).
|
|
6
|
+
|
|
7
|
+
See [DAP Client Library Documentation](https://developerdocs.instructure.com/services/dap/dap-cli-readme).
|
|
8
|
+
|
|
9
|
+
Docs source repo: https://github.com/instructure/data-access-platform-documentation.
|
|
10
|
+
|
|
11
|
+
For development guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
12
|
+
|
|
13
|
+
## Generate lib-reference documentation
|
|
14
|
+
|
|
15
|
+
`util/markdown_generator.py` (in the repository root, not under `cli/`) generates the GitBook Markdown
|
|
16
|
+
client library reference — `lib-reference.md` — from the public API, the database replicator, the shared
|
|
17
|
+
types, and the exception hierarchy.
|
|
18
|
+
|
|
19
|
+
It depends on `instructure-markdown-doc`, hosted on the internal Artifactory (authentication required). It
|
|
20
|
+
is **not** declared as a project dependency: it would force Artifactory authentication on every
|
|
21
|
+
`pip install`. Instead it is installed on its own, after authenticating, only when generating the docs. In
|
|
22
|
+
CI this is handled by the [`cli-doc.yml`](../.github/workflows/cli-doc.yml) workflow.
|
|
23
|
+
|
|
24
|
+
### Authenticate to Artifactory
|
|
25
|
+
|
|
26
|
+
`pip` needs to be pointed at the authenticated Artifactory PyPI index to resolve `instructure-markdown-doc`.
|
|
27
|
+
|
|
28
|
+
1. Generate a personal **Identity Token** in the [JFrog UI](https://instructure.jfrog.io): click your
|
|
29
|
+
username (top right) → **Edit Profile** → **Generate an Identity Token**. Copy it.
|
|
30
|
+
2. Export `ARTIFACTORY_INDEX_URL` in your shell profile (e.g. `~/.zshrc`), using your JFrog username (shown
|
|
31
|
+
under **Edit Profile**) as the username and the identity token as the password:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
export ARTIFACTORY_INDEX_URL="https://<jfrog-username>:<identity-token>@instructure.jfrog.io/artifactory/api/pypi/virtual-pypi/simple"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Restart the shell (or `source` the profile) so the variable is set.
|
|
38
|
+
|
|
39
|
+
> The token grants access under your account — treat it like a password. Keep it in your shell profile or a
|
|
40
|
+
> secrets manager; do not commit it.
|
|
41
|
+
|
|
42
|
+
### Run the generator
|
|
43
|
+
|
|
44
|
+
With `ARTIFACTORY_INDEX_URL` set, install `instructure-markdown-doc` into the workspace environment, then
|
|
45
|
+
run the generator from the **repository root** — the `cli.dap` imports resolve against `PYTHONPATH`, and the
|
|
46
|
+
output path is relative to the script:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
uv sync --all-packages --all-extras
|
|
50
|
+
uv pip install --index "$ARTIFACTORY_INDEX_URL" instructure-markdown-doc==0.2.1
|
|
51
|
+
PYTHONPATH=. uv run --no-sync python util/markdown_generator.py
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`--no-sync` on the last command is required: a plain `uv run` re-syncs the environment, and because sync is
|
|
55
|
+
exact it would uninstall the ad-hoc `instructure-markdown-doc` installed on the line before.
|
|
56
|
+
|
|
57
|
+
The generated Markdown is written to `util/markdown/target/`.
|
|
58
|
+
|
|
59
|
+
### Automatic publishing on release
|
|
60
|
+
|
|
61
|
+
The [`cli-doc.yml`](../.github/workflows/cli-doc.yml) workflow runs the generator in CI. Its behavior
|
|
62
|
+
depends on the trigger:
|
|
63
|
+
|
|
64
|
+
- **Push/PR to `main`, or `workflow_dispatch`** — generates `lib-reference.md` and uploads it as a build
|
|
65
|
+
artifact (`generated-docs`). Nothing is published.
|
|
66
|
+
- **Version tag push (`v*`, e.g. `v2.2.0`)** — first verifies the tag matches `__version__` in
|
|
67
|
+
`cli/dap/__init__.py` (failing fast on a mistagged release), then generates the doc and **opens a PR**
|
|
68
|
+
against the [documentation repo](https://github.com/instructure/data-access-platform-documentation),
|
|
69
|
+
replacing its `docs/lib-reference.md`. If the generated doc is unchanged, no PR is opened.
|
|
70
|
+
|
|
71
|
+
The PR is authored by the "DAP Docs Updater" GitHub App (installed on the documentation repo with
|
|
72
|
+
`Contents: write` + `Pull requests: write`); a human reviews and merges it. The branch and commit carry
|
|
73
|
+
provenance back to the source tag and the Actions run.
|
|
@@ -6,9 +6,9 @@ to data collected across various educational products in bulk with high fidelity
|
|
|
6
6
|
a canonical data model.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
__version__ = "2.
|
|
10
|
-
__copyright__ = "Copyright 2022-
|
|
9
|
+
__version__ = "2.3.0"
|
|
10
|
+
__copyright__ = "Copyright 2022-2026, Instructure, Inc."
|
|
11
11
|
__license__ = "MIT"
|
|
12
12
|
__maintainer__ = "Instructure, Inc."
|
|
13
13
|
__email__ = "dapclient@instructure.com"
|
|
14
|
-
__status__ = "
|
|
14
|
+
__status__ = "Production"
|
|
@@ -23,4 +23,6 @@ async def init_db(
|
|
|
23
23
|
async def replicate_table_fn(namespace: str, table: str) -> None:
|
|
24
24
|
await sql_replicator.initialize(namespace, table)
|
|
25
25
|
|
|
26
|
-
await session.execute_operation_on_tables(
|
|
26
|
+
await session.execute_operation_on_tables(
|
|
27
|
+
namespace, table_names, "initdb", replicate_table_fn
|
|
28
|
+
)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from pysqlsync.model.id_types import PrefixedId, QualifiedId
|
|
6
|
+
|
|
7
|
+
from .. import ui
|
|
8
|
+
from ..api import DAPClient
|
|
9
|
+
from ..dap_types import Credentials
|
|
10
|
+
from ..integration.database import DatabaseConnection
|
|
11
|
+
from ..replicator import meta_schema
|
|
12
|
+
from ..replicator.sql_metatable_handler import (
|
|
13
|
+
get_table_meta_record,
|
|
14
|
+
get_table_names_in_namespace_from_meta,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _count_from_clause(
|
|
19
|
+
dialect: str, target_schema: Optional[str], target_table: str
|
|
20
|
+
) -> str:
|
|
21
|
+
if dialect == "mysql":
|
|
22
|
+
# MySQL has no schemas; pysqlsync uses namespace__table (ANSI_QUOTES mode is always enabled)
|
|
23
|
+
return str(PrefixedId(target_schema, target_table))
|
|
24
|
+
else:
|
|
25
|
+
# PostgreSQL and MSSQL use schema.table
|
|
26
|
+
return str(QualifiedId(target_schema, target_table))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def cell(value: str | None, fallback: str = "-") -> tuple[ui.MsgType, str]:
|
|
30
|
+
if value is not None:
|
|
31
|
+
return (ui.MsgType.TITLE, value)
|
|
32
|
+
return (ui.MsgType.INFO, fallback)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def calculate_age(timestamp: datetime) -> str:
|
|
36
|
+
# timestamp is naive but represents UTC time, so get current UTC time for comparison
|
|
37
|
+
now = (
|
|
38
|
+
datetime.now(timezone.utc).replace(tzinfo=None)
|
|
39
|
+
if timestamp.tzinfo is None
|
|
40
|
+
else datetime.now(timestamp.tzinfo)
|
|
41
|
+
)
|
|
42
|
+
age_seconds = int((now - timestamp).total_seconds())
|
|
43
|
+
days, remainder = divmod(age_seconds, 86400)
|
|
44
|
+
hours, remainder = divmod(remainder, 3600)
|
|
45
|
+
minutes, seconds = divmod(remainder, 60)
|
|
46
|
+
if days > 0:
|
|
47
|
+
return f"{days}d {hours}h" if hours > 0 else f"{days}d"
|
|
48
|
+
elif hours > 0:
|
|
49
|
+
return f"{hours}h {minutes}m" if minutes > 0 else f"{hours}h"
|
|
50
|
+
elif minutes > 0:
|
|
51
|
+
return f"{minutes}m {seconds}s" if seconds > 0 else f"{minutes}m"
|
|
52
|
+
else:
|
|
53
|
+
return f"{seconds}s"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def list_db(
|
|
57
|
+
base_url: str,
|
|
58
|
+
credentials: Credentials,
|
|
59
|
+
connection_string: str,
|
|
60
|
+
namespace: str,
|
|
61
|
+
tracking: Optional[bool] = None,
|
|
62
|
+
omit_not_replicated: bool = False,
|
|
63
|
+
omit_record_count: bool = False,
|
|
64
|
+
) -> None:
|
|
65
|
+
dap_tables_names = []
|
|
66
|
+
async with DAPClient(
|
|
67
|
+
base_url=base_url, credentials=credentials, tracking=tracking
|
|
68
|
+
) as session:
|
|
69
|
+
dap_tables_names = await session.get_tables(namespace)
|
|
70
|
+
if not omit_record_count:
|
|
71
|
+
ui.info("Querying table record counts...")
|
|
72
|
+
db_connection = DatabaseConnection(connection_string)
|
|
73
|
+
async with db_connection.connection as base_connection:
|
|
74
|
+
explorer = db_connection.engine.create_explorer(base_connection)
|
|
75
|
+
await explorer.synchronize(modules=[meta_schema])
|
|
76
|
+
db_table_names = await get_table_names_in_namespace_from_meta(
|
|
77
|
+
base_connection, namespace
|
|
78
|
+
)
|
|
79
|
+
db_table_meta_records: dict[str, Optional[meta_schema.table_sync]] = {}
|
|
80
|
+
for table_name in sorted(db_table_names):
|
|
81
|
+
meta = await get_table_meta_record(
|
|
82
|
+
base_connection, namespace, table_name
|
|
83
|
+
)
|
|
84
|
+
if meta is not None:
|
|
85
|
+
db_table_meta_records[table_name] = meta
|
|
86
|
+
if not omit_not_replicated:
|
|
87
|
+
for table_name in sorted(dap_tables_names):
|
|
88
|
+
if table_name not in db_table_meta_records:
|
|
89
|
+
db_table_meta_records[table_name] = None
|
|
90
|
+
|
|
91
|
+
db_table_record_counts = {}
|
|
92
|
+
for table_name, meta in db_table_meta_records.items():
|
|
93
|
+
if meta is not None and not omit_record_count:
|
|
94
|
+
from_clause = _count_from_clause(
|
|
95
|
+
db_connection.dialect, meta.target_schema, meta.target_table
|
|
96
|
+
)
|
|
97
|
+
count = await base_connection.query_one(
|
|
98
|
+
int,
|
|
99
|
+
f"SELECT COUNT(*) FROM {from_clause}",
|
|
100
|
+
)
|
|
101
|
+
db_table_record_counts[table_name] = count
|
|
102
|
+
|
|
103
|
+
title_str = f"Local database replication status of tables in namespace [bold]{namespace}[/bold]"
|
|
104
|
+
columns = [
|
|
105
|
+
ui.TableColumn("Name", style="bold"),
|
|
106
|
+
*(
|
|
107
|
+
[]
|
|
108
|
+
if omit_record_count
|
|
109
|
+
else [ui.TableColumn("Records", style="bold", justify="right")]
|
|
110
|
+
),
|
|
111
|
+
ui.TableColumn("Data as of"),
|
|
112
|
+
ui.TableColumn("Staleness"),
|
|
113
|
+
ui.TableColumn("Schema", justify="right"),
|
|
114
|
+
ui.TableColumn("Use command"),
|
|
115
|
+
]
|
|
116
|
+
ui.print_table(
|
|
117
|
+
title=title_str,
|
|
118
|
+
columns=columns,
|
|
119
|
+
rows=[
|
|
120
|
+
[
|
|
121
|
+
cell(table_name if meta is not None else None, table_name),
|
|
122
|
+
*(
|
|
123
|
+
[]
|
|
124
|
+
if omit_record_count
|
|
125
|
+
else [
|
|
126
|
+
cell(
|
|
127
|
+
str(db_table_record_counts[table_name])
|
|
128
|
+
if table_name in db_table_record_counts
|
|
129
|
+
else None
|
|
130
|
+
)
|
|
131
|
+
]
|
|
132
|
+
),
|
|
133
|
+
cell(
|
|
134
|
+
meta.timestamp.replace(tzinfo=timezone.utc)
|
|
135
|
+
.astimezone()
|
|
136
|
+
.isoformat()
|
|
137
|
+
if meta is not None
|
|
138
|
+
else None,
|
|
139
|
+
"Not replicated",
|
|
140
|
+
),
|
|
141
|
+
cell(
|
|
142
|
+
calculate_age(meta.timestamp) if meta is not None else None
|
|
143
|
+
),
|
|
144
|
+
cell(str(meta.schema_version) if meta is not None else None),
|
|
145
|
+
cell("syncdb" if meta is not None else None, "initdb"),
|
|
146
|
+
]
|
|
147
|
+
for table_name, meta in db_table_meta_records.items()
|
|
148
|
+
],
|
|
149
|
+
)
|
|
150
|
+
if not ui.is_interactive():
|
|
151
|
+
rows = []
|
|
152
|
+
for table_name, meta in db_table_meta_records.items():
|
|
153
|
+
row: dict = {
|
|
154
|
+
"name": table_name,
|
|
155
|
+
"timestamp": meta.timestamp.replace(tzinfo=timezone.utc)
|
|
156
|
+
.astimezone()
|
|
157
|
+
.isoformat()
|
|
158
|
+
if meta is not None
|
|
159
|
+
else None,
|
|
160
|
+
"staleness": calculate_age(meta.timestamp)
|
|
161
|
+
if meta is not None
|
|
162
|
+
else None,
|
|
163
|
+
"schema": meta.schema_version if meta is not None else None,
|
|
164
|
+
"command": "syncdb" if meta is not None else "initdb",
|
|
165
|
+
}
|
|
166
|
+
if not omit_record_count:
|
|
167
|
+
row["records"] = db_table_record_counts.get(table_name)
|
|
168
|
+
rows.append(row)
|
|
169
|
+
print(json.dumps(rows, indent=2))
|
|
170
|
+
|
|
171
|
+
if session.tracking_data:
|
|
172
|
+
session.tracking_data.set_cmd_info("listdb", namespace, None)
|
|
173
|
+
session.tracking_data.db_dialect = db_connection.dialect
|
|
174
|
+
session.tracking_data.db_version = await db_connection.get_version(
|
|
175
|
+
db_connection.dialect, base_connection
|
|
176
|
+
)
|
|
@@ -23,4 +23,6 @@ async def sync_db(
|
|
|
23
23
|
async def replicate_table_fn(namespace: str, table: str) -> None:
|
|
24
24
|
await sql_replicator.synchronize(namespace, table)
|
|
25
25
|
|
|
26
|
-
await session.execute_operation_on_tables(
|
|
26
|
+
await session.execute_operation_on_tables(
|
|
27
|
+
namespace, table_names, "syncdb", replicate_table_fn
|
|
28
|
+
)
|