Skip to content

Commit 9e03b46

Browse files
committed
feat(python): encode positional-only arguments in signatures
Now that Python 3.7 reached end-of-life, and since Python 3.8 and greater have support for positional-only argument notation (any argument before a `/` delimiter may only be passed positionally), adjust code generation to leverage this feature in order to address issues where inheritance hierarchies would rename parameters, which is a non-issue in all languages but Python, where those could always be provided as keyword arguments before. Fixes #2927 BREAKING CHANGE: the generated Python code now requires Python 3.8 or later and encodes positional arguments as positional-only, making keyword-style usage impossible. Users who used the keyword-style convention need to update their code to use the positional syntax instead.
1 parent ef6e5b1 commit 9e03b46

File tree

5 files changed

+213
-199
lines changed

5 files changed

+213
-199
lines changed

packages/jsii-pacmak/lib/targets/python.ts

+19-5
Original file line numberDiff line numberDiff line change
@@ -639,9 +639,13 @@ abstract class BaseMethod implements PythonBase {
639639
const liftedProperties = this.getLiftedProperties(context.resolver);
640640

641641
if (liftedProperties.length >= 1) {
642+
// Anything before the 1st keyword argument is positional-only
643+
if (pythonParams.length > 0) {
644+
pythonParams.push('/'); // marks the end of positional-only arguments
645+
}
642646
// All of these parameters are keyword only arguments, so we'll mark them
643647
// as such.
644-
pythonParams.push('*');
648+
pythonParams.push('*'); // marks the start of keyword-only arguments
645649

646650
// Iterate over all of our props, and reflect them into our params.
647651
for (const prop of liftedProperties) {
@@ -2102,7 +2106,7 @@ class Package {
21022106
package_dir: { '': 'src' },
21032107
packages: modules.map((m) => m.pythonName),
21042108
package_data: packageData,
2105-
python_requires: '~=3.7',
2109+
python_requires: '~=3.8',
21062110
install_requires: [
21072111
`jsii${toPythonVersionRange(`^${VERSION}`)}`,
21082112
'publication>=0.0.3',
@@ -2115,7 +2119,6 @@ class Package {
21152119
'Operating System :: OS Independent',
21162120
'Programming Language :: JavaScript',
21172121
'Programming Language :: Python :: 3 :: Only',
2118-
'Programming Language :: Python :: 3.7',
21192122
'Programming Language :: Python :: 3.8',
21202123
'Programming Language :: Python :: 3.9',
21212124
'Programming Language :: Python :: 3.10',
@@ -2236,7 +2239,7 @@ class Package {
22362239
code.line();
22372240
code.line('[tool.pyright]');
22382241
code.line('defineConstant = { DEBUG = true }');
2239-
code.line('pythonVersion = "3.7"');
2242+
code.line('pythonVersion = "3.8"');
22402243
code.line('pythonPlatform = "All"');
22412244
code.line('reportSelfClsParameterName = false');
22422245
code.closeFile('pyproject.toml');
@@ -3144,6 +3147,8 @@ function emitParameterTypeChecks(
31443147
const [name] = param.split(/\s*[:=#]\s*/, 1);
31453148
if (name === '*') {
31463149
return { kwargsMark: true };
3150+
} else if (name === '/') {
3151+
return { positionalOnlyEndMark: true };
31473152
} else if (name.startsWith('*')) {
31483153
return { name: name.slice(1), is_rest: true };
31493154
}
@@ -3156,7 +3161,12 @@ function emitParameterTypeChecks(
31563161
const typesVar = slugifyAsNeeded('type_hints', paramNames);
31573162

31583163
let openedBlock = false;
3159-
for (const { is_rest, kwargsMark, name } of paramInfo) {
3164+
for (const {
3165+
is_rest,
3166+
kwargsMark,
3167+
positionalOnlyEndMark,
3168+
name,
3169+
} of paramInfo) {
31603170
if (kwargsMark) {
31613171
if (!context.runtimeTypeCheckKwargs) {
31623172
// This is the keyword-args separator, we won't check keyword arguments here because the kwargs will be rolled
@@ -3166,6 +3176,10 @@ function emitParameterTypeChecks(
31663176
// Skip this (there is nothing to be checked as this is just a marker...)
31673177
continue;
31683178
}
3179+
if (positionalOnlyEndMark) {
3180+
// This is the end-of-positional-only-arguments separator, this is just a marker.
3181+
continue;
3182+
}
31693183

31703184
if (!openedBlock) {
31713185
code.openBlock('if __debug__');

packages/jsii-pacmak/test/generated-code/__snapshots__/examples.test.js.snap

+4-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/jsii-pacmak/test/generated-code/__snapshots__/prerelease-identifiers.test.js.snap

+8-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)