plotly-cloud 0.1.0rc1__tar.gz → 0.1.0rc2__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.
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/.gitignore +5 -1
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/PKG-INFO +3 -28
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/README.md +2 -27
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/plotly_cloud/_commands.py +8 -8
- plotly_cloud-0.1.0rc2/plotly_cloud/_devtool_hooks.py +61 -0
- plotly_cloud-0.1.0rc2/plotly_cloud/_devtool_publish_rpc.py +186 -0
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/plotly_cloud/_oauth.py +69 -22
- plotly_cloud-0.1.0rc2/plotly_cloud/cloud_devtools.css +1 -0
- plotly_cloud-0.1.0rc2/plotly_cloud/cloud_devtools.js +15 -0
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/pyproject.toml +5 -1
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/LICENSE +0 -0
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/plotly_cloud/__init__.py +0 -0
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/plotly_cloud/_api_types.py +0 -0
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/plotly_cloud/_changes.py +0 -0
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/plotly_cloud/_cloud_env.py +0 -0
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/plotly_cloud/_definitions.py +0 -0
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/plotly_cloud/_deploy.py +0 -0
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/plotly_cloud/_parser.py +0 -0
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/plotly_cloud/cli.py +0 -0
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/plotly_cloud/cloud-env.toml +0 -0
- {plotly_cloud-0.1.0rc1 → plotly_cloud-0.1.0rc2}/plotly_cloud/exceptions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotly-cloud
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0rc2
|
|
4
4
|
Summary: CLI for interacting with Plotly Cloud to deploy Dash apps
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -26,33 +26,8 @@ A command-line interface for interacting with Plotly Cloud to publish and manage
|
|
|
26
26
|
|
|
27
27
|
## Installation
|
|
28
28
|
|
|
29
|
-
### From Source
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
git clone <repository-url>
|
|
33
|
-
cd plotly-cloud-cli
|
|
34
|
-
pip install -e .
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Development Installation
|
|
38
|
-
|
|
39
|
-
For development, we recommend using UV and the provided Justfile:
|
|
40
|
-
|
|
41
29
|
```bash
|
|
42
|
-
|
|
43
|
-
cd plotly-cloud-cli
|
|
44
|
-
|
|
45
|
-
# Install UV if you haven't already
|
|
46
|
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
47
|
-
|
|
48
|
-
# Install dependencies
|
|
49
|
-
just install
|
|
50
|
-
|
|
51
|
-
# Setup cloud environment configuration
|
|
52
|
-
just setup-cloud-env "your-staging-client-id" "your-production-client-id"
|
|
53
|
-
|
|
54
|
-
# Install CLI in development mode
|
|
55
|
-
just install-cli
|
|
30
|
+
pip install plotly-cloud
|
|
56
31
|
```
|
|
57
32
|
|
|
58
33
|
## Quick Start
|
|
@@ -313,7 +288,7 @@ plotly-cloud-cli/
|
|
|
313
288
|
|
|
314
289
|
## License
|
|
315
290
|
|
|
316
|
-
|
|
291
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
317
292
|
|
|
318
293
|
## Support
|
|
319
294
|
|
|
@@ -11,33 +11,8 @@ A command-line interface for interacting with Plotly Cloud to publish and manage
|
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
|
-
### From Source
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
git clone <repository-url>
|
|
18
|
-
cd plotly-cloud-cli
|
|
19
|
-
pip install -e .
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
### Development Installation
|
|
23
|
-
|
|
24
|
-
For development, we recommend using UV and the provided Justfile:
|
|
25
|
-
|
|
26
14
|
```bash
|
|
27
|
-
|
|
28
|
-
cd plotly-cloud-cli
|
|
29
|
-
|
|
30
|
-
# Install UV if you haven't already
|
|
31
|
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
32
|
-
|
|
33
|
-
# Install dependencies
|
|
34
|
-
just install
|
|
35
|
-
|
|
36
|
-
# Setup cloud environment configuration
|
|
37
|
-
just setup-cloud-env "your-staging-client-id" "your-production-client-id"
|
|
38
|
-
|
|
39
|
-
# Install CLI in development mode
|
|
40
|
-
just install-cli
|
|
15
|
+
pip install plotly-cloud
|
|
41
16
|
```
|
|
42
17
|
|
|
43
18
|
## Quick Start
|
|
@@ -298,7 +273,7 @@ plotly-cloud-cli/
|
|
|
298
273
|
|
|
299
274
|
## License
|
|
300
275
|
|
|
301
|
-
|
|
276
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
302
277
|
|
|
303
278
|
## Support
|
|
304
279
|
|
|
@@ -428,43 +428,43 @@ class PublishCommand(BaseCommand):
|
|
|
428
428
|
{
|
|
429
429
|
"name": "--project-path",
|
|
430
430
|
"default": ".",
|
|
431
|
-
"help": "Path to project directory to
|
|
431
|
+
"help": "Path to project directory to publish (default: current directory)",
|
|
432
432
|
},
|
|
433
433
|
{
|
|
434
434
|
"name": "--config",
|
|
435
435
|
"default": "plotly-cloud.toml",
|
|
436
|
-
"help": "Path to configuration file
|
|
436
|
+
"help": "Path to configuration file",
|
|
437
437
|
},
|
|
438
438
|
{
|
|
439
439
|
"name": "--name",
|
|
440
|
-
"help": "Application name (will prompt if not provided
|
|
440
|
+
"help": "Application name (will prompt if not provided first time app is published)",
|
|
441
441
|
},
|
|
442
442
|
{
|
|
443
443
|
"name": "--output",
|
|
444
|
-
"help": "Output path for
|
|
444
|
+
"help": "Output path for zip file of the published app (default: temporary file)",
|
|
445
445
|
},
|
|
446
446
|
{
|
|
447
447
|
"name": "--keep-zip",
|
|
448
448
|
"action": "store_true",
|
|
449
|
-
"help": "Keep the
|
|
449
|
+
"help": "Keep the zip file of the published app after upload",
|
|
450
450
|
},
|
|
451
451
|
{
|
|
452
452
|
"name": "--poll-status",
|
|
453
453
|
"type": lambda x: x.lower() in ("true", "1", "yes", "on"), # type: ignore
|
|
454
454
|
"default": True,
|
|
455
|
-
"help": "Poll
|
|
455
|
+
"help": "Poll publishing status until completion (default: True)",
|
|
456
456
|
},
|
|
457
457
|
{
|
|
458
458
|
"name": "--poll-interval",
|
|
459
459
|
"type": float,
|
|
460
460
|
"default": 1.0,
|
|
461
|
-
"help": "Polling interval in seconds
|
|
461
|
+
"help": "Polling interval in seconds",
|
|
462
462
|
},
|
|
463
463
|
{
|
|
464
464
|
"name": "--poll-timeout",
|
|
465
465
|
"type": int,
|
|
466
466
|
"default": 180,
|
|
467
|
-
"help": "Polling timeout in seconds
|
|
467
|
+
"help": "Polling timeout in seconds",
|
|
468
468
|
},
|
|
469
469
|
]
|
|
470
470
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import dash
|
|
4
|
+
import flask
|
|
5
|
+
from packaging.version import parse as _parse_version
|
|
6
|
+
|
|
7
|
+
from plotly_cloud._devtool_publish_rpc import PlotlyCloudPublishRPC
|
|
8
|
+
|
|
9
|
+
dash_version = _parse_version(dash.__version__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _run_sync(coro):
|
|
13
|
+
loop = asyncio.get_event_loop()
|
|
14
|
+
return loop.run_until_complete(coro)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def install_hook():
|
|
18
|
+
import nest_asyncio
|
|
19
|
+
|
|
20
|
+
# Make asyncio stuff runs smoothly.
|
|
21
|
+
# Prevent no loop and existing loop errors.
|
|
22
|
+
nest_asyncio.apply()
|
|
23
|
+
|
|
24
|
+
rpc = PlotlyCloudPublishRPC()
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
# The style only works with the position left defined.
|
|
28
|
+
dash.hooks.devtool( # type: ignore
|
|
29
|
+
"plotly_cloud_publish_component",
|
|
30
|
+
"PlotlyCloudPublishComponent",
|
|
31
|
+
{"id": "_plotly-cloud-publish"},
|
|
32
|
+
position="left",
|
|
33
|
+
)
|
|
34
|
+
except Exception:
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
dash.hooks.script(
|
|
38
|
+
[
|
|
39
|
+
{"dev_package_path": "cloud_devtools.js", "namespace": "plotly_cloud", "dev_only": True},
|
|
40
|
+
]
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
dash.hooks.stylesheet(
|
|
44
|
+
[
|
|
45
|
+
{"relative_package_path": "cloud_devtools.css", "namespace": "plotly_cloud"},
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@dash.hooks.route("_plotly_cloud_publish", methods=["POST"])
|
|
50
|
+
def plotly_cloud_publish_rpc():
|
|
51
|
+
data = flask.request.get_json()
|
|
52
|
+
data = _run_sync(rpc.handle_operation(data))
|
|
53
|
+
return flask.jsonify(data)
|
|
54
|
+
|
|
55
|
+
@dash.hooks.setup()
|
|
56
|
+
def plotly_cloud_setup(app):
|
|
57
|
+
rpc._app_setup = app
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if hasattr(dash.hooks, "devtool"):
|
|
61
|
+
install_hook()
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""RPC interface for Plotly Cloud publishing in dev tools."""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
import traceback
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import dash
|
|
10
|
+
from typing_extensions import Literal, NotRequired, TypedDict
|
|
11
|
+
|
|
12
|
+
from plotly_cloud._cloud_env import cloud_config
|
|
13
|
+
from plotly_cloud._definitions import AppDeploymentConfig
|
|
14
|
+
from plotly_cloud._deploy import (
|
|
15
|
+
DeploymentClient,
|
|
16
|
+
create_deployment_zip,
|
|
17
|
+
get_config_path,
|
|
18
|
+
load_deployment_config,
|
|
19
|
+
save_deployment_config,
|
|
20
|
+
validate_dependencies,
|
|
21
|
+
)
|
|
22
|
+
from plotly_cloud._oauth import OAuthClient
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PublishOperations:
|
|
26
|
+
check_auth = "initialize"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PublishOperation(TypedDict):
|
|
30
|
+
"""RPC operation structure for dev tools publishing."""
|
|
31
|
+
|
|
32
|
+
operation: Literal["initialize", "authenticate", "auth_poll", "publish", "status"]
|
|
33
|
+
data: Any
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RPCResponse(TypedDict):
|
|
37
|
+
"""Standard RPC response structure."""
|
|
38
|
+
|
|
39
|
+
result: NotRequired[Any]
|
|
40
|
+
error: NotRequired[str]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PlotlyCloudPublishRPC:
|
|
44
|
+
"""RPC handler for Plotly Cloud publishing operations in dev tools."""
|
|
45
|
+
|
|
46
|
+
def __init__(self) -> None:
|
|
47
|
+
"""Initialize the RPC handler."""
|
|
48
|
+
client_id = cloud_config.get_oauth_client_id()
|
|
49
|
+
self.oauth_client = OAuthClient(client_id)
|
|
50
|
+
self._app_setup = None # Fallback incase get_app fails.
|
|
51
|
+
|
|
52
|
+
def get_project_path(self):
|
|
53
|
+
app = None
|
|
54
|
+
try:
|
|
55
|
+
app = dash.det_app() # type: ignore
|
|
56
|
+
except Exception:
|
|
57
|
+
app = self._app_setup
|
|
58
|
+
|
|
59
|
+
assert app
|
|
60
|
+
app_module = importlib.import_module(app.config.name)
|
|
61
|
+
return os.path.dirname(str(app_module.__file__))
|
|
62
|
+
|
|
63
|
+
async def handle_operation(self, publish_operation: PublishOperation) -> RPCResponse:
|
|
64
|
+
"""Handle a publish operation from dev tools.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
publish_operation: The operation to perform with its data
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
RPCResponse with data and optional error
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
ValueError: If operation is not supported
|
|
74
|
+
"""
|
|
75
|
+
operation_name = publish_operation["operation"]
|
|
76
|
+
data = publish_operation.get("data")
|
|
77
|
+
|
|
78
|
+
# Get the method by operation name (direct match)
|
|
79
|
+
if not hasattr(self, operation_name):
|
|
80
|
+
raise ValueError(f"Unsupported operation: {operation_name}")
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
method = getattr(self, operation_name)
|
|
84
|
+
return await method(data)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
traceback.print_last()
|
|
87
|
+
return {"error": str(e)}
|
|
88
|
+
|
|
89
|
+
async def initialize(self, data: Any) -> RPCResponse:
|
|
90
|
+
is_authenticated = await self.oauth_client.is_authenticated()
|
|
91
|
+
|
|
92
|
+
project_path = self.get_project_path()
|
|
93
|
+
|
|
94
|
+
config_path = get_config_path(project_path, os.path.join(project_path, "plotly-cloud.toml"))
|
|
95
|
+
config = load_deployment_config(config_path)
|
|
96
|
+
|
|
97
|
+
app_id = config.get("app_id")
|
|
98
|
+
existing = app_id is not None
|
|
99
|
+
status = "new"
|
|
100
|
+
app_name = config.get("name", os.path.basename(project_path))
|
|
101
|
+
|
|
102
|
+
if app_id is not None and is_authenticated:
|
|
103
|
+
async with DeploymentClient(self.oauth_client) as deploy_client:
|
|
104
|
+
status_data = await deploy_client.get_app_status(app_id)
|
|
105
|
+
status = status_data.get("status", "")
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
"result": {
|
|
109
|
+
"authenticated": is_authenticated,
|
|
110
|
+
"existing": existing,
|
|
111
|
+
"status": status,
|
|
112
|
+
"app_name": app_name,
|
|
113
|
+
"app_path": project_path,
|
|
114
|
+
"app_id": app_id,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async def authenticate(self, data: Any) -> RPCResponse:
|
|
119
|
+
device_auth = await self.oauth_client.request_device_authorization()
|
|
120
|
+
return {"result": device_auth}
|
|
121
|
+
|
|
122
|
+
async def auth_poll(self, data: Any) -> RPCResponse:
|
|
123
|
+
device_code = data.get("device_code")
|
|
124
|
+
status_code, response = await self.oauth_client.check_authentication_status(device_code)
|
|
125
|
+
if status_code == 200:
|
|
126
|
+
await self.oauth_client._save_credentials(dict(response))
|
|
127
|
+
return {"result": {"success": True}}
|
|
128
|
+
else:
|
|
129
|
+
error = response.get("error", "unknown_error")
|
|
130
|
+
if error == "authorization_pending":
|
|
131
|
+
return {"result": {}}
|
|
132
|
+
elif error == "slow_down":
|
|
133
|
+
delay = 1 + data.get("delayed", 0)
|
|
134
|
+
return {"result": {"delay": delay}}
|
|
135
|
+
elif error == "expired_token":
|
|
136
|
+
return {"result": {"try_again": True}}
|
|
137
|
+
elif error == "access_denied":
|
|
138
|
+
return {"error": "Access denied by user"}
|
|
139
|
+
else:
|
|
140
|
+
return {"error": "Authentication Failed"}
|
|
141
|
+
|
|
142
|
+
async def status(self, data: Any) -> RPCResponse:
|
|
143
|
+
app_id = data.get("app_id")
|
|
144
|
+
async with DeploymentClient(self.oauth_client) as deploy_client:
|
|
145
|
+
status_data = await deploy_client.get_app_status(app_id)
|
|
146
|
+
return {"result": {"status": status_data.get("status", "")}}
|
|
147
|
+
|
|
148
|
+
async def publish(self, data: Any) -> RPCResponse:
|
|
149
|
+
app_path = data.get("app_path")
|
|
150
|
+
app_id = data.get("app_id")
|
|
151
|
+
app_name = data.get("app_name")
|
|
152
|
+
|
|
153
|
+
config_path = get_config_path(app_path)
|
|
154
|
+
|
|
155
|
+
temp_file = tempfile.NamedTemporaryFile(suffix=".zip", delete=False)
|
|
156
|
+
zip_path = temp_file.name
|
|
157
|
+
temp_file.close()
|
|
158
|
+
|
|
159
|
+
validate_dependencies(app_path)
|
|
160
|
+
|
|
161
|
+
await create_deployment_zip(app_path, zip_path)
|
|
162
|
+
|
|
163
|
+
async with DeploymentClient(self.oauth_client) as deploy_client:
|
|
164
|
+
if app_id:
|
|
165
|
+
# update app
|
|
166
|
+
app_data = await deploy_client.publish_app(app_id, zip_path)
|
|
167
|
+
config = load_deployment_config(config_path)
|
|
168
|
+
|
|
169
|
+
if config.get("app_url") != app_data.get("app_url"):
|
|
170
|
+
config["app_url"] = app_data.get("app_url", "")
|
|
171
|
+
save_deployment_config(config, config_path)
|
|
172
|
+
|
|
173
|
+
return {"result": {"app_id": app_id}}
|
|
174
|
+
else:
|
|
175
|
+
# create new app
|
|
176
|
+
app_data = await deploy_client.create_app(app_name, zip_path)
|
|
177
|
+
|
|
178
|
+
config: AppDeploymentConfig = {
|
|
179
|
+
"name": app_name,
|
|
180
|
+
"app_id": app_data.get("id", ""),
|
|
181
|
+
"app_url": app_data.get("app_url", ""),
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
save_deployment_config(config, config_path)
|
|
185
|
+
|
|
186
|
+
return {"result": {"app_id": app_data.get("id")}}
|
|
@@ -6,13 +6,14 @@ import os
|
|
|
6
6
|
import time
|
|
7
7
|
import webbrowser
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Optional
|
|
9
|
+
from typing import Any, Dict, Optional, Tuple, Union
|
|
10
10
|
|
|
11
11
|
import httpx
|
|
12
12
|
from rich.console import Console
|
|
13
13
|
from rich.live import Live
|
|
14
14
|
from rich.panel import Panel
|
|
15
15
|
from rich.spinner import Spinner
|
|
16
|
+
from typing_extensions import TypedDict
|
|
16
17
|
|
|
17
18
|
from .exceptions import (
|
|
18
19
|
CredentialError,
|
|
@@ -36,6 +37,25 @@ WORKOS_ENDPOINTS = {
|
|
|
36
37
|
DEFAULT_AUTH_PROVIDER = "authkit"
|
|
37
38
|
|
|
38
39
|
|
|
40
|
+
class AuthTokenResponse(TypedDict):
|
|
41
|
+
"""Response from successful OAuth authentication."""
|
|
42
|
+
|
|
43
|
+
access_token: str
|
|
44
|
+
refresh_token: str
|
|
45
|
+
token_type: str
|
|
46
|
+
expires_in: int
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AuthErrorResponse(TypedDict):
|
|
50
|
+
"""Response from failed OAuth authentication."""
|
|
51
|
+
|
|
52
|
+
error: str
|
|
53
|
+
error_description: Optional[str]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
AuthResponse = Union[AuthTokenResponse, AuthErrorResponse]
|
|
57
|
+
|
|
58
|
+
|
|
39
59
|
class OAuthClient:
|
|
40
60
|
"""OAuth client for WorkOS CLI Auth using device authorization flow."""
|
|
41
61
|
|
|
@@ -70,7 +90,45 @@ class OAuthClient:
|
|
|
70
90
|
|
|
71
91
|
return response.json()
|
|
72
92
|
|
|
73
|
-
async def
|
|
93
|
+
async def check_authentication_status(
|
|
94
|
+
self,
|
|
95
|
+
device_code: str,
|
|
96
|
+
client: Optional[httpx.AsyncClient] = None,
|
|
97
|
+
) -> Tuple[int, AuthResponse]:
|
|
98
|
+
"""Check authentication status without terminal output or polling loop.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
device_code: The device code from device authorization
|
|
102
|
+
client: Optional httpx client to use, creates new one if not provided
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Tuple of (status_code, response_data)
|
|
106
|
+
"""
|
|
107
|
+
token_data = {
|
|
108
|
+
"client_id": self.client_id,
|
|
109
|
+
"device_code": device_code,
|
|
110
|
+
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if client:
|
|
114
|
+
response = await client.post(
|
|
115
|
+
f"{WORKOS_API_BASE_URL}{WORKOS_ENDPOINTS['AUTHENTICATE']}",
|
|
116
|
+
data=token_data,
|
|
117
|
+
headers={"Content-Type": "application/x-www-form-urlencoded", "user-agent": "PlotlyCloudCLI"},
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
async with httpx.AsyncClient() as new_client:
|
|
121
|
+
response = await new_client.post(
|
|
122
|
+
f"{WORKOS_API_BASE_URL}{WORKOS_ENDPOINTS['AUTHENTICATE']}",
|
|
123
|
+
data=token_data,
|
|
124
|
+
headers={"Content-Type": "application/x-www-form-urlencoded", "user-agent": "PlotlyCloudCLI"},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return response.status_code, response.json()
|
|
128
|
+
|
|
129
|
+
async def poll_for_authentication(
|
|
130
|
+
self, device_code: str, interval: int = 5, timeout: int = 300
|
|
131
|
+
) -> AuthTokenResponse:
|
|
74
132
|
"""Poll for authentication completion with exponential backoff."""
|
|
75
133
|
start_time = time.time()
|
|
76
134
|
current_interval = interval
|
|
@@ -79,24 +137,13 @@ class OAuthClient:
|
|
|
79
137
|
spinner = Spinner("dots", text="⏳ Waiting for authorization...")
|
|
80
138
|
|
|
81
139
|
with Live(spinner, console=console, refresh_per_second=4):
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"device_code": device_code,
|
|
86
|
-
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async with httpx.AsyncClient() as client:
|
|
90
|
-
response = await client.post(
|
|
91
|
-
f"{WORKOS_API_BASE_URL}{WORKOS_ENDPOINTS['AUTHENTICATE']}",
|
|
92
|
-
data=token_data,
|
|
93
|
-
headers={"Content-Type": "application/x-www-form-urlencoded", "user-agent": "PlotlyCloudCLI"},
|
|
94
|
-
)
|
|
140
|
+
async with httpx.AsyncClient() as client:
|
|
141
|
+
while time.time() - start_time < timeout:
|
|
142
|
+
status_code, response_data = await self.check_authentication_status(device_code, client)
|
|
95
143
|
|
|
96
|
-
if
|
|
97
|
-
return
|
|
144
|
+
if status_code == 200:
|
|
145
|
+
return response_data # type: ignore
|
|
98
146
|
|
|
99
|
-
response_data = response.json()
|
|
100
147
|
error = response_data.get("error", "unknown_error")
|
|
101
148
|
|
|
102
149
|
if error == "authorization_pending":
|
|
@@ -121,7 +168,7 @@ class OAuthClient:
|
|
|
121
168
|
|
|
122
169
|
raise TimeoutError("Authentication timed out. Please try again.")
|
|
123
170
|
|
|
124
|
-
async def login(self, open_browser: bool = True, provider: str = DEFAULT_AUTH_PROVIDER) ->
|
|
171
|
+
async def login(self, open_browser: bool = True, provider: str = DEFAULT_AUTH_PROVIDER) -> AuthTokenResponse:
|
|
125
172
|
"""Perform CLI Auth device flow login."""
|
|
126
173
|
|
|
127
174
|
# Step 1: Request device authorization
|
|
@@ -167,11 +214,11 @@ class OAuthClient:
|
|
|
167
214
|
raise
|
|
168
215
|
|
|
169
216
|
# Step 4: Save credentials
|
|
170
|
-
await self._save_credentials(tokens)
|
|
217
|
+
await self._save_credentials(dict(tokens))
|
|
171
218
|
|
|
172
219
|
return tokens
|
|
173
220
|
|
|
174
|
-
async def _save_credentials(self, credentials:
|
|
221
|
+
async def _save_credentials(self, credentials: Dict[str, Any]):
|
|
175
222
|
"""Save credentials to file."""
|
|
176
223
|
try:
|
|
177
224
|
# Ensure parent directory exists
|
|
@@ -187,7 +234,7 @@ class OAuthClient:
|
|
|
187
234
|
except Exception as e:
|
|
188
235
|
raise CredentialError("Failed to save credentials", str(e)) from e
|
|
189
236
|
|
|
190
|
-
async def load_credentials(self) -> Optional[
|
|
237
|
+
async def load_credentials(self) -> Optional[Dict[str, Any]]:
|
|
191
238
|
"""Load saved credentials."""
|
|
192
239
|
try:
|
|
193
240
|
if not self.credentials_path.exists():
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.plotly-cloud-publish-container{position:relative;font-family:Verdana,Geneva,sans-serif}.plotly-cloud-publish-button{cursor:pointer}.plotly-cloud-publish-button:hover{color:#7f4bc4}.plotly-cloud-publish-modal-overlay{position:absolute;bottom:100%;left:-16px;z-index:10000;margin-bottom:1.25rem}.plotly-cloud-publish-modal-content{width:522px;background:#fff;border:1px solid #d1d5db;border-radius:4px 4px 0 0;box-shadow:0 -4px 6px #00000014;overflow:hidden}.plotly-cloud-publish-modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #e5e5e5;background:#f9fafb}.plotly-cloud-publish-modal-header h3{margin:0;color:#333}.plotly-cloud-publish-modal-close{background:none;border:none;font-size:24px;cursor:pointer;color:#666;padding:0;width:30px;height:30px;display:flex;align-items:center;justify-content:center}.plotly-cloud-publish-modal-close:hover{color:#333}.plotly-cloud-publish-modal-body{padding:20px;color:#000!important;font-weight:100}button.plotly-cloud-publish-modal-button{display:inline-flex;align-items:center;gap:6px;padding:6px 10px!important;border-radius:4px!important;font-size:12px!important;font-weight:600!important;cursor:pointer;border:1px solid transparent;border-top-color:transparent;border-right-color:transparent;border-bottom-color:transparent;border-left-color:transparent;background:#f3f4f6;color:#374151}.plotly-cloud-publish-modal-button:disabled{cursor:not-allowed}button.plotly-cloud-btn-primary{background:#8b5cf6!important;color:#fff;border-color:#7c3aed!important}button.plotly-cloud-btn-secondary{background:#fff;border:none;color:#8b5cf6!important}.plotly-cloud-button-bar{display:flex;justify-content:flex-end;align-items:center}.plotly-cloud-publish-signin{display:flex;justify-content:space-between;align-items:center}.plotly-cloud-name-input-label{font-weight:600;color:#374151;font-size:12px;margin-bottom:6px}.plotly-cloud-name-input-field{margin-bottom:12px;width:calc(100% - 8px)}.plotly-cloud-status-info{display:flex;border-radius:5px;margin-bottom:12px;align-items:center;padding:6px;font-size:12px!important}.plotly-cloud-status-red{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.plotly-cloud-status-description{font-size:12px!important;margin-left:8px}.plotly-cloud-status-title{font-weight:700}.plotly-cloud-status-green{background:#ecfdf5;color:#065f46;border:1px solid #a7f3d0}.plotly-cloud-status-red .plotly-cloud-status-title{color:#a10}.plotly-cloud-status-green .plotly-cloud-status-title{color:#065f46}.plotly-cloud-status-gray{background:#f9fafb;color:#000;border:1px solid #dadada}.plotly-cloud-status-gray .plotly-cloud-status-title{color:#2e2e2e}.plotly-cloud-green-icon{color:#10b981!important}.plotly-cloud-red-icon{color:#ef4444}.plotly-cloud-gray-icon{color:#9ca3af}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.plotly-cloud-spin{animation:spin 2s linear infinite}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const React = window.React; const ReactDOM = window.ReactDOM;
|
|
2
|
+
"use strict";var plotly_cloud_publish_component=(()=>{var Go=Object.create;var G=Object.defineProperty;var Oo=Object.getOwnPropertyDescriptor;var Wo=Object.getOwnPropertyNames;var qo=Object.getPrototypeOf,Vo=Object.prototype.hasOwnProperty;var ao=(o,a)=>()=>(a||o((a={exports:{}}).exports,a),a.exports),zo=(o,a)=>{for(var s in a)G(o,s,{get:a[s],enumerable:!0})},mo=(o,a,s,r)=>{if(a&&typeof a=="object"||typeof a=="function")for(let n of Wo(a))!Vo.call(o,n)&&n!==s&&G(o,n,{get:()=>a[n],enumerable:!(r=Oo(a,n))||r.enumerable});return o};var e=(o,a,s)=>(s=o!=null?Go(qo(o)):{},mo(a||!o||!o.__esModule?G(s,"default",{value:o,enumerable:!0}):s,o)),Xo=o=>mo(G({},"__esModule",{value:!0}),o);var u=ao((fa,po)=>{po.exports=React});var fo=ao(O=>{"use strict";var _o=u(),Zo=Symbol.for("react.element"),Jo=Symbol.for("react.fragment"),Ko=Object.prototype.hasOwnProperty,jo=_o.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,Qo={key:!0,ref:!0,__self:!0,__source:!0};function Io(o,a,s){var r,n={},I=null,h=null;s!==void 0&&(I=""+s),a.key!==void 0&&(I=""+a.key),a.ref!==void 0&&(h=a.ref);for(r in a)Ko.call(a,r)&&!Qo.hasOwnProperty(r)&&(n[r]=a[r]);if(o&&o.defaultProps)for(r in a=o.defaultProps,a)n[r]===void 0&&(n[r]=a[r]);return{$$typeof:Zo,type:o,key:I,ref:h,props:n,_owner:jo.current}}O.Fragment=Jo;O.jsx=Io;O.jsxs=Io});var S=ao((Sa,uo)=>{"use strict";uo.exports=fo()});var ia={};zo(ia,{PlotlyCloudPublishComponent:()=>ro});var t=e(u());var k=e(S()),Yo=({title:o,onClose:a,children:s})=>(0,k.jsx)("div",{className:"plotly-cloud-publish-modal-overlay",children:(0,k.jsxs)("div",{className:"plotly-cloud-publish-modal-content",children:[(0,k.jsxs)("div",{className:"plotly-cloud-publish-modal-header",children:[(0,k.jsx)("h3",{children:o},"modal-title"),(0,k.jsx)("button",{className:"plotly-cloud-publish-modal-close",onClick:a,children:"\xD7"},"modal-close")]}),(0,k.jsx)("div",{className:"plotly-cloud-publish-modal-body",children:s},"modal-body")]})}),So=Yo;var go=e(u()),L=e(S()),$o=o=>{let[a,s]=(0,go.useState)(o.waiting);return(0,L.jsx)("div",{className:"plotly-cloud-publish-signin",children:a?(0,L.jsx)("span",{children:"Waiting for succesful sign-in on signin.cloud.plotly.com"}):(0,L.jsxs)(L.Fragment,{children:[(0,L.jsx)("span",{children:"Sign in to publish your app to Plotly Cloud."}),(0,L.jsx)("button",{className:"plotly-cloud-publish-modal-button plotly-cloud-btn-primary",onClick:()=>{s(!0),o.onSignIn()},children:"Sign In"})]})})},ho=$o;var oa=o=>{let a=async r=>await(await fetch(`${o}_plotly_cloud_publish`,{method:"POST",body:JSON.stringify(r),headers:{"Content-Type":"application/json"}})).json(),s=r=>n=>a({operation:r,data:n});return{initialize:s("initialize"),authenticate:s("authenticate"),auth_poll:s("auth_poll"),publish:s("publish"),status:s("status")}},Co=oa;var wo=e(u()),b=e(S()),aa=({initialAppName:o="",onCancel:a,onPublish:s})=>{let[r,n]=(0,wo.useState)(o);return(0,b.jsxs)("div",{className:"plotly-cloud-name-input",children:[(0,b.jsx)("div",{className:"plotly-cloud-name-input-label",children:"App Name"}),(0,b.jsxs)("form",{onSubmit:h=>{h.preventDefault(),r.trim()&&s(r.trim())},children:[(0,b.jsx)("input",{type:"text",value:r,onChange:h=>n(h.target.value),placeholder:"My Dash App",className:"plotly-cloud-name-input-field"}),(0,b.jsxs)("div",{className:"plotly-cloud-button-bar",children:[(0,b.jsx)("button",{type:"button",onClick:a,className:"plotly-cloud-publish-modal-button plotly-cloud-btn-secondary",children:"Cancel"}),(0,b.jsx)("button",{type:"submit",disabled:!r.trim(),className:"plotly-cloud-publish-modal-button plotly-cloud-btn-primary",children:"Publish App"})]})]})]})},Lo=aa;var Q=e(u());var c=e(u(),1),bo=new Map([["bold",c.createElement(c.Fragment,null,c.createElement("path",{d:"M236,128a108,108,0,0,1-216,0c0-42.52,24.73-81.34,63-98.9A12,12,0,1,1,93,50.91C63.24,64.57,44,94.83,44,128a84,84,0,0,0,168,0c0-33.17-19.24-63.43-49-77.09A12,12,0,1,1,173,29.1C211.27,46.66,236,85.48,236,128Z"}))],["duotone",c.createElement(c.Fragment,null,c.createElement("path",{d:"M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z",opacity:"0.2"}),c.createElement("path",{d:"M232,128a104,104,0,0,1-208,0c0-41,23.81-78.36,60.66-95.27a8,8,0,0,1,6.68,14.54C60.15,61.59,40,93.27,40,128a88,88,0,0,0,176,0c0-34.73-20.15-66.41-51.34-80.73a8,8,0,0,1,6.68-14.54C208.19,49.64,232,87,232,128Z"}))],["fill",c.createElement(c.Fragment,null,c.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,176A72,72,0,0,1,92,65.64a8,8,0,0,1,8,13.85,56,56,0,1,0,56,0,8,8,0,0,1,8-13.85A72,72,0,0,1,128,200Z"}))],["light",c.createElement(c.Fragment,null,c.createElement("path",{d:"M230,128a102,102,0,0,1-204,0c0-40.18,23.35-76.86,59.5-93.45a6,6,0,0,1,5,10.9C58.61,60.09,38,92.49,38,128a90,90,0,0,0,180,0c0-35.51-20.61-67.91-52.5-82.55a6,6,0,0,1,5-10.9C206.65,51.14,230,87.82,230,128Z"}))],["regular",c.createElement(c.Fragment,null,c.createElement("path",{d:"M232,128a104,104,0,0,1-208,0c0-41,23.81-78.36,60.66-95.27a8,8,0,0,1,6.68,14.54C60.15,61.59,40,93.27,40,128a88,88,0,0,0,176,0c0-34.73-20.15-66.41-51.34-80.73a8,8,0,0,1,6.68-14.54C208.19,49.64,232,87,232,128Z"}))],["thin",c.createElement(c.Fragment,null,c.createElement("path",{d:"M228,128a100,100,0,0,1-200,0c0-39.4,22.9-75.37,58.33-91.63a4,4,0,1,1,3.34,7.27C57.07,58.6,36,91.71,36,128a92,92,0,0,0,184,0c0-36.29-21.07-69.4-53.67-84.36a4,4,0,1,1,3.34-7.27C205.1,52.63,228,88.6,228,128Z"}))]]);var l=e(u(),1),Po=new Map([["bold",l.createElement(l.Fragment,null,l.createElement("path",{d:"M160,36A92.09,92.09,0,0,0,79,84.36,68,68,0,1,0,72,220h88a92,92,0,0,0,0-184Zm0,160H72a44,44,0,0,1-1.82-88A91.86,91.86,0,0,0,68,128a12,12,0,0,0,24,0,68,68,0,1,1,68,68Zm40.49-92.49a12,12,0,0,1,0,17l-48,48a12,12,0,0,1-17,0l-24-24a12,12,0,1,1,17-17L144,143l39.51-39.52A12,12,0,0,1,200.49,103.51Z"}))],["duotone",l.createElement(l.Fragment,null,l.createElement("path",{d:"M240,128a80,80,0,0,1-80,80H72A56,56,0,1,1,85.92,97.74l0,.1A80,80,0,0,1,240,128Z",opacity:"0.2"}),l.createElement("path",{d:"M160,40A88.09,88.09,0,0,0,81.29,88.67,64,64,0,1,0,72,216h88a88,88,0,0,0,0-176Zm0,160H72a48,48,0,0,1,0-96c1.1,0,2.2,0,3.29.11A88,88,0,0,0,72,128a8,8,0,0,0,16,0,72,72,0,1,1,72,72Zm37.66-93.66a8,8,0,0,1,0,11.32l-48,48a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L144,148.69l42.34-42.35A8,8,0,0,1,197.66,106.34Z"}))],["fill",l.createElement(l.Fragment,null,l.createElement("path",{d:"M247.93,124.52C246.11,77.54,207.07,40,160.06,40A88.1,88.1,0,0,0,81.29,88.67h0A87.48,87.48,0,0,0,72,127.73,8.18,8.18,0,0,1,64.57,136,8,8,0,0,1,56,128a103.66,103.66,0,0,1,5.34-32.92,4,4,0,0,0-4.75-5.18A64.09,64.09,0,0,0,8,152c0,35.19,29.75,64,65,64H160A88.09,88.09,0,0,0,247.93,124.52Zm-50.27-6.86-48,48a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L144,148.69l42.34-42.35a8,8,0,0,1,11.32,11.32Z"}))],["light",l.createElement(l.Fragment,null,l.createElement("path",{d:"M160,42A86.11,86.11,0,0,0,82.43,90.88,62,62,0,1,0,72,214h88a86,86,0,0,0,0-172Zm0,160H72a50,50,0,0,1,0-100,50.67,50.67,0,0,1,5.91.35A85.61,85.61,0,0,0,74,128a6,6,0,0,0,12,0,74,74,0,1,1,74,74Zm36.24-94.24a6,6,0,0,1,0,8.48l-48,48a6,6,0,0,1-8.48,0l-24-24a6,6,0,0,1,8.48-8.48L144,151.51l43.76-43.75A6,6,0,0,1,196.24,107.76Z"}))],["regular",l.createElement(l.Fragment,null,l.createElement("path",{d:"M160,40A88.09,88.09,0,0,0,81.29,88.67,64,64,0,1,0,72,216h88a88,88,0,0,0,0-176Zm0,160H72a48,48,0,0,1,0-96c1.1,0,2.2,0,3.29.11A88,88,0,0,0,72,128a8,8,0,0,0,16,0,72,72,0,1,1,72,72Zm37.66-93.66a8,8,0,0,1,0,11.32l-48,48a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L144,148.69l42.34-42.35A8,8,0,0,1,197.66,106.34Z"}))],["thin",l.createElement(l.Fragment,null,l.createElement("path",{d:"M160,44A84.11,84.11,0,0,0,83.59,93.12,60.71,60.71,0,0,0,72,92a60,60,0,0,0,0,120h88a84,84,0,0,0,0-168Zm0,160H72a52,52,0,1,1,8.55-103.3A83.66,83.66,0,0,0,76,128a4,4,0,0,0,8,0,76,76,0,1,1,76,76Zm34.83-94.83a4,4,0,0,1,0,5.66l-48,48a4,4,0,0,1-5.66,0l-24-24a4,4,0,0,1,5.66-5.66L144,154.34l45.17-45.17A4,4,0,0,1,194.83,109.17Z"}))]]);var m=e(u(),1),Ao=new Map([["bold",m.createElement(m.Fragment,null,m.createElement("path",{d:"M56.88,31.93A12,12,0,1,0,39.12,48.07L71.79,84A68,68,0,0,0,72,220h88a91.26,91.26,0,0,0,30.66-5.24l8.46,9.31a12,12,0,0,0,17.76-16.14ZM160,196H72a44,44,0,0,1-1.8-87.95A91.91,91.91,0,0,0,68,128a12,12,0,0,0,24,0,68.22,68.22,0,0,1,2.66-18.84l77.88,85.67A68.67,68.67,0,0,1,160,196Zm92-68a91.32,91.32,0,0,1-17.53,54,12,12,0,1,1-19.41-14.11,68,68,0,0,0-89.57-98.53,12,12,0,0,1-12.2-20.66A92,92,0,0,1,252,128Z"}))],["duotone",m.createElement(m.Fragment,null,m.createElement("path",{d:"M240,128a80,80,0,0,1-80,80H72A56,56,0,1,1,85.92,97.74l0,.1A80,80,0,0,1,240,128Z",opacity:"0.2"}),m.createElement("path",{d:"M53.92,34.62A8,8,0,1,0,42.08,45.38L81.32,88.55l-.06.12A65,65,0,0,0,72,88a64,64,0,0,0,0,128h88a87.34,87.34,0,0,0,31.8-5.93l10.28,11.31a8,8,0,1,0,11.84-10.76ZM160,200H72a48,48,0,0,1,0-96c1.1,0,2.2,0,3.3.12A88.4,88.4,0,0,0,72,128a8,8,0,0,0,16,0,72.25,72.25,0,0,1,5.06-26.54l87,95.7A71.66,71.66,0,0,1,160,200Zm88-72a87.89,87.89,0,0,1-22.35,58.61A8,8,0,0,1,213.71,176,72,72,0,0,0,117.37,70a8,8,0,0,1-9.48-12.89A88,88,0,0,1,248,128Z"}))],["fill",m.createElement(m.Fragment,null,m.createElement("path",{d:"M248,128.72A87.74,87.74,0,0,1,222.41,190a4,4,0,0,1-5.77-.16L103.78,65.67a4,4,0,0,1,.39-5.76A87.82,87.82,0,0,1,160.87,40C209.15,40.47,248.38,80.43,248,128.72ZM53.92,34.62A8,8,0,1,0,42.08,45.38L81.33,88.56l-.06.11A64,64,0,0,0,8,153c.53,35.12,29.84,63,65,63h87a87.65,87.65,0,0,0,31.78-5.95l10.3,11.33a8,8,0,0,0,11.33.52,8.32,8.32,0,0,0,.29-11.52Z"}))],["light",m.createElement(m.Fragment,null,m.createElement("path",{d:"M52.44,36A6,6,0,0,0,43.56,44l40.18,44.2c-.45.87-.9,1.75-1.32,2.64A62,62,0,1,0,72,214h88a85.23,85.23,0,0,0,32.35-6.3L203.56,220a6,6,0,0,0,8.88-8.08ZM160,202H72a50,50,0,1,1,5.9-99.64A86.25,86.25,0,0,0,74,128a6,6,0,0,0,12,0,73.92,73.92,0,0,1,6.44-30.2l91.22,100.34A73.65,73.65,0,0,1,160,202Zm86-74a85.85,85.85,0,0,1-21.85,57.27,6,6,0,0,1-4.47,2,6,6,0,0,1-4.47-10,74,74,0,0,0-99-108.92,6,6,0,1,1-7.11-9.67A86,86,0,0,1,246,128Z"}))],["regular",m.createElement(m.Fragment,null,m.createElement("path",{d:"M53.92,34.62A8,8,0,1,0,42.08,45.38L81.32,88.55l-.06.12A65,65,0,0,0,72,88a64,64,0,0,0,0,128h88a87.34,87.34,0,0,0,31.8-5.93l10.28,11.31a8,8,0,1,0,11.84-10.76ZM160,200H72a48,48,0,0,1,0-96c1.1,0,2.2,0,3.3.12A88.4,88.4,0,0,0,72,128a8,8,0,0,0,16,0,72.25,72.25,0,0,1,5.06-26.54l87,95.7A71.66,71.66,0,0,1,160,200Zm88-72a87.89,87.89,0,0,1-22.35,58.61A8,8,0,0,1,213.71,176,72,72,0,0,0,117.37,70a8,8,0,0,1-9.48-12.89A88,88,0,0,1,248,128Z"}))],["thin",m.createElement(m.Fragment,null,m.createElement("path",{d:"M51,37.31A4,4,0,0,0,45,42.69L86.16,87.93q-1.38,2.55-2.59,5.19A60,60,0,1,0,72,212h88a83.19,83.19,0,0,0,32.88-6.69L205,218.69a4,4,0,1,0,5.92-5.38ZM160,204H72a52,52,0,0,1,0-104,52.92,52.92,0,0,1,8.54.72A84.21,84.21,0,0,0,76,128a4,4,0,0,0,8,0,76,76,0,0,1,7.9-33.76L187.13,199A75.37,75.37,0,0,1,160,204Zm84-76a83.86,83.86,0,0,1-21.34,55.94,4,4,0,1,1-6-5.33A76,76,0,0,0,115,66.75a4,4,0,0,1-4.74-6.45A84,84,0,0,1,244,128Z"}))]]);var p=e(u(),1),ko=new Map([["bold",p.createElement(p.Fragment,null,p.createElement("path",{d:"M160,36A92.09,92.09,0,0,0,79,84.36,68,68,0,1,0,72,220h88a92,92,0,0,0,0-184Zm0,160H72a44,44,0,0,1-1.82-88A91.86,91.86,0,0,0,68,128a12,12,0,0,0,24,0,68,68,0,1,1,68,68Zm32.49-79.51L177,132l15.52,15.51a12,12,0,0,1-17,17L160,149l-15.51,15.52a12,12,0,1,1-17-17L143,132l-15.52-15.51a12,12,0,1,1,17-17L160,115l15.51-15.52a12,12,0,0,1,17,17Z"}))],["duotone",p.createElement(p.Fragment,null,p.createElement("path",{d:"M240,127.62a80,80,0,0,1-80,80H72A56,56,0,1,1,85.92,97.36l0,.1A80,80,0,0,1,240,127.62Z",opacity:"0.2"}),p.createElement("path",{d:"M160,40A88.09,88.09,0,0,0,81.29,88.67,64,64,0,1,0,72,216h88a88,88,0,0,0,0-176Zm0,160H72a48,48,0,0,1,0-96c1.1,0,2.2,0,3.29.11A88,88,0,0,0,72,128a8,8,0,0,0,16,0,72,72,0,1,1,72,72Zm29.66-82.34L171.31,136l18.35,18.34a8,8,0,0,1-11.32,11.32L160,147.31l-18.34,18.35a8,8,0,0,1-11.32-11.32L148.69,136l-18.35-18.34a8,8,0,0,1,11.32-11.32L160,124.69l18.34-18.35a8,8,0,0,1,11.32,11.32Z"}))],["fill",p.createElement(p.Fragment,null,p.createElement("path",{d:"M247.93,124.52C246.11,77.54,207.07,40,160.06,40A88.1,88.1,0,0,0,81.29,88.67h0A87.48,87.48,0,0,0,72,127.73,8.18,8.18,0,0,1,64.57,136,8,8,0,0,1,56,128a103.66,103.66,0,0,1,5.34-32.92,4,4,0,0,0-4.75-5.18A64.09,64.09,0,0,0,8,152c0,35.19,29.75,64,65,64H160A88.09,88.09,0,0,0,247.93,124.52Zm-58.27,29.82a8,8,0,0,1-11.32,11.32L160,147.31l-18.34,18.35a8,8,0,0,1-11.32-11.32L148.69,136l-18.35-18.34a8,8,0,0,1,11.32-11.32L160,124.69l18.34-18.35a8,8,0,0,1,11.32,11.32L171.31,136Z"}))],["light",p.createElement(p.Fragment,null,p.createElement("path",{d:"M160,42A86.11,86.11,0,0,0,82.43,90.88,62,62,0,1,0,72,214h88a86,86,0,0,0,0-172Zm0,160H72a50,50,0,0,1,0-100,50.67,50.67,0,0,1,5.91.35A85.61,85.61,0,0,0,74,128a6,6,0,0,0,12,0,74,74,0,1,1,74,74Zm28.24-85.76L168.48,136l19.76,19.76a6,6,0,1,1-8.48,8.48L160,144.48l-19.76,19.76a6,6,0,0,1-8.48-8.48L151.52,136l-19.76-19.76a6,6,0,0,1,8.48-8.48L160,127.52l19.76-19.76a6,6,0,0,1,8.48,8.48Z"}))],["regular",p.createElement(p.Fragment,null,p.createElement("path",{d:"M160,40A88.09,88.09,0,0,0,81.29,88.67,64,64,0,1,0,72,216h88a88,88,0,0,0,0-176Zm0,160H72a48,48,0,0,1,0-96c1.1,0,2.2,0,3.29.11A88,88,0,0,0,72,128a8,8,0,0,0,16,0,72,72,0,1,1,72,72Zm29.66-82.34L171.31,136l18.35,18.34a8,8,0,0,1-11.32,11.32L160,147.31l-18.34,18.35a8,8,0,0,1-11.32-11.32L148.69,136l-18.35-18.34a8,8,0,0,1,11.32-11.32L160,124.69l18.34-18.35a8,8,0,0,1,11.32,11.32Z"}))],["thin",p.createElement(p.Fragment,null,p.createElement("path",{d:"M160,44A84.11,84.11,0,0,0,83.59,93.12,60.71,60.71,0,0,0,72,92a60,60,0,0,0,0,120h88a84,84,0,0,0,0-168Zm0,160H72a52,52,0,1,1,8.55-103.3A83.66,83.66,0,0,0,76,128a4,4,0,0,0,8,0,76,76,0,1,1,76,76Zm26.83-89.17L165.66,136l21.17,21.17a4,4,0,0,1-5.66,5.66L160,141.66l-21.17,21.17a4,4,0,0,1-5.66-5.66L154.34,136l-21.17-21.17a4,4,0,0,1,5.66-5.66L160,130.34l21.17-21.17a4,4,0,1,1,5.66,5.66Z"}))]]);var y=e(u(),1);var To=e(u(),1),yo=(0,To.createContext)({color:"currentColor",size:"1em",weight:"regular",mirrored:!1});var P=y.forwardRef((o,a)=>{let{alt:s,color:r,size:n,weight:I,mirrored:h,children:B,weights:F,...D}=o,{color:H="currentColor",size:C,weight:Y="regular",mirrored:M=!1,...x}=y.useContext(yo);return y.createElement("svg",{ref:a,xmlns:"http://www.w3.org/2000/svg",width:n??C,height:n??C,fill:r??H,viewBox:"0 0 256 256",transform:h||M?"scale(-1, 1)":void 0,...x,...D},!!s&&y.createElement("title",null,s),B,F.get(I??Y))});P.displayName="IconBase";var W=e(u(),1);var q=W.forwardRef((o,a)=>W.createElement(P,{ref:a,...o,weights:bo}));q.displayName="CircleNotchIcon";var V=e(u(),1);var z=V.forwardRef((o,a)=>V.createElement(P,{ref:a,...o,weights:Po}));z.displayName="CloudCheckIcon";var X=e(u(),1);var _=X.forwardRef((o,a)=>X.createElement(P,{ref:a,...o,weights:Ao}));_.displayName="CloudSlashIcon";var Z=e(u(),1);var J=Z.forwardRef((o,a)=>Z.createElement(P,{ref:a,...o,weights:ko}));J.displayName="CloudXIcon";var Bo=e(S()),sa=o=>(0,Bo.jsx)(q,{size:o.size,className:"plotly-cloud-spin plotly-cloud-gray-icon",weight:"bold"}),K=sa;var R=e(S()),ra=({status:o,size:a=16,isButtonIcon:s})=>o==="BUILDING"||o==="STARTING"||o==="unknown"?(0,R.jsx)(K,{size:a}):o==="RUNNING"?(0,R.jsx)(z,{size:a,className:"plotly-cloud-green-icon",weight:s?"fill":"duotone"}):o==="new"?(0,R.jsx)(_,{size:a,className:"plotly-cloud-gray-icon",weight:s?"fill":"duotone"}):(0,R.jsx)(J,{size:a,className:"plotly-cloud-red-icon",weight:"duotone"}),j=ra;var d=e(S()),ea=({status:o,onPublish:a,isUpdate:s,publishing:r,onCancel:n})=>{let I=()=>{n&&n()},[h,B,F]=(0,Q.useMemo)(()=>o==="BUILDING"||o==="STARTING"?["App is building","Wait for your app to finish publishing before publishing again","gray"]:o==="RUNNING"?["App is live","Your app is running in Plotly Cloud.","green"]:["App is stopped","Start your app by publishing again or starting it in the app manager","red"],[o]),[D,H]=(0,Q.useMemo)(()=>r?s?["Open Settings in Plotly Cloud","Republishing"]:["Cancel","Publishing"]:s?["Open Settings in Plotly Cloud","Republish"]:["Cancel","Publish"],[r,s]),C=(0,Q.useMemo)(()=>["BUILDING","STARTING"].includes(o),[o]);return(0,d.jsxs)("div",{className:"plotly-cloud-status-banner",children:[(0,d.jsxs)("div",{className:`plotly-cloud-status-info plotly-cloud-status-${F}`,children:[(0,d.jsx)(j,{status:o,size:20}),(0,d.jsxs)("div",{className:"plotly-cloud-status-description",children:[(0,d.jsx)("div",{className:"plotly-cloud-status-title",children:h}),(0,d.jsx)("div",{className:"plotly-cloud-status-text",children:B})]})]}),(0,d.jsxs)("div",{className:"plotly-cloud-button-bar",children:[(0,d.jsx)("button",{type:"button",onClick:I,disabled:s?!1:r,className:"plotly-cloud-publish-modal-button plotly-cloud-btn-secondary",children:D}),(0,d.jsxs)("button",{type:"button",onClick:a,className:"plotly-cloud-publish-modal-button plotly-cloud-btn-primary",disabled:r||C,children:[r?(0,d.jsx)(K,{size:12}):null,H]})]})]})},so=ea;var g=e(S()),na=()=>{let[o,a]=(0,t.useState)(!1),[s,r]=(0,t.useState)(""),[n,I]=(0,t.useState)("initialize"),[h,B]=(0,t.useState)(!1),[F,D]=(0,t.useState)(!1),[H,C]=(0,t.useState)(!1),[Y,M]=(0,t.useState)(!1),[x,Fo]=(0,t.useState)(""),[Do,eo]=(0,t.useState)(""),[$,T]=(0,t.useState)("unknown"),[ta,Ho]=(0,t.useState)(!1),[N,U]=(0,t.useState)(-1),[Mo,Ro]=(0,t.useState)(1),v=(0,t.useRef)(""),A=(0,t.useRef)(""),[ca,la]=(0,t.useReducer)((i,f)=>{switch(f.type){case"init":return{...f.payload};case"slow_down":return{...i,delay:f.payload.delay};case"try_again":case"success":return{}}return{...i}},{}),w=(0,t.useMemo)(()=>Co("/"),[]),ma=()=>{a(!0)},oo=()=>{a(!1)},xo=()=>{a(!o)},No=(0,t.useMemo)(()=>o?"plotly-cloud-publish-container dash-debug-menu__button dash-debug-menu__button--selected":"plotly-cloud-publish-container dash-debug-menu__button",[o]),no=()=>{D(!0),w.authenticate().then(i=>{i.error?r(i.error):(I("auth_poll"),v.current=i.result.device_code,window.open(i.result.verification_uri_complete,"_blank"),U(setTimeout(()=>{w.auth_poll({device_code:v.current}).then(io)},8e3)))})},io=i=>{if(i.error)r(i.error);else{if(i.result.success){B(!0),v.current="",A.current?(T("unknown"),I("update_app"),w.status({app_id:A.current}).then(E=>{T(E.result.status)})):I("enter_name");return}if(i.result.try_again){no();return}let f=Mo||1;i.result.delay&&(f=i.result.delay,Ro(f)),U(setTimeout(()=>{w.auth_poll({device_code:v.current}).then(io)},1e3+f*1e3))}},Uo=no;(0,t.useEffect)(()=>{n==="initialize"&&w.initialize().then(i=>{let{result:f,error:E}=i;E?r(E):(B(!f?.authenticated),Fo(f.app_path),eo(f.app_name),T(f.status),f.app_id&&(A.current=f.app_id),f?.authenticated?f.status==="new"?I("enter_name"):I("update_app"):I("sign_in"))})},[n,o]);let to=i=>{T(i.result.status),i.result.status==="RUNNING"?(C(!1),M(!0),I("update_app")):["BUILDING","STARTING"].includes(i.result.status)?U(setTimeout(()=>{w.status({app_id:A.current}).then(to)},2e3)):(C(!1),I("update_app"))},co=i=>{if(i.error){r(i.error);return}I("status_poll"),A.current=i.result.app_id,T("BUILDING"),N>0&&clearTimeout(N),U(setTimeout(()=>{w.status({app_id:A.current}).then(to)},2500))},vo=i=>{Ho(!0),eo(i),C(!0),M(!1),T("BUILDING"),w.publish({app_path:x,app_name:i}).then(co)},lo=()=>{C(!0),M(!0),T("BUILDING"),w.publish({app_id:A.current,app_path:x}).then(co)};(0,t.useEffect)(()=>()=>{N&&clearTimeout(N)});let Eo=()=>{window.open(`https://cloud.plotly.com/app/${A.current}/settings`,"_blank")};return(0,g.jsxs)("div",{className:No,children:[(0,g.jsxs)("button",{className:"plotly-cloud-publish-button",onClick:xo,children:[(0,g.jsx)(j,{status:$,isButtonIcon:!0})," Plotly Cloud"]},"publish-button"),o?(0,g.jsx)(So,{title:"Plotly Cloud",onClose:oo,children:s||(()=>{switch(n){case"authenticate":case"auth_poll":case"sign_in":return(0,g.jsx)(ho,{onSignIn:Uo,waiting:F});case"enter_name":return(0,g.jsx)(Lo,{onPublish:vo,onCancel:oo,initialAppName:Do});case"update_app":return(0,g.jsx)(so,{onPublish:lo,status:$,isUpdate:!0,onCancel:Eo});case"status_poll":case"publish":return(0,g.jsx)(so,{publishing:H,onPublish:lo,status:$,onCancel:oo,isUpdate:Y});default:return(0,g.jsx)(g.Fragment,{})}})()},"publish-modal"):null]})},ro=na;typeof window<"u"&&(window.plotly_cloud_publish_component={PlotlyCloudPublishComponent:ro});return Xo(ia);})();
|
|
3
|
+
/*! Bundled license information:
|
|
4
|
+
|
|
5
|
+
react/cjs/react-jsx-runtime.production.min.js:
|
|
6
|
+
(**
|
|
7
|
+
* @license React
|
|
8
|
+
* react-jsx-runtime.production.min.js
|
|
9
|
+
*
|
|
10
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
11
|
+
*
|
|
12
|
+
* This source code is licensed under the MIT license found in the
|
|
13
|
+
* LICENSE file in the root directory of this source tree.
|
|
14
|
+
*)
|
|
15
|
+
*/
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "plotly-cloud"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.0rc2"
|
|
8
8
|
description = "CLI for interacting with Plotly Cloud to deploy Dash apps"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -21,6 +21,8 @@ dependencies = [
|
|
|
21
21
|
[project.scripts]
|
|
22
22
|
plotly = "plotly_cloud.cli:main"
|
|
23
23
|
|
|
24
|
+
[project.entry-points.dash_hooks]
|
|
25
|
+
plotly_cloud_devtools = "plotly_cloud._devtool_hooks"
|
|
24
26
|
|
|
25
27
|
[tool.ruff]
|
|
26
28
|
line-length = 120
|
|
@@ -35,6 +37,8 @@ ignore-vcs = true
|
|
|
35
37
|
include = [
|
|
36
38
|
"plotly_cloud/**/*.py",
|
|
37
39
|
"plotly_cloud/cloud-env.toml",
|
|
40
|
+
"plotly_cloud/cloud_devtools.js",
|
|
41
|
+
"plotly_cloud/cloud_devtools.css",
|
|
38
42
|
]
|
|
39
43
|
|
|
40
44
|
[tool.hatch.build.targets.wheel]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|