snowflake-cli-labs 2.8.0rc1__py3-none-any.whl → 2.8.2__py3-none-any.whl
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.
- README.md +21 -0
- {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-2.8.2.dist-info}/METADATA +7 -95
- snowflake_cli_labs-2.8.2.dist-info/RECORD +5 -0
- snowflake/cli/__about__.py +0 -17
- snowflake/cli/__init__.py +0 -13
- snowflake/cli/api/__init__.py +0 -48
- snowflake/cli/api/cli_global_context.py +0 -390
- snowflake/cli/api/commands/__init__.py +0 -13
- snowflake/cli/api/commands/alias.py +0 -23
- snowflake/cli/api/commands/decorators.py +0 -354
- snowflake/cli/api/commands/execution_metadata.py +0 -40
- snowflake/cli/api/commands/experimental_behaviour.py +0 -19
- snowflake/cli/api/commands/flags.py +0 -640
- snowflake/cli/api/commands/project_initialisation.py +0 -65
- snowflake/cli/api/commands/snow_typer.py +0 -237
- snowflake/cli/api/commands/typer_pre_execute.py +0 -26
- snowflake/cli/api/config.py +0 -348
- snowflake/cli/api/console/__init__.py +0 -17
- snowflake/cli/api/console/abc.py +0 -89
- snowflake/cli/api/console/console.py +0 -134
- snowflake/cli/api/console/enum.py +0 -17
- snowflake/cli/api/constants.py +0 -79
- snowflake/cli/api/errno.py +0 -27
- snowflake/cli/api/exceptions.py +0 -164
- snowflake/cli/api/feature_flags.py +0 -55
- snowflake/cli/api/identifiers.py +0 -154
- snowflake/cli/api/output/__init__.py +0 -13
- snowflake/cli/api/output/formats.py +0 -20
- snowflake/cli/api/output/types.py +0 -118
- snowflake/cli/api/plugins/__init__.py +0 -13
- snowflake/cli/api/plugins/command/__init__.py +0 -72
- snowflake/cli/api/plugins/command/plugin_hook_specs.py +0 -21
- snowflake/cli/api/plugins/plugin_config.py +0 -32
- snowflake/cli/api/project/__init__.py +0 -13
- snowflake/cli/api/project/definition.py +0 -84
- snowflake/cli/api/project/definition_manager.py +0 -134
- snowflake/cli/api/project/errors.py +0 -56
- snowflake/cli/api/project/project_verification.py +0 -23
- snowflake/cli/api/project/schemas/__init__.py +0 -13
- snowflake/cli/api/project/schemas/entities/application_entity.py +0 -44
- snowflake/cli/api/project/schemas/entities/application_package_entity.py +0 -66
- snowflake/cli/api/project/schemas/entities/common.py +0 -78
- snowflake/cli/api/project/schemas/entities/entities.py +0 -30
- snowflake/cli/api/project/schemas/identifier_model.py +0 -49
- snowflake/cli/api/project/schemas/native_app/__init__.py +0 -13
- snowflake/cli/api/project/schemas/native_app/application.py +0 -62
- snowflake/cli/api/project/schemas/native_app/native_app.py +0 -93
- snowflake/cli/api/project/schemas/native_app/package.py +0 -78
- snowflake/cli/api/project/schemas/native_app/path_mapping.py +0 -65
- snowflake/cli/api/project/schemas/project_definition.py +0 -199
- snowflake/cli/api/project/schemas/snowpark/__init__.py +0 -13
- snowflake/cli/api/project/schemas/snowpark/argument.py +0 -28
- snowflake/cli/api/project/schemas/snowpark/callable.py +0 -69
- snowflake/cli/api/project/schemas/snowpark/snowpark.py +0 -36
- snowflake/cli/api/project/schemas/streamlit/__init__.py +0 -13
- snowflake/cli/api/project/schemas/streamlit/streamlit.py +0 -46
- snowflake/cli/api/project/schemas/template.py +0 -77
- snowflake/cli/api/project/schemas/updatable_model.py +0 -194
- snowflake/cli/api/project/util.py +0 -261
- snowflake/cli/api/rendering/__init__.py +0 -13
- snowflake/cli/api/rendering/jinja.py +0 -112
- snowflake/cli/api/rendering/project_definition_templates.py +0 -39
- snowflake/cli/api/rendering/project_templates.py +0 -98
- snowflake/cli/api/rendering/sql_templates.py +0 -60
- snowflake/cli/api/rest_api.py +0 -172
- snowflake/cli/api/sanitizers.py +0 -43
- snowflake/cli/api/secure_path.py +0 -362
- snowflake/cli/api/secure_utils.py +0 -29
- snowflake/cli/api/sql_execution.py +0 -260
- snowflake/cli/api/utils/__init__.py +0 -13
- snowflake/cli/api/utils/cursor.py +0 -34
- snowflake/cli/api/utils/definition_rendering.py +0 -383
- snowflake/cli/api/utils/dict_utils.py +0 -73
- snowflake/cli/api/utils/error_handling.py +0 -23
- snowflake/cli/api/utils/graph.py +0 -97
- snowflake/cli/api/utils/models.py +0 -63
- snowflake/cli/api/utils/naming_utils.py +0 -13
- snowflake/cli/api/utils/path_utils.py +0 -36
- snowflake/cli/api/utils/templating_functions.py +0 -144
- snowflake/cli/api/utils/types.py +0 -35
- snowflake/cli/app/__init__.py +0 -22
- snowflake/cli/app/__main__.py +0 -31
- snowflake/cli/app/api_impl/__init__.py +0 -13
- snowflake/cli/app/api_impl/plugin/__init__.py +0 -13
- snowflake/cli/app/api_impl/plugin/plugin_config_provider_impl.py +0 -66
- snowflake/cli/app/build_and_push.sh +0 -8
- snowflake/cli/app/cli_app.py +0 -243
- snowflake/cli/app/commands_registration/__init__.py +0 -33
- snowflake/cli/app/commands_registration/builtin_plugins.py +0 -54
- snowflake/cli/app/commands_registration/command_plugins_loader.py +0 -169
- snowflake/cli/app/commands_registration/commands_registration_with_callbacks.py +0 -105
- snowflake/cli/app/commands_registration/exception_logging.py +0 -26
- snowflake/cli/app/commands_registration/threadsafe.py +0 -48
- snowflake/cli/app/commands_registration/typer_registration.py +0 -153
- snowflake/cli/app/constants.py +0 -19
- snowflake/cli/app/dev/__init__.py +0 -13
- snowflake/cli/app/dev/commands_structure.py +0 -48
- snowflake/cli/app/dev/docs/__init__.py +0 -13
- snowflake/cli/app/dev/docs/commands_docs_generator.py +0 -100
- snowflake/cli/app/dev/docs/generator.py +0 -35
- snowflake/cli/app/dev/docs/project_definition_docs_generator.py +0 -58
- snowflake/cli/app/dev/docs/project_definition_generate_json_schema.py +0 -227
- snowflake/cli/app/dev/docs/template_utils.py +0 -23
- snowflake/cli/app/dev/docs/templates/definition_description.rst.jinja2 +0 -38
- snowflake/cli/app/dev/docs/templates/overview.rst.jinja2 +0 -9
- snowflake/cli/app/dev/docs/templates/usage.rst.jinja2 +0 -57
- snowflake/cli/app/dev/pycharm_remote_debug.py +0 -46
- snowflake/cli/app/loggers.py +0 -199
- snowflake/cli/app/main_typer.py +0 -62
- snowflake/cli/app/printing.py +0 -181
- snowflake/cli/app/snow_connector.py +0 -243
- snowflake/cli/app/telemetry.py +0 -189
- snowflake/cli/plugins/__init__.py +0 -13
- snowflake/cli/plugins/connection/__init__.py +0 -13
- snowflake/cli/plugins/connection/commands.py +0 -330
- snowflake/cli/plugins/connection/plugin_spec.py +0 -30
- snowflake/cli/plugins/connection/util.py +0 -179
- snowflake/cli/plugins/cortex/__init__.py +0 -13
- snowflake/cli/plugins/cortex/commands.py +0 -327
- snowflake/cli/plugins/cortex/constants.py +0 -17
- snowflake/cli/plugins/cortex/manager.py +0 -189
- snowflake/cli/plugins/cortex/plugin_spec.py +0 -30
- snowflake/cli/plugins/cortex/types.py +0 -22
- snowflake/cli/plugins/git/__init__.py +0 -13
- snowflake/cli/plugins/git/commands.py +0 -305
- snowflake/cli/plugins/git/manager.py +0 -96
- snowflake/cli/plugins/git/plugin_spec.py +0 -30
- snowflake/cli/plugins/init/__init__.py +0 -13
- snowflake/cli/plugins/init/commands.py +0 -244
- snowflake/cli/plugins/init/plugin_spec.py +0 -30
- snowflake/cli/plugins/nativeapp/__init__.py +0 -13
- snowflake/cli/plugins/nativeapp/artifacts.py +0 -742
- snowflake/cli/plugins/nativeapp/codegen/__init__.py +0 -13
- snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +0 -91
- snowflake/cli/plugins/nativeapp/codegen/compiler.py +0 -130
- snowflake/cli/plugins/nativeapp/codegen/sandbox.py +0 -306
- snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +0 -172
- snowflake/cli/plugins/nativeapp/codegen/setup/setup_driver.py.source +0 -56
- snowflake/cli/plugins/nativeapp/codegen/snowpark/callback_source.py.jinja +0 -181
- snowflake/cli/plugins/nativeapp/codegen/snowpark/extension_function_utils.py +0 -217
- snowflake/cli/plugins/nativeapp/codegen/snowpark/models.py +0 -61
- snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +0 -528
- snowflake/cli/plugins/nativeapp/commands.py +0 -439
- snowflake/cli/plugins/nativeapp/common_flags.py +0 -44
- snowflake/cli/plugins/nativeapp/constants.py +0 -27
- snowflake/cli/plugins/nativeapp/exceptions.py +0 -122
- snowflake/cli/plugins/nativeapp/feature_flags.py +0 -24
- snowflake/cli/plugins/nativeapp/init.py +0 -345
- snowflake/cli/plugins/nativeapp/manager.py +0 -823
- snowflake/cli/plugins/nativeapp/plugin_spec.py +0 -30
- snowflake/cli/plugins/nativeapp/policy.py +0 -50
- snowflake/cli/plugins/nativeapp/project_model.py +0 -195
- snowflake/cli/plugins/nativeapp/run_processor.py +0 -389
- snowflake/cli/plugins/nativeapp/teardown_processor.py +0 -301
- snowflake/cli/plugins/nativeapp/utils.py +0 -98
- snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +0 -135
- snowflake/cli/plugins/nativeapp/version/__init__.py +0 -13
- snowflake/cli/plugins/nativeapp/version/commands.py +0 -170
- snowflake/cli/plugins/nativeapp/version/version_processor.py +0 -362
- snowflake/cli/plugins/notebook/__init__.py +0 -13
- snowflake/cli/plugins/notebook/commands.py +0 -84
- snowflake/cli/plugins/notebook/exceptions.py +0 -20
- snowflake/cli/plugins/notebook/manager.py +0 -71
- snowflake/cli/plugins/notebook/plugin_spec.py +0 -30
- snowflake/cli/plugins/notebook/types.py +0 -16
- snowflake/cli/plugins/object/__init__.py +0 -13
- snowflake/cli/plugins/object/command_aliases.py +0 -94
- snowflake/cli/plugins/object/commands.py +0 -174
- snowflake/cli/plugins/object/common.py +0 -85
- snowflake/cli/plugins/object/manager.py +0 -96
- snowflake/cli/plugins/object/plugin_spec.py +0 -30
- snowflake/cli/plugins/object_stage_deprecated/__init__.py +0 -15
- snowflake/cli/plugins/object_stage_deprecated/commands.py +0 -122
- snowflake/cli/plugins/object_stage_deprecated/plugin_spec.py +0 -32
- snowflake/cli/plugins/snowpark/__init__.py +0 -13
- snowflake/cli/plugins/snowpark/commands.py +0 -548
- snowflake/cli/plugins/snowpark/common.py +0 -307
- snowflake/cli/plugins/snowpark/manager.py +0 -109
- snowflake/cli/plugins/snowpark/models.py +0 -156
- snowflake/cli/plugins/snowpark/package/__init__.py +0 -13
- snowflake/cli/plugins/snowpark/package/anaconda_packages.py +0 -233
- snowflake/cli/plugins/snowpark/package/commands.py +0 -256
- snowflake/cli/plugins/snowpark/package/manager.py +0 -43
- snowflake/cli/plugins/snowpark/package/utils.py +0 -26
- snowflake/cli/plugins/snowpark/package_utils.py +0 -354
- snowflake/cli/plugins/snowpark/plugin_spec.py +0 -30
- snowflake/cli/plugins/snowpark/snowpark_package_paths.py +0 -65
- snowflake/cli/plugins/snowpark/snowpark_shared.py +0 -95
- snowflake/cli/plugins/snowpark/zipper.py +0 -81
- snowflake/cli/plugins/spcs/__init__.py +0 -35
- snowflake/cli/plugins/spcs/common.py +0 -99
- snowflake/cli/plugins/spcs/compute_pool/__init__.py +0 -13
- snowflake/cli/plugins/spcs/compute_pool/commands.py +0 -240
- snowflake/cli/plugins/spcs/compute_pool/manager.py +0 -121
- snowflake/cli/plugins/spcs/image_registry/__init__.py +0 -13
- snowflake/cli/plugins/spcs/image_registry/commands.py +0 -65
- snowflake/cli/plugins/spcs/image_registry/manager.py +0 -105
- snowflake/cli/plugins/spcs/image_repository/__init__.py +0 -13
- snowflake/cli/plugins/spcs/image_repository/commands.py +0 -196
- snowflake/cli/plugins/spcs/image_repository/manager.py +0 -84
- snowflake/cli/plugins/spcs/jobs/__init__.py +0 -13
- snowflake/cli/plugins/spcs/jobs/commands.py +0 -78
- snowflake/cli/plugins/spcs/jobs/manager.py +0 -53
- snowflake/cli/plugins/spcs/plugin_spec.py +0 -30
- snowflake/cli/plugins/spcs/services/__init__.py +0 -13
- snowflake/cli/plugins/spcs/services/commands.py +0 -311
- snowflake/cli/plugins/spcs/services/manager.py +0 -170
- snowflake/cli/plugins/sql/__init__.py +0 -13
- snowflake/cli/plugins/sql/commands.py +0 -83
- snowflake/cli/plugins/sql/manager.py +0 -92
- snowflake/cli/plugins/sql/plugin_spec.py +0 -30
- snowflake/cli/plugins/sql/snowsql_templating.py +0 -28
- snowflake/cli/plugins/stage/__init__.py +0 -13
- snowflake/cli/plugins/stage/commands.py +0 -261
- snowflake/cli/plugins/stage/diff.py +0 -326
- snowflake/cli/plugins/stage/manager.py +0 -544
- snowflake/cli/plugins/stage/md5.py +0 -160
- snowflake/cli/plugins/stage/plugin_spec.py +0 -30
- snowflake/cli/plugins/streamlit/__init__.py +0 -13
- snowflake/cli/plugins/streamlit/commands.py +0 -186
- snowflake/cli/plugins/streamlit/manager.py +0 -222
- snowflake/cli/plugins/streamlit/plugin_spec.py +0 -30
- snowflake/cli/plugins/workspace/__init__.py +0 -13
- snowflake/cli/plugins/workspace/commands.py +0 -35
- snowflake/cli/plugins/workspace/plugin_spec.py +0 -30
- snowflake/cli/templates/default_snowpark/.gitignore +0 -4
- snowflake/cli/templates/default_snowpark/app/__init__.py +0 -0
- snowflake/cli/templates/default_snowpark/app/common.py +0 -2
- snowflake/cli/templates/default_snowpark/app/functions.py +0 -15
- snowflake/cli/templates/default_snowpark/app/procedures.py +0 -22
- snowflake/cli/templates/default_snowpark/requirements.txt +0 -1
- snowflake/cli/templates/default_snowpark/snowflake.yml +0 -23
- snowflake/cli/templates/default_streamlit/.gitignore +0 -4
- snowflake/cli/templates/default_streamlit/common/hello.py +0 -2
- snowflake/cli/templates/default_streamlit/environment.yml +0 -6
- snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -3
- snowflake/cli/templates/default_streamlit/snowflake.yml +0 -10
- snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -4
- snowflake_cli_labs-2.8.0rc1.dist-info/RECORD +0 -240
- snowflake_cli_labs-2.8.0rc1.dist-info/entry_points.txt +0 -2
- {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-2.8.2.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-2.8.2.dist-info}/licenses/LICENSE +0 -0
snowflake/cli/api/rest_api.py
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import json
|
|
18
|
-
import logging
|
|
19
|
-
from typing import Any, Dict, Optional
|
|
20
|
-
|
|
21
|
-
from click import ClickException
|
|
22
|
-
from snowflake.cli.api.constants import SF_REST_API_URL_PREFIX
|
|
23
|
-
from snowflake.connector.connection import SnowflakeConnection
|
|
24
|
-
from snowflake.connector.errors import BadRequest, InterfaceError
|
|
25
|
-
from snowflake.connector.network import SnowflakeRestful
|
|
26
|
-
|
|
27
|
-
log = logging.getLogger(__name__)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _pluralize_object_type(object_type: str) -> str:
|
|
31
|
-
"""
|
|
32
|
-
Pluralize object type without depending on OBJECT_TO_NAMES.
|
|
33
|
-
"""
|
|
34
|
-
if object_type.endswith("y"):
|
|
35
|
-
return object_type[:-1].lower() + "ies"
|
|
36
|
-
else:
|
|
37
|
-
return object_type.lower() + "s"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class RestApi:
|
|
41
|
-
def __init__(self, connection: SnowflakeConnection):
|
|
42
|
-
self.conn = connection
|
|
43
|
-
self.rest: SnowflakeRestful = connection.rest
|
|
44
|
-
|
|
45
|
-
def get_endpoint_exists(self, url: str) -> bool:
|
|
46
|
-
"""
|
|
47
|
-
Check whether [get] endpoint exists under given URL.
|
|
48
|
-
"""
|
|
49
|
-
try:
|
|
50
|
-
result = self.send_rest_request(url, method="get")
|
|
51
|
-
return bool(result) or result == []
|
|
52
|
-
except InterfaceError as err:
|
|
53
|
-
if "404 Not Found" in str(err):
|
|
54
|
-
return False
|
|
55
|
-
raise err
|
|
56
|
-
|
|
57
|
-
def _fetch_endpoint_exists(self, url: str) -> bool:
|
|
58
|
-
try:
|
|
59
|
-
result = self.send_rest_request(url, method="get")
|
|
60
|
-
return bool(result)
|
|
61
|
-
except BadRequest:
|
|
62
|
-
return False
|
|
63
|
-
|
|
64
|
-
def send_rest_request(
|
|
65
|
-
self, url: str, method: str, data: Optional[Dict[str, Any]] = None
|
|
66
|
-
):
|
|
67
|
-
"""
|
|
68
|
-
Executes rest request via snowflake.connector.network.SnowflakeRestful
|
|
69
|
-
"""
|
|
70
|
-
# SnowflakeRestful.request assumes that API response is always a dict,
|
|
71
|
-
# which is not true in case of this API, so we need to do this workaround:
|
|
72
|
-
from snowflake.connector.network import (
|
|
73
|
-
CONTENT_TYPE_APPLICATION_JSON,
|
|
74
|
-
HTTP_HEADER_ACCEPT,
|
|
75
|
-
HTTP_HEADER_CONTENT_TYPE,
|
|
76
|
-
HTTP_HEADER_USER_AGENT,
|
|
77
|
-
PYTHON_CONNECTOR_USER_AGENT,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
log.debug("Sending %s request to %s", method, url)
|
|
81
|
-
full_url = f"{self.rest.server_url}{url}"
|
|
82
|
-
headers = {
|
|
83
|
-
HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
|
|
84
|
-
HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
|
|
85
|
-
HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
|
|
86
|
-
}
|
|
87
|
-
return self.rest.fetch(
|
|
88
|
-
method=method,
|
|
89
|
-
full_url=full_url,
|
|
90
|
-
headers=headers,
|
|
91
|
-
token=self.rest.token,
|
|
92
|
-
data=json.dumps(data if data else {}),
|
|
93
|
-
no_retry=True,
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
def _database_exists(self, db_name: str) -> bool:
|
|
97
|
-
url = f"{SF_REST_API_URL_PREFIX}/databases/{db_name}"
|
|
98
|
-
return self._fetch_endpoint_exists(url)
|
|
99
|
-
|
|
100
|
-
def _schema_exists(self, db_name: str, schema_name: str) -> bool:
|
|
101
|
-
url = f"{SF_REST_API_URL_PREFIX}/databases/{db_name}/schemas/{schema_name}"
|
|
102
|
-
return self._fetch_endpoint_exists(url)
|
|
103
|
-
|
|
104
|
-
def determine_url_for_create_query(self, object_type: str) -> str:
|
|
105
|
-
"""
|
|
106
|
-
Determine an url for creating an object of given type via REST API.
|
|
107
|
-
If URL cannot be determined, the function throws CannotDetermineCreateURLException exception.
|
|
108
|
-
|
|
109
|
-
URLs we check:
|
|
110
|
-
* /api/v2/<type>/
|
|
111
|
-
* /api/v2/databases/<database>/<type>/
|
|
112
|
-
* /api/v2/databases/<database>/schemas/<schema>/<type>/
|
|
113
|
-
|
|
114
|
-
We assume that the URLs for CREATE and LIST are the same for every type of object
|
|
115
|
-
(endpoints differ by method: POST vs GET, accordingly).
|
|
116
|
-
To check whether an URL exists, we send read-only GET request (LIST endpoint,
|
|
117
|
-
which should imply CREATE endpoint).
|
|
118
|
-
"""
|
|
119
|
-
plural_object_type = _pluralize_object_type(object_type)
|
|
120
|
-
|
|
121
|
-
if self.get_endpoint_exists(
|
|
122
|
-
url := f"{SF_REST_API_URL_PREFIX}/{plural_object_type}/"
|
|
123
|
-
):
|
|
124
|
-
return url
|
|
125
|
-
|
|
126
|
-
db = self.conn.database
|
|
127
|
-
if not db:
|
|
128
|
-
raise DatabaseNotDefinedException(
|
|
129
|
-
"Database not defined in connection. Please try again with `--database` flag."
|
|
130
|
-
)
|
|
131
|
-
if not self._database_exists(db):
|
|
132
|
-
raise DatabaseNotExistsException(f"Database '{db}' does not exist.")
|
|
133
|
-
if self.get_endpoint_exists(
|
|
134
|
-
url := f"{SF_REST_API_URL_PREFIX}/databases/{db}/{plural_object_type}/"
|
|
135
|
-
):
|
|
136
|
-
return url
|
|
137
|
-
|
|
138
|
-
schema = self.conn.schema
|
|
139
|
-
if not schema:
|
|
140
|
-
raise SchemaNotDefinedException(
|
|
141
|
-
"Schema not defined in connection. Please try again with `--schema` flag."
|
|
142
|
-
)
|
|
143
|
-
if not self._schema_exists(db_name=db, schema_name=schema):
|
|
144
|
-
raise SchemaNotExistsException(f"Schema '{schema}' does not exist.")
|
|
145
|
-
if self.get_endpoint_exists(
|
|
146
|
-
url := f"{SF_REST_API_URL_PREFIX}/databases/{self.conn.database}/schemas/{self.conn.schema}/{plural_object_type}/"
|
|
147
|
-
):
|
|
148
|
-
return url
|
|
149
|
-
|
|
150
|
-
raise CannotDetermineCreateURLException(
|
|
151
|
-
f"Create operation for type {object_type} is not supported. Try using `sql -q 'CREATE ...'` command."
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
class DatabaseNotDefinedException(ClickException):
|
|
156
|
-
pass
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
class SchemaNotDefinedException(ClickException):
|
|
160
|
-
pass
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class DatabaseNotExistsException(ClickException):
|
|
164
|
-
pass
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
class SchemaNotExistsException(ClickException):
|
|
168
|
-
pass
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
class CannotDetermineCreateURLException(ClickException):
|
|
172
|
-
pass
|
snowflake/cli/api/sanitizers.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import re
|
|
18
|
-
|
|
19
|
-
# 7-bit C1 ANSI sequences
|
|
20
|
-
_ANSI_ESCAPE = re.compile(
|
|
21
|
-
r"""
|
|
22
|
-
\x1B # ESC
|
|
23
|
-
(?: # 7-bit C1 Fe (except CSI)
|
|
24
|
-
[@-Z\\-_]
|
|
25
|
-
| # or [ for CSI, followed by a control sequence
|
|
26
|
-
\[
|
|
27
|
-
[0-?]* # Parameter bytes
|
|
28
|
-
[ -/]* # Intermediate bytes
|
|
29
|
-
[@-~] # Final byte
|
|
30
|
-
)
|
|
31
|
-
""",
|
|
32
|
-
re.VERBOSE,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def sanitize_for_terminal(text: str) -> str | None:
|
|
37
|
-
"""
|
|
38
|
-
Escape ASCII escape codes in string. This should be always used
|
|
39
|
-
when printing output to terminal.
|
|
40
|
-
"""
|
|
41
|
-
if text is None:
|
|
42
|
-
return None
|
|
43
|
-
return _ANSI_ESCAPE.sub("", text)
|
snowflake/cli/api/secure_path.py
DELETED
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import errno
|
|
18
|
-
import logging
|
|
19
|
-
import os
|
|
20
|
-
import shutil
|
|
21
|
-
import tempfile
|
|
22
|
-
from contextlib import contextmanager
|
|
23
|
-
from pathlib import Path
|
|
24
|
-
from typing import Optional, Union
|
|
25
|
-
|
|
26
|
-
from snowflake.cli.api.exceptions import DirectoryIsNotEmptyError, FileTooLargeError
|
|
27
|
-
|
|
28
|
-
log = logging.getLogger(__name__)
|
|
29
|
-
|
|
30
|
-
UNLIMITED = -1
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class SecurePath:
|
|
34
|
-
def __init__(self, path: Union[Path, str]):
|
|
35
|
-
self._path = Path(path)
|
|
36
|
-
|
|
37
|
-
def __repr__(self):
|
|
38
|
-
return f'SecurePath("{self._path}")'
|
|
39
|
-
|
|
40
|
-
def __truediv__(self, key):
|
|
41
|
-
return SecurePath(self._path / key)
|
|
42
|
-
|
|
43
|
-
@property
|
|
44
|
-
def path(self) -> Path:
|
|
45
|
-
"""
|
|
46
|
-
Returns itself in pathlib.Path format
|
|
47
|
-
"""
|
|
48
|
-
return self._path
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def parent(self):
|
|
52
|
-
"""
|
|
53
|
-
The logical parent of the path. For details, check pathlib.Path.parent
|
|
54
|
-
"""
|
|
55
|
-
return SecurePath(self._path.parent)
|
|
56
|
-
|
|
57
|
-
def absolute(self):
|
|
58
|
-
"""
|
|
59
|
-
Make the path absolute, without normalization or resolving symlinks.
|
|
60
|
-
"""
|
|
61
|
-
return SecurePath(self._path.absolute())
|
|
62
|
-
|
|
63
|
-
def iterdir(self):
|
|
64
|
-
"""
|
|
65
|
-
When the path points to a directory, yield path objects of the directory contents.
|
|
66
|
-
Otherwise, NotADirectoryError is raised.
|
|
67
|
-
If the location does not exist, FileNotFoundError is raised.
|
|
68
|
-
|
|
69
|
-
For details, check pathlib.Path.iterdir()
|
|
70
|
-
"""
|
|
71
|
-
self.assert_exists()
|
|
72
|
-
self.assert_is_directory()
|
|
73
|
-
return (SecurePath(p) for p in self._path.iterdir())
|
|
74
|
-
|
|
75
|
-
def exists(self) -> bool:
|
|
76
|
-
"""
|
|
77
|
-
Return True if the path points to an existing file or directory.
|
|
78
|
-
"""
|
|
79
|
-
return self._path.exists()
|
|
80
|
-
|
|
81
|
-
def is_dir(self) -> bool:
|
|
82
|
-
"""
|
|
83
|
-
Return True if the path points to a directory (or a symbolic link pointing to a directory),
|
|
84
|
-
False if it points to another kind of file.
|
|
85
|
-
"""
|
|
86
|
-
return self._path.is_dir()
|
|
87
|
-
|
|
88
|
-
def is_file(self) -> bool:
|
|
89
|
-
"""
|
|
90
|
-
Return True if the path points to a regular file (or a symbolic link pointing to a regular file),
|
|
91
|
-
False if it points to another kind of file.
|
|
92
|
-
"""
|
|
93
|
-
return self._path.is_file()
|
|
94
|
-
|
|
95
|
-
@property
|
|
96
|
-
def name(self) -> str:
|
|
97
|
-
"""A string representing the final path component."""
|
|
98
|
-
return self._path.name
|
|
99
|
-
|
|
100
|
-
def chmod(self, permissions_mask: int) -> None:
|
|
101
|
-
"""
|
|
102
|
-
Change the file mode and permissions, like os.chmod().
|
|
103
|
-
"""
|
|
104
|
-
log.info(
|
|
105
|
-
"Update permissions of file %s to %s", self._path, oct(permissions_mask)
|
|
106
|
-
)
|
|
107
|
-
self._path.chmod(permissions_mask)
|
|
108
|
-
|
|
109
|
-
def restrict_permissions(self) -> None:
|
|
110
|
-
"""
|
|
111
|
-
Restrict file/directory permissions to owner-only.
|
|
112
|
-
"""
|
|
113
|
-
import stat
|
|
114
|
-
|
|
115
|
-
owner_permissions = (
|
|
116
|
-
# https://docs.python.org/3/library/stat.html
|
|
117
|
-
stat.S_IRUSR # readable by owner
|
|
118
|
-
| stat.S_IWUSR # writeable by owner
|
|
119
|
-
| stat.S_IXUSR # executable by owner
|
|
120
|
-
)
|
|
121
|
-
self.chmod(self._path.stat().st_mode & owner_permissions)
|
|
122
|
-
|
|
123
|
-
def touch(self, permissions_mask: int = 0o600, exist_ok: bool = True) -> None:
|
|
124
|
-
"""
|
|
125
|
-
Create a file at this given path. For details, check pathlib.Path.touch()
|
|
126
|
-
"""
|
|
127
|
-
if not self.exists():
|
|
128
|
-
log.info("Creating file %s", str(self._path))
|
|
129
|
-
self._path.touch(mode=permissions_mask, exist_ok=exist_ok)
|
|
130
|
-
|
|
131
|
-
def mkdir(
|
|
132
|
-
self,
|
|
133
|
-
permissions_mask: int = 0o700,
|
|
134
|
-
parents: bool = False,
|
|
135
|
-
exist_ok: bool = False,
|
|
136
|
-
) -> None:
|
|
137
|
-
"""
|
|
138
|
-
Create a directory at this given path. For details, check pathlib.Path.mkdir()
|
|
139
|
-
"""
|
|
140
|
-
if parents and not self.parent.exists():
|
|
141
|
-
self.parent.mkdir(
|
|
142
|
-
permissions_mask=permissions_mask, exist_ok=exist_ok, parents=True
|
|
143
|
-
)
|
|
144
|
-
if not self.exists():
|
|
145
|
-
log.info("Creating directory %s", str(self._path))
|
|
146
|
-
self._path.mkdir(mode=permissions_mask, exist_ok=exist_ok)
|
|
147
|
-
|
|
148
|
-
def read_text(self, file_size_limit_mb: int, *args, **kwargs) -> str:
|
|
149
|
-
"""
|
|
150
|
-
Return the decoded contents of the file as a string.
|
|
151
|
-
Raises an error of the file exceeds the specified size limit.
|
|
152
|
-
For details, check pathlib.Path.read_text()
|
|
153
|
-
"""
|
|
154
|
-
self._assert_exists_and_is_file()
|
|
155
|
-
self._assert_file_size_limit(file_size_limit_mb)
|
|
156
|
-
log.info("Reading file %s", self._path)
|
|
157
|
-
return self._path.read_text(*args, **kwargs)
|
|
158
|
-
|
|
159
|
-
def write_text(self, *args, **kwargs):
|
|
160
|
-
"""
|
|
161
|
-
Open the file pointed to in text mode, write data to it, and close the file.
|
|
162
|
-
"""
|
|
163
|
-
if not self.exists():
|
|
164
|
-
self.touch()
|
|
165
|
-
log.info("Writing to file %s", self._path)
|
|
166
|
-
self.path.write_text(*args, **kwargs)
|
|
167
|
-
|
|
168
|
-
@contextmanager
|
|
169
|
-
def open( # noqa: A003
|
|
170
|
-
self,
|
|
171
|
-
mode="r",
|
|
172
|
-
read_file_limit_mb: Optional[int] = None,
|
|
173
|
-
**open_kwargs,
|
|
174
|
-
):
|
|
175
|
-
"""
|
|
176
|
-
Open the file pointed by this path and return a file object, as
|
|
177
|
-
the built-in open() function does.
|
|
178
|
-
If the file is opened for reading, [read_file_limit_kb] parameter must be provided.
|
|
179
|
-
Raises error if the read file exceeds the specified size limit.
|
|
180
|
-
"""
|
|
181
|
-
opened_for_reading = "r" in mode
|
|
182
|
-
if opened_for_reading:
|
|
183
|
-
assert (
|
|
184
|
-
read_file_limit_mb is not None
|
|
185
|
-
), "For reading mode ('r') read_file_limit_mb must be provided"
|
|
186
|
-
self._assert_exists_and_is_file()
|
|
187
|
-
self._assert_file_size_limit(read_file_limit_mb)
|
|
188
|
-
|
|
189
|
-
if self.exists():
|
|
190
|
-
self.assert_is_file()
|
|
191
|
-
else:
|
|
192
|
-
self.touch() # makes sure permissions of freshly-created file are strict
|
|
193
|
-
|
|
194
|
-
log.info("Opening file %s in mode '%s'", self._path, mode)
|
|
195
|
-
with self._path.open(mode=mode, **open_kwargs) as fd:
|
|
196
|
-
yield fd
|
|
197
|
-
log.info("Closing file %s", self._path)
|
|
198
|
-
|
|
199
|
-
def move(self, destination: Union[Path, str]) -> "SecurePath":
|
|
200
|
-
"""Recursively move a file or directory (src) to another location and return the destination.
|
|
201
|
-
|
|
202
|
-
If dst is an existing directory or a symlink to a directory, then src is moved inside that directory.
|
|
203
|
-
The destination path in that directory must not already exist.
|
|
204
|
-
"""
|
|
205
|
-
destination = Path(destination)
|
|
206
|
-
if destination.is_dir():
|
|
207
|
-
destination = destination / self._path.name
|
|
208
|
-
if destination.exists():
|
|
209
|
-
_raise_file_exists_error(destination)
|
|
210
|
-
log.info("Moving %s to %s", str(self._path), destination.resolve())
|
|
211
|
-
return SecurePath(shutil.move(str(self._path), destination))
|
|
212
|
-
|
|
213
|
-
def copy(
|
|
214
|
-
self, destination: Union[Path, str], dirs_exist_ok: bool = False
|
|
215
|
-
) -> "SecurePath":
|
|
216
|
-
"""
|
|
217
|
-
Copy the file/directory into the destination.
|
|
218
|
-
If source is a directory, its whole content is copied recursively.
|
|
219
|
-
Permissions of the copy are limited only to the owner.
|
|
220
|
-
|
|
221
|
-
If destination is an existing directory, the copy will be created inside it,
|
|
222
|
-
unless dirs_exist_ok is true and the destination has the same name as this path.
|
|
223
|
-
|
|
224
|
-
Otherwise, the copied file/base directory will be renamed to match destination.
|
|
225
|
-
If dirs_exist_ok is false (the default) and dst already exists,
|
|
226
|
-
a FileExistsError is raised. If dirs_exist_ok is true,
|
|
227
|
-
the copying operation will continue if it encounters existing directories,
|
|
228
|
-
and files within the destination tree will be overwritten by corresponding
|
|
229
|
-
files from the src tree.
|
|
230
|
-
"""
|
|
231
|
-
self.assert_exists()
|
|
232
|
-
|
|
233
|
-
destination = Path(destination)
|
|
234
|
-
if destination.exists():
|
|
235
|
-
if destination.is_dir() and (
|
|
236
|
-
destination.name != self._path.name or self.path.is_file()
|
|
237
|
-
):
|
|
238
|
-
destination = destination / self._path.name
|
|
239
|
-
|
|
240
|
-
if destination.exists():
|
|
241
|
-
if not all([destination.is_dir(), self._path.is_dir(), dirs_exist_ok]):
|
|
242
|
-
raise FileExistsError(
|
|
243
|
-
errno.EEXIST, os.strerror(errno.EEXIST), self._path.resolve()
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
def _recursive_check_for_conflicts(src: Path, dst: Path):
|
|
247
|
-
if dst.exists() and not dirs_exist_ok:
|
|
248
|
-
_raise_file_exists_error(dst)
|
|
249
|
-
if dst.is_file() and not src.is_file():
|
|
250
|
-
_raise_not_a_directory_error(dst)
|
|
251
|
-
if dst.is_dir() and not src.is_dir():
|
|
252
|
-
_raise_is_a_directory_error(dst)
|
|
253
|
-
if src.is_dir():
|
|
254
|
-
for child in src.iterdir():
|
|
255
|
-
_recursive_check_for_conflicts(child, dst / child.name)
|
|
256
|
-
|
|
257
|
-
def _recursive_copy(src: SecurePath, dst: SecurePath):
|
|
258
|
-
if src.is_file():
|
|
259
|
-
log.info("Copying file %s into %s", src.path, dst.path)
|
|
260
|
-
if dst.exists():
|
|
261
|
-
dst.unlink()
|
|
262
|
-
shutil.copyfile(src.path, dst.path)
|
|
263
|
-
dst.restrict_permissions()
|
|
264
|
-
if src.is_dir():
|
|
265
|
-
dst.mkdir(exist_ok=True)
|
|
266
|
-
for child in src.iterdir():
|
|
267
|
-
_recursive_copy(child, dst / child.name)
|
|
268
|
-
|
|
269
|
-
_recursive_check_for_conflicts(self._path, destination)
|
|
270
|
-
_recursive_copy(self, self.__class__(destination))
|
|
271
|
-
|
|
272
|
-
return SecurePath(destination)
|
|
273
|
-
|
|
274
|
-
def unlink(self, missing_ok=False):
|
|
275
|
-
"""
|
|
276
|
-
Remove this file or symbolic link.
|
|
277
|
-
If the path points to a directory, use SecurePath.rmdir() instead.
|
|
278
|
-
|
|
279
|
-
Check pathlib.Path.unlink() for details.
|
|
280
|
-
"""
|
|
281
|
-
if not self.exists():
|
|
282
|
-
if not missing_ok:
|
|
283
|
-
self.assert_exists()
|
|
284
|
-
return
|
|
285
|
-
|
|
286
|
-
self.assert_is_file()
|
|
287
|
-
log.info("Removing file %s", self._path)
|
|
288
|
-
self._path.unlink()
|
|
289
|
-
|
|
290
|
-
def rmdir(self, recursive=False, missing_ok=False):
|
|
291
|
-
"""
|
|
292
|
-
Remove this directory.
|
|
293
|
-
If the path points to a file, use SecurePath.unlink() instead.
|
|
294
|
-
|
|
295
|
-
If path points to a file, NotADirectoryError will be raised.
|
|
296
|
-
If directory does not exist, FileNotFoundError will be raised unless [missing_ok] is True.
|
|
297
|
-
If the directory is not empty, DirectoryNotEmpty will be raised unless [recursive] is True.
|
|
298
|
-
"""
|
|
299
|
-
if not self.exists():
|
|
300
|
-
if not missing_ok:
|
|
301
|
-
self.assert_exists()
|
|
302
|
-
return
|
|
303
|
-
|
|
304
|
-
self.assert_is_directory()
|
|
305
|
-
|
|
306
|
-
if not recursive and any(self._path.iterdir()):
|
|
307
|
-
raise DirectoryIsNotEmptyError(self._path.resolve())
|
|
308
|
-
|
|
309
|
-
log.info("Removing directory %s", self._path)
|
|
310
|
-
shutil.rmtree(str(self._path))
|
|
311
|
-
|
|
312
|
-
@classmethod
|
|
313
|
-
@contextmanager
|
|
314
|
-
def temporary_directory(cls):
|
|
315
|
-
"""
|
|
316
|
-
Creates a temporary directory in the most secure manner possible.
|
|
317
|
-
The directory is readable, writable, and searchable only by the creating user ID.
|
|
318
|
-
Yields SecurePath pointing to the absolute location of created directory.
|
|
319
|
-
|
|
320
|
-
Works similarly to tempfile.TemporaryDirectory
|
|
321
|
-
"""
|
|
322
|
-
with tempfile.TemporaryDirectory(prefix="snowflake-cli") as tmpdir:
|
|
323
|
-
log.info("Created temporary directory %s", tmpdir)
|
|
324
|
-
yield SecurePath(tmpdir)
|
|
325
|
-
log.info("Removing temporary directory %s", tmpdir)
|
|
326
|
-
|
|
327
|
-
def _assert_exists_and_is_file(self) -> None:
|
|
328
|
-
self.assert_exists()
|
|
329
|
-
self.assert_is_file()
|
|
330
|
-
|
|
331
|
-
def assert_exists(self) -> None:
|
|
332
|
-
if not self.exists():
|
|
333
|
-
raise FileNotFoundError(
|
|
334
|
-
errno.ENOENT, os.strerror(errno.ENOENT), self._path.resolve()
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
def assert_is_file(self) -> None:
|
|
338
|
-
if not self._path.is_file():
|
|
339
|
-
_raise_is_a_directory_error(self._path.resolve())
|
|
340
|
-
|
|
341
|
-
def assert_is_directory(self) -> None:
|
|
342
|
-
if not self._path.is_dir():
|
|
343
|
-
_raise_not_a_directory_error(self._path.resolve())
|
|
344
|
-
|
|
345
|
-
def _assert_file_size_limit(self, size_limit_in_mb):
|
|
346
|
-
if (
|
|
347
|
-
size_limit_in_mb != UNLIMITED
|
|
348
|
-
and self._path.stat().st_size > size_limit_in_mb * 1024 * 1024
|
|
349
|
-
):
|
|
350
|
-
raise FileTooLargeError(self._path.resolve(), size_limit_in_mb)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
def _raise_file_exists_error(path: Path):
|
|
354
|
-
raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), path)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
def _raise_is_a_directory_error(path: Path):
|
|
358
|
-
raise IsADirectoryError(errno.EISDIR, os.strerror(errno.EISDIR), path)
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
def _raise_not_a_directory_error(path: Path):
|
|
362
|
-
raise NotADirectoryError(errno.ENOTDIR, os.strerror(errno.ENOTDIR), path)
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
import stat
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def file_permissions_are_strict(file_path: Path) -> bool:
|
|
20
|
-
accessible_by_others = (
|
|
21
|
-
# https://docs.python.org/3/library/stat.html
|
|
22
|
-
stat.S_IRGRP # readable by group
|
|
23
|
-
| stat.S_IROTH # readable by others
|
|
24
|
-
| stat.S_IWGRP # writeable by group
|
|
25
|
-
| stat.S_IWOTH # writeable by others
|
|
26
|
-
| stat.S_IXGRP # executable by group
|
|
27
|
-
| stat.S_IXOTH # executable by others
|
|
28
|
-
)
|
|
29
|
-
return (file_path.stat().st_mode & accessible_by_others) == 0
|