microsoft / jupyter-Kqlmagic

Extension (Magic) to Jupyter notebook and Jupyter lab, that enable notebook experience working with Kusto, ApplicationInsights, and LogAnalytics data.
Other
85 stars 31 forks source link

`validate_database_name` fails when Azure app with minimum required permissions queries ARIA database #106

Closed debonte closed 5 months ago

debonte commented 6 months ago

Code Sample, a copy-pastable example if possible

IPython.get_ipython().run_line_magic('env', 'KQLMAGIC_LOAD_MODE=silent')
IPython.get_ipython().run_line_magic('env', 'KQLMAGIC_CONFIGURATION="show_query_time=False;show_init_banner=False;check_magic_version=False;show_what_new=False;show_conn_info=None;try_azcli_login=True;display_limit=50;timeout=240"')
IPython.get_ipython().run_line_magic('reload_ext', 'Kqlmagic')
IPython.get_ipython().run_line_magic('env', 'KQLMAGIC_NOTEBOOK_APP=VisualStudioCode')

connection = f"aria://tenant='{TENANT}';clientid='{CLIENT_ID}';clientsecret='{CLIENT_SECRET}';database='{database}'"

IPython.get_ipython().run_line_magic('kql', f'-conn=\"{connection}\" -!se')

Problem description

I'm attempting to query an ARIA database using an Azure app id. The ARIA admins have granted that app the "monitor" and "viewer" roles for a single database. When connecting, kqlmagic fails with KqlEngineError: Database wasn't found in cluster. because the response to .show databases doesn't include the target database.

The request that is being sent is:

{
  "csl": ".show databases",
  "db": "NetDefaultDB"
}

And the response looks good except that the array of databases ("Rows") is empty:

{
    "Tables": [
        {
            "Columns": [
                {
                    "ColumnName": "DatabaseName",
                    "ColumnType": "string",
                    "DataType": "String"
                },
                {
                    "ColumnName": "PersistentStorage",
                    "ColumnType": "string",
                    "DataType": "String"
                },
                {
                    "ColumnName": "Version",
                    "ColumnType": "string",
                    "DataType": "String"
                },
                {
                    "ColumnName": "IsCurrent",
                    "ColumnType": "bool",
                    "DataType": "Boolean"
                },
                {
                    "ColumnName": "DatabaseAccessMode",
                    "ColumnType": "string",
                    "DataType": "String"
                },
                {
                    "ColumnName": "PrettyName",
                    "ColumnType": "string",
                    "DataType": "String"
                },
                {
                    "ColumnName": "ReservedSlot1",
                    "ColumnType": "bool",
                    "DataType": "Boolean"
                },
                {
                    "ColumnName": "DatabaseId",
                    "ColumnType": "guid",
                    "DataType": "Guid"
                },
                {
                    "ColumnName": "InTransitionTo",
                    "ColumnType": "string",
                    "DataType": "String"
                },
                {
                    "ColumnName": "SuspensionState",
                    "ColumnType": "string",
                    "DataType": "String"
                }
            ],
            "Rows": [],
            "TableName": "Table_0"
        }
    ]
}

If I change _get_databases_by_pretty_name to use self.database_name instead of "NetDefaultDB", then the response contains the expected database:

Request:

{
  "csl": ".show databases",
  "db": "SNIP-my-database"
}

Response:

{
    "Tables": [
        {
            "Columns": [
                {
                    "ColumnName": "DatabaseName",
                    "ColumnType": "string",
                    "DataType": "String"
                },
                {
                    "ColumnName": "PersistentStorage",
                    "ColumnType": "string",
                    "DataType": "String"
                },
                {
                    "ColumnName": "Version",
                    "ColumnType": "string",
                    "DataType": "String"
                },
                {
                    "ColumnName": "IsCurrent",
                    "ColumnType": "bool",
                    "DataType": "Boolean"
                },
                {
                    "ColumnName": "DatabaseAccessMode",
                    "ColumnType": "string",
                    "DataType": "String"
                },
                {
                    "ColumnName": "PrettyName",
                    "ColumnType": "string",
                    "DataType": "String"
                },
                {
                    "ColumnName": "ReservedSlot1",
                    "ColumnType": "bool",
                    "DataType": "Boolean"
                },
                {
                    "ColumnName": "DatabaseId",
                    "ColumnType": "guid",
                    "DataType": "Guid"
                },
                {
                    "ColumnName": "InTransitionTo",
                    "ColumnType": "string",
                    "DataType": "String"
                },
                {
                    "ColumnName": "SuspensionState",
                    "ColumnType": "string",
                    "DataType": "String"
                }
            ],
            "Rows": [
                [
                    SNIP-expected-database-info
                ]
            ],
            "TableName": "Table_0"
        }
    ]
}

For what it's worth, if I use my domain account, .show databases with "NetDefaultDB" gives me a large number of databases (all of the databases in the cluster?) because my domain account has more access. And if I replace "NetDefaultDB" with a specific database name, .show databases still returns all of those databases. That surprised me. I was expecting that it would only return the specified database.

I'd suggest that the self.execute call in KustoEngine._get_databases_by_pretty_name be changed from:

        kql_response = self.execute(query, database="NetDefaultDB", **options)

to:

        kql_response = self.execute(query, database=self.database_name or "NetDefaultDB", **options)
%kql --bugreport ```text { "Kqlmagic_last_execution": { "args": { "cell": null, "line": "-conn=\"aria://tenant='SNIP-my-tenant-guid';clientid='SNIP-my-client-id';clientsecret='SNIP-my-client-secret';database='SNIP-my-database'\" -!se", "override_connection": null, "override_options": null, "override_query_properties": null, "override_result_set": null, "override_vars": null }, "connection": "SNIP-my-database@aria", "error": "Database wasn't found in cluster.", "log": [ "Kqlmagic_core::execute - input: \n\rline: -conn=\"aria://tenant='SNIP-my-tenant-guid';clientid='SNIP-my-client-id';clientsecret='SNIP-my-client-secret';database='SNIP-my-database'\" -!se\n\rcell:\n\rNone", "Kqlmagic_core::execute - parsed_queries: [{'connection_string': \"aria://tenant='SNIP-my-tenant-guid';clientid='SNIP-my-client-id';clientsecret='SNIP-my-client-secret';database='SNIP-my-database'\", 'query': '', 'options': {'auto_dataframe': False, 'short_errors': False, 'feedback': True, 'show_conn_info': None, 'columns_to_local_vars': False, 'show_query_time': False, 'show_query': False, 'show_query_link': False, 'query_link_destination': 'Kusto.WebExplorer', 'enable_suppress_result': True, 'plotly_fs_includejs': False, 'popup_window': False, 'auto_limit': 0, 'display_limit': 50, 'timeout': 240, 'prettytable_style': 'DEFAULT', 'last_raw_result_var': '_kql_raw_result_', 'table_package': 'prettytable', 'plot_package': 'plotly', 'dsn_filename': 'odbc.ini', 'validate_connection_string': True, 'auto_popup_schema': True, 'json_display': 'formatted', 'schema_json_display': 'auto', 'palette_desaturation': 1.0, 'params_dict': None, 'palette_name': 'tab10', 'cache': None, 'use_cache': None, 'temp_folder_name': 'temp_files', 'cache_folder_name': 'cache_files', 'export_folder_name': 'exported_files', 'add_kql_ref_to_help': True, 'add_schema_to_help': True, 'enable_add_items_to_help': True, 'notebook_app': 'visualstudiocode', 'debug': False, 'check_magic_version': False, 'show_what_new': False, 'show_init_banner': False, 'is_kernel_intializtion': False, 'warn_missing_dependencies': True, 'warn_missing_env_variables': True, 'allow_single_line_cell': True, 'allow_py_comments_before_cell': True, 'kqlmagic_kernel': False, 'extras_require': 'default', 'test_notebook_app': 'none', 'cloud': 'public', 'enable_sso': False, 'sso_db_gc_interval': 168, 'auth_use_http_client': False, 'try_azcli_login': True, 'try_azcli_login_by_profile': False, 'try_vscode_login': False, 'try_azcli_login_subscription': None, 'try_token': None, 'try_msi': None, 'request_id_tag': None, 'request_app_tag': None, 'request_user_tag': None, 'request_user_agent_tag': None, 'request_cache_max_age': 0, 'device_code_login_notification': 'auto', 'device_code_notification_email': '', 'save_as': None, 'save_to': None, 'query_properties': {}, 'palette_colors': 10, 'palette_reverse': False, 'popup_schema': False, 'display_id': False, 'display_handlers': {}, 'popup_interaction': 'auto', 'temp_files_server': 'auto', 'temp_files_server_address': None, 'kernel_location': 'local', 'kernel_id': 'v2-290805m3clbYviwyV', 'notebook_service_address': None, 'dynamic_to_dataframe': 'object', 'temp_folder_location': 'user_dir', 'plotly_layout': None, 'auth_token_warnings': False, 'enable_curly_brackets_params': False, 'nop': False, 'assign_var': None, 'cursor_var': None, 'is_magic': True, 'code_auth_interactive_mode': 'device_code'}, 'command': {}, 'last_query': True}]", "FilesServerManagement::startServer init: server_py_code_path: SNIP-my-workspace\\dashboard\\Kqlmagic\\my_files_server.py, server_url: http://127.0.0.1:61334, base_folder: C:\\Users\\SNIP-my-username, folders: .kqlmagic/temp_files/v2-290805m3clbYviwyV, _kernel_id: v2-290805m3clbYviwyV, liveness_mode: parent_process_state", "FilesServerManagement::startServer", "FilesServerManagement::startServer with show_window: False", "FilesServerManagement::startServer start sub process command: SNIP-my-workspace/.venv/Scripts/python.exe \"SNIP-my-workspace\\\\dashboard\\\\Kqlmagic\\\\my_files_server.py\" -protocol=http -host=127.0.0.1 -port=61334 -base_folder=\"C:\\\\Users\\\\SNIP-my-username\" -folders=\".kqlmagic/temp_files/v2-290805m3clbYviwyV\" -parent_id=\"46152\" -parent_kernel_id=\"v2-290805m3clbYviwyV\" -liveness_mode=\"parent_process_state\" -clean=\"folders\"", "FilesServerManagement::startServer try_register_to_ipython_atexit: _abortServer(http://127.0.0.1:61334, v2-290805m3clbYviwyV)", "FilesServerManagement::pingServer: url: http://127.0.0.1:61334/ping?kernelid=v2-290805m3clbYviwyV, result: True", "kql_engine.py -_parse_common_connection_str - params: conn_str: aria://tenant='SNIP-my-tenant-guid';clientid='SNIP-my-client-id';clientsecret='SNIP-my-client-secret';database='SNIP-my-database'; current: None, uri_schema_name: aria;mandatory_key: database, valid_keys_combinations: [['database', 'alias', 'tenant', 'aadurl', 'clientid', 'clientsecret'], ['database', 'alias', 'tenant', 'aadurl', 'clientid', 'certificate', 'certificatethumbprint'], ['database', 'alias', 'tenant', 'aadurl', 'clientid', 'code'], ['database', 'alias', 'tenant', 'aadurl', 'code'], ['database', 'alias', 'tenant', 'aadurl', 'clientid', 'username', 'password'], ['database', 'alias', 'tenant', 'aadurl', 'username', 'password'], ['database', 'alias', 'anonymous']]", "kql_engine.py - _parse_connection_str - params: conn_str: aria://tenant='SNIP-my-tenant-guid';clientid='SNIP-my-client-id';clientsecret='SNIP-my-client-secret';database='SNIP-my-database'", "kql_engine.py - _parse_connection_str - parsed_conn_kv: {'tenant': 'SNIP-my-tenant-guid', 'clientid': 'SNIP-my-client-id', 'clientsecret': 'SNIP-my-client-secret', 'database': 'SNIP-my-database'} (return of Parser.parse_and_get_kv_string)", "kql_engine.py -_parse_common_connection_str - matched_keys_set: {'database', 'tenant', 'clientid', 'clientsecret'}", "kql_engine.py - _find_combination - found these valid_combinations: [['database', 'alias', 'tenant', 'aadurl', 'clientid', 'clientsecret']]", "kql_engine.py - _find_combination - chose combination_keys_list - shortest out of valid_combinations: ['database', 'alias', 'tenant', 'aadurl', 'clientid', 'clientsecret']", "kql_engine.py - _inherit_keys - add inherited to combination_keys_set and now inherit_keys_set: {'aadurl'}", "kql_engine.py - _check_for_restricted_values - make sure that all required keys are with proper value: {'database', 'tenant', 'clientid', 'clientsecret'}", "kql_engine.py -_set_and_check_for_cluster_name - setting attributes - cluster_name: aria", "kql_engine.py -_set_and_check_for_cluster_name - setting attributes - cluster_friendly_name: aria", "kql_engine.py - _set_and_check_for_database_name - setting attributes - database_name: SNIP-my-database", "kql_engine.py - _set_and_check_for_database_name - setting attributes - database_friendly_name: SNIP-my-database", "kql_engine.py - _set_and_check_for_alias - setting attributes - alias: SNIP-my-database", "kql_engine.py - _create_and_set_bind_url - setting attributes - bind_url:aria://tenant('SNIP-my-tenant-guid').aadurl('None').clientid('SNIP-my-client-id').clientsecret('SNIP-my-client-secret')", "_MyAadHelper::_get_azcli_token_by_profile failed to get token - subscription: 'None', tenant: 'SNIP-my-tenant-guid'", "_MyAadHelper::_get_azcli_token failed to get token - subscription: 'None', tenant: 'SNIP-my-tenant-guid'", "Account(s) exists in cache, probably with token too. Let's try.", "No suitable token exists in cache. Let's get a new one from AAD.", "KustoClient::execute - POST request - url: https://kusto.aria.microsoft.com/v1/rest/mgmt, headers: {'User-Agent': 'Kqlmagic/0.1.114.post22', 'x-ms-client-version': 'Kqlmagic.Python.Client:0.1.114.post22', 'x-ms-client-request-id': 'Kqlmagic.execute;b6d7eb96-0f24-4e6e-8600-efd463833821/4cced200-f228-475c-adb6-5aa78b4f5f68/AzureDataExplorer', 'x-ms-app': 'Kqlmagic;visualstudiocode', 'Authorization': '...', 'Fed': 'True', 'Cache-Control': 'no-cache'}, payload: {'db': 'NetDefaultDB', 'csl': '.show databases'}, timeout: 240", "POST Request: https://kusto.aria.microsoft.com/v1/rest/mgmt, params: None, payload: {'db': 'NetDefaultDB', 'csl': '.show databases'}, headers: {'User-Agent': 'Kqlmagic/0.1.114.post22', 'x-ms-client-version': 'Kqlmagic.Python.Client:0.1.114.post22', 'x-ms-client-request-id': 'Kqlmagic.execute;b6d7eb96-0f24-4e6e-8600-efd463833821/4cced200-f228-475c-adb6-5aa78b4f5f68/AzureDataExplorer', 'x-ms-app': 'Kqlmagic;visualstudiocode', 'Authorization': 'Bearer SNIP-my-token', 'Fed': 'True', 'Cache-Control': 'no-cache', 'Content-Type': 'application/json'}", "POST Response: 200 b'{\"Tables\":[{\"TableName\":\"Table_0\",\"Columns\":[{\"ColumnName\":\"DatabaseName\",\"DataType\":\"String\",\"ColumnType\":\"string\"},{\"ColumnName\":\"PersistentStorage\",\"DataType\":\"String\",\"ColumnType\":\"string\"},{\"ColumnName\":\"Version\",\"DataType\":\"String\",\"ColumnType\":\"string\"},{\"ColumnName\":\"IsCurrent\",\"DataType\":\"Boolean\",\"ColumnType\":\"bool\"},{\"ColumnName\":\"DatabaseAccessMode\",\"DataType\":\"String\",\"ColumnType\":\"string\"},{\"ColumnName\":\"PrettyName\",\"DataType\":\"String\",\"ColumnType\":\"string\"},{\"ColumnName\":\"ReservedSlot1\",\"DataType\":\"Boolean\",\"ColumnType\":\"bool\"},{\"ColumnName\":\"DatabaseId\",\"DataType\":\"Guid\",\"ColumnType\":\"guid\"},{\"ColumnName\":\"InTransitionTo\",\"DataType\":\"String\",\"ColumnType\":\"string\"},{\"ColumnName\":\"SuspensionState\",\"DataType\":\"String\",\"ColumnType\":\"string\"}],\"Rows\":[]}]}'", "KustoClient::execute - response - status: 200, headers: {'Content-Length': '224', 'Content-Type': 'application/json', 'Content-Encoding': 'gzip', 'Server': 'Microsoft-HTTPAPI/2.0', 'mise-correlation-id': 'a96dcd2c-ad7f-4923-aea0-14634cf62dab', 'x-ms-client-request-id': 'Kqlmagic.execute;b6d7eb96-0f24-4e6e-8600-efd463833821/4cced200-f228-475c-adb6-5aa78b4f5f68/AzureDataExplorer', 'Date': 'Wed, 28 Feb 2024 22:38:40 GMT'}, payload: {\"Tables\":[{\"TableName\":\"Table_0\",\"Columns\":[{\"ColumnName\":\"DatabaseName\",\"DataType\":\"String\",\"ColumnType\":\"string\"},{\"ColumnName\":\"PersistentStorage\",\"DataType\":\"String\",\"ColumnType\":\"string\"},{\"ColumnName\":\"Version\",\"DataType\":\"String\",\"ColumnType\":\"string\"},{\"ColumnName\":\"IsCurrent\",\"DataType\":\"Boolean\",\"ColumnType\":\"bool\"},{\"ColumnName\":\"DatabaseAccessMode\",\"DataType\":\"String\",\"ColumnType\":\"string\"},{\"ColumnName\":\"PrettyName\",\"DataType\":\"String\",\"ColumnType\":\"string\"},{\"ColumnName\":\"ReservedSlot1\",\"DataType\":\"Boolean\",\"ColumnType\":\"bool\"},{\"ColumnName\":\"DatabaseId\",\"DataType\":\"Guid\",\"ColumnType\":\"guid\"},{\"ColumnName\":\"InTransitionTo\",\"DataType\":\"String\",\"ColumnType\":\"string\"},{\"ColumnName\":\"SuspensionState\",\"DataType\":\"String\",\"ColumnType\":\"string\"}],\"Rows\":[]}]}", "Kqlmagic_core::execute - failed - command: None param: None" ], "query": { "request": { "endpoint": "https://kusto.aria.microsoft.com/v1/rest/mgmt", "headers": { "Authorization": "...", "Cache-Control": "no-cache", "Fed": "True", "User-Agent": "Kqlmagic/0.1.114.post22", "x-ms-app": "Kqlmagic;visualstudiocode", "x-ms-client-request-id": "Kqlmagic.execute;b6d7eb96-0f24-4e6e-8600-efd463833821/4cced200-f228-475c-adb6-5aa78b4f5f68/AzureDataExplorer", "x-ms-client-version": "Kqlmagic.Python.Client:0.1.114.post22" }, "payload": { "csl": ".show databases", "db": "NetDefaultDB" }, "timeout": 240 }, "response": { "status_code": 200 } }, "traceback": [ "Traceback (most recent call last):", " File \"SNIP-my-workspace\\dashboard\\Kqlmagic\\kql_magic_core.py\", line 1342, in _execute_query", " engine.validate_database_name(**options)", " File \"SNIP-my-workspace\\dashboard\\Kqlmagic\\kql_engine.py\", line 245, in validate_database_name", " raise KqlEngineError(\"Database wasn't found in cluster.\")", "Kqlmagic.exceptions.KqlEngineError: Database wasn't found in cluster.", "", "During handling of the above exception, another exception occurred:", "", "Traceback (most recent call last):", " File \"SNIP-my-workspace\\dashboard\\Kqlmagic\\kql_magic_core.py\", line 869, in execute", " result = self._execute_query(parsed, user_ns, result_set=override_result_set, override_vars=override_vars)", " File \"SNIP-my-workspace\\dashboard\\Kqlmagic\\kql_magic_core.py\", line 1534, in _execute_query", " raise ShortError(e, messages)", "Kqlmagic.kql_magic_core.ShortError: Database wasn't found in cluster." ] }, "kqlmagic": { "version": "0.1.114.post22" }, "kqlmagic_connections:": { "SNIP-my-database@aria": { "auth": { "aad_login_url": "https://login.microsoftonline.com", "authentication_method": "aad_application_key", "authority_uri": "https://login.microsoftonline.com/SNIP-my-tenant-guid", "client_app_type": "confidential", "client_id": "SNIP-my-client-id", "client_secret": "*****", "scopes": [ "['https://kusto.aria.microsoft.com']/.default" ], "username": "SNIP-user-guid" }, "cluster_name": "aria", "database_name": "SNIP-my-database", "parsed_conn": { "clientid": "SNIP-my-client-id", "clientsecret": "*****", "database": "SNIP-my-database", "tenant": "SNIP-my-tenant-guid" } } }, "kqlmagic_default_env": { "KQLMAGIC_CONFIGURATION": "\"show_query_time=False;show_init_banner=False;check_magic_version=False;show_what_new=False;show_conn_info=None;try_azcli_login=True;display_limit=50;timeout=240\"", "KQLMAGIC_LOAD_MODE": "silent" }, "kqlmagic_default_options": { "add_kql_ref_to_help": true, "add_schema_to_help": true, "allow_py_comments_before_cell": true, "allow_single_line_cell": true, "assign_var": null, "auth_token_warnings": false, "auth_use_http_client": false, "auto_dataframe": false, "auto_limit": 0, "auto_popup_schema": true, "cache": null, "cache_folder_name": "cache_files", "check_magic_version": false, "cloud": "public", "code_auth_interactive_mode": "device_code", "columns_to_local_vars": false, "cursor_var": null, "debug": false, "device_code_login_notification": "auto", "device_code_notification_email": "", "display_limit": 50, "dsn_filename": "odbc.ini", "dynamic_to_dataframe": "object", "enable_add_items_to_help": true, "enable_curly_brackets_params": false, "enable_sso": false, "enable_suppress_result": true, "export_folder_name": "exported_files", "extras_require": "default", "feedback": true, "is_kernel_intializtion": false, "is_magic": true, "json_display": "formatted", "kernel_id": "v2-290805m3clbYviwyV", "kernel_location": "local", "kqlmagic_kernel": false, "last_raw_result_var": "_kql_raw_result_", "notebook_app": "visualstudiocode", "notebook_service_address": null, "palette_colors": 10, "palette_desaturation": 1.0, "palette_name": "tab10", "plot_package": "plotly", "plotly_config": null, "plotly_fs_includejs": false, "plotly_layout": null, "popup_interaction": "auto", "prettytable_style": "DEFAULT", "query_link_destination": "Kusto.WebExplorer", "request_app_tag": null, "request_cache_max_age": 0, "request_id_tag": null, "request_user_agent_tag": null, "request_user_tag": null, "schema_json_display": "auto", "short_errors": true, "show_conn_info": null, "show_init_banner": false, "show_query": false, "show_query_link": false, "show_query_time": false, "show_what_new": false, "sso_db_gc_interval": 168, "table_package": "prettytable", "temp_files_server": "auto", "temp_files_server_address": "http://127.0.0.1:61334", "temp_folder_location": "user_dir", "temp_folder_name": "temp_files", "test_notebook_app": "none", "timeout": 240, "try_azcli_login": true, "try_azcli_login_by_profile": false, "try_azcli_login_subscription": null, "try_kqlmaic_sso": false, "try_msi": null, "try_token": null, "try_vscode_login": false, "use_cache": null, "validate_connection_string": true, "warn_missing_dependencies": true, "warn_missing_env_variables": true }, "packages": { "azure-common": "1.1.28", "azure-identity": "1.15.0", "beautifulsoup4": "4.12.3", "cryptography": "42.0.5", "flask": "3.0.2", "ipykernel": "6.29.3", "ipython": "8.22.1", "ipywidgets": "8.1.2", "isodate": "0.6.1", "lxml": "5.1.0", "markdown": "3.5.2", "matplotlib": "3.8.3", "msal": "1.27.0", "msal_extensions": "1.1.0", "msrestazure": "0.6.4", "pandas": "2.2.1", "password_strength": "0.0.3.post2", "plotly": "5.19.0", "prettytable": "3.10.0", "psutil": "5.9.8", "pygments": "2.17.2", "pyperclip": "1.8.2", "python-dateutil": "2.8.2", "requests": "2.31.0", "setuptools": "57.4.0", "traitlets": "5.14.1" }, "platform": { "release": "10", "system": "Windows" }, "python": { "branch": "tags/v3.10.0", "name": "CPython", "version": "3.10.0" } } ```
pip freeze ```text accessible-pygments==0.0.4 adal==1.2.7 alabaster==0.7.16 altair==5.2.0 anyio==4.3.0 argon2-cffi==23.1.0 argon2-cffi-bindings==21.2.0 arrow==1.3.0 asttokens==2.4.1 async-lru==2.0.4 attrs==23.2.0 azure-common==1.1.28 azure-core==1.30.0 azure-data-tables==12.5.0 azure-identity==1.15.0 Babel==2.14.0 beautifulsoup4==4.12.3 bleach==6.1.0 blinker==1.7.0 bokeh==2.4.3 certifi==2024.2.2 cffi==1.16.0 charset-normalizer==3.3.2 click==8.1.7 colorama==0.4.6 comm==0.2.1 contourpy==1.2.0 cryptography==42.0.5 cycler==0.12.1 debugpy==1.8.1 decorator==5.1.1 defusedxml==0.7.1 Deprecated==1.2.14 docutils==0.18.1 exceptiongroup==1.2.0 executing==2.0.1 fastjsonschema==2.19.1 Flask==3.0.2 fonttools==4.49.0 fqdn==1.5.1 greenlet==3.0.3 h11==0.14.0 httpcore==1.0.4 httpx==0.27.0 idna==3.6 imagesize==1.4.1 importlib-metadata==7.0.1 ipykernel==6.29.3 ipython==8.22.1 ipywidgets==8.1.2 isodate==0.6.1 isoduration==20.11.0 itsdangerous==2.1.2 jedi==0.19.1 Jinja2==3.1.3 json5==0.9.17 jsonpointer==2.4 jsonschema==4.21.1 jsonschema-specifications==2023.12.1 jupyter==1.0.0 jupyter-book==0.15.1 jupyter-cache==0.6.1 jupyter-console==6.6.3 jupyter-events==0.9.0 jupyter-lsp==2.2.3 jupyter_bokeh==3.0.5 jupyter_client==8.6.0 jupyter_core==5.7.1 jupyter_server==2.12.5 jupyter_server_terminals==0.5.2 jupyterlab==4.1.2 jupyterlab_pygments==0.3.0 jupyterlab_server==2.25.3 jupyterlab_widgets==3.0.10 kiwisolver==1.4.5 Kqlmagic==0.1.114.post22 latexcodec==2.0.1 linkify-it-py==2.0.3 lxml==5.1.0 Markdown==3.5.2 markdown-it-py==2.2.0 MarkupSafe==2.1.5 matplotlib==3.8.3 matplotlib-inline==0.1.6 mdit-py-plugins==0.3.5 mdurl==0.1.2 mistune==3.0.2 msal==1.27.0 msal-extensions==1.1.0 msrest==0.7.1 msrestazure==0.6.4 multidict==6.0.5 myst-nb==0.17.2 myst-parser==0.18.1 nbclient==0.7.4 nbconvert==7.16.1 nbformat==5.9.2 nest-asyncio==1.6.0 notebook==7.1.1 notebook_shim==0.2.4 numpy==1.26.4 oauthlib==3.2.2 overrides==7.7.0 packaging==23.2 pandas==2.2.1 pandocfilters==1.5.1 panel==0.14.3 param==1.13.0 parso==0.8.3 password-strength==0.0.3.post2 pexpect==4.9.0 pillow==10.2.0 platformdirs==4.2.0 plotly==5.19.0 portalocker==2.8.2 prettytable==3.10.0 prometheus_client==0.20.0 prompt-toolkit==3.0.43 psutil==5.9.8 ptyprocess==0.7.0 pure-eval==0.2.2 pybtex==0.24.0 pybtex-docutils==1.0.3 pycparser==2.21 pyct==0.5.0 pydata-sphinx-theme==0.13.1 PyGithub==1.59.0 Pygments==2.17.2 PyJWT==2.8.0 PyNaCl==1.5.0 pyparsing==3.1.1 pyperclip==1.8.2 python-dateutil==2.8.2 python-json-logger==2.0.7 pytz==2024.1 pyviz_comms==3.0.1 pywin32==306 pywinpty==2.0.13 PyYAML==6.0.1 pyzmq==25.1.2 qtconsole==5.5.1 QtPy==2.4.1 referencing==0.33.0 requests==2.31.0 requests-oauthlib==1.3.1 rfc3339-validator==0.1.4 rfc3986-validator==0.1.1 rpds-py==0.18.0 Send2Trash==1.8.2 six==1.16.0 sniffio==1.3.1 snowballstemmer==2.2.0 soupsieve==2.5 sourcemap==0.2.1 Sphinx==5.0.2 sphinx-book-theme==1.0.0 sphinx-comments==0.0.3 sphinx-copybutton==0.5.2 sphinx-jupyterbook-latex==0.5.2 sphinx-multitoc-numbering==0.1.3 sphinx-thebe==0.2.1 sphinx-togglebutton==0.3.2 sphinx_design==0.3.0 sphinx_external_toc==0.3.1 sphinxcontrib-applehelp==1.0.8 sphinxcontrib-bibtex==2.5.0 sphinxcontrib-devhelp==1.0.6 sphinxcontrib-htmlhelp==2.0.5 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.7 sphinxcontrib-serializinghtml==1.1.10 SQLAlchemy==2.0.27 stack-data==0.6.3 tabulate==0.9.0 tenacity==8.2.3 terminado==0.18.0 tinycss2==1.2.1 tomli==2.0.1 toolz==0.12.1 tornado==6.4 tqdm==4.66.2 traitlets==5.14.1 types-python-dateutil==2.8.19.20240106 typing_extensions==4.10.0 tzdata==2024.1 uc-micro-py==1.0.3 uri-template==1.3.0 urllib3==2.2.1 wcwidth==0.2.13 webcolors==1.13 webencodings==0.5.1 websocket-client==1.7.0 Werkzeug==3.0.1 widgetsnbextension==4.0.10 wrapt==1.16.0 yarl==1.9.4 zipp==3.17.0 ```