plotly-cloud 0.1.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.
@@ -0,0 +1,3 @@
1
+ """Plotly Cloud CLI package."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,37 @@
1
+ """API type definitions for Plotly Cloud."""
2
+
3
+ from typing import Any, Dict, List
4
+
5
+ from typing_extensions import NotRequired, TypedDict
6
+
7
+
8
+ class AppRequest(TypedDict):
9
+ pythonVersion: NotRequired[str]
10
+ entrypointModule: NotRequired[str]
11
+ name: NotRequired[str]
12
+ appUrl: NotRequired[str]
13
+ description: NotRequired[str]
14
+ isViewPrivate: NotRequired[str]
15
+ invitations: NotRequired[List[str]]
16
+ environmentVariables: NotRequired[List[Dict[str, Any]]]
17
+
18
+ EnvironmentVariables = Dict[str, Any]
19
+
20
+ class ErrorResponse(TypedDict):
21
+ error: NotRequired[str]
22
+ error_description: NotRequired[str]
23
+
24
+ class App(TypedDict):
25
+ id: NotRequired[str]
26
+ author_id: NotRequired[str]
27
+ name: NotRequired[str]
28
+ description: NotRequired[str]
29
+ app_url: NotRequired[str]
30
+ is_view_private: NotRequired[bool]
31
+ created_with_desktop: NotRequired[bool]
32
+ desktop_app_version: NotRequired[str]
33
+ environment_variables: NotRequired[EnvironmentVariables]
34
+ invitations: NotRequired[List[str]]
35
+ status: NotRequired[str]
36
+ created_at: NotRequired[str]
37
+ last_published: NotRequired[str]
@@ -0,0 +1,77 @@
1
+ import asyncio
2
+ import hashlib
3
+ import os
4
+ import sys
5
+ from concurrent.futures import ThreadPoolExecutor
6
+
7
+ _builtins_modules = list(sys.builtin_module_names) + ["frozen", "builtin"]
8
+
9
+
10
+ if hasattr(sys, "stdlib_module_names"):
11
+ _builtins_modules += list(sys.stdlib_module_names) # type: ignore
12
+
13
+
14
+ def collect_module_files(*extras):
15
+ collected = []
16
+ for mod in sys.modules.values():
17
+ parent = getattr(mod.__spec__, "parent", "")
18
+ if (
19
+ hasattr(mod, "__file__")
20
+ and mod.__file__ is not None
21
+ and mod.__file__.endswith(".py")
22
+ and parent not in _builtins_modules
23
+ and mod.__name__ not in _builtins_modules
24
+ ):
25
+ collected.append(mod.__file__)
26
+ return collected + list(extras)
27
+
28
+
29
+ def hash_file_content(filename):
30
+ with open(filename, encoding="utf-8") as f:
31
+ content = f.read()
32
+ return hashlib.sha256(content.encode("utf-8"), usedforsecurity=False).hexdigest(), filename
33
+
34
+
35
+ def stat_file_time(filename):
36
+ return os.stat(filename).st_mtime, filename
37
+
38
+
39
+ async def until_change(collector, *extras, initial_hashes=None):
40
+ # We hash ten files at a time.
41
+ pool = ThreadPoolExecutor(max_workers=10)
42
+ loop = asyncio.get_event_loop()
43
+
44
+ changed = False
45
+ hashed_files = {}
46
+
47
+ async def collect():
48
+ collected = []
49
+ files = collector(*extras)
50
+ for f in files:
51
+ collected.append(loop.run_in_executor(pool, stat_file_time, f))
52
+
53
+ content = await asyncio.gather(*collected)
54
+ result = {}
55
+
56
+ for hashed, filename in content:
57
+ result[filename] = hashed
58
+
59
+ return result
60
+
61
+ if initial_hashes is None or not hashed_files:
62
+ hashed_files = await collect()
63
+
64
+ while not changed:
65
+ collected = await collect()
66
+
67
+ if len(collected) != len(hashed_files):
68
+ return
69
+
70
+ for co_key, co_value in collected.items():
71
+ if co_key not in hashed_files:
72
+ return
73
+
74
+ if co_value != hashed_files[co_key]:
75
+ return
76
+
77
+ await asyncio.sleep(1.0)
@@ -0,0 +1,93 @@
1
+ """Cloud environment configuration management for Plotly Cloud CLI."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+ import tomli
7
+ from rich.console import Console
8
+
9
+ from .exceptions import CloudConfigError, EnvironmentError
10
+
11
+ console = Console()
12
+
13
+
14
+ class CloudConfig:
15
+ """Manages cloud configuration for Plotly Cloud CLI."""
16
+
17
+ def __init__(self):
18
+ self._config_cache = None
19
+
20
+ @property
21
+ def config_path(self) -> Path:
22
+ """Path to the cloud-env.toml configuration file."""
23
+ return Path(__file__).parent / "cloud-env.toml"
24
+
25
+ @property
26
+ def config(self) -> dict:
27
+ """Load and cache cloud configuration."""
28
+ if self._config_cache is None:
29
+ if not self.config_path.exists():
30
+ raise CloudConfigError(
31
+ f"Cloud configuration not found at {self.config_path}",
32
+ "Configuration file is missing from the package.",
33
+ )
34
+
35
+ try:
36
+ with open(self.config_path, "rb") as f:
37
+ self._config_cache = tomli.load(f)
38
+ except Exception as e:
39
+ raise CloudConfigError("Failed to load cloud configuration", str(e)) from e
40
+
41
+ return self._config_cache
42
+
43
+ def get_oauth_client_id(self) -> str:
44
+ """Get OAuth client ID from configuration."""
45
+ # Check environment variable first (overrides config file)
46
+ client_id = os.getenv("PLOTLY_OAUTH_CLIENT_ID", "")
47
+ if client_id:
48
+ return client_id
49
+
50
+ try:
51
+ client_id = self.config.get("oauth_client_id", "")
52
+ if client_id:
53
+ return client_id
54
+ except CloudConfigError:
55
+ pass
56
+
57
+ raise EnvironmentError("OAuth client ID not configured. Set 'oauth_client_id' in cloud-env.toml.")
58
+
59
+ def get_api_base_url(self) -> str:
60
+ """Get API base URL from configuration."""
61
+ # Check environment variable first (overrides config file)
62
+ api_url = os.getenv("PLOTLY_API_BASE_URL", "")
63
+ if api_url:
64
+ return api_url
65
+
66
+ try:
67
+ api_url = self.config.get("api_base_url", "")
68
+ if api_url:
69
+ return api_url
70
+ except CloudConfigError:
71
+ pass
72
+
73
+ raise EnvironmentError("API base URL not configured. Set 'api_base_url' in cloud-env.toml.")
74
+
75
+ def validate(self) -> bool:
76
+ """Validate cloud configuration setup."""
77
+ try:
78
+ client_id = self.get_oauth_client_id()
79
+ api_url = self.get_api_base_url()
80
+
81
+ if not all([client_id, api_url]):
82
+ console.print("✗ Incomplete cloud configuration")
83
+ return False
84
+
85
+ console.print("✓ Cloud configuration is valid")
86
+ return True
87
+ except (CloudConfigError, EnvironmentError) as e:
88
+ console.print(f"✗ Configuration error: {e}")
89
+ return False
90
+
91
+
92
+ # Global instance
93
+ cloud_config = CloudConfig()