Skip to content

Commit f449114

Browse files
committed
Major refactor - generic and aws specific user agent. Codegen useragent plugin per service.
1 parent 296811b commit f449114

File tree

12 files changed

+231
-56
lines changed

12 files changed

+231
-56
lines changed

codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsUserAgentIntegration.java

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.List;
88
import software.amazon.smithy.codegen.core.Symbol;
99
import software.amazon.smithy.codegen.core.SymbolReference;
10+
import software.amazon.smithy.python.codegen.CodegenUtils;
1011
import software.amazon.smithy.python.codegen.ConfigProperty;
1112
import software.amazon.smithy.python.codegen.GenerationContext;
1213
import software.amazon.smithy.python.codegen.integrations.PythonIntegration;
@@ -21,34 +22,79 @@ public class AwsUserAgentIntegration implements PythonIntegration {
2122
@Override
2223
public List<RuntimeClientPlugin> getClientPlugins(GenerationContext context) {
2324
if (context.applicationProtocol().isHttpProtocol()) {
25+
final ConfigProperty userAgentExtra = ConfigProperty.builder()
26+
.name("user_agent_extra")
27+
.documentation("Additional suffix to be added to the User-Agent header.")
28+
.type(Symbol.builder().name("str").build())
29+
.nullable(true)
30+
.build();
31+
32+
final ConfigProperty uaAppId = ConfigProperty.builder()
33+
.name("sdk_ua_app_id")
34+
.documentation(
35+
"A unique and opaque application ID that is appended to the User-Agent header.")
36+
.type(Symbol.builder().name("str").build())
37+
.nullable(true)
38+
.build();
39+
40+
final String user_agent_plugin_file = "user_agent";
41+
42+
final String moduleName = context.settings().moduleName();
43+
final SymbolReference userAgentPlugin = SymbolReference.builder()
44+
.symbol(Symbol.builder()
45+
.namespace(String.format("%s.%s",
46+
moduleName,
47+
user_agent_plugin_file.replace('/', '.')), ".")
48+
.definitionFile(String
49+
.format("./%s/%s.py", moduleName, user_agent_plugin_file))
50+
.name("aws_user_agent_plugin")
51+
.build())
52+
.build();
53+
final SymbolReference userAgentInterceptor = SymbolReference.builder()
54+
.symbol(Symbol.builder()
55+
.namespace("smithy_aws_core.interceptors.user_agent", ".")
56+
.name("UserAgentInterceptor")
57+
.build())
58+
.build();
59+
final SymbolReference versionSymbol = SymbolReference.builder()
60+
.symbol(Symbol.builder()
61+
.namespace(moduleName, ".")
62+
.name("__version__")
63+
.build()
64+
).build();
65+
2466
return List.of(
2567
RuntimeClientPlugin.builder()
26-
.addConfigProperty(
27-
ConfigProperty.builder()
28-
.name("user_agent_extra")
29-
.documentation("Additional suffix to be added to the User-Agent header.")
30-
.type(Symbol.builder().name("str").build())
31-
.nullable(true)
32-
.build())
33-
.addConfigProperty(
34-
ConfigProperty.builder()
35-
.name("sdk_ua_app_id")
36-
.documentation(
37-
"A unique and opaque application ID that is appended to the User-Agent header.")
38-
.type(Symbol.builder().name("str").build())
39-
.nullable(true)
40-
.build())
41-
.pythonPlugin(
42-
SymbolReference.builder()
43-
.symbol(Symbol.builder()
44-
.namespace(
45-
AwsPythonDependency.SMITHY_AWS_CORE.packageName()
46-
+ ".plugins",
47-
".")
48-
.name("aws_user_agent_plugin")
49-
.addDependency(AwsPythonDependency.SMITHY_AWS_CORE)
50-
.build())
51-
.build())
68+
.addConfigProperty(userAgentExtra)
69+
.addConfigProperty(uaAppId)
70+
.pythonPlugin(userAgentPlugin)
71+
.writeAdditionalFiles((c) -> {
72+
String filename = "%s/%s.py".formatted(moduleName, user_agent_plugin_file);
73+
c.writerDelegator()
74+
.useFileWriter(
75+
filename,
76+
moduleName + ".",
77+
writer -> {
78+
writer.write("""
79+
def aws_user_agent_plugin(config: $1T):
80+
config.interceptors.append(
81+
$2T(
82+
ua_suffix=config.user_agent_extra,
83+
ua_app_id=config.sdk_ua_app_id,
84+
sdk_version=$3T,
85+
service_id='$4L'
86+
)
87+
)
88+
""",
89+
CodegenUtils.getConfigSymbol(c.settings()),
90+
userAgentInterceptor,
91+
versionSymbol,
92+
c.settings().service().getName()
93+
);
94+
95+
});
96+
return List.of(filename);
97+
})
5298
.build());
5399
} else {
54100
return List.of();

codegen/core/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonCodegen.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import software.amazon.smithy.python.codegen.generators.StructureGenerator;
5151
import software.amazon.smithy.python.codegen.generators.UnionGenerator;
5252
import software.amazon.smithy.python.codegen.integrations.PythonIntegration;
53+
import software.amazon.smithy.python.codegen.integrations.RuntimeClientPlugin;
5354
import software.amazon.smithy.python.codegen.writer.PythonDelegator;
5455
import software.amazon.smithy.python.codegen.writer.PythonWriter;
5556
import software.amazon.smithy.utils.SmithyUnstableApi;
@@ -274,9 +275,24 @@ public void generateIntEnumShape(GenerateIntEnumDirective<GenerationContext, Pyt
274275
@Override
275276
public void customizeBeforeIntegrations(CustomizeDirective<GenerationContext, PythonSettings> directive) {
276277
generateServiceModuleInit(directive);
278+
generatePluginFiles(directive);
277279
generateInits(directive);
278280
}
279281

282+
/**
283+
* Writes out all extra files required by runtime plugins.
284+
*/
285+
private void generatePluginFiles(CustomizeDirective<GenerationContext, PythonSettings> directive) {
286+
GenerationContext context = directive.context();
287+
for (PythonIntegration integration : context.integrations()) {
288+
for (RuntimeClientPlugin runtimeClientPlugin : integration.getClientPlugins(context)) {
289+
if (runtimeClientPlugin.matchesService(context.model(), directive.service())) {
290+
runtimeClientPlugin.writeAdditionalFiles(context);
291+
}
292+
}
293+
}
294+
}
295+
280296
/**
281297
* Creates top level __init__.py file.
282298
*/
@@ -287,7 +303,7 @@ private void generateServiceModuleInit(CustomizeDirective<GenerationContext, Pyt
287303
"%s/__init__.py".formatted(directive.context().settings().moduleName()),
288304
writer -> {
289305
writer
290-
.write("__version__ = '$L'", directive.context().settings().moduleVersion());
306+
.write("__version__: str = '$L'", directive.context().settings().moduleVersion());
291307
});
292308
}
293309

codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/RuntimeClientPlugin.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import software.amazon.smithy.model.shapes.OperationShape;
1717
import software.amazon.smithy.model.shapes.ServiceShape;
1818
import software.amazon.smithy.python.codegen.ConfigProperty;
19+
import software.amazon.smithy.python.codegen.GenerationContext;
1920
import software.amazon.smithy.utils.SmithyBuilder;
2021
import software.amazon.smithy.utils.SmithyUnstableApi;
2122
import software.amazon.smithy.utils.ToSmithyBuilder;
@@ -38,6 +39,7 @@ public final class RuntimeClientPlugin implements ToSmithyBuilder<RuntimeClientP
3839
private final OperationPredicate operationPredicate;
3940
private final List<ConfigProperty> configProperties;
4041
private final SymbolReference pythonPlugin;
42+
private final WriteAdditionalFiles writeAdditionalFiles;
4143

4244
private final AuthScheme authScheme;
4345

@@ -47,6 +49,7 @@ private RuntimeClientPlugin(Builder builder) {
4749
configProperties = Collections.unmodifiableList(builder.configProperties);
4850
this.pythonPlugin = builder.pythonPlugin;
4951
this.authScheme = builder.authScheme;
52+
this.writeAdditionalFiles = builder.writeAdditionalFiles;
5053
}
5154

5255
/**
@@ -65,6 +68,20 @@ public interface OperationPredicate {
6568
boolean test(Model model, ServiceShape service, OperationShape operation);
6669
}
6770

71+
@FunctionalInterface
72+
/**
73+
* Called to write out additional files.
74+
*/
75+
public interface WriteAdditionalFiles {
76+
/**
77+
* Called to write out additional files needed by a generator.
78+
*
79+
* @param context GenerationContext - allows access to file manifest and symbol providers
80+
* @return List of the relative paths of files written.
81+
*/
82+
List<String> writeAdditionalFiles(GenerationContext context);
83+
}
84+
6885
/**
6986
* Returns true if this plugin applies to the given service.
7087
*
@@ -120,6 +137,16 @@ public Optional<AuthScheme> getAuthScheme() {
120137
return Optional.ofNullable(authScheme);
121138
}
122139

140+
/**
141+
* Write additional files required by this plugin.
142+
*
143+
* @param context generation context
144+
* @return relative paths of additional files written.
145+
*/
146+
public List<String> writeAdditionalFiles(GenerationContext context) {
147+
return writeAdditionalFiles.writeAdditionalFiles(context);
148+
}
149+
123150
/**
124151
* @return Returns a new builder for a {@link RuntimeClientPlugin}.
125152
*/
@@ -132,7 +159,8 @@ public SmithyBuilder<RuntimeClientPlugin> toBuilder() {
132159
var builder = builder()
133160
.pythonPlugin(pythonPlugin)
134161
.authScheme(authScheme)
135-
.configProperties(configProperties);
162+
.configProperties(configProperties)
163+
.writeAdditionalFiles(writeAdditionalFiles);
136164

137165
if (operationPredicate == OPERATION_ALWAYS_FALSE) {
138166
builder.servicePredicate(servicePredicate);
@@ -152,6 +180,7 @@ public static final class Builder implements SmithyBuilder<RuntimeClientPlugin>
152180
private List<ConfigProperty> configProperties = new ArrayList<>();
153181
private SymbolReference pythonPlugin = null;
154182
private AuthScheme authScheme = null;
183+
private WriteAdditionalFiles writeAdditionalFiles = (context) -> Collections.emptyList();
155184

156185
Builder() {}
157186

@@ -264,5 +293,16 @@ public Builder authScheme(AuthScheme authScheme) {
264293
this.authScheme = authScheme;
265294
return this;
266295
}
296+
297+
/**
298+
* Write additional files required by this plugin.
299+
*
300+
* @param writeAdditionalFiles additional files to write.
301+
* @return Returns the builder.
302+
*/
303+
public Builder writeAdditionalFiles(WriteAdditionalFiles writeAdditionalFiles) {
304+
this.writeAdditionalFiles = writeAdditionalFiles;
305+
return this;
306+
}
267307
}
268308
}

codegen/core/src/main/java/software/amazon/smithy/python/codegen/writer/ImportDeclarations.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,12 @@ private String relativize(String namespace) {
7070
}
7171
}
7272
var prefix = StringUtils.repeat(".", localParts.length - commonSegments);
73-
return prefix + namespace.split("\\.", commonSegments + 1)[commonSegments];
73+
String[] segments = namespace.split("\\.", commonSegments + 1);
74+
if (commonSegments >= segments.length) {
75+
return ".";
76+
} else {
77+
return prefix + segments[commonSegments];
78+
}
7479
}
7580

7681
ImportDeclarations addStdlibImport(String namespace) {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4-
__version__ = "0.1.0"
4+
__version__: str = "0.1.0"
Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,73 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
3-
from smithy_core.interceptors import Interceptor, Request
4-
from smithy_http.aio.interfaces import HTTPRequest
3+
# pyright: reportMissingTypeStubs=false
4+
from typing import Any
55

6+
import smithy_aws_core
7+
import smithy_core
8+
from smithy_core.interceptors import Interceptor, InterceptorContext
9+
from smithy_http.user_agent import UserAgentComponent, RawStringUserAgentComponent
610

7-
class UserAgentInterceptor(Interceptor[Request, None, HTTPRequest, None]):
8-
"""Adds UserAgent header to the Request before signing."""
11+
_USERAGENT_SDK_NAME = "python"
12+
13+
14+
class UserAgentInterceptor(Interceptor[Any, Any, Any, Any]):
15+
"""Adds AWS fields to the UserAgent."""
16+
17+
def __init__(
18+
self,
19+
*,
20+
ua_suffix: str | None,
21+
ua_app_id: str | None,
22+
sdk_version: str,
23+
service_id: str,
24+
) -> None:
25+
"""Initialize the UserAgentInterceptor.
26+
27+
:param ua_suffix: Additional suffix to be added to the UserAgent header.
28+
:param ua_app_id: User defined and opaque application ID to be added to the
29+
UserAgent header.
30+
:param sdk_version: SDK version to be added to the UserAgent header.
31+
:param service_id: ServiceId to be added to the UserAgent header.
32+
"""
33+
super().__init__()
34+
self._ua_suffix = ua_suffix
35+
self._ua_app_id = ua_app_id
36+
self._sdk_version = sdk_version
37+
self._service_id = service_id
38+
39+
def read_after_serialization(
40+
self, context: InterceptorContext[Any, Any, Any, Any]
41+
) -> None:
42+
if "user_agent" in context.properties:
43+
user_agent = context.properties["user_agent"]
44+
user_agent.sdk_metadata = self._build_sdk_metadata()
45+
user_agent.api_metadata.append(
46+
UserAgentComponent("api", self._service_id, self._sdk_version)
47+
)
48+
49+
if self._ua_app_id is not None:
50+
user_agent.additional_metadata.append(
51+
UserAgentComponent("app", self._ua_app_id)
52+
)
53+
54+
if self._ua_suffix is not None:
55+
user_agent.additional_metadata.append(
56+
RawStringUserAgentComponent(self._ua_suffix)
57+
)
58+
59+
def _build_sdk_metadata(self) -> list[UserAgentComponent]:
60+
return [
61+
UserAgentComponent(_USERAGENT_SDK_NAME, self._sdk_version),
62+
UserAgentComponent("md", "smithy-aws-core", smithy_aws_core.__version__),
63+
UserAgentComponent("md", "smithy-core", smithy_core.__version__),
64+
*self._crt_version(),
65+
]
66+
67+
def _crt_version(self) -> list[UserAgentComponent]:
68+
try:
69+
import awscrt
70+
71+
return [UserAgentComponent("md", "awscrt", awscrt.__version__)]
72+
except AttributeError:
73+
return []

packages/smithy-aws-core/src/smithy_aws_core/plugins/__init__.py

Lines changed: 0 additions & 9 deletions
This file was deleted.

packages/smithy-core/src/smithy_core/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from . import interfaces, rfc3986
99
from .exceptions import SmithyException
1010

11-
__version__ = "0.1.0"
11+
__version__: str = "0.1.0"
1212

1313

1414
class HostType(Enum):

packages/smithy-http/src/smithy_http/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from . import interfaces
77
from .interfaces import FieldPosition
88

9+
910
class Field(interfaces.Field):
1011
"""A name-value pair representing a single field in an HTTP Request or Response.
1112

0 commit comments

Comments
 (0)