Skip to content

Commit

Permalink
(feat, python): write out example ids in generated snippets (#3750)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsinghvi authored Jun 1, 2024
1 parent ea4c4a3 commit 9e57409
Show file tree
Hide file tree
Showing 2,074 changed files with 910 additions and 7,629,276 deletions.
2 changes: 0 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
**/ruby/playground/** linguist-generated=true
seed/** linguist-generated=true
seed/**/seed.yml linguist-generated=false
seed/**/.inputs/ir.json linguist-generated=true
seed/**/.inputs/config.json linguist-generated=true
seed/**/.mock linguist-generated=true
1 change: 0 additions & 1 deletion .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions generators/python/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion generators/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ authors = []
python = "^3.8"
pydantic = "^1.9.2,<=1.10.14"
typer = {extras = ["all"], version = "^0.6.1"}
fern-fern-generator-exec-sdk = {version = "0.0.831", source = "fern-prod"}
fern-fern-generator-exec-sdk = {version = "0.0.846", source = "fern-prod"}
ordered-set = "^4.1.0"
fern-fern-fdr-sdk = {version = "0.0.5435", source = "fern-prod"}
fern-fern-ir-v39 = "^0.0.3473"
Expand Down
6 changes: 6 additions & 0 deletions generators/python/sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.7.0] - 2024-05-30

- Improvement: The generator now outputs an `exampleId` alongside each generated snippet so that
we can correlate snippets with the relevant examples. This is useful for retrieving examples from
Fern's API and making sure that you can show multiple snippets in the generated docs.

## [2.6.1] - 2024-05-31

- Fix: this adds a back door token getter function to OAuth clients to better test the functionality.
Expand Down
2 changes: 1 addition & 1 deletion generators/python/sdk/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.6.1
2.7.0
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,14 @@ def _create_class_declaration(self, *, is_async: bool) -> AST.ClassDeclaration:
):
self._is_default_body_parameter_used = True

if generated_endpoint_function.snippet is not None:
for snippet in generated_endpoint_function.snippets or []:
if is_async:
self._snippet_registry.register_async_client_endpoint_snippet(
endpoint=endpoint, expr=generated_endpoint_function.snippet
endpoint=endpoint, expr=snippet.snippet, example_id=snippet.example_id
)
else:
self._snippet_registry.register_sync_client_endpoint_snippet(
endpoint=endpoint, expr=generated_endpoint_function.snippet
endpoint=endpoint, expr=snippet.snippet, example_id=snippet.example_id
)

return class_declaration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,17 @@
)


@dataclass
class GeneratedEndpointFunctionSnippet:
example_id: str
snippet: AST.Expression


@dataclass
class GeneratedEndpointFunction:
function: AST.FunctionDeclaration
is_default_body_parameter_used: bool
snippet: Optional[AST.Expression]
snippets: List[GeneratedEndpointFunctionSnippet]


class EndpointFunctionGenerator:
Expand Down Expand Up @@ -137,7 +143,7 @@ def generate(self) -> GeneratedEndpointFunction:
else False
)

endpoint_snippet = self._generate_endpoint_snippet(
endpoint_snippets = self._generate_endpoint_snippets(
package=self._package,
service=self._service,
endpoint=self._endpoint,
Expand All @@ -153,7 +159,9 @@ def generate(self) -> GeneratedEndpointFunction:
endpoint=self._endpoint,
named_parameters=self._named_parameters,
path_parameters=self._endpoint.all_path_parameters,
snippet=endpoint_snippet,
snippet=endpoint_snippets[0].snippet
if endpoint_snippets is not None and len(endpoint_snippets) > 0
else None,
),
signature=AST.FunctionSignature(
parameters=unnamed_parameters,
Expand All @@ -174,7 +182,7 @@ def generate(self) -> GeneratedEndpointFunction:
return GeneratedEndpointFunction(
function=function_declaration,
is_default_body_parameter_used=self.request_body_parameters is not None,
snippet=endpoint_snippet,
snippets=endpoint_snippets or [],
)

def _get_endpoint_return_type(self) -> AST.TypeHint:
Expand Down Expand Up @@ -495,42 +503,70 @@ def _generate_endpoint_snippet_raw(self, example: ir_types.HttpEndpointExample)
request_parameter_names=self._request_parameter_name_rewrites,
).generate_snippet()

def _generate_endpoint_snippet(
def _generate_endpoint_snippets(
self,
package: ir_types.Package,
service: ir_types.HttpService,
endpoint: ir_types.HttpEndpoint,
generated_root_client: GeneratedRootClient,
snippet_writer: SnippetWriter,
is_async: bool,
) -> Optional[AST.Expression]:
) -> Optional[List[GeneratedEndpointFunctionSnippet]]:
if len(endpoint.examples) == 0:
return None

# Stick to user provided examples for snippets for now,
# only use autogenerated if no user-provided examples are available.
user_provided_examples = list(
filter(lambda ex: ex.get_as_union().example_type == "userProvided", endpoint.examples)
)
example = endpoint.examples[0]
if len(user_provided_examples) > 0:
example = user_provided_examples[0]

endpoint_snippet_generator = EndpointFunctionSnippetGenerator(
context=self._context,
snippet_writer=snippet_writer,
service=service,
endpoint=endpoint,
example=example,
path_parameter_names=self._path_parameter_names,
request_parameter_names=self._request_parameter_name_rewrites,
)
# only use autogenerated if no user-provided examples are available, and if you're doing this, just pick the first.
examples = list(filter(lambda ex: ex.get_as_union().example_type == "userProvided", endpoint.examples))
if len(examples) == 0:
examples = [endpoint.examples[0]]
snippets: List[GeneratedEndpointFunctionSnippet] = []
for example in examples:

endpoint_snippet_generator = EndpointFunctionSnippetGenerator(
context=self._context,
snippet_writer=snippet_writer,
service=service,
endpoint=endpoint,
example=example,
path_parameter_names=self._path_parameter_names,
request_parameter_names=self._request_parameter_name_rewrites,
)

endpoint_snippet = endpoint_snippet_generator.generate_snippet()
endpoint_snippet = endpoint_snippet_generator.generate_snippet()
response_name = "response"
endpoint_usage = endpoint_snippet_generator.generate_usage(is_async=is_async, response_name=response_name)

example_raw = example.get_as_union()
# HACK: IR should provide stable ids for example
example_id = example_raw.name.original_name if example_raw.name is not None else "default"
snippets.append(
GeneratedEndpointFunctionSnippet(
example_id=example_id,
snippet=AST.Expression(
self._get_snippet_writer(
is_async=is_async,
endpoint_snippet=endpoint_snippet,
response_name=response_name,
endpoint_usage=endpoint_usage,
generated_root_client=generated_root_client,
package=package,
)
),
)
)

response_name = "response"
endpoint_usage = endpoint_snippet_generator.generate_usage(is_async=is_async, response_name=response_name)
return snippets

def _get_snippet_writer(
self,
is_async: bool,
endpoint_usage: Optional[AST.Expression],
endpoint_snippet: AST.Expression,
response_name: str,
generated_root_client: GeneratedRootClient,
package: ir_types.Package,
) -> AST.CodeWriter:
def write(writer: AST.NodeWriter) -> None:
if is_async:
writer.write_node(generated_root_client.async_instantiation)
Expand All @@ -554,7 +590,7 @@ def write(writer: AST.NodeWriter) -> None:

writer.write_newline_if_last_line_not()

return AST.Expression(AST.CodeWriter(write))
return AST.CodeWriter(write)

def _get_subpackage_client_accessor(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,14 +257,14 @@ def _create_class_declaration(
):
self._is_default_body_parameter_used = True

if generated_endpoint_function.snippet is not None:
for val in generated_endpoint_function.snippets or []:
if is_async:
self._snippet_registry.register_async_client_endpoint_snippet(
endpoint=endpoint, expr=generated_endpoint_function.snippet
endpoint=endpoint, expr=val.snippet, example_id=val.example_id
)
else:
self._snippet_registry.register_sync_client_endpoint_snippet(
endpoint=endpoint, expr=generated_endpoint_function.snippet
endpoint=endpoint, expr=val.snippet, example_id=val.example_id
)

return class_declaration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@

@dataclass
class EndpointExpression:
example_id: str
endpoint_id: generator_exec.EndpointIdentifier
expr: AST.Expression
60 changes: 38 additions & 22 deletions generators/python/src/fern_python/snippet/snippet_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ class SnippetRegistry:
def __init__(self) -> None:
self._snippets: Dict[ir_types.TypeId, AST.Expression] = {}
self._endpoint_snippets: Dict[ir_types.EndpointId, AST.Expression] = {}
self._sync_client_endpoint_snippets: Dict[ir_types.EndpointId, EndpointExpression] = {}
self._async_client_endpoint_snippets: Dict[ir_types.EndpointId, EndpointExpression] = {}
self._sync_client_endpoint_snippets: Dict[ir_types.EndpointId, List[EndpointExpression]] = {}
self._async_client_endpoint_snippets: Dict[ir_types.EndpointId, List[EndpointExpression]] = {}

def snippets(self) -> Optional[generator_exec.Snippets]:
if (
Expand All @@ -29,22 +29,24 @@ def snippets(self) -> Optional[generator_exec.Snippets]:
types[generator_exec.TypeId(typeId.get_as_str())] = self._expression_to_snippet_str(expr)

endpoints: List[generator_exec.Endpoint] = []
for endpointId, sync_endpoint_expression in self._sync_client_endpoint_snippets.items():
endpoints.append(
generator_exec.Endpoint(
id=sync_endpoint_expression.endpoint_id,
snippet=generator_exec.EndpointSnippet.factory.python(
value=generator_exec.PythonEndpointSnippet(
sync_client=self._expression_to_snippet_str(sync_endpoint_expression.expr),
async_client=self._expression_to_snippet_str(
self._async_client_endpoint_snippets[endpointId].expr
for endpointId, sync_endpoint_expressions in self._sync_client_endpoint_snippets.items():
for idx, expression in enumerate(sync_endpoint_expressions):
endpoints.append(
generator_exec.Endpoint(
id=expression.endpoint_id,
example_identifier=expression.example_id,
snippet=generator_exec.EndpointSnippet.factory.python(
value=generator_exec.PythonEndpointSnippet(
sync_client=self._expression_to_snippet_str(expression.expr),
async_client=self._expression_to_snippet_str(
self._async_client_endpoint_snippets[endpointId][idx].expr
)
if endpointId in self._async_client_endpoint_snippets
else "",
)
if endpointId in self._async_client_endpoint_snippets
else "",
)
),
),
)
)
)

return generator_exec.Snippets(
types=types,
Expand All @@ -60,23 +62,37 @@ def register_snippet(

def register_async_client_endpoint_snippet(
self,
*,
endpoint: ir_types.HttpEndpoint,
expr: AST.Expression,
example_id: str,
) -> None:
self._async_client_endpoint_snippets[endpoint.id] = EndpointExpression(
endpoint_id=self._endpoint_to_identifier(endpoint),
expr=expr,
init = self._async_client_endpoint_snippets.get(endpoint.id) or []
init.append(
EndpointExpression(
endpoint_id=self._endpoint_to_identifier(endpoint),
expr=expr,
example_id=example_id,
)
)
self._async_client_endpoint_snippets[endpoint.id] = init

def register_sync_client_endpoint_snippet(
self,
*,
endpoint: ir_types.HttpEndpoint,
expr: AST.Expression,
example_id: str,
) -> None:
self._sync_client_endpoint_snippets[endpoint.id] = EndpointExpression(
endpoint_id=self._endpoint_to_identifier(endpoint),
expr=expr,
init = self._sync_client_endpoint_snippets.get(endpoint.id) or []
init.append(
EndpointExpression(
endpoint_id=self._endpoint_to_identifier(endpoint),
expr=expr,
example_id=example_id,
)
)
self._sync_client_endpoint_snippets[endpoint.id] = init

def _endpoint_to_identifier(
self,
Expand Down
1 change: 0 additions & 1 deletion packages/seed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
"@fern-api/task-context": "workspace:*",
"@fern-api/workspace-loader": "workspace:*",
"@fern-fern/fiddle-sdk": "^0.0.552",
"@fern-fern/generator-exec-sdk": "^0.0.837",
"@types/pretty-ms": "^5.0.1",
"chalk": "^5.0.1",
"console-table-printer": "^2.12.0",
Expand Down
Loading

0 comments on commit 9e57409

Please sign in to comment.