polyapi-python 0.3.1.dev1__py3-none-any.whl → 0.3.1.dev3__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.
- polyapi/__init__.py +4 -4
- polyapi/cli.py +165 -52
- polyapi/deployables.py +296 -0
- polyapi/function_cli.py +21 -212
- polyapi/generate.py +1 -2
- polyapi/parser.py +519 -0
- polyapi/prepare.py +135 -0
- polyapi/sync.py +122 -0
- polyapi/typedefs.py +21 -3
- {polyapi_python-0.3.1.dev1.dist-info → polyapi_python-0.3.1.dev3.dist-info}/METADATA +1 -1
- {polyapi_python-0.3.1.dev1.dist-info → polyapi_python-0.3.1.dev3.dist-info}/RECORD +14 -10
- {polyapi_python-0.3.1.dev1.dist-info → polyapi_python-0.3.1.dev3.dist-info}/WHEEL +1 -1
- {polyapi_python-0.3.1.dev1.dist-info → polyapi_python-0.3.1.dev3.dist-info}/LICENSE +0 -0
- {polyapi_python-0.3.1.dev1.dist-info → polyapi_python-0.3.1.dev3.dist-info}/top_level.txt +0 -0
polyapi/__init__.py
CHANGED
|
@@ -16,8 +16,8 @@ if len(sys.argv) > 1 and sys.argv[1] not in CLI_COMMANDS:
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
polyCustom: Dict[str, Any] = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
"executionId": None,
|
|
20
|
+
"executionApiKey": None,
|
|
21
|
+
"responseStatusCode": 200,
|
|
22
|
+
"responseContentType": None,
|
|
23
23
|
}
|
polyapi/cli.py
CHANGED
|
@@ -1,73 +1,186 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import argparse
|
|
2
3
|
|
|
3
|
-
from polyapi.utils import print_green
|
|
4
|
+
from polyapi.utils import print_green, print_red
|
|
4
5
|
|
|
5
6
|
from .config import initialize_config, set_api_key_and_url
|
|
6
7
|
from .generate import generate, clear
|
|
7
8
|
from .function_cli import function_add_or_update, function_execute
|
|
8
9
|
from .rendered_spec import get_and_update_rendered_spec
|
|
10
|
+
from .prepare import prepare_deployables
|
|
11
|
+
from .sync import sync_deployables
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
CLI_COMMANDS = ["setup", "generate", "function", "clear", "help", "update_rendered_spec"]
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
python -m polyapi function <command> Manages functions
|
|
17
|
-
python -m polyapi clear Clear current generated Poly library
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def execute_from_cli() -> None:
|
|
16
|
+
def execute_from_cli():
|
|
17
|
+
# First we setup all our argument parsing logic
|
|
18
|
+
# Then we parse the arguments (waaay at the bottom)
|
|
22
19
|
parser = argparse.ArgumentParser(
|
|
23
|
-
prog="python -m polyapi",
|
|
20
|
+
prog="python -m polyapi",
|
|
21
|
+
description="Manage your Poly API configurations and functions",
|
|
22
|
+
formatter_class=argparse.RawTextHelpFormatter
|
|
24
23
|
)
|
|
25
|
-
|
|
26
|
-
parser.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
|
|
25
|
+
subparsers = parser.add_subparsers(help="Available commands")
|
|
26
|
+
|
|
27
|
+
###########################################################################
|
|
28
|
+
# Setup command
|
|
29
|
+
setup_parser = subparsers.add_parser("setup", help="Setup your Poly connection")
|
|
30
|
+
setup_parser.add_argument("api_key", nargs="?", help="API key for Poly API")
|
|
31
|
+
setup_parser.add_argument("url", nargs="?", help="URL for the Poly API")
|
|
32
|
+
|
|
33
|
+
def setup(args):
|
|
34
|
+
if args.api_key and args.url:
|
|
35
|
+
set_api_key_and_url(args.url, args.api_key)
|
|
36
|
+
else:
|
|
37
|
+
initialize_config(force=True)
|
|
38
|
+
generate()
|
|
39
|
+
|
|
40
|
+
setup_parser.set_defaults(command=setup)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
###########################################################################
|
|
44
|
+
# Generate command
|
|
45
|
+
generate_parser = subparsers.add_parser("generate", help="Generates Poly library")
|
|
46
|
+
|
|
47
|
+
def generate_command(args):
|
|
48
|
+
initialize_config()
|
|
40
49
|
print("Generating Poly functions...", end="")
|
|
41
50
|
generate()
|
|
42
51
|
print_green("DONE")
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
|
|
53
|
+
generate_parser.set_defaults(command=generate_command)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
###########################################################################
|
|
57
|
+
# Function commands
|
|
58
|
+
fn_parser = subparsers.add_parser("function", help="Manage and execute functions")
|
|
59
|
+
fn_subparsers = fn_parser.add_subparsers(help="Available commands")
|
|
60
|
+
|
|
61
|
+
# Function - Add command
|
|
62
|
+
fn_add_parser = fn_subparsers.add_parser("add", help="Add or update the function")
|
|
63
|
+
fn_add_parser.add_argument("name", help="Name of the function")
|
|
64
|
+
fn_add_parser.add_argument("file", help="Path to the function file")
|
|
65
|
+
fn_add_parser.add_argument("--context", required=False, default="", help="Context of the function")
|
|
66
|
+
fn_add_parser.add_argument("--description", required=False, default="", help="Description of the function")
|
|
67
|
+
fn_add_parser.add_argument("--server", action="store_true", help="Marks the function as a server function")
|
|
68
|
+
fn_add_parser.add_argument("--client", action="store_true", help="Marks the function as a client function")
|
|
69
|
+
fn_add_parser.add_argument("--logs", choices=["enabled", "disabled"], default="disabled", help="Enable or disable logs for the function.")
|
|
70
|
+
fn_add_parser.add_argument("--execution-api-key", required=False, default="", help="API key for execution (for server functions only).")
|
|
71
|
+
fn_add_parser.add_argument("--disable-ai", "--skip-generate", action="store_true", help="Pass --disable-ai skip AI generation of missing descriptions")
|
|
72
|
+
|
|
73
|
+
def add_function(args):
|
|
74
|
+
initialize_config()
|
|
75
|
+
logs_enabled = args.logs == "enabled"
|
|
76
|
+
err = ""
|
|
77
|
+
if args.server and args.client:
|
|
78
|
+
err = "Specify either `--server` or `--client`. Found both."
|
|
79
|
+
elif not args.server and not args.client:
|
|
80
|
+
err = "You must specify `--server` or `--client`."
|
|
81
|
+
elif logs_enabled and not args.server:
|
|
82
|
+
err = "Option `logs` is only for server functions (--server)."
|
|
83
|
+
|
|
84
|
+
if err:
|
|
85
|
+
print_red("ERROR")
|
|
86
|
+
print(err)
|
|
87
|
+
exit(1)
|
|
88
|
+
|
|
89
|
+
function_add_or_update(
|
|
90
|
+
context=args.context,
|
|
91
|
+
description=args.description,
|
|
92
|
+
client=args.client,
|
|
93
|
+
server=args.server,
|
|
94
|
+
logs_enabled=logs_enabled,
|
|
95
|
+
generate=not args.disable_ai,
|
|
96
|
+
execution_api_key=args.execution_api_key
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
fn_add_parser.set_defaults(command=add_function)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# Function - Execute command
|
|
103
|
+
fn_exec_parser = fn_subparsers.add_parser("execute", help="Execute a function with the provided arguments")
|
|
104
|
+
fn_exec_parser.add_argument("name", help="Name of the function")
|
|
105
|
+
fn_exec_parser.add_argument("args", nargs="*", help="Arguments for the function")
|
|
106
|
+
fn_exec_parser.add_argument("--context", required=False, default="", help="Context of the function")
|
|
107
|
+
|
|
108
|
+
def execute_function(args):
|
|
109
|
+
initialize_config()
|
|
110
|
+
print(function_execute(args.context, args.name, args.args))
|
|
111
|
+
|
|
112
|
+
fn_exec_parser.set_defaults(command=execute_function)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
###########################################################################
|
|
116
|
+
# Clear command
|
|
117
|
+
clear_parser = subparsers.add_parser("clear", help="Clear current generated Poly library")
|
|
118
|
+
|
|
119
|
+
def clear_command(_):
|
|
120
|
+
print("Clearing the generated library...")
|
|
121
|
+
clear()
|
|
122
|
+
|
|
123
|
+
clear_parser.set_defaults(command=clear_command)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
###########################################################################
|
|
127
|
+
# Update rendered spec command
|
|
128
|
+
update_spec_parser = subparsers.add_parser("update_rendered_spec", help="Update the rendered spec file")
|
|
129
|
+
update_spec_parser.add_argument("spec", help="Specification file to update")
|
|
130
|
+
|
|
131
|
+
def update_rendered_spec(args):
|
|
132
|
+
updated = get_and_update_rendered_spec(args.spec)
|
|
51
133
|
if updated:
|
|
52
134
|
print("Updated rendered spec!")
|
|
53
135
|
else:
|
|
54
136
|
print("Failed to update rendered spec!")
|
|
55
137
|
exit(1)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
138
|
+
|
|
139
|
+
update_spec_parser.set_defaults(command=update_rendered_spec)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
###########################################################################
|
|
143
|
+
# Prepare command
|
|
144
|
+
prepare_parser = subparsers.add_parser('prepare', help="Find and prepare all Poly deployables")
|
|
145
|
+
prepare_parser.add_argument("--lazy", action="store_true", help="Skip prepare work if the cache is up to date. (Relies on `git`)")
|
|
146
|
+
prepare_parser.add_argument("--disable-docs", action="store_true", help="Don't write any docstrings into the deployable files.")
|
|
147
|
+
prepare_parser.add_argument("--disable-ai", action="store_true", help="Don't use AI to fill in any missing descriptions.")
|
|
148
|
+
|
|
149
|
+
def prepare(args):
|
|
150
|
+
initialize_config()
|
|
151
|
+
disable_ai = args.disable_ai or bool(os.getenv("DISABLE_AI"))
|
|
152
|
+
prepare_deployables(lazy=args.lazy, disable_docs=args.disable_docs, disable_ai=disable_ai)
|
|
153
|
+
|
|
154
|
+
prepare_parser.set_defaults(command=prepare)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
###########################################################################
|
|
158
|
+
# Sync command
|
|
159
|
+
sync_parser = subparsers.add_parser("sync", help="Find and sync all Poly deployables")
|
|
160
|
+
sync_parser.add_argument("--dry-run", action="store_true", help="Run through sync steps with logging but don't make any changes.")
|
|
161
|
+
|
|
162
|
+
def sync(args):
|
|
163
|
+
initialize_config()
|
|
164
|
+
prepare_deployables(lazy=True, disable_docs=True, disable_ai=True)
|
|
165
|
+
if args.dry_run:
|
|
166
|
+
print("Running dry-run of sync...")
|
|
167
|
+
sync_deployables(dry_run=args.dry_run)
|
|
168
|
+
print("Poly deployments synced.")
|
|
169
|
+
|
|
170
|
+
sync_parser.set_defaults(command=sync)
|
|
171
|
+
|
|
172
|
+
###########################################################################
|
|
173
|
+
# _------. #
|
|
174
|
+
# / , \_ __ __ _ ________ #
|
|
175
|
+
# / / /{}\ |o\_ / / ___ / /( )_____ / ____/ /_ __ #
|
|
176
|
+
# / \ `--' /-' \ / / / _ \/ __/// ___/ / /_ / / / / / #
|
|
177
|
+
# | \ \ | / /___/ __/ /_ (__ ) / __/ / / /_/ / #
|
|
178
|
+
# | |`-, | /_____/\___/\__/ /____/ /_/ /_/\__, / #
|
|
179
|
+
# / /__/)/ /____/ #
|
|
180
|
+
# / | #
|
|
181
|
+
###########################################################################
|
|
182
|
+
parsed_args = parser.parse_args()
|
|
183
|
+
if hasattr(parsed_args, "command"):
|
|
184
|
+
parsed_args.command(parsed_args)
|
|
185
|
+
else:
|
|
186
|
+
parser.print_help()
|
polyapi/deployables.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import json
|
|
4
|
+
import hashlib
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TypedDict, List, Dict, Tuple, Optional, Any, Union
|
|
7
|
+
from subprocess import check_output, CalledProcessError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Constants
|
|
11
|
+
CACHE_VERSION_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "poly/deployments_revision")
|
|
12
|
+
CACHE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "poly/deployables")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DeployableTypes(str):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
class DeployableTypeNames(str):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
class Deployment(TypedDict):
|
|
22
|
+
context: str
|
|
23
|
+
name: str
|
|
24
|
+
type: DeployableTypes
|
|
25
|
+
instance: str
|
|
26
|
+
id: str
|
|
27
|
+
deployed: str
|
|
28
|
+
fileRevision: str
|
|
29
|
+
|
|
30
|
+
class ParsedDeployableConfig(TypedDict):
|
|
31
|
+
context: str
|
|
32
|
+
name: str
|
|
33
|
+
type: DeployableTypes
|
|
34
|
+
disableAi: Optional[bool]
|
|
35
|
+
config: Dict[str, Any]
|
|
36
|
+
|
|
37
|
+
class DeployableFunctionParamBase(TypedDict):
|
|
38
|
+
type: str
|
|
39
|
+
typeSchema: Optional[Dict[str, Any]]
|
|
40
|
+
description: str
|
|
41
|
+
|
|
42
|
+
class DeployableFunctionParam(DeployableFunctionParamBase):
|
|
43
|
+
name: str
|
|
44
|
+
|
|
45
|
+
class DeployableFunctionTypes(TypedDict):
|
|
46
|
+
description: str
|
|
47
|
+
params: List[DeployableFunctionParam]
|
|
48
|
+
returns: DeployableFunctionParamBase
|
|
49
|
+
|
|
50
|
+
class DeployableRecord(ParsedDeployableConfig, total=False):
|
|
51
|
+
gitRevision: str
|
|
52
|
+
fileRevision: str
|
|
53
|
+
file: str
|
|
54
|
+
types: DeployableFunctionTypes
|
|
55
|
+
typeSchemas: Dict[str, Any]
|
|
56
|
+
dependencies: List[str]
|
|
57
|
+
deployments: List[Deployment]
|
|
58
|
+
deploymentCommentRanges: List[Tuple[int, int]]
|
|
59
|
+
docStartIndex: int
|
|
60
|
+
docEndIndex: int
|
|
61
|
+
dirty: Optional[bool]
|
|
62
|
+
|
|
63
|
+
class SyncDeployment(TypedDict, total=False):
|
|
64
|
+
context: str
|
|
65
|
+
name: str
|
|
66
|
+
description: str
|
|
67
|
+
type: str # This should be an enumeration or a predefined set of strings if you have known types.
|
|
68
|
+
fileRevision: str
|
|
69
|
+
file: str
|
|
70
|
+
typeSchemas: Dict[str, any]
|
|
71
|
+
dependencies: List[str]
|
|
72
|
+
config: Dict[str, any]
|
|
73
|
+
instance: str
|
|
74
|
+
id: Optional[str] = None
|
|
75
|
+
deployed: Optional[str] = None
|
|
76
|
+
|
|
77
|
+
DeployableTypeEntries: List[Tuple[DeployableTypeNames, DeployableTypes]] = [
|
|
78
|
+
("PolyServerFunction", "server-function"),
|
|
79
|
+
("PolyClientFunction", "client-function"),
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
DeployableTypeToName: Dict[DeployableTypeNames, DeployableTypes] = {name: type for name, type in DeployableTypeEntries}
|
|
83
|
+
|
|
84
|
+
def prepare_deployable_directory() -> None:
|
|
85
|
+
Path(CACHE_DIR).mkdir(parents=True, exist_ok=True)
|
|
86
|
+
|
|
87
|
+
def load_deployable_records() -> List[DeployableRecord]:
|
|
88
|
+
return [read_json_file(os.path.join(CACHE_DIR, name)) for name in os.listdir(CACHE_DIR) if name.endswith(".json")]
|
|
89
|
+
|
|
90
|
+
def save_deployable_records(records: List[DeployableRecord]) -> None:
|
|
91
|
+
for record in records:
|
|
92
|
+
write_json_file(os.path.join(CACHE_DIR, f'{record["context"]}.{record["name"]}.json'), record)
|
|
93
|
+
|
|
94
|
+
def remove_deployable_records(records: List[DeployableRecord]) -> None:
|
|
95
|
+
for record in records:
|
|
96
|
+
os.remove(os.path.join(CACHE_DIR, f'{record["context"]}.{record["name"]}.json'))
|
|
97
|
+
|
|
98
|
+
def read_json_file(path: Union[str, Path]) -> Any:
|
|
99
|
+
with open(path, "r", encoding="utf-8") as file:
|
|
100
|
+
return json.load(file)
|
|
101
|
+
|
|
102
|
+
def write_json_file(path: Union[str, Path], contents: Any) -> None:
|
|
103
|
+
with open(path, "w", encoding="utf-8") as file:
|
|
104
|
+
json.dump(contents, file, indent=2)
|
|
105
|
+
|
|
106
|
+
class PolyDeployConfig(TypedDict):
|
|
107
|
+
type_names: List[str]
|
|
108
|
+
include_dirs: List[str]
|
|
109
|
+
include_files_or_extensions: List[str]
|
|
110
|
+
exclude_dirs: List[str]
|
|
111
|
+
|
|
112
|
+
def get_all_deployable_files_windows(config: PolyDeployConfig) -> List[str]:
|
|
113
|
+
# Constructing the Windows command using dir and findstr
|
|
114
|
+
include_pattern = " ".join(f"*.{f}" if "." in f else f"*.{f}" for f in config["include_files_or_extensions"]) or "*"
|
|
115
|
+
exclude_pattern = '|'.join(config["exclude_dirs"])
|
|
116
|
+
pattern = '|'.join(f"polyConfig: {name}" for name in config["type_names"]) or 'polyConfig'
|
|
117
|
+
|
|
118
|
+
exclude_command = f" | findstr /V /I \"{exclude_pattern}\"" if exclude_pattern else ''
|
|
119
|
+
search_command = f" | findstr /M /I /F:/ /C:\"{pattern}\""
|
|
120
|
+
|
|
121
|
+
result = []
|
|
122
|
+
for dir_path in config["include_dirs"]:
|
|
123
|
+
dir_command = f"dir /S /P /B {include_pattern} {dir_path}"
|
|
124
|
+
full_command = f"{dir_command}{exclude_command}{search_command}"
|
|
125
|
+
try:
|
|
126
|
+
output = subprocess.check_output(full_command, shell=True, text=True)
|
|
127
|
+
result.extend(output.strip().split('\r\n'))
|
|
128
|
+
except subprocess.CalledProcessError:
|
|
129
|
+
pass
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
def get_all_deployable_files_linux(config: PolyDeployConfig) -> List[str]:
|
|
133
|
+
# Constructing the Linux grep command
|
|
134
|
+
include = " ".join(f'--include={f if "." in f else f"*.{f}"}' for f in config["include_files_or_extensions"])
|
|
135
|
+
exclude_dir = " ".join(f"--exclude-dir={dir}" for dir in config["exclude_dirs"])
|
|
136
|
+
|
|
137
|
+
search_path = " ".join(config["include_dirs"]) or "."
|
|
138
|
+
patterns = " ".join(f"-e 'polyConfig: {name}'" for name in config["type_names"]) or "-e 'polyConfig'"
|
|
139
|
+
grep_command = f'grep {include} {exclude_dir} -Rl {patterns} {search_path}'
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
output = subprocess.check_output(grep_command, shell=True, text=True)
|
|
143
|
+
return output.strip().split('\n')
|
|
144
|
+
except subprocess.CalledProcessError:
|
|
145
|
+
return []
|
|
146
|
+
|
|
147
|
+
def get_all_deployable_files(config: PolyDeployConfig) -> List[str]:
|
|
148
|
+
# Setting default values if not provided
|
|
149
|
+
if not config.get("type_names"):
|
|
150
|
+
config["type_names"] = [entry[0] for entry in DeployableTypeEntries] # Assuming DeployableTypeEntries is defined elsewhere
|
|
151
|
+
if not config.get("include_dirs"):
|
|
152
|
+
config["include_dirs"] = ["."]
|
|
153
|
+
if not config.get("include_files_or_extensions"):
|
|
154
|
+
config["include_files_or_extensions"] = ["py"]
|
|
155
|
+
if not config.get("exclude_dirs"):
|
|
156
|
+
config["exclude_dirs"] = ["poly", "node_modules", "dist", "build", "output", ".vscode", ".poly", ".github", ".husky", ".yarn"]
|
|
157
|
+
|
|
158
|
+
is_windows = os.name == "nt"
|
|
159
|
+
if is_windows:
|
|
160
|
+
return get_all_deployable_files_windows(config)
|
|
161
|
+
else:
|
|
162
|
+
return get_all_deployable_files_linux(config)
|
|
163
|
+
|
|
164
|
+
def get_deployable_file_revision(file_contents: str) -> str:
|
|
165
|
+
# Remove leading single-line comments and hash the remaining contents
|
|
166
|
+
file_contents = "\n".join(line for line in file_contents.split("\n") if not line.strip().startswith("#"))
|
|
167
|
+
return hashlib.sha256(file_contents.encode('utf-8')).hexdigest()[:7]
|
|
168
|
+
|
|
169
|
+
def get_git_revision(branch_or_tag: str = "HEAD") -> str:
|
|
170
|
+
try:
|
|
171
|
+
return check_output(["git", "rev-parse", "--short", branch_or_tag], text=True).strip()
|
|
172
|
+
except CalledProcessError:
|
|
173
|
+
# Return a random 7-character hash as a fallback
|
|
174
|
+
return "".join(format(ord(c), 'x') for c in os.urandom(4))[:7]
|
|
175
|
+
|
|
176
|
+
def get_cache_deployments_revision() -> str:
|
|
177
|
+
"""Retrieve the cache deployments revision from a file."""
|
|
178
|
+
try:
|
|
179
|
+
with open(CACHE_VERSION_FILE, 'r', encoding='utf-8') as file:
|
|
180
|
+
return file.read().strip()
|
|
181
|
+
except FileNotFoundError:
|
|
182
|
+
return ''
|
|
183
|
+
|
|
184
|
+
def write_cache_revision(git_revision: Optional[str] = None) -> None:
|
|
185
|
+
if git_revision is None:
|
|
186
|
+
git_revision = get_git_revision()
|
|
187
|
+
with open(CACHE_VERSION_FILE, 'w', encoding='utf-8') as file:
|
|
188
|
+
file.write(git_revision)
|
|
189
|
+
|
|
190
|
+
def is_cache_up_to_date() -> bool:
|
|
191
|
+
if not Path(CACHE_VERSION_FILE).exists():
|
|
192
|
+
return False
|
|
193
|
+
with open(CACHE_VERSION_FILE, 'r', encoding='utf-8') as file:
|
|
194
|
+
cached_revision = file.read().strip()
|
|
195
|
+
git_revision = get_git_revision()
|
|
196
|
+
return cached_revision == git_revision
|
|
197
|
+
|
|
198
|
+
def is_cache_up_to_date() -> bool:
|
|
199
|
+
"""Check if the cached revision matches the current Git revision."""
|
|
200
|
+
cached_revision = get_cache_deployments_revision()
|
|
201
|
+
git_revision = get_git_revision() # This function needs to be defined or imported
|
|
202
|
+
return cached_revision == git_revision
|
|
203
|
+
|
|
204
|
+
def write_deploy_comments(deployments: List[Dict]) -> str:
|
|
205
|
+
"""Generate a string of deployment comments for each deployment."""
|
|
206
|
+
canopy_path = 'polyui/collections' if 'localhost' in os.getenv('POLY_API_BASE_URL', '') else 'canopy/polyui/collections'
|
|
207
|
+
comments = []
|
|
208
|
+
for d in deployments:
|
|
209
|
+
instance_url = d['instance'].replace(':8000', ':3000') if d['instance'].endswith(':8000') else d['instance']
|
|
210
|
+
comment = f"# Poly deployed @ {d['deployed']} - {d['context']}.{d['name']} - {instance_url}/{canopy_path}/{d['type']}s/{d['id']} - {d['fileRevision']}"
|
|
211
|
+
comments.append(comment)
|
|
212
|
+
return '\n'.join(comments)
|
|
213
|
+
|
|
214
|
+
def print_docstring_function_comment(description: str, args: list, returns: dict) -> str:
|
|
215
|
+
docstring = f'"""{description}\n\n'
|
|
216
|
+
if args:
|
|
217
|
+
docstring += ' Args:\n'
|
|
218
|
+
for arg in args:
|
|
219
|
+
name = arg.get('name')
|
|
220
|
+
arg_type = arg.get('type', '')
|
|
221
|
+
desc = arg.get('description', '')
|
|
222
|
+
if arg_type:
|
|
223
|
+
docstring += f' {name} ({arg_type}): {desc}\n'
|
|
224
|
+
else:
|
|
225
|
+
docstring += f' {name}: {desc}\n'
|
|
226
|
+
|
|
227
|
+
return_type = returns.get('type', '')
|
|
228
|
+
return_description = returns.get('description', '')
|
|
229
|
+
if return_type:
|
|
230
|
+
docstring += f'\n Returns:\n {return_type}: {return_description}\n'
|
|
231
|
+
else:
|
|
232
|
+
docstring += f'\n Returns:\n {return_description}\n'
|
|
233
|
+
|
|
234
|
+
docstring += ' """'
|
|
235
|
+
return docstring
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def update_deployment_comments(file_content: str, deployable: dict) -> str:
|
|
239
|
+
"""
|
|
240
|
+
Remove old deployment comments based on the provided ranges and add new ones.
|
|
241
|
+
"""
|
|
242
|
+
for range in reversed(deployable['deploymentCommentRanges']):
|
|
243
|
+
file_content = file_content[:range[0]] + file_content[range[1]:]
|
|
244
|
+
if deployable['deployments']:
|
|
245
|
+
deployment_comments = write_deploy_comments(deployable['deployments'])
|
|
246
|
+
deployable['deploymentCommentRanges'] = [(0, len(deployment_comments) + 1)]
|
|
247
|
+
file_content = f"{deployment_comments}\n{file_content}"
|
|
248
|
+
return file_content
|
|
249
|
+
|
|
250
|
+
def update_deployable_function_comments(file_content: str, deployable: dict, disable_docs: bool = False) -> str:
|
|
251
|
+
"""
|
|
252
|
+
Update the docstring in the file content based on the deployable's documentation data.
|
|
253
|
+
"""
|
|
254
|
+
if not disable_docs:
|
|
255
|
+
docstring = print_docstring_function_comment(
|
|
256
|
+
deployable['types']['description'],
|
|
257
|
+
deployable['types']['params'],
|
|
258
|
+
deployable['types']['returns']
|
|
259
|
+
)
|
|
260
|
+
if deployable["docStartIndex"] == deployable["docEndIndex"]:
|
|
261
|
+
# Function doesn't yet have any docstrings so we need to add additional whitespace
|
|
262
|
+
docstring = " " + docstring + "\n"
|
|
263
|
+
|
|
264
|
+
return f"{file_content[:deployable['docStartIndex']]}{docstring}{file_content[deployable['docEndIndex']:]}"
|
|
265
|
+
return file_content
|
|
266
|
+
|
|
267
|
+
def write_updated_deployable(deployable: dict, disable_docs: bool = False) -> dict:
|
|
268
|
+
"""
|
|
269
|
+
Read the deployable's file, update its comments and docstring, and write back to the file.
|
|
270
|
+
"""
|
|
271
|
+
with open(deployable['file'], 'r', encoding='utf-8') as file:
|
|
272
|
+
file_contents = file.read()
|
|
273
|
+
|
|
274
|
+
if deployable['type'] in ['client-function', 'server-function']:
|
|
275
|
+
file_contents = update_deployable_function_comments(file_contents, deployable, disable_docs)
|
|
276
|
+
else:
|
|
277
|
+
raise ValueError(f"Unsupported deployable type: '{deployable['type']}'")
|
|
278
|
+
|
|
279
|
+
file_contents = update_deployment_comments(file_contents, deployable)
|
|
280
|
+
|
|
281
|
+
with open(deployable['file'], 'w', encoding='utf-8') as file:
|
|
282
|
+
file.write(file_contents)
|
|
283
|
+
|
|
284
|
+
deployable['fileRevision'] = get_deployable_file_revision(file_contents)
|
|
285
|
+
return deployable
|
|
286
|
+
|
|
287
|
+
def write_deploy_comments(deployments: list) -> str:
|
|
288
|
+
"""
|
|
289
|
+
Generate deployment comments for each deployment record.
|
|
290
|
+
"""
|
|
291
|
+
canopy_path = 'polyui/collections' if 'localhost' in os.getenv('POLY_API_BASE_URL', '') else 'canopy/polyui/collections'
|
|
292
|
+
comments = []
|
|
293
|
+
for d in deployments:
|
|
294
|
+
instance_url = d['instance'].replace(':8000', ':3000') if d['instance'].endswith(':8000') else d['instance']
|
|
295
|
+
comments.append(f"# Poly deployed @ {d['deployed']} - {d['context']}.{d['name']} - {instance_url}/{canopy_path}/{d['type']}s/{d['id']} - {d['fileRevision']}")
|
|
296
|
+
return "\n".join(comments)
|