plotly-cloud 0.2.0__py3-none-any.whl → 0.3.0__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.
- plotly_cloud/__init__.py +1 -1
- plotly_cloud/_changes.py +1 -2
- plotly_cloud/_commands.py +16 -16
- plotly_cloud/_deploy.py +23 -8
- plotly_cloud/_devtool_hooks.py +3 -28
- plotly_cloud/_devtool_publish_rpc.py +3 -1
- plotly_cloud/_oauth.py +8 -1
- plotly_cloud/_parser.py +11 -1
- plotly_cloud/_run_sync.py +34 -0
- plotly_cloud/cli.py +8 -3
- {plotly_cloud-0.2.0.dist-info → plotly_cloud-0.3.0.dist-info}/METADATA +2 -3
- plotly_cloud-0.3.0.dist-info/RECORD +22 -0
- {plotly_cloud-0.2.0.dist-info → plotly_cloud-0.3.0.dist-info}/WHEEL +1 -1
- plotly_cloud-0.2.0.dist-info/RECORD +0 -21
- {plotly_cloud-0.2.0.dist-info → plotly_cloud-0.3.0.dist-info}/entry_points.txt +0 -0
- {plotly_cloud-0.2.0.dist-info → plotly_cloud-0.3.0.dist-info}/licenses/LICENSE +0 -0
plotly_cloud/__init__.py
CHANGED
plotly_cloud/_changes.py
CHANGED
|
@@ -7,8 +7,7 @@ from concurrent.futures import ThreadPoolExecutor
|
|
|
7
7
|
_builtins_modules = list(sys.builtin_module_names) + ["frozen", "builtin"]
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
_builtins_modules += list(sys.stdlib_module_names) # type: ignore
|
|
10
|
+
_builtins_modules += list(getattr(sys, "stdlib_module_names", []))
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
def collect_module_files(*extras):
|
plotly_cloud/_commands.py
CHANGED
|
@@ -461,7 +461,7 @@ class PublishCommand(BaseCommand):
|
|
|
461
461
|
},
|
|
462
462
|
{
|
|
463
463
|
"name": "--poll-status",
|
|
464
|
-
"type": lambda x: x.lower() in ("true", "1", "yes", "on"),
|
|
464
|
+
"type": lambda x: x.lower() in ("true", "1", "yes", "on"),
|
|
465
465
|
"default": True,
|
|
466
466
|
"help": "Poll publishing status until completion (default: True)",
|
|
467
467
|
},
|
|
@@ -477,6 +477,11 @@ class PublishCommand(BaseCommand):
|
|
|
477
477
|
"default": 180,
|
|
478
478
|
"help": "Polling timeout in seconds",
|
|
479
479
|
},
|
|
480
|
+
{
|
|
481
|
+
"name": "--skip-size-check",
|
|
482
|
+
"action": "store_true",
|
|
483
|
+
"help": "Skip the standard 200MB project size validation (note: a hard 700MB maximum still applies)",
|
|
484
|
+
},
|
|
480
485
|
]
|
|
481
486
|
|
|
482
487
|
@classmethod
|
|
@@ -584,7 +589,7 @@ class PublishCommand(BaseCommand):
|
|
|
584
589
|
auth_task = progress.add_task("🔐 Checking authentication...", total=None)
|
|
585
590
|
|
|
586
591
|
client_id = cloud_config.get_oauth_client_id()
|
|
587
|
-
oauth_client = OAuthClient(client_id)
|
|
592
|
+
oauth_client = OAuthClient(client_id, token=getattr(args, "api_key", None))
|
|
588
593
|
|
|
589
594
|
# Check if authenticated, if not, perform login
|
|
590
595
|
if not await oauth_client.is_authenticated():
|
|
@@ -666,7 +671,8 @@ class PublishCommand(BaseCommand):
|
|
|
666
671
|
try:
|
|
667
672
|
# Create deployment zip
|
|
668
673
|
package_task = progress.add_task("Creating deployment package...", total=None)
|
|
669
|
-
|
|
674
|
+
skip_size_check = getattr(args, "skip_size_check", False)
|
|
675
|
+
zip_size = await create_deployment_zip(project_path, zip_path, skip_size_check)
|
|
670
676
|
progress.update(
|
|
671
677
|
package_task, description=f"✓ Created deployment package: {zip_size / (1024 * 1024):.1f}MB"
|
|
672
678
|
)
|
|
@@ -816,15 +822,12 @@ class StatusCommand(BaseCommand):
|
|
|
816
822
|
if not app_id:
|
|
817
823
|
raise ApplicationError("No app_id found in configuration. Publish your app first using 'plotly publish'.")
|
|
818
824
|
|
|
819
|
-
# Get OAuth client for authentication
|
|
820
825
|
client_id = cloud_config.get_oauth_client_id()
|
|
821
|
-
oauth_client = OAuthClient(client_id)
|
|
826
|
+
oauth_client = OAuthClient(client_id, token=getattr(args, "api_key", None))
|
|
822
827
|
|
|
823
|
-
# Check if authenticated
|
|
824
828
|
if not await oauth_client.is_authenticated():
|
|
825
829
|
raise ApplicationError("Not authenticated. Please run 'plotly login' first.")
|
|
826
830
|
|
|
827
|
-
# Get access token
|
|
828
831
|
auth_token = await oauth_client.get_access_token()
|
|
829
832
|
if not auth_token:
|
|
830
833
|
raise ApplicationError("Unable to retrieve access token. Please try logging in again.")
|
|
@@ -885,15 +888,12 @@ class TeamsCommand(BaseCommand):
|
|
|
885
888
|
@classmethod
|
|
886
889
|
async def execute(cls, args: ParsedArguments) -> None:
|
|
887
890
|
"""Execute teams command."""
|
|
888
|
-
# Get OAuth client for authentication
|
|
889
891
|
client_id = cloud_config.get_oauth_client_id()
|
|
890
|
-
oauth_client = OAuthClient(client_id)
|
|
892
|
+
oauth_client = OAuthClient(client_id, token=getattr(args, "api_key", None))
|
|
891
893
|
|
|
892
|
-
# Check if authenticated
|
|
893
894
|
if not await oauth_client.is_authenticated():
|
|
894
895
|
raise ApplicationError("Not authenticated. Please run 'plotly login' first.")
|
|
895
896
|
|
|
896
|
-
# Get access token
|
|
897
897
|
auth_token = await oauth_client.get_access_token()
|
|
898
898
|
if not auth_token:
|
|
899
899
|
raise ApplicationError("Unable to retrieve access token. Please try logging in again.")
|
|
@@ -944,25 +944,26 @@ class WhoamiCommand(BaseCommand):
|
|
|
944
944
|
@classmethod
|
|
945
945
|
async def execute(cls, args: ParsedArguments) -> None:
|
|
946
946
|
"""Execute the whoami command."""
|
|
947
|
+
token = getattr(args, "api_key", None)
|
|
948
|
+
if token:
|
|
949
|
+
console.print("✓ Authenticated via bearer token")
|
|
950
|
+
return
|
|
951
|
+
|
|
947
952
|
client_id = cloud_config.get_oauth_client_id()
|
|
948
953
|
oauth_client = OAuthClient(client_id)
|
|
949
954
|
|
|
950
|
-
# Check if authenticated
|
|
951
955
|
if not await oauth_client.is_authenticated():
|
|
952
956
|
console.print("✗ Not logged in")
|
|
953
957
|
return
|
|
954
958
|
|
|
955
|
-
# Load credentials to get user info
|
|
956
959
|
credentials = await oauth_client.load_credentials()
|
|
957
960
|
if not credentials:
|
|
958
961
|
console.print("✗ No credentials found")
|
|
959
962
|
return
|
|
960
963
|
|
|
961
|
-
# Try to refresh token to validate it
|
|
962
964
|
try:
|
|
963
965
|
await oauth_client.refresh_access_token()
|
|
964
966
|
|
|
965
|
-
# Extract user information from credentials
|
|
966
967
|
user_info = credentials.get("user", {})
|
|
967
968
|
email = user_info.get("email") or credentials.get("email")
|
|
968
969
|
|
|
@@ -972,6 +973,5 @@ class WhoamiCommand(BaseCommand):
|
|
|
972
973
|
console.print("✓ Logged in (no email information available)")
|
|
973
974
|
|
|
974
975
|
except (TokenError, CredentialError):
|
|
975
|
-
# Token is invalid and cannot be refreshed, clear credentials
|
|
976
976
|
await oauth_client.logout()
|
|
977
977
|
console.print("✗ Invalid token - credentials cleared")
|
plotly_cloud/_deploy.py
CHANGED
|
@@ -30,8 +30,9 @@ from .exceptions import (
|
|
|
30
30
|
PlotlyCloudError,
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
-
# Maximum allowed zip file size (200MB)
|
|
33
|
+
# Maximum allowed zip file size (200MB default, 700MB absolute max)
|
|
34
34
|
MAX_ZIP_SIZE = 200 * 1024 * 1024
|
|
35
|
+
ABSOLUTE_MAX_ZIP_SIZE = 700 * 1024 * 1024
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
def parse_gitignore(project_path: str) -> set[str]:
|
|
@@ -111,19 +112,23 @@ def should_exclude_path(path: str, exclude_patterns: set[str]) -> bool:
|
|
|
111
112
|
return False
|
|
112
113
|
|
|
113
114
|
|
|
114
|
-
async def create_deployment_zip(
|
|
115
|
+
async def create_deployment_zip(
|
|
116
|
+
project_path: str, output_path: str, skip_size_check: bool = False
|
|
117
|
+
) -> int:
|
|
115
118
|
"""
|
|
116
119
|
Create a zip file for deployment, excluding files based on .gitignore.
|
|
117
120
|
|
|
118
121
|
Args:
|
|
119
122
|
project_path: Path to the project directory
|
|
120
123
|
output_path: Path where the zip file should be created
|
|
124
|
+
skip_size_check: If True, skip the maximum size validation
|
|
121
125
|
|
|
122
126
|
Returns:
|
|
123
127
|
Size of the created zip file in bytes
|
|
124
128
|
|
|
125
129
|
Raises:
|
|
126
|
-
|
|
130
|
+
FileSizeError: If zip file exceeds size limit and skip_size_check is False
|
|
131
|
+
FileSystemError: If the .gitignore file cannot be read
|
|
127
132
|
"""
|
|
128
133
|
exclude_patterns = parse_gitignore(project_path)
|
|
129
134
|
total_uncompressed_size = 0
|
|
@@ -135,9 +140,7 @@ async def create_deployment_zip(project_path: str, output_path: str) -> int:
|
|
|
135
140
|
dirs[:] = [
|
|
136
141
|
d
|
|
137
142
|
for d in dirs
|
|
138
|
-
if not should_exclude_path(
|
|
139
|
-
str(os.path.relpath(os.path.join(root, d), project_path)), exclude_patterns
|
|
140
|
-
)
|
|
143
|
+
if not should_exclude_path(str(os.path.relpath(os.path.join(root, d), project_path)), exclude_patterns)
|
|
141
144
|
]
|
|
142
145
|
|
|
143
146
|
for file in files:
|
|
@@ -167,7 +170,18 @@ async def create_deployment_zip(project_path: str, output_path: str) -> int:
|
|
|
167
170
|
# Check uncompressed size only
|
|
168
171
|
zip_size = os.path.getsize(output_path)
|
|
169
172
|
|
|
170
|
-
|
|
173
|
+
# Always enforce absolute maximum (700MB)
|
|
174
|
+
if total_uncompressed_size > ABSOLUTE_MAX_ZIP_SIZE:
|
|
175
|
+
os.remove(output_path) # Clean up the oversized zip
|
|
176
|
+
raise FileSizeError(
|
|
177
|
+
f"This directory exceeds {ABSOLUTE_MAX_ZIP_SIZE / (1024 * 1024):.0f}MB and couldn't be published",
|
|
178
|
+
f"Total size: {total_uncompressed_size / (1024 * 1024):.1f}MB. "
|
|
179
|
+
f"Maximum allowed: {ABSOLUTE_MAX_ZIP_SIZE / (1024 * 1024):.0f}MB. "
|
|
180
|
+
"Consider excluding large files in your .gitignore.",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Check default limit (200MB) unless skipped
|
|
184
|
+
if not skip_size_check and total_uncompressed_size > MAX_ZIP_SIZE:
|
|
171
185
|
os.remove(output_path) # Clean up the oversized zip
|
|
172
186
|
raise FileSizeError(
|
|
173
187
|
f"This directory exceeds {MAX_ZIP_SIZE / (1024 * 1024):.0f}MB and couldn't be published",
|
|
@@ -249,7 +263,8 @@ def _handle_error_response(response: Optional[httpx.Response], operation: str) -
|
|
|
249
263
|
|
|
250
264
|
# Handle 403 Forbidden specifically
|
|
251
265
|
if response.status_code == 403:
|
|
252
|
-
|
|
266
|
+
details = response.text.strip()[:200] if response.text else ""
|
|
267
|
+
return ForbiddenError(f"Failed to {operation}: Access forbidden", details=details)
|
|
253
268
|
|
|
254
269
|
# Parse error based on content type
|
|
255
270
|
content_type = response.headers.get("content-type", "").lower()
|
plotly_cloud/_devtool_hooks.py
CHANGED
|
@@ -1,41 +1,16 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
|
|
3
1
|
import dash
|
|
4
2
|
import flask
|
|
5
|
-
from packaging.version import parse as _parse_version
|
|
6
3
|
|
|
7
4
|
from plotly_cloud._devtool_publish_rpc import PlotlyCloudPublishRPC
|
|
8
|
-
|
|
9
|
-
dash_version = _parse_version(dash.__version__)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def _run_sync(coro):
|
|
13
|
-
"""Run an async coroutine synchronously, handling event loop correctly for Python 3.10+."""
|
|
14
|
-
try:
|
|
15
|
-
# Check if there's already a running loop
|
|
16
|
-
loop = asyncio.get_running_loop()
|
|
17
|
-
except RuntimeError:
|
|
18
|
-
# No running loop, try to get or create one
|
|
19
|
-
loop = asyncio.get_event_loop()
|
|
20
|
-
if loop is None or loop.is_closed():
|
|
21
|
-
loop = asyncio.new_event_loop()
|
|
22
|
-
asyncio.set_event_loop(loop)
|
|
23
|
-
|
|
24
|
-
return loop.run_until_complete(coro)
|
|
5
|
+
from plotly_cloud._run_sync import run_sync
|
|
25
6
|
|
|
26
7
|
|
|
27
8
|
def install_hook():
|
|
28
|
-
import nest_asyncio
|
|
29
|
-
|
|
30
|
-
# Make asyncio stuff runs smoothly.
|
|
31
|
-
# Prevent no loop and existing loop errors.
|
|
32
|
-
nest_asyncio.apply()
|
|
33
|
-
|
|
34
9
|
rpc = PlotlyCloudPublishRPC()
|
|
35
10
|
|
|
36
11
|
try:
|
|
37
12
|
# The style only works with the position left defined.
|
|
38
|
-
dash.hooks.devtool(
|
|
13
|
+
dash.hooks.devtool(
|
|
39
14
|
"plotly_cloud_publish_component",
|
|
40
15
|
"PlotlyCloudPublishComponent",
|
|
41
16
|
{"id": "_plotly-cloud-publish"},
|
|
@@ -59,7 +34,7 @@ def install_hook():
|
|
|
59
34
|
@dash.hooks.route("_plotly_cloud_publish", methods=["POST"])
|
|
60
35
|
def plotly_cloud_publish_rpc():
|
|
61
36
|
data = flask.request.get_json()
|
|
62
|
-
data =
|
|
37
|
+
data = run_sync(rpc.handle_operation(data))
|
|
63
38
|
return flask.jsonify(data)
|
|
64
39
|
|
|
65
40
|
@dash.hooks.setup()
|
|
@@ -4,6 +4,7 @@ import asyncio
|
|
|
4
4
|
import importlib
|
|
5
5
|
import os
|
|
6
6
|
import tempfile
|
|
7
|
+
import traceback
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
import dash
|
|
@@ -167,6 +168,7 @@ class PlotlyCloudPublishRPC:
|
|
|
167
168
|
method = getattr(self, operation_name)
|
|
168
169
|
return await method(data)
|
|
169
170
|
except Exception as e:
|
|
171
|
+
traceback.print_exc()
|
|
170
172
|
return {"error": str(e)}
|
|
171
173
|
|
|
172
174
|
async def initialize(self, data: Any) -> RPCResponse:
|
|
@@ -224,7 +226,7 @@ class PlotlyCloudPublishRPC:
|
|
|
224
226
|
device_code = data.get("device_code")
|
|
225
227
|
status_code, response = await self.oauth_client.check_authentication_status(device_code)
|
|
226
228
|
if status_code == 200:
|
|
227
|
-
await self.oauth_client._save_credentials(
|
|
229
|
+
await self.oauth_client._save_credentials({**response})
|
|
228
230
|
return {"result": {"success": True}}
|
|
229
231
|
else:
|
|
230
232
|
error = response.get("error", "unknown_error")
|
plotly_cloud/_oauth.py
CHANGED
|
@@ -59,8 +59,9 @@ AuthResponse = Union[AuthTokenResponse, AuthErrorResponse]
|
|
|
59
59
|
class OAuthClient:
|
|
60
60
|
"""OAuth client for WorkOS CLI Auth using device authorization flow."""
|
|
61
61
|
|
|
62
|
-
def __init__(self, client_id: str):
|
|
62
|
+
def __init__(self, client_id: str, token: Optional[str] = None):
|
|
63
63
|
self.client_id = client_id
|
|
64
|
+
self.token = token
|
|
64
65
|
self.credentials_path = self._get_credentials_path()
|
|
65
66
|
|
|
66
67
|
def _get_credentials_path(self) -> Path:
|
|
@@ -278,11 +279,15 @@ class OAuthClient:
|
|
|
278
279
|
|
|
279
280
|
async def is_authenticated(self) -> bool:
|
|
280
281
|
"""Check if user is authenticated."""
|
|
282
|
+
if self.token:
|
|
283
|
+
return True
|
|
281
284
|
credentials = await self.load_credentials()
|
|
282
285
|
return credentials is not None and "access_token" in credentials
|
|
283
286
|
|
|
284
287
|
async def get_access_token(self) -> Optional[str]:
|
|
285
288
|
"""Get current access token."""
|
|
289
|
+
if self.token:
|
|
290
|
+
return self.token
|
|
286
291
|
credentials = await self.load_credentials()
|
|
287
292
|
if credentials:
|
|
288
293
|
return credentials.get("access_token")
|
|
@@ -294,6 +299,8 @@ class OAuthClient:
|
|
|
294
299
|
Raises:
|
|
295
300
|
TokenError: If no refresh token available or refresh fails
|
|
296
301
|
"""
|
|
302
|
+
if self.token:
|
|
303
|
+
raise TokenError("Cannot refresh a static API key")
|
|
297
304
|
credentials = await self.load_credentials()
|
|
298
305
|
if not credentials or "refresh_token" not in credentials:
|
|
299
306
|
raise TokenError("No refresh token available")
|
plotly_cloud/_parser.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Simple argument parser to replace argparse."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import sys
|
|
4
5
|
from typing import Any, List
|
|
5
6
|
|
|
@@ -78,9 +79,10 @@ def parse_args(command_arguments: List[CommandArgument], args_index=3) -> Parsed
|
|
|
78
79
|
|
|
79
80
|
result[key] = arg_spec.get("default")
|
|
80
81
|
|
|
81
|
-
# Add global
|
|
82
|
+
# Add global flags
|
|
82
83
|
result["verbose"] = False
|
|
83
84
|
result["help"] = False
|
|
85
|
+
result["api_key"] = os.getenv("PLOTLY_API_KEY", None)
|
|
84
86
|
|
|
85
87
|
# Check if the first argument is "help" and handle it specially
|
|
86
88
|
if len(args) > 0 and args[0] == "help":
|
|
@@ -103,6 +105,14 @@ def parse_args(command_arguments: List[CommandArgument], args_index=3) -> Parsed
|
|
|
103
105
|
i += 1
|
|
104
106
|
continue
|
|
105
107
|
|
|
108
|
+
if arg == "--api-key":
|
|
109
|
+
if i + 1 < len(args):
|
|
110
|
+
result["api_key"] = args[i + 1]
|
|
111
|
+
i += 2
|
|
112
|
+
else:
|
|
113
|
+
i += 1
|
|
114
|
+
continue
|
|
115
|
+
|
|
106
116
|
# Check if this is an optional argument
|
|
107
117
|
if arg.startswith("-"):
|
|
108
118
|
# Find matching optional argument specification
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Background event loop for async operations
|
|
2
|
+
import asyncio
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
_loop: asyncio.AbstractEventLoop = None # type: ignore
|
|
6
|
+
_loop_thread: threading.Thread = None # type: ignore
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _get_event_loop():
|
|
10
|
+
"""Get or create a background event loop running in a separate thread."""
|
|
11
|
+
global _loop, _loop_thread
|
|
12
|
+
|
|
13
|
+
if _loop is None or _loop.is_closed():
|
|
14
|
+
_loop = asyncio.new_event_loop()
|
|
15
|
+
|
|
16
|
+
def run_loop():
|
|
17
|
+
asyncio.set_event_loop(_loop)
|
|
18
|
+
_loop.run_forever()
|
|
19
|
+
|
|
20
|
+
_loop_thread = threading.Thread(target=run_loop, daemon=True)
|
|
21
|
+
_loop_thread.start()
|
|
22
|
+
|
|
23
|
+
return _loop
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run_sync(coro):
|
|
27
|
+
"""Run an async coroutine synchronously using a background event loop.
|
|
28
|
+
|
|
29
|
+
This avoids issues with nested event loops and ensures proper task context
|
|
30
|
+
for Python 3.14+ where anyio requires asyncio.current_task() to return a valid task.
|
|
31
|
+
"""
|
|
32
|
+
loop = _get_event_loop()
|
|
33
|
+
future = asyncio.run_coroutine_threadsafe(coro, loop)
|
|
34
|
+
return future.result()
|
plotly_cloud/cli.py
CHANGED
|
@@ -7,7 +7,6 @@ import textwrap
|
|
|
7
7
|
from collections.abc import Sequence
|
|
8
8
|
from typing import Union
|
|
9
9
|
|
|
10
|
-
import nest_asyncio
|
|
11
10
|
from rich.console import Console
|
|
12
11
|
from rich.panel import Panel
|
|
13
12
|
|
|
@@ -111,6 +110,10 @@ def print_main_help(show_banner=True) -> None:
|
|
|
111
110
|
|
|
112
111
|
# Show global options
|
|
113
112
|
global_options = [
|
|
113
|
+
{
|
|
114
|
+
"name": "--api-key",
|
|
115
|
+
"help": "API key for authentication (overrides OAuth, or set PLOTLY_API_KEY env var)",
|
|
116
|
+
},
|
|
114
117
|
{
|
|
115
118
|
"name": "--verbose, -v",
|
|
116
119
|
"help": "Enable verbose output with detailed error information",
|
|
@@ -197,6 +200,10 @@ def print_command_help(command_class: BaseCommand, group: str, command: str) ->
|
|
|
197
200
|
|
|
198
201
|
# Show global options
|
|
199
202
|
global_options = [
|
|
203
|
+
{
|
|
204
|
+
"name": "--api-key",
|
|
205
|
+
"help": "API key for authentication (overrides OAuth, or set PLOTLY_API_KEY env var)",
|
|
206
|
+
},
|
|
200
207
|
{
|
|
201
208
|
"name": "--verbose, -v",
|
|
202
209
|
"help": "Enable verbose output with detailed error information",
|
|
@@ -216,8 +223,6 @@ def print_command_help(command_class: BaseCommand, group: str, command: str) ->
|
|
|
216
223
|
|
|
217
224
|
def main() -> None:
|
|
218
225
|
"""Main CLI entry point."""
|
|
219
|
-
# Allow to run multiple calls to asyncio run.
|
|
220
|
-
nest_asyncio.apply()
|
|
221
226
|
|
|
222
227
|
# Parse group and command
|
|
223
228
|
group, command, args_index = parse_group_and_command()
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotly-cloud
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python extension for Plotly Cloud with CLI and Dash dev tools integration
|
|
5
5
|
Project-URL: Repository, https://github.com/plotly/plotly-cloud-extension
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Requires-Python: >=3.9
|
|
8
|
-
Requires-Dist: dash>=2.0
|
|
8
|
+
Requires-Dist: dash>=3.2.0
|
|
9
9
|
Requires-Dist: httpx<1.0.0,>=0.24.0
|
|
10
|
-
Requires-Dist: nest-asyncio>=1.6.0
|
|
11
10
|
Requires-Dist: rich>=10.0.0
|
|
12
11
|
Requires-Dist: tomli-w>=1.2.0
|
|
13
12
|
Requires-Dist: tomli>=2.2.1
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
plotly_cloud/__init__.py,sha256=-mqc6UM_gyoGqxNbCnS8Yy3Kc9hjnIij_Qn4KEiTz4k,61
|
|
2
|
+
plotly_cloud/_api_types.py,sha256=CdP0w2YGjBUourO_vsoPcpHVXxemW6V3ezHewKXuIyc,1529
|
|
3
|
+
plotly_cloud/_changes.py,sha256=oNGLYQfrJTFJgEsRneY2uNTPz4C6yRTWif31Wmodiy0,2040
|
|
4
|
+
plotly_cloud/_cloud_env.py,sha256=FDjIBUsJssKMW14HOrITg-5tdgkCI1FnmEy8U6zulcQ,2953
|
|
5
|
+
plotly_cloud/_commands.py,sha256=DB_c285mj--r2uuMKlfBBS_vM7f5gXF2nqH3jRXKGBc,39295
|
|
6
|
+
plotly_cloud/_definitions.py,sha256=GKLat5c9fkM-qJQ6_jPu5zjC6g3fkDANpTVKzMO50z0,2456
|
|
7
|
+
plotly_cloud/_deploy.py,sha256=h2SKGDkKfZBuSW0_bDMV_9Gc9Ixjf5ZQl40q1wfY8Xk,18549
|
|
8
|
+
plotly_cloud/_devtool_hooks.py,sha256=QJZMwxLz7LZffnhylpvukcBQcwA9xQncwLIljRraCqc,1173
|
|
9
|
+
plotly_cloud/_devtool_publish_rpc.py,sha256=BNUYsRbRIFQJzgYj8LBusjFK_wrD-vCY5C2imoTXHrk,11439
|
|
10
|
+
plotly_cloud/_oauth.py,sha256=VzUMAahDjxdaTEJbUw_SQeG4AnuMA4NtHVGnjGQcsGw,12646
|
|
11
|
+
plotly_cloud/_parser.py,sha256=kN06LfOxFm1itfQEC11b6X9HjZj18lBogPgnN4kcEtE,6041
|
|
12
|
+
plotly_cloud/_run_sync.py,sha256=Km782HXOwp-o6murS_Ng_vC7wG_7AR9bwVVfF0aY4Zk,1012
|
|
13
|
+
plotly_cloud/cli.py,sha256=SZzqT6QCBPZkuv92rEwbbaqEM-giWENSrpytmYIjfYY,10685
|
|
14
|
+
plotly_cloud/cloud-env.toml,sha256=VhoG3DY6yBEwQG17_FqlJP8BxDqaffHbYlIFrxV6k9o,254
|
|
15
|
+
plotly_cloud/cloud_devtools.css,sha256=3y2Q9NzM1arrkniLGZHfkiqtttuD5X7LkmCuxrkMRtI,3730
|
|
16
|
+
plotly_cloud/cloud_devtools.js,sha256=rGhhRjM8BHWrrIspGlHgvjRz_OvfEQXueiwzrb_fVIQ,44265
|
|
17
|
+
plotly_cloud/exceptions.py,sha256=QtNXLtryTbLqw3fUvho5GjsvR2W6BZ6rcMmvXJ0nVd0,4045
|
|
18
|
+
plotly_cloud-0.3.0.dist-info/METADATA,sha256=yDGYPknff8AKoeDH3aV4cRX8Dcf63LHOSuyyfy5rIFo,8102
|
|
19
|
+
plotly_cloud-0.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
20
|
+
plotly_cloud-0.3.0.dist-info/entry_points.txt,sha256=iZVffCSWjpG8Ht2HZAJDwZBmG-kVlIbH_V6SF8cwLNY,115
|
|
21
|
+
plotly_cloud-0.3.0.dist-info/licenses/LICENSE,sha256=ElWBU-rl1xjn3Pvcguv5MNomrIMao1mPtuT_j8Q6WaI,1068
|
|
22
|
+
plotly_cloud-0.3.0.dist-info/RECORD,,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
plotly_cloud/__init__.py,sha256=oVX8cZCjc1qrroC9dAdwHPNfeQ2HKb4K4r9_zfs5dWE,61
|
|
2
|
-
plotly_cloud/_api_types.py,sha256=CdP0w2YGjBUourO_vsoPcpHVXxemW6V3ezHewKXuIyc,1529
|
|
3
|
-
plotly_cloud/_changes.py,sha256=YQJGChUzSGCgQOVnsA98vqWLARaY893UqVePYsCAhh8,2084
|
|
4
|
-
plotly_cloud/_cloud_env.py,sha256=FDjIBUsJssKMW14HOrITg-5tdgkCI1FnmEy8U6zulcQ,2953
|
|
5
|
-
plotly_cloud/_commands.py,sha256=8rPwUoGZ9BpwVNt2xT-NtQ7kEwZBO92sqQg_aB0Ii0Q,39206
|
|
6
|
-
plotly_cloud/_definitions.py,sha256=GKLat5c9fkM-qJQ6_jPu5zjC6g3fkDANpTVKzMO50z0,2456
|
|
7
|
-
plotly_cloud/_deploy.py,sha256=q76NaEI9iN137fhYzWsGuEk6VPjoGrZmsTnR-HQLqKU,17620
|
|
8
|
-
plotly_cloud/_devtool_hooks.py,sha256=IPpX1tcMwHpLtBKhTkaXz48FvfFjv0-cgoZsZP2Aqcg,1913
|
|
9
|
-
plotly_cloud/_devtool_publish_rpc.py,sha256=JY8FlP0yue6Yjsil6EDHpGSEdXfYnjEmLnK75CGKopM,11390
|
|
10
|
-
plotly_cloud/_oauth.py,sha256=BwkeBOVHqoUfguiP4WBpT_xXB-oFJoFL-8VGqp6cuoQ,12403
|
|
11
|
-
plotly_cloud/_parser.py,sha256=EoyehZZRy_A8PHO2yTMUkJcXTgztgh-7gAQF2uaP640,5795
|
|
12
|
-
plotly_cloud/cli.py,sha256=keez1KpXxZEDLWYo58VnvzPBG6mctMf1aNqDWGqfvq4,10474
|
|
13
|
-
plotly_cloud/cloud-env.toml,sha256=VhoG3DY6yBEwQG17_FqlJP8BxDqaffHbYlIFrxV6k9o,254
|
|
14
|
-
plotly_cloud/cloud_devtools.css,sha256=3y2Q9NzM1arrkniLGZHfkiqtttuD5X7LkmCuxrkMRtI,3730
|
|
15
|
-
plotly_cloud/cloud_devtools.js,sha256=rGhhRjM8BHWrrIspGlHgvjRz_OvfEQXueiwzrb_fVIQ,44265
|
|
16
|
-
plotly_cloud/exceptions.py,sha256=QtNXLtryTbLqw3fUvho5GjsvR2W6BZ6rcMmvXJ0nVd0,4045
|
|
17
|
-
plotly_cloud-0.2.0.dist-info/METADATA,sha256=zlHwCUtx33W4rORzOrr4b2diItblXVTZuXqoWvfjvyg,8137
|
|
18
|
-
plotly_cloud-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
19
|
-
plotly_cloud-0.2.0.dist-info/entry_points.txt,sha256=iZVffCSWjpG8Ht2HZAJDwZBmG-kVlIbH_V6SF8cwLNY,115
|
|
20
|
-
plotly_cloud-0.2.0.dist-info/licenses/LICENSE,sha256=ElWBU-rl1xjn3Pvcguv5MNomrIMao1mPtuT_j8Q6WaI,1068
|
|
21
|
-
plotly_cloud-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|