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 CHANGED
@@ -1,3 +1,3 @@
1
1
  """Plotly Cloud Extension package."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.3.0"
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
- if hasattr(sys, "stdlib_module_names"):
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"), # type: ignore
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
- zip_size = await create_deployment_zip(project_path, zip_path)
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(project_path: str, output_path: str) -> int:
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
- ValueError: If zip file exceeds size limit
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
- if total_uncompressed_size > MAX_ZIP_SIZE:
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
- return ForbiddenError(f"Failed to {operation}: Access forbidden")
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()
@@ -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( # type: ignore
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 = _run_sync(rpc.handle_operation(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(dict(response))
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 verbose flag and help flag
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.2.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.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,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.28.0
2
+ Generator: hatchling 1.29.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,