Skip to content

Commit ea34c9c

Browse files
authored
[Dpg] DPG integration (Azure#25397)
* add PyYAML * code * README.md format * multi client * code * gen multi client * single client gen * compatible with meta storage code * add log to lookup readme * dpg package does not need _meta.json in MAINFEST.in * add language, apiviewartifacts * optimize * Update setup.py
1 parent 57882c9 commit ea34c9c

File tree

5 files changed

+194
-31
lines changed

5 files changed

+194
-31
lines changed

swagger_to_sdk_config_dpg.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
"meta": {
33
"autorest_options": {
44
"version": "3.8.1",
5-
"use": ["@autorest/[email protected]", "@autorest/[email protected]"],
6-
"python": "",
7-
"sdkrel:python-sdks-folder": "./sdk/.",
8-
"version-tolerant": ""
5+
"use": ["@autorest/[email protected]", "@autorest/[email protected]"]
96
},
107
"advanced_options": {
118
"create_sdk_pull_requests": true,

tools/azure-sdk-tools/packaging_tools/auto_codegen.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
from pathlib import Path
55
from subprocess import check_call
66

7-
from .swaggertosdk.SwaggerToSdkCore import CONFIG_FILE, CONFIG_FILE_DPG
7+
from .swaggertosdk.SwaggerToSdkCore import CONFIG_FILE
88
from .generate_sdk import generate
99
from .generate_utils import (get_package_names, init_new_service, update_servicemetadata, judge_tag_preview,
10-
format_samples)
10+
format_samples, gen_dpg)
1111

1212
_LOGGER = logging.getLogger(__name__)
1313

@@ -24,8 +24,11 @@ def main(generate_input, generate_output):
2424
for input_readme in data["relatedReadmeMdFiles"]:
2525
relative_path_readme = str(Path(spec_folder, input_readme))
2626
_LOGGER.info(f"[CODEGEN]({input_readme})codegen begin")
27-
config_file = CONFIG_FILE if 'resource-manager' in input_readme else CONFIG_FILE_DPG
28-
config = generate(config_file, sdk_folder, [], relative_path_readme, spec_folder, force_generation=True, python_tag=python_tag)
27+
if 'resource-manager' in input_readme:
28+
config = generate(CONFIG_FILE, sdk_folder, [], relative_path_readme, spec_folder, force_generation=True,
29+
python_tag=python_tag)
30+
else:
31+
config = gen_dpg(input_readme, data.get('autorestConfig', ''))
2932
package_names = get_package_names(sdk_folder)
3033
_LOGGER.info(f"[CODEGEN]({input_readme})codegen end. [(packages:{str(package_names)})]")
3134

@@ -54,7 +57,7 @@ def main(generate_input, generate_output):
5457
try:
5558
update_servicemetadata(sdk_folder, data, config, folder_name, package_name, spec_folder, input_readme)
5659
except Exception as e:
57-
_LOGGER.info(str(e))
60+
_LOGGER.info(f"fail to update meta: {str(e)}")
5861

5962
# Setup package locally
6063
check_call(

tools/azure-sdk-tools/packaging_tools/auto_package.py

+5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ def main(generate_input, generate_output):
4646
# to distinguish with track1
4747
if 'azure-mgmt-' in package_name:
4848
package["packageName"] = "track2_" + package["packageName"]
49+
for artifact in package["artifacts"]:
50+
if ".whl" in artifact:
51+
package["apiViewArtifact"] = artifact
52+
package["language"] = "Python"
53+
break
4954
result["packages"].append(package)
5055

5156
with open(generate_output, "w") as writer:

tools/azure-sdk-tools/packaging_tools/generate_utils.py

+179-22
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,19 @@
77
from azure_devtools.ci_tools.git_tools import get_add_diff_file_list
88
from pathlib import Path
99
from subprocess import check_call
10-
from typing import List
10+
from typing import List, Dict, Any
11+
from glob import glob
12+
import yaml
13+
14+
from .swaggertosdk.autorest_tools import build_autorest_options, generate_code
15+
from .swaggertosdk.SwaggerToSdkCore import CONFIG_FILE_DPG, read_config
1116

12-
from .swaggertosdk.autorest_tools import build_autorest_options
1317

1418
_LOGGER = logging.getLogger(__name__)
1519
_SDK_FOLDER_RE = re.compile(r"^(sdk/[\w-]+)/(azure[\w-]+)/", re.ASCII)
1620

1721
DEFAULT_DEST_FOLDER = "./dist"
18-
22+
_DPG_README = "README.md"
1923

2024
def get_package_names(sdk_folder):
2125
files = get_add_diff_file_list(sdk_folder)
@@ -42,9 +46,13 @@ def update_servicemetadata(sdk_folder, data, config, folder_name, package_name,
4246

4347
readme_file = str(Path(spec_folder, input_readme))
4448
global_conf = config["meta"]
45-
local_conf = config["projects"][readme_file]
49+
local_conf = config.get("projects", {}).get(readme_file, {})
4650

47-
cmd = ["autorest", input_readme]
51+
if "resource-manager" in input_readme:
52+
cmd = ["autorest", input_readme]
53+
else:
54+
# autorest for DPG will be executed in package folder like: sdk/deviceupdate/azure-iot-deviceupdate/swagger
55+
cmd = ["autorest", _DPG_README]
4856
cmd += build_autorest_options(global_conf, local_conf)
4957

5058
# metadata
@@ -71,23 +79,24 @@ def update_servicemetadata(sdk_folder, data, config, folder_name, package_name,
7179
_LOGGER.info(f"Saved metadata to {metadata_file_path}")
7280

7381
# Check whether MANIFEST.in includes _meta.json
74-
require_meta = "include _meta.json\n"
75-
manifest_file = os.path.join(package_folder, "MANIFEST.in")
76-
if not os.path.exists(manifest_file):
77-
_LOGGER.info(f"MANIFEST.in doesn't exist: {manifest_file}")
78-
return
79-
80-
includes = []
81-
write_flag = False
82-
with open(manifest_file, "r") as f:
83-
includes = f.readlines()
84-
if require_meta not in includes:
85-
includes = [require_meta] + includes
86-
write_flag = True
87-
88-
if write_flag:
89-
with open(manifest_file, "w") as f:
90-
f.write("".join(includes))
82+
if "resource-manager" in input_readme:
83+
require_meta = "include _meta.json\n"
84+
manifest_file = os.path.join(package_folder, "MANIFEST.in")
85+
if not os.path.exists(manifest_file):
86+
_LOGGER.info(f"MANIFEST.in doesn't exist: {manifest_file}")
87+
return
88+
89+
includes = []
90+
write_flag = False
91+
with open(manifest_file, "r") as f:
92+
includes = f.readlines()
93+
if require_meta not in includes:
94+
includes = [require_meta] + includes
95+
write_flag = True
96+
97+
if write_flag:
98+
with open(manifest_file, "w") as f:
99+
f.write("".join(includes))
91100

92101

93102
# find all the files of one folder, including files in subdirectory
@@ -129,6 +138,154 @@ def judge_tag_preview(path: str) -> bool:
129138
return 'preview' in api_version
130139

131140

141+
def extract_yaml_content(autorest_config: str) -> str:
142+
num = []
143+
content = autorest_config.split('\r\n')
144+
for i in range(len(content)):
145+
if "```" in content[i]:
146+
num.append(i)
147+
if len(num) != 2:
148+
raise Exception(f"autorestConfig content is not valid: {autorest_config}")
149+
return '\n'.join(content[num[0] + 1: num[1]])
150+
151+
152+
def add_config_title(content: str) -> str:
153+
return f"# autorest configuration for Python\n\n{content}"
154+
155+
156+
def yaml_block(content: str, annotation: str = "", tag: str = "") -> str:
157+
annotation = f"{annotation}\n\n" if annotation else annotation
158+
return f"{annotation}" + f"``` yaml {tag}\n" + content + "```\n"
159+
160+
161+
def gen_package_name(origin_config: Dict[str, Any]) -> str:
162+
return Path(origin_config["output-folder"]).parts[-1]
163+
164+
165+
def gen_basic_config(origin_config: Dict[str, Any]) -> Dict[str, Any]:
166+
return {
167+
"package-name": gen_package_name(origin_config),
168+
"license-header": "MICROSOFT_MIT_NO_VERSION",
169+
"package-version": origin_config.get("package-version", "1.0.0b1"),
170+
"require": ["../../../../../azure-rest-api-specs/" + line for line in origin_config["require"]],
171+
"package-mode": "dataplane",
172+
"output-folder": "../",
173+
}
174+
175+
176+
def gen_general_namespace(package_name: str) -> str:
177+
return package_name.replace('-', '.')
178+
179+
180+
def gen_dpg_config_single_client(origin_config: Dict[str, Any]) -> str:
181+
package_name = Path(origin_config["output-folder"]).parts[-1]
182+
readme_config = gen_basic_config(origin_config)
183+
readme_config.update({
184+
"namespace": gen_general_namespace(package_name),
185+
})
186+
readme_content = yaml_block(yaml.safe_dump(readme_config), "### Settings")
187+
return add_config_title(readme_content)
188+
189+
190+
def gen_tag_config(origin_config: Dict[str, Any]) -> Dict[str, Any]:
191+
tag_config = {}
192+
package_name = gen_package_name(origin_config)
193+
for tag in origin_config["batch"]:
194+
tag_name = tag["tag"]
195+
extra_part = tag_name.split("-")[-1]
196+
tag_config[tag_name] = {
197+
"namespace": gen_general_namespace(package_name) + f".{extra_part}",
198+
}
199+
200+
return tag_config
201+
202+
203+
def gen_batch_config(origin_config: Dict[str, Any]) -> Dict[str, Any]:
204+
batch_config = []
205+
for item in origin_config["batch"]:
206+
for _, value in item.items():
207+
batch_config.append({value: True})
208+
return {"batch": batch_config}
209+
210+
211+
def gen_dpg_config_multi_client(origin_config: Dict[str, Any]) -> str:
212+
# generate config
213+
basic_config = gen_basic_config(origin_config)
214+
batch_config = gen_batch_config(origin_config)
215+
tag_config = gen_tag_config(origin_config)
216+
217+
# convert to string
218+
readme_content = yaml_block(yaml.dump(basic_config), "### Settings")
219+
readme_content += yaml_block(yaml.dump(batch_config), "\n### Python multi-client")
220+
for tag, value in tag_config.items():
221+
readme_content += yaml_block(
222+
yaml.dump(value),
223+
f"\n### Tag: {tag}",
224+
f"$({tag})",
225+
)
226+
227+
return add_config_title(readme_content)
228+
229+
230+
# generate swagger/README.md and return relative path based on SDK repo root path
231+
def gen_dpg_config(autorest_config: str) -> str:
232+
# remove useless lines
233+
autorest_config = extract_yaml_content(autorest_config)
234+
_LOGGER.info(f"autorestConfig after remove useless lines:\n{autorest_config}")
235+
236+
# make dir if not exist
237+
origin_config = yaml.safe_load(autorest_config)
238+
_LOGGER.info(f"autorestConfig: {origin_config}")
239+
swagger_folder = str(Path(origin_config["output-folder"], "swagger"))
240+
if not os.path.exists(swagger_folder):
241+
os.makedirs(swagger_folder)
242+
243+
# generate autorest configuration
244+
if "batch:" in autorest_config:
245+
readme_content = gen_dpg_config_multi_client(origin_config)
246+
else:
247+
readme_content = gen_dpg_config_single_client(origin_config)
248+
249+
# output autorest configuration
250+
swagger_readme = str(Path(swagger_folder, _DPG_README))
251+
with open(swagger_readme, "w") as file:
252+
file.write(readme_content)
253+
return swagger_readme
254+
255+
256+
def lookup_swagger_readme(rest_readme_path: str) -> str:
257+
all_swagger_readme = glob(str(Path(f'sdk/*/*/swagger/{_DPG_README}')))
258+
for readme in all_swagger_readme:
259+
with open(readme, 'r') as file:
260+
content = file.read()
261+
if rest_readme_path in content:
262+
_LOGGER.info(f"find swagger readme: {readme}")
263+
return readme
264+
_LOGGER.info(f"do not find swagger readme which contains {rest_readme_path}")
265+
return ""
266+
267+
268+
def gen_dpg(rest_readme_path: str, autorest_config: str) -> Dict[str, Any]:
269+
# generate or find swagger/README.md
270+
if autorest_config:
271+
swagger_readme = gen_dpg_config(autorest_config)
272+
else:
273+
swagger_readme = lookup_swagger_readme(rest_readme_path)
274+
if not swagger_readme:
275+
return
276+
277+
# extract global config
278+
global_config = read_config('.', CONFIG_FILE_DPG)
279+
280+
# generate code
281+
current_path = os.getcwd()
282+
os.chdir(Path(swagger_readme).parent)
283+
generate_code(_DPG_README, global_config["meta"], {})
284+
os.chdir(current_path)
285+
286+
return global_config
287+
288+
132289
def format_samples(sdk_code_path) -> None:
133290
generate_sample_path = Path(sdk_code_path) / 'generated_samples'
134291
if not generate_sample_path.exists():

tools/azure-sdk-tools/setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"azure-mgmt-storage",
2626
"azure-mgmt-keyvault",
2727
"python-dotenv",
28+
"PyYAML"
2829
]
2930

3031
setup(

0 commit comments

Comments
 (0)