plotly-cloud 0.1.0rc1__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 +3 -0
- plotly_cloud/_api_types.py +36 -0
- plotly_cloud/_changes.py +77 -0
- plotly_cloud/_cloud_env.py +93 -0
- plotly_cloud/_commands.py +878 -0
- plotly_cloud/_definitions.py +109 -0
- plotly_cloud/_deploy.py +524 -0
- plotly_cloud/_oauth.py +283 -0
- plotly_cloud/_parser.py +171 -0
- plotly_cloud/cli.py +286 -0
- plotly_cloud/cloud-env.toml +6 -0
- plotly_cloud/exceptions.py +198 -0
- plotly_cloud-0.1.0rc1.dist-info/METADATA +320 -0
- plotly_cloud-0.1.0rc1.dist-info/RECORD +17 -0
- plotly_cloud-0.1.0rc1.dist-info/WHEEL +4 -0
- plotly_cloud-0.1.0rc1.dist-info/entry_points.txt +2 -0
- plotly_cloud-0.1.0rc1.dist-info/licenses/LICENSE +21 -0
plotly_cloud/cli.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""Main CLI entry point for Plotly Cloud."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import sys
|
|
5
|
+
import textwrap
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from typing import Union
|
|
8
|
+
|
|
9
|
+
import nest_asyncio
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
|
|
13
|
+
from plotly_cloud._commands import BaseCommand, CommandRegistry
|
|
14
|
+
from plotly_cloud._definitions import CommandArgument, HelpPanelStyle
|
|
15
|
+
from plotly_cloud._parser import parse_args, parse_group_and_command
|
|
16
|
+
from plotly_cloud.exceptions import PlotlyCloudError
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_help_panel(arguments: Sequence[Union[CommandArgument, dict]], title: str, style: HelpPanelStyle) -> None:
|
|
22
|
+
"""Create a Rich panel for command arguments with proper styling and layout."""
|
|
23
|
+
formatted_lines = []
|
|
24
|
+
|
|
25
|
+
for arg in arguments:
|
|
26
|
+
# Format the argument name with padding and color
|
|
27
|
+
arg_name = arg["name"].ljust(style["argument_width"])
|
|
28
|
+
formatted_arg = f"[{style['argument_color']}]{arg_name}[/{style['argument_color']}]"
|
|
29
|
+
|
|
30
|
+
# Format description with color and wrap to terminal width
|
|
31
|
+
max_width = console.width - style["argument_width"] - 4 # Account for padding and spacing
|
|
32
|
+
wrapper = textwrap.TextWrapper(width=max_width)
|
|
33
|
+
wrapped_desc = wrapper.fill(arg["help"])
|
|
34
|
+
description = f"[{style['description_color']}]{wrapped_desc}[/{style['description_color']}]"
|
|
35
|
+
|
|
36
|
+
# Combine argument and description on same line
|
|
37
|
+
arg_line = f"{formatted_arg} {description}"
|
|
38
|
+
formatted_lines.append(arg_line)
|
|
39
|
+
|
|
40
|
+
arg_default = arg.get("default")
|
|
41
|
+
|
|
42
|
+
# Add default value on separate line if present
|
|
43
|
+
if arg_default is not None:
|
|
44
|
+
default_text = (
|
|
45
|
+
f"[{style['description_color']}]{''.ljust(style['argument_width'])} "
|
|
46
|
+
f"(default: {arg_default})[/{style['description_color']}]"
|
|
47
|
+
)
|
|
48
|
+
formatted_lines.append(default_text)
|
|
49
|
+
|
|
50
|
+
content = "\n".join(formatted_lines)
|
|
51
|
+
panel = Panel(
|
|
52
|
+
content,
|
|
53
|
+
title=title,
|
|
54
|
+
title_align="left",
|
|
55
|
+
border_style=style["border_style"],
|
|
56
|
+
padding=(0, 1),
|
|
57
|
+
)
|
|
58
|
+
console.print(panel)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def create_error_panel(content, title="✗ Error"):
|
|
62
|
+
console.print()
|
|
63
|
+
panel = Panel(
|
|
64
|
+
content,
|
|
65
|
+
title=title,
|
|
66
|
+
title_align="left",
|
|
67
|
+
border_style="red",
|
|
68
|
+
padding=(0, 1),
|
|
69
|
+
)
|
|
70
|
+
console.print(panel)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def print_main_help(show_banner=True) -> None:
|
|
74
|
+
"""Print main help with ASCII art and command groups."""
|
|
75
|
+
# Add ASCII art for main command
|
|
76
|
+
ascii_art = """[blue]
|
|
77
|
+
██████╗ ██╗ ██████╗ ████████╗██╗ ██╗ ██╗
|
|
78
|
+
██╔══██╗██║ ██╔═══██╗╚══██╔══╝██║ ╚██╗ ██╔╝
|
|
79
|
+
██████╔╝██║ ██║ ██║ ██║ ██║ ╚████╔╝
|
|
80
|
+
██╔═══╝ ██║ ██║ ██║ ██║ ██║ ╚██╔╝
|
|
81
|
+
██║ ███████╗╚██████╔╝ ██║ ███████╗██║
|
|
82
|
+
╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝[/blue]
|
|
83
|
+
[cyan] 📊 Interactive Data Visualization Platform 📈[/cyan]
|
|
84
|
+
|
|
85
|
+
[dim]Publish and manage your Dash applications with ease[/dim]
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
if show_banner:
|
|
89
|
+
console.print(ascii_art)
|
|
90
|
+
|
|
91
|
+
# Show command groups
|
|
92
|
+
for group_name, group in CommandRegistry.commands.items():
|
|
93
|
+
commands = []
|
|
94
|
+
for cmd_name, cmd_class in group["commands"].items():
|
|
95
|
+
commands.append({"name": f"{group_name} {cmd_name}", "help": cmd_class.short_description})
|
|
96
|
+
|
|
97
|
+
commands_style: HelpPanelStyle = {
|
|
98
|
+
"border_style": "dim blue",
|
|
99
|
+
"argument_color": "yellow",
|
|
100
|
+
"description_color": "dim",
|
|
101
|
+
"argument_width": 32,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
panel_title = f"{group_name.upper()} COMMANDS"
|
|
105
|
+
create_help_panel(commands, panel_title, commands_style)
|
|
106
|
+
|
|
107
|
+
# Show global options
|
|
108
|
+
global_options = [
|
|
109
|
+
{
|
|
110
|
+
"name": "--verbose, -v",
|
|
111
|
+
"help": "Enable verbose output with detailed error information",
|
|
112
|
+
},
|
|
113
|
+
{"name": "--help, -h", "help": "Show this help message and exit"},
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
options_style: HelpPanelStyle = {
|
|
117
|
+
"border_style": "dim white",
|
|
118
|
+
"argument_color": "yellow",
|
|
119
|
+
"description_color": "dim",
|
|
120
|
+
"argument_width": 32,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
create_help_panel(global_options, "GLOBAL OPTIONS", options_style)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def print_group_help(group_name: str) -> None:
|
|
127
|
+
"""Print help for a specific group."""
|
|
128
|
+
group = CommandRegistry.commands.get(group_name)
|
|
129
|
+
if not group:
|
|
130
|
+
create_error_panel(
|
|
131
|
+
f"Unknown command group: [yellow]'{group_name}[/yellow]'\n\n"
|
|
132
|
+
f"Available groups: [cyan]{', '.join(CommandRegistry.commands.keys())}[/cyan]"
|
|
133
|
+
)
|
|
134
|
+
sys.exit(1)
|
|
135
|
+
|
|
136
|
+
# Type guard to ensure group is not None
|
|
137
|
+
assert group is not None
|
|
138
|
+
|
|
139
|
+
# Create commands list for the panel
|
|
140
|
+
commands = []
|
|
141
|
+
for cmd_name, cmd_class in group["commands"].items():
|
|
142
|
+
commands.append({"name": f"{group_name} {cmd_name}", "help": cmd_class.short_description})
|
|
143
|
+
|
|
144
|
+
# Define style for group help
|
|
145
|
+
group_style: HelpPanelStyle = {
|
|
146
|
+
"border_style": "dim blue",
|
|
147
|
+
"argument_color": "yellow",
|
|
148
|
+
"description_color": "dim",
|
|
149
|
+
"argument_width": 32,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
panel_title = f"{group_name.upper()} COMMANDS"
|
|
153
|
+
create_help_panel(commands, panel_title, group_style)
|
|
154
|
+
|
|
155
|
+
console.print(f"\nUse 'plotly {group_name} <command> --help' for more information about a command.")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def print_command_help(command_class: BaseCommand, group: str, command: str) -> None:
|
|
159
|
+
"""Print help for a specific command."""
|
|
160
|
+
console.print(f"\n[bold blue]plotly {group} {command}[/bold blue]\n")
|
|
161
|
+
|
|
162
|
+
if command_class.description:
|
|
163
|
+
console.print(f"{command_class.description}\n")
|
|
164
|
+
|
|
165
|
+
# Use the PlotlyHelpFormatter logic for command arguments
|
|
166
|
+
if command_class.arguments:
|
|
167
|
+
# Define styles
|
|
168
|
+
args_style: HelpPanelStyle = {
|
|
169
|
+
"border_style": "dim blue",
|
|
170
|
+
"argument_color": "yellow",
|
|
171
|
+
"description_color": "dim",
|
|
172
|
+
"argument_width": 32,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
options_style: HelpPanelStyle = {
|
|
176
|
+
"border_style": "dim white",
|
|
177
|
+
"argument_color": "yellow",
|
|
178
|
+
"description_color": "dim",
|
|
179
|
+
"argument_width": 32,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
# Separate positional and optional arguments
|
|
183
|
+
positional_args = [arg for arg in command_class.arguments if not arg["name"].startswith("-")]
|
|
184
|
+
optional_args = [arg for arg in command_class.arguments if arg["name"].startswith("-")]
|
|
185
|
+
|
|
186
|
+
# Create panels for arguments
|
|
187
|
+
if positional_args:
|
|
188
|
+
create_help_panel(positional_args, "ARGUMENTS", args_style)
|
|
189
|
+
|
|
190
|
+
if optional_args:
|
|
191
|
+
create_help_panel(optional_args, "OPTIONS", options_style)
|
|
192
|
+
|
|
193
|
+
# Show global options
|
|
194
|
+
global_options = [
|
|
195
|
+
{
|
|
196
|
+
"name": "--verbose, -v",
|
|
197
|
+
"help": "Enable verbose output with detailed error information",
|
|
198
|
+
},
|
|
199
|
+
{"name": "--help, -h", "help": "Show this help message and exit"},
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
options_style: HelpPanelStyle = {
|
|
203
|
+
"border_style": "dim white",
|
|
204
|
+
"argument_color": "yellow",
|
|
205
|
+
"description_color": "dim",
|
|
206
|
+
"argument_width": 32,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
create_help_panel(global_options, "GLOBAL OPTIONS", options_style)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def main() -> None:
|
|
213
|
+
"""Main CLI entry point."""
|
|
214
|
+
# Allow to run multiple calls to asyncio run.
|
|
215
|
+
nest_asyncio.apply()
|
|
216
|
+
|
|
217
|
+
# Parse group and command
|
|
218
|
+
group, command, args_index = parse_group_and_command()
|
|
219
|
+
|
|
220
|
+
# Handle help command
|
|
221
|
+
if group == "help" or command == "help":
|
|
222
|
+
if group == "help" and command == "help":
|
|
223
|
+
# Show main help
|
|
224
|
+
print_main_help()
|
|
225
|
+
sys.exit(0)
|
|
226
|
+
elif command == "help":
|
|
227
|
+
# Show group help
|
|
228
|
+
print_group_help(group)
|
|
229
|
+
sys.exit(0)
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
# Find the command group
|
|
233
|
+
group_data = CommandRegistry.commands.get(group)
|
|
234
|
+
|
|
235
|
+
# Type guard to ensure group_data is not None
|
|
236
|
+
assert group_data is not None
|
|
237
|
+
|
|
238
|
+
# Find the command in the group
|
|
239
|
+
command_class = group_data["commands"].get(command)
|
|
240
|
+
if not command_class:
|
|
241
|
+
create_error_panel(
|
|
242
|
+
f"Unknown command: {group} {command}\n\n"
|
|
243
|
+
f"Available commands in [yellow]'{group}'[/yellow]: "
|
|
244
|
+
f"[cyan]{', '.join(group_data['commands'].keys())}[/cyan]"
|
|
245
|
+
)
|
|
246
|
+
sys.exit(1)
|
|
247
|
+
|
|
248
|
+
# Type guard to ensure command_class is not None
|
|
249
|
+
assert command_class is not None
|
|
250
|
+
|
|
251
|
+
# Parse arguments specific to this command
|
|
252
|
+
command_args = parse_args(command_class.arguments)
|
|
253
|
+
|
|
254
|
+
# Handle command help
|
|
255
|
+
if command_args.help:
|
|
256
|
+
print_command_help(command_class, group, command)
|
|
257
|
+
sys.exit(0)
|
|
258
|
+
|
|
259
|
+
# Execute the command
|
|
260
|
+
asyncio.run(command_class.execute(command_args))
|
|
261
|
+
|
|
262
|
+
except KeyboardInterrupt:
|
|
263
|
+
sys.exit(1)
|
|
264
|
+
except PlotlyCloudError as e:
|
|
265
|
+
# Display custom exceptions in a red panel with class name as title
|
|
266
|
+
args = parse_args([], args_index)
|
|
267
|
+
if args.verbose:
|
|
268
|
+
console.print_exception()
|
|
269
|
+
else:
|
|
270
|
+
content = str(e)
|
|
271
|
+
if e.details:
|
|
272
|
+
content += f"\n\n{e.details}"
|
|
273
|
+
create_error_panel(content, f"✗ {e.__class__.__name__}")
|
|
274
|
+
sys.exit(1)
|
|
275
|
+
except Exception as e:
|
|
276
|
+
# Fallback for unexpected exceptions
|
|
277
|
+
args = parse_args([], args_index)
|
|
278
|
+
if args.verbose:
|
|
279
|
+
console.print_exception()
|
|
280
|
+
else:
|
|
281
|
+
create_error_panel(str(e), "✗ Unexpected Error")
|
|
282
|
+
sys.exit(1)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
if __name__ == "__main__":
|
|
286
|
+
main()
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Custom exceptions for Plotly Cloud CLI."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PlotlyCloudError(Exception):
|
|
7
|
+
"""Base exception for all Plotly Cloud CLI errors."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str, details: Optional[str] = None):
|
|
10
|
+
"""Initialize exception with message and optional details."""
|
|
11
|
+
super().__init__(message)
|
|
12
|
+
self.message = message
|
|
13
|
+
self.details = details
|
|
14
|
+
|
|
15
|
+
def __str__(self) -> str:
|
|
16
|
+
"""Return formatted error message."""
|
|
17
|
+
if self.details:
|
|
18
|
+
return f"{self.message}: {self.details}"
|
|
19
|
+
return self.message
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AuthenticationError(PlotlyCloudError):
|
|
23
|
+
"""Raised when authentication fails."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConfigurationError(PlotlyCloudError):
|
|
29
|
+
"""Raised when configuration is invalid or missing."""
|
|
30
|
+
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DeploymentError(PlotlyCloudError):
|
|
35
|
+
"""Raised when deployment operations fail."""
|
|
36
|
+
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ApplicationError(PlotlyCloudError):
|
|
41
|
+
"""Raised when application operations fail."""
|
|
42
|
+
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Authentication-specific exceptions
|
|
47
|
+
class OAuthClientError(AuthenticationError):
|
|
48
|
+
"""Raised when OAuth client operations fail."""
|
|
49
|
+
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TokenError(AuthenticationError):
|
|
54
|
+
"""Raised when token operations fail."""
|
|
55
|
+
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class CredentialError(AuthenticationError):
|
|
60
|
+
"""Raised when credential operations fail."""
|
|
61
|
+
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ForbiddenError(AuthenticationError):
|
|
66
|
+
"""Raised when access is forbidden (HTTP 403)."""
|
|
67
|
+
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Configuration-specific exceptions
|
|
72
|
+
class CloudConfigError(ConfigurationError):
|
|
73
|
+
"""Raised when cloud configuration operations fail."""
|
|
74
|
+
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class EnvironmentError(ConfigurationError):
|
|
79
|
+
"""Raised when environment configuration is invalid."""
|
|
80
|
+
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Deployment-specific exceptions
|
|
85
|
+
class DependencyValidationError(DeploymentError):
|
|
86
|
+
"""Raised when dependency validation fails."""
|
|
87
|
+
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class PackagingError(DeploymentError):
|
|
92
|
+
"""Raised when packaging operations fail."""
|
|
93
|
+
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class UploadError(DeploymentError):
|
|
98
|
+
"""Raised when upload operations fail."""
|
|
99
|
+
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class AppCreationError(DeploymentError):
|
|
104
|
+
"""Raised when app creation fails."""
|
|
105
|
+
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class AppPublishError(DeploymentError):
|
|
110
|
+
"""Raised when app publishing fails."""
|
|
111
|
+
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class DeploymentClientError(DeploymentError):
|
|
116
|
+
"""Raised when deployment client operations fail."""
|
|
117
|
+
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# Application-specific exceptions
|
|
122
|
+
class ModuleImportError(ApplicationError):
|
|
123
|
+
"""Raised when module import fails."""
|
|
124
|
+
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class DashAppError(ApplicationError):
|
|
129
|
+
"""Raised when Dash app operations fail."""
|
|
130
|
+
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class ServerError(ApplicationError):
|
|
135
|
+
"""Raised when server operations fail."""
|
|
136
|
+
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Network-specific exceptions
|
|
141
|
+
class APIError(PlotlyCloudError):
|
|
142
|
+
"""Raised when API operations fail."""
|
|
143
|
+
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
message: str,
|
|
147
|
+
status_code: Optional[int] = None,
|
|
148
|
+
details: Optional[str] = None,
|
|
149
|
+
):
|
|
150
|
+
"""Initialize API error with optional status code."""
|
|
151
|
+
super().__init__(message, details)
|
|
152
|
+
self.status_code = status_code
|
|
153
|
+
|
|
154
|
+
def __str__(self) -> str:
|
|
155
|
+
"""Return formatted error message with status code."""
|
|
156
|
+
msg = self.message
|
|
157
|
+
if self.status_code:
|
|
158
|
+
msg = f"{msg} (HTTP {self.status_code})"
|
|
159
|
+
if self.details:
|
|
160
|
+
msg = f"{msg}: {self.details}"
|
|
161
|
+
return msg
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class NetworkError(PlotlyCloudError):
|
|
165
|
+
"""Raised when network operations fail."""
|
|
166
|
+
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class TimeoutError(PlotlyCloudError):
|
|
171
|
+
"""Raised when operations timeout."""
|
|
172
|
+
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# File system exceptions
|
|
177
|
+
class FileSystemError(PlotlyCloudError):
|
|
178
|
+
"""Raised when file system operations fail."""
|
|
179
|
+
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class FileNotFoundError(FileSystemError):
|
|
184
|
+
"""Raised when required files are not found."""
|
|
185
|
+
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class FilePermissionError(FileSystemError):
|
|
190
|
+
"""Raised when file permission operations fail."""
|
|
191
|
+
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class FileSizeError(FileSystemError):
|
|
196
|
+
"""Raised when file size limits are exceeded."""
|
|
197
|
+
|
|
198
|
+
pass
|