Skip to content

Commit 20e6271

Browse files
authored
Add DocGen method to replace entities. (#56)
* Add DocGen method to replace entities. * Change entities import to relative to package. * Update doc gen cli to expand entities in additional fields and add a flag to disable entity expansion.
1 parent 032fe0c commit 20e6271

File tree

9 files changed

+345
-24
lines changed

9 files changed

+345
-24
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
expanded_override:
2+
"&AWS;": "AWS"
3+
"&aws_sec_sdk_use-federation-warning;": ""
4+
"&ASH;": "Security Hub"
5+
"&DAX;": "DynamoDB Accelerator"
6+
"&EC2long;": "Amazon Elastic Compute Cloud"
7+
"&ELB;": "Elastic Load Balancing"
8+
"&ELBlong;": "Elastic Load Balancing"
9+
"&GLUDCLong;": "AWS Glue Data Catalog"
10+
"&GLUDC;": "Data Catalog"
11+
"&IAM-user;": "IAM user"
12+
"&IAM-users;": "IAM users"
13+
"&kms-key;": "KMS key"
14+
"&kms-keys;": "KMS keys"
15+
"&kms-keys-long;": "AWS KMS keys"
16+
"&S3long;": "Amazon Simple Storage Service"
17+
"&SLN;": "Amazon States Language"

aws_doc_sdk_examples_tools/doc_gen.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from dataclasses import dataclass, field, is_dataclass, asdict
99
from functools import reduce
1010
from pathlib import Path
11-
from typing import Dict, Iterable, Optional, Set
11+
from typing import Dict, Iterable, Optional, Set, Tuple
1212

1313
# from os import glob
1414

@@ -17,6 +17,7 @@
1717
parse as parse_examples,
1818
validate_no_duplicate_api_examples,
1919
)
20+
from .entities import expand_all_entities, EntityErrors
2021
from .metadata_errors import MetadataErrors, MetadataError
2122
from .metadata_validator import validate_metadata
2223
from .project_validator import ValidationConfig
@@ -39,6 +40,7 @@ class DocGenMergeWarning(MetadataError):
3940
class DocGen:
4041
root: Path
4142
errors: MetadataErrors
43+
entities: Dict[str, str] = field(default_factory=dict)
4244
prefix: Optional[str] = None
4345
validation: ValidationConfig = field(default_factory=ValidationConfig)
4446
sdks: Dict[str, Sdk] = field(default_factory=dict)
@@ -72,6 +74,9 @@ def languages(self) -> Set[str]:
7274
languages.add(f"{sdk_name}:{version.version}")
7375
return languages
7476

77+
def expand_entities(self, text: str) -> Tuple[str, EntityErrors]:
78+
return expand_all_entities(text, self.entities)
79+
7580
def merge(self, other: "DocGen") -> MetadataErrors:
7681
"""Merge fields from other into self, prioritizing self fields."""
7782
warnings = MetadataErrors()
@@ -99,6 +104,16 @@ def merge(self, other: "DocGen") -> MetadataErrors:
99104
)
100105
)
101106

107+
for entity, expanded in other.entities.items():
108+
if entity not in self.entities:
109+
self.entities[entity] = expanded
110+
else:
111+
warnings.append(
112+
DocGenMergeWarning(
113+
file=other.root, id=f"conflict in entity {entity}"
114+
)
115+
)
116+
102117
self.snippet_files.update(other.snippet_files)
103118
self.cross_blocks.update(other.cross_blocks)
104119
self.extend_examples(other.examples.values(), warnings)
@@ -166,10 +181,23 @@ def for_root(
166181
meta = yaml.safe_load(file)
167182
services, service_errors = parse_services(services_path, meta)
168183
self.services = services
184+
for service in self.services.values():
185+
if service.expanded:
186+
self.entities[service.long] = service.expanded.long
187+
self.entities[service.short] = service.expanded.short
169188
self.errors.extend(service_errors)
170189
except Exception:
171190
pass
172191

192+
try:
193+
entities_config_path = config / "entities.yaml"
194+
with entities_config_path.open(encoding="utf-8") as file:
195+
entities_config = yaml.safe_load(file)
196+
for entity, expanded in entities_config["expanded_override"].items():
197+
self.entities[entity] = expanded
198+
except Exception as e:
199+
pass
200+
173201
metadata = root / ".doc_gen/metadata"
174202
try:
175203
self.cross_blocks = set(
@@ -280,6 +308,11 @@ def default(self, obj):
280308
if isinstance(obj, MetadataErrors):
281309
return {"__metadata_errors__": [asdict(error) for error in obj]}
282310

311+
if isinstance(obj, EntityErrors):
312+
return {
313+
"__entity_errors__": [{error.entity: error.message()} for error in obj]
314+
}
315+
283316
if isinstance(obj, set):
284317
return {"__set__": list(obj)}
285318

aws_doc_sdk_examples_tools/doc_gen_cli.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import logging
88

99
from .doc_gen import DocGen, DocGenEncoder
10+
from .entities import EntityErrors
1011

1112
logging.basicConfig(level=logging.INFO)
1213

@@ -34,6 +35,12 @@ def main():
3435
help="Exit with non-zero code if errors are present. By default errors are written to the output.",
3536
)
3637

38+
parser.add_argument(
39+
"--skip-entity-expansion",
40+
action="store_true",
41+
help="Do not expand entities. Entities are expanded by default.",
42+
)
43+
3744
args = parser.parse_args()
3845

3946
merged_doc_gen = DocGen.empty()
@@ -45,6 +52,41 @@ def main():
4552
logging.error("Errors found in metadata: %s", merged_doc_gen.errors)
4653
exit(1)
4754

55+
if not args.skip_entity_expansion:
56+
# Replace entities
57+
for example in merged_doc_gen.examples.values():
58+
errors = EntityErrors()
59+
print(example)
60+
title, title_errors = merged_doc_gen.expand_entities(example.title)
61+
errors.extend(title_errors)
62+
63+
title_abbrev, title_abbrev_errors = merged_doc_gen.expand_entities(
64+
example.title_abbrev
65+
)
66+
errors.extend(title_abbrev_errors)
67+
68+
synopsis, synopsis_errors = merged_doc_gen.expand_entities(example.synopsis)
69+
errors.extend(synopsis_errors)
70+
71+
synopsis_list = []
72+
for synopsis in example.synopsis_list:
73+
expanded_synopsis, synopsis_errors = merged_doc_gen.expand_entities(
74+
synopsis
75+
)
76+
synopsis_list.append(expanded_synopsis)
77+
errors.extend(synopsis_errors)
78+
79+
if errors:
80+
logging.error(
81+
f"Errors expanding entities for example: {example}. {errors}"
82+
)
83+
exit(1)
84+
85+
example.title = title
86+
example.title_abbrev = title_abbrev
87+
example.synopsis = synopsis
88+
example.synopsis_list = synopsis_list
89+
4890
serialized = json.dumps(merged_doc_gen, cls=DocGenEncoder)
4991

5092
with open(args.write_json, "w") as out:

aws_doc_sdk_examples_tools/doc_gen_cli_test.py

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,60 @@
11
import pytest
22
from unittest.mock import patch, mock_open
33
from argparse import Namespace
4-
import logging
5-
from .doc_gen import DocGen, MetadataError
4+
from pathlib import Path
5+
6+
from .doc_gen import DocGen, MetadataError, Example
67
from .doc_gen_cli import main
8+
from .metadata import DocFilenames, Sdk, Language, SDKPageVersion, Version
9+
from .sdks import SdkVersion
10+
11+
12+
@pytest.fixture
13+
def mock_example():
14+
return Example(
15+
file=Path("test_cpp.yaml"),
16+
id="medical-imaging_GoodScenario",
17+
title="Scenario title",
18+
title_abbrev="Scenario title abbrev",
19+
synopsis="scenario synopsis.",
20+
category="Scenarios",
21+
doc_filenames=DocFilenames(
22+
service_pages={
23+
"medical-imaging": "link",
24+
},
25+
sdk_pages={
26+
"cpp": {
27+
1: SDKPageVersion(actions_scenarios={"medical-imaging": f"link"})
28+
}
29+
},
30+
),
31+
services={
32+
"medical-imaging": {"GoodOne"},
33+
},
34+
languages={
35+
"C++": Language(
36+
name="C++", property="cpp", versions=[Version(sdk_version=1)]
37+
)
38+
},
39+
)
740

841

942
@pytest.fixture
10-
def mock_doc_gen():
43+
def mock_doc_gen(mock_example):
1144
doc_gen = DocGen.empty()
1245
doc_gen.errors._errors = [
1346
MetadataError(file="a.yaml", id="Error 1"),
1447
MetadataError(file="b.yaml", id="Error 2"),
1548
]
49+
doc_gen.sdks = {
50+
"JavaScript": Sdk(
51+
name="JavaScript",
52+
versions=[SdkVersion(version=3, long="&JS;", short="&JSlong")],
53+
guide="",
54+
property="javascript",
55+
)
56+
}
57+
doc_gen.examples = {"ex": mock_example}
1658
return doc_gen
1759

1860

@@ -32,7 +74,10 @@ def patched_environment(mock_doc_gen):
3274
def test_doc_gen_strict_option(strict, should_raise, patched_environment):
3375
mock_parse_args, mock_json_dump = patched_environment
3476
mock_args = Namespace(
35-
from_root=["/mock/path"], write_json="mock_output.json", strict=strict
77+
from_root=["/mock/path"],
78+
write_json="mock_output.json",
79+
strict=strict,
80+
skip_entity_expansion=False,
3681
)
3782
mock_parse_args.return_value = mock_args
3883

@@ -42,3 +87,38 @@ def test_doc_gen_strict_option(strict, should_raise, patched_environment):
4287
assert exc_info.value.code == 1
4388
else:
4489
main()
90+
91+
92+
def test_skip_entity_expansion(patched_environment):
93+
mock_parse_args, mock_json_dump = patched_environment
94+
mock_args = Namespace(
95+
from_root=["/mock/path"],
96+
write_json="mock_output.json",
97+
strict=False,
98+
skip_entity_expansion=True,
99+
)
100+
mock_parse_args.return_value = mock_args
101+
102+
with patch(
103+
"aws_doc_sdk_examples_tools.doc_gen.DocGen.expand_entities"
104+
) as mock_expand_entities:
105+
main()
106+
assert not mock_expand_entities.called
107+
108+
109+
def test_default_entity_expansion(patched_environment):
110+
mock_parse_args, mock_json_dump = patched_environment
111+
mock_args = Namespace(
112+
from_root=["/mock/path"],
113+
write_json="mock_output.json",
114+
strict=False,
115+
skip_entity_expansion=False,
116+
)
117+
mock_parse_args.return_value = mock_args
118+
119+
with patch(
120+
"aws_doc_sdk_examples_tools.doc_gen.DocGen.expand_entities"
121+
) as mock_expand_entities:
122+
mock_expand_entities.return_value = None, []
123+
main()
124+
assert mock_expand_entities.called

aws_doc_sdk_examples_tools/doc_gen_test.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from .metadata_errors import MetadataErrors, MetadataError
1313
from .doc_gen import DocGen, DocGenEncoder
1414
from .sdks import Sdk, SdkVersion
15-
from .services import Service
15+
from .services import Service, ServiceExpanded
1616
from .snippets import Snippet
1717

1818

@@ -80,6 +80,7 @@ def sample_doc_gen() -> DocGen:
8080
root=Path("/test/root"),
8181
errors=metadata_errors,
8282
prefix="test_prefix",
83+
entities={"&S3long;": "Amazon Simple Storage Service", "&S3;": "Amazon S3"},
8384
sdks={
8485
"python": Sdk(
8586
name="python",
@@ -92,8 +93,11 @@ def sample_doc_gen() -> DocGen:
9293
},
9394
services={
9495
"s3": Service(
95-
long="Amazon S3",
96-
short="S3",
96+
long="&S3long;",
97+
short="&S3;",
98+
expanded=ServiceExpanded(
99+
long="Amazon Simple Storage Service", short="Amazon S3"
100+
),
97101
sort="Amazon S3",
98102
version="2006-03-01",
99103
)
@@ -113,6 +117,12 @@ def sample_doc_gen() -> DocGen:
113117
)
114118

115119

120+
def test_expand_entities(sample_doc_gen: DocGen):
121+
expanded, errors = sample_doc_gen.expand_entities("Hello &S3;")
122+
assert expanded == "Hello Amazon S3"
123+
assert not errors
124+
125+
116126
def test_doc_gen_encoder(sample_doc_gen: DocGen):
117127
encoded = json.dumps(sample_doc_gen, cls=DocGenEncoder)
118128
decoded = json.loads(encoded)
@@ -132,8 +142,8 @@ def test_doc_gen_encoder(sample_doc_gen: DocGen):
132142
# Verify service information
133143
assert "services" in decoded
134144
assert "s3" in decoded["services"]
135-
assert decoded["services"]["s3"]["long"] == "Amazon S3"
136-
assert decoded["services"]["s3"]["short"] == "S3"
145+
assert decoded["services"]["s3"]["long"] == "&S3long;"
146+
assert decoded["services"]["s3"]["short"] == "&S3;"
137147

138148
# Verify snippet information
139149
assert "snippets" in decoded

0 commit comments

Comments
 (0)