99import java .util .List ;
1010import java .util .Map ;
1111import java .util .Optional ;
12+ import java .util .TreeMap ;
13+ import java .util .function .BinaryOperator ;
14+ import java .util .function .Function ;
15+ import java .util .stream .Collectors ;
16+ import java .util .stream .Stream ;
17+ import software .amazon .smithy .codegen .core .CodegenException ;
1218import software .amazon .smithy .codegen .core .SymbolDependency ;
1319import software .amazon .smithy .codegen .core .WriterDelegator ;
1420import software .amazon .smithy .model .traits .DocumentationTrait ;
3036@ SmithyInternalApi
3137public final class SetupGenerator {
3238
33- private SetupGenerator () {}
39+ private SetupGenerator () {
40+ }
3441
3542 public static void generateSetup (
3643 PythonSettings settings ,
3744 GenerationContext context
3845 ) {
39- var dependencies = SymbolDependency . gatherDependencies (context .writerDelegator ().getDependencies ().stream ());
46+ var dependencies = gatherDependencies (context .writerDelegator ().getDependencies ().stream ());
4047 writePyproject (settings , context .writerDelegator (), dependencies );
4148 writeReadme (settings , context );
4249 }
4350
51+ /**
52+ * Merge all the symbol dependencies. Also merges optional dependencies.
53+ * Modification of : SymbolDependency.gatherDependencies that also considers the OPTIONAL_DEPENDENCIES
54+ * property.
55+ */
56+ @ SuppressWarnings ("unchecked" )
57+ private static Map <String , Map <String , SymbolDependency >> gatherDependencies (
58+ Stream <SymbolDependency > symbolStream
59+ ) {
60+ BinaryOperator <SymbolDependency > guardedMergeWithProperties = (a , b ) -> {
61+ if (!a .getVersion ().equals (b .getVersion ())) {
62+ throw new CodegenException (String .format (
63+ "Found a conflicting `%s` dependency for `%s`: `%s` conflicts with `%s`" ,
64+ a .getDependencyType (),
65+ a .getPackageName (),
66+ a .getVersion (),
67+ b .getVersion ()));
68+ }
69+ // For our purposes, we need only consider OPTIONAL_DEPENDENCIES property.
70+ // The only other property currently used is IS_LINK, and it is consistent across all usages of
71+ // a given SymbolDependency.
72+ if (!b .getTypedProperties ().isEmpty ()) {
73+ var optional_a = a .getProperty (SymbolProperties .OPTIONAL_DEPENDENCIES ).orElse (List .of ());
74+ var optional_b = b .getProperty (SymbolProperties .OPTIONAL_DEPENDENCIES ).orElse (List .of ());
75+
76+ if (optional_b .isEmpty ()) {
77+ return a ;
78+ }
79+
80+ if (optional_a .isEmpty ()) {
81+ return b ;
82+ }
83+
84+ var merged = Stream .concat (optional_a .stream (), optional_b .stream ())
85+ .distinct ()
86+ .toList ();
87+
88+ return a .toBuilder ()
89+ .putProperty (SymbolProperties .OPTIONAL_DEPENDENCIES , merged )
90+ .build ();
91+ } else {
92+ return a ;
93+ }
94+ };
95+ return symbolStream .sorted ()
96+ .collect (Collectors .groupingBy (SymbolDependency ::getDependencyType ,
97+ Collectors .toMap (SymbolDependency ::getPackageName ,
98+ Function .identity (),
99+ guardedMergeWithProperties ,
100+ TreeMap ::new )));
101+ }
102+
44103 /**
45104 * Write a pyproject.toml file.
46105 *
@@ -64,7 +123,7 @@ private static void writePyproject(
64123 [build-system]
65124 requires = ["setuptools", "setuptools-scm", "wheel"]
66125 build-backend = "setuptools.build_meta"
67-
126+
68127 [project]
69128 name = $1S
70129 version = $2S
@@ -100,18 +159,18 @@ private static void writePyproject(
100159 writer .write ("""
101160 [tool.setuptools.packages.find]
102161 exclude=["tests*"]
103-
162+
104163 [tool.pyright]
105164 typeCheckingMode = "strict"
106165 reportPrivateUsage = false
107166 reportUnusedFunction = false
108167 reportUnusedVariable = false
109168 reportUnnecessaryComparison = false
110169 reportUnusedClass = false
111-
170+
112171 [tool.black]
113172 target-version = ["py311"]
114-
173+
115174 [tool.pytest.ini_options]
116175 python_classes = ["!Test"]
117176 asyncio_mode = "auto"
@@ -122,17 +181,17 @@ private static void writePyproject(
122181 }
123182
124183 private static void writeDependencyList (PythonWriter writer , Collection <SymbolDependency > dependencies ) {
125- for (var iter = dependencies .iterator (); iter .hasNext ();) {
184+ for (var iter = dependencies .iterator (); iter .hasNext (); ) {
126185 writer .pushState ();
127186 var dependency = iter .next ();
128187 writer .putContext ("deps" , getOptionalDependencies (dependency ));
129188 writer .putContext ("isLink" , dependency .getProperty (SymbolProperties .IS_LINK ).orElse (false ));
130189 writer .putContext ("last" , !iter .hasNext ());
131190 writer .write ("""
132- "$L\
133- ${?deps}[${#deps}${value:L}${^key.last}, ${/key.last}${/deps}]${/deps}\
134- ${?isLink} @ ${/isLink}$L"\
135- ${^last},${/last}""" ,
191+ "$L\
192+ ${?deps}[${#deps}${value:L}${^key.last}, ${/key.last}${/deps}]${/deps}\
193+ ${?isLink} @ ${/isLink}$L"\
194+ ${^last},${/last}""" ,
136195 dependency .getPackageName (),
137196 dependency .getVersion ());
138197 writer .popState ();
@@ -152,7 +211,7 @@ private static List<String> getOptionalDependencies(SymbolDependency dependency)
152211 })
153212 .orElse (Collections .emptyList ());
154213 try {
155- return ( List < String >) optionals ;
214+ return optionals ;
156215 } catch (Exception e ) {
157216 return Collections .emptyList ();
158217 }
@@ -177,7 +236,7 @@ private static void writeReadme(
177236 writer .pushState (new ReadmeSection ());
178237 writer .write ("""
179238 ## $L Client
180-
239+
181240 $L
182241 """ , title , description );
183242
@@ -190,7 +249,7 @@ private static void writeReadme(
190249 // since the python code docs are RST format.
191250 writer .write ("""
192251 ### Documentation
193-
252+
194253 $L
195254 """ , documentation );
196255 });
0 commit comments