Skip to content

feat(python): upgrade to IR v66#14475

Open
Swimburger wants to merge 16 commits intomainfrom
devin/1775086696-python-ir-v66-upgrade
Open

feat(python): upgrade to IR v66#14475
Swimburger wants to merge 16 commits intomainfrom
devin/1775086696-python-ir-v66-upgrade

Conversation

@Swimburger
Copy link
Copy Markdown
Member

@Swimburger Swimburger commented Apr 1, 2026

Description

Implements full IR v66 support for the Python SDK, Pydantic, and FastAPI generators. IR v66 compresses the Name type into a NameOrString union (plain string or full Name object), reducing IR wire size and improving performance.

This PR adds CaseConverter and getWireValue/getOriginalName helpers throughout the Python generators so they can transparently handle both compressed (string) and full (Name/NameAndWireValue) forms.

Changes Made

Generator code updates

  • AbstractPythonGeneratorContext: Added CaseConverter instance; replaced direct .snakeCase.unsafeName, .pascalCase.safeName, .snakeCase.safeName accesses with caseConverter.snakeUnsafe(), caseConverter.pascalSafe(), caseConverter.snakeSafe()
  • DynamicSnippetsGeneratorContext: Same CaseConverter pattern for getClassName, getPropertyName, getMethodName, getEnvironmentEnumName, and fernFilepath.allParts mapping
  • DynamicTypeLiteralMapper: Replaced .wireValue direct accesses with getWireValue() for discriminant values, property names, and enum values
  • EndpointSnippetGenerator: Replaced .wireValue with getWireValue() for auth parameter and header lookups
  • FilePropertyMapper: Added getWireValue() for file property wire value fallbacks
  • ReadmeSnippetBuilder: CaseConverter for environment names, auth scheme names, service paths, endpoint names; null-safety guards for environment name access
  • buildReference.ts: CaseConverter for endpoint names, service paths, parameter names, type names, section titles; null-safety guards for environment names
  • WireTestGenerator: CaseConverter for service/endpoint names, getWireValue() for query params, headers, path params, and file upload keys; getOriginalName() for path parameter names
  • WireTestSetupGenerator: CaseConverter for base URL names, auth scheme names, global header names; getWireValue() for query parameter datetime detection
  • EnumGenerator: CaseConverter for enum member names (screamingSnakeSafe, snakeSafe); getWireValue() for enum wire values
  • ObjectGenerator: getWireValue() for property wire value comparison and alias detection
  • WrappedAliasGenerator: CaseConverter for named type visitor method names (snakeUnsafe)

Version & config updates

  • Added version entries in versions.yml for SDK (5.3.0-rc.0), Pydantic (1.12.0-rc.0), FastAPI (2.2.0-rc.0) with irVersion: 66
  • Updated migrateFromV66ToV65.ts to register all three generators
  • Updated seed.yml irVersion to v66 for python-sdk, pydantic, pydantic-v2, and fastapi

Test fix

  • test_ir_deserialization.py: Pinned --version v65 when generating IR via the CLI, because the published CLI now produces v66 IR with compressed names that the Python IR SDK (fern_fern_ir_v65 Pydantic models) cannot deserialize. This is a targeted fix — when a v66 Python IR SDK is published, this version pin should be updated to match.

Testing

  • All seed tests pass (python-sdk, pydantic, fastapi)
  • All required CI checks pass (306 pass, 0 fail)
  • Manual testing completed

Human Review Checklist

  • test_ir_deserialization.py version pin: The test now passes --version v65 to generate IR the Python SDK can parse. This is correct for now but creates a maintenance item — when fern_fern_ir_v66 is published, this pin and the pyproject.toml dependency should both be updated.
  • CaseConverter initialized with keywords: undefined in multiple files — confirm this is intentional for Python generators (the TypeScript reference impl also passes undefined for keywords in some cases)
  • Null-safety in ReadmeSnippetBuilder and buildReference: Environment name access now uses if (defaultEnv?.name != null) guard before calling caseConverter.screamingSnakeUnsafe() — this changes prior behavior where defaultEnv?.name.screamingSnakeCase.unsafeName would have thrown on a compressed string. Verify the guard logic is correct.
  • Multiple module-level caseConverter instances with identical config across files — not a bug, but could be centralized if desired

Link to Devin session: https://app.devin.ai/sessions/1947204928ba4bfe9b663dfe828ac3d7
Requested by: @Swimburger


Open with Devin

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

devin-ai-integration[bot]

This comment was marked as resolved.

Swimburger and others added 2 commits April 1, 2026 23:44
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…WireValue()

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@@ -1,3 +1,4 @@
import { getWireValue } from "@fern-api/base-generator";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import inconsistency detected. This file imports getWireValue from @fern-api/base-generator, but all other files in the dynamic-snippets module import from @fern-api/browser-compatible-base-generator (see EndpointSnippetGenerator.ts, DynamicSnippetsGeneratorContext.ts, and DynamicTypeLiteralMapper.ts). This will cause a runtime import error if getWireValue is not exported from @fern-api/base-generator or if the browser-compatible variant is required.

Fix:

import { getWireValue } from "@fern-api/browser-compatible-base-generator";
Suggested change
import { getWireValue } from "@fern-api/base-generator";
import { getWireValue } from "@fern-api/browser-compatible-base-generator";

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

…onflict

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 11 additional findings in Devin Review.

Open in Devin Review

@@ -1,3 +1,4 @@
import { getWireValue } from "@fern-api/base-generator";
import { assertNever } from "@fern-api/core-utils";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 FilePropertyMapper imports getWireValue from non-browser-compatible package

In FilePropertyMapper.ts, getWireValue is imported from @fern-api/base-generator, while all other files in the same dynamic-snippets package (DynamicTypeLiteralMapper.ts, EndpointSnippetGenerator.ts, DynamicSnippetsGeneratorContext.ts) import from @fern-api/browser-compatible-base-generator. The dynamic-snippets package is designed to be browser-compatible, and importing from @fern-api/base-generator could introduce Node.js-specific dependencies into the browser bundle, potentially breaking browser builds or bloating bundle size.

Suggested change
import { assertNever } from "@fern-api/core-utils";
import { getWireValue } from "@fern-api/browser-compatible-base-generator";
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — however @fern-api/browser-compatible-base-generator doesn't currently export getWireValue. The dynamic-snippets package.json lists @fern-api/base-generator as a dependency, so the import works. If browser compatibility is a concern, getWireValue would need to be re-exported from the browser-compatible package first.

Swimburger and others added 9 commits April 2, 2026 18:12
…e compressed names

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…cannot deserialize v66 compressed names

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…h Python IR SDK

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…on-ir-v66-upgrade

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…on-ir-v66-upgrade

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

SDK Generation Benchmark Results

Comparing PR branch against main baseline.

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
python-sdk square N/A N/A 168s N/A

main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via fern generate). main (E2E): full customer-observable time including build/test scripts (nightly baseline, informational). Delta is computed against generator-only baseline.
⚠️ = generation exited with a non-zero exit code (timing may not reflect a successful run).

…on-ir-v66-upgrade

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

SDK Generation Benchmark Results

Comparing PR branch against main baseline.

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
python-sdk square N/A N/A 167s N/A

main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via fern generate). main (E2E): full customer-observable time including build/test scripts (nightly baseline, informational). Delta is computed against generator-only baseline.
⚠️ = generation exited with a non-zero exit code (timing may not reflect a successful run).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant