replit-river 0.17.13__tar.gz → 0.17.15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. {replit_river-0.17.13 → replit_river-0.17.15}/PKG-INFO +1 -1
  2. {replit_river-0.17.13 → replit_river-0.17.15}/pyproject.toml +1 -1
  3. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/codegen/client.py +59 -25
  4. replit_river-0.17.15/tests/v1/codegen/snapshot/snapshots/test_anyof_mixed_types/__init__.py +13 -0
  5. replit_river-0.17.15/tests/v1/codegen/snapshot/snapshots/test_anyof_mixed_types/test_service/__init__.py +44 -0
  6. replit_river-0.17.15/tests/v1/codegen/snapshot/snapshots/test_anyof_mixed_types/test_service/anyof_mixed_method.py +118 -0
  7. replit_river-0.17.15/tests/v1/codegen/snapshot/test_anyof_mixed.py +20 -0
  8. replit_river-0.17.15/tests/v1/codegen/types/anyof_mixed_schema.json +85 -0
  9. {replit_river-0.17.13 → replit_river-0.17.15}/.envrc +0 -0
  10. {replit_river-0.17.13 → replit_river-0.17.15}/.github/CODEOWNERS +0 -0
  11. {replit_river-0.17.13 → replit_river-0.17.15}/.github/pull_request_template.md +0 -0
  12. {replit_river-0.17.13 → replit_river-0.17.15}/.github/release-drafter.yml +0 -0
  13. {replit_river-0.17.13 → replit_river-0.17.15}/.github/workflows/ci.yml +0 -0
  14. {replit_river-0.17.13 → replit_river-0.17.15}/.github/workflows/python-publish.yml +0 -0
  15. {replit_river-0.17.13 → replit_river-0.17.15}/.github/workflows/release-drafter.yml +0 -0
  16. {replit_river-0.17.13 → replit_river-0.17.15}/.gitignore +0 -0
  17. {replit_river-0.17.13 → replit_river-0.17.15}/.python-version +0 -0
  18. {replit_river-0.17.13 → replit_river-0.17.15}/.replit +0 -0
  19. {replit_river-0.17.13 → replit_river-0.17.15}/LICENSE +0 -0
  20. {replit_river-0.17.13 → replit_river-0.17.15}/Makefile +0 -0
  21. {replit_river-0.17.13 → replit_river-0.17.15}/README.md +0 -0
  22. {replit_river-0.17.13 → replit_river-0.17.15}/flake.lock +0 -0
  23. {replit_river-0.17.13 → replit_river-0.17.15}/flake.nix +0 -0
  24. {replit_river-0.17.13 → replit_river-0.17.15}/mypy.ini +0 -0
  25. {replit_river-0.17.13 → replit_river-0.17.15}/py.typed +0 -0
  26. {replit_river-0.17.13 → replit_river-0.17.15}/pyrightconfig.json +0 -0
  27. {replit_river-0.17.13 → replit_river-0.17.15}/replit.nix +0 -0
  28. {replit_river-0.17.13 → replit_river-0.17.15}/scripts/lint/README.md +0 -0
  29. {replit_river-0.17.13 → replit_river-0.17.15}/scripts/lint/pyproject.toml +0 -0
  30. {replit_river-0.17.13 → replit_river-0.17.15}/scripts/lint/src/lint/__init__.py +0 -0
  31. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/__init__.py +0 -0
  32. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/client.py +0 -0
  33. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/client_session.py +0 -0
  34. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/client_transport.py +0 -0
  35. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/codegen/__init__.py +0 -0
  36. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/codegen/__main__.py +0 -0
  37. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/codegen/format.py +0 -0
  38. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/codegen/run.py +0 -0
  39. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/codegen/schema.py +0 -0
  40. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/codegen/server.py +0 -0
  41. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/codegen/typing.py +0 -0
  42. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/common_session.py +0 -0
  43. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/error_schema.py +0 -0
  44. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/message_buffer.py +0 -0
  45. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/messages.py +0 -0
  46. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/py.typed +0 -0
  47. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/rate_limiter.py +0 -0
  48. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/rpc.py +0 -0
  49. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/seq_manager.py +0 -0
  50. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/server.py +0 -0
  51. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/server_session.py +0 -0
  52. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/server_transport.py +0 -0
  53. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/session.py +0 -0
  54. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/task_manager.py +0 -0
  55. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/transport_options.py +0 -0
  56. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/v2/__init__.py +0 -0
  57. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/v2/client.py +0 -0
  58. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/v2/client_transport.py +0 -0
  59. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/v2/session.py +0 -0
  60. {replit_river-0.17.13 → replit_river-0.17.15}/src/replit_river/websocket_wrapper.py +0 -0
  61. {replit_river-0.17.13 → replit_river-0.17.15}/tests/__init__.py +0 -0
  62. {replit_river-0.17.13 → replit_river-0.17.15}/tests/conftest.py +0 -0
  63. {replit_river-0.17.13 → replit_river-0.17.15}/tests/fixtures/codegen_snapshot_fixtures.py +0 -0
  64. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/rpc/generated/__init__.py +0 -0
  65. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/rpc/generated/test_service/__init__.py +0 -0
  66. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/rpc/generated/test_service/rpc_method.py +0 -0
  67. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/rpc/generated_special_chars/__init__.py +0 -0
  68. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/rpc/generated_special_chars/test_service/__init__.py +0 -0
  69. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/rpc/generated_special_chars/test_service/rpc_method.py +0 -0
  70. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/rpc/input-collision-schema.json +0 -0
  71. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/rpc/input-special-chars-schema.json +0 -0
  72. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/rpc/invalid-schema.json +0 -0
  73. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/rpc/schema.json +0 -0
  74. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/parity/__init__.py +0 -0
  75. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/parity/check_parity.py +0 -0
  76. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/parity/gen.py +0 -0
  77. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/parity/schema.json +0 -0
  78. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/pydantic_inputs/__init__.py +0 -0
  79. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/pydantic_inputs/agentToolLanguageServer/__init__.py +0 -0
  80. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/pydantic_inputs/agentToolLanguageServer/openDocument.py +0 -0
  81. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/pydantic_inputs/aiExec/__init__.py +0 -0
  82. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/pydantic_inputs/aiExec/exec.py +0 -0
  83. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/pydantic_inputs/conmanFilesystem/__init__.py +0 -0
  84. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/pydantic_inputs/conmanFilesystem/persist.py +0 -0
  85. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/pydantic_inputs/replspaceApi/__init__.py +0 -0
  86. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/pydantic_inputs/replspaceApi/init.py +0 -0
  87. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/pydantic_inputs/shellExec/__init__.py +0 -0
  88. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/pydantic_inputs/shellExec/spawn.py +0 -0
  89. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/typeddict_inputs/__init__.py +0 -0
  90. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/typeddict_inputs/agentToolLanguageServer/__init__.py +0 -0
  91. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/typeddict_inputs/agentToolLanguageServer/openDocument.py +0 -0
  92. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/typeddict_inputs/aiExec/__init__.py +0 -0
  93. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/typeddict_inputs/aiExec/exec.py +0 -0
  94. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/typeddict_inputs/conmanFilesystem/__init__.py +0 -0
  95. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/typeddict_inputs/conmanFilesystem/persist.py +0 -0
  96. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/typeddict_inputs/replspaceApi/__init__.py +0 -0
  97. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/typeddict_inputs/replspaceApi/init.py +0 -0
  98. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/typeddict_inputs/shellExec/__init__.py +0 -0
  99. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/parity/typeddict_inputs/shellExec/spawn.py +0 -0
  100. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_basic_stream/__init__.py +0 -0
  101. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_basic_stream/test_service/__init__.py +0 -0
  102. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_basic_stream/test_service/emit_error.py +0 -0
  103. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_basic_stream/test_service/stream_method.py +0 -0
  104. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_pathological_types/__init__.py +0 -0
  105. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_pathological_types/test_service/__init__.py +0 -0
  106. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_pathological_types/test_service/pathological_method.py +0 -0
  107. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_recursive_types/__init__.py +0 -0
  108. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_recursive_types/recursiveService/__init__.py +0 -0
  109. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_recursive_types/recursiveService/getTree.py +0 -0
  110. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_unknown_enum/__init__.py +0 -0
  111. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_unknown_enum/enumService/__init__.py +0 -0
  112. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_unknown_enum/enumService/needsEnum.py +0 -0
  113. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/snapshots/test_unknown_enum/enumService/needsEnumObject.py +0 -0
  114. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/test_enum.py +0 -0
  115. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/test_pathological.py +0 -0
  116. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/snapshot/test_recursive.py +0 -0
  117. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/stream/schema.json +0 -0
  118. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/stream/test_stream.py +0 -0
  119. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/test_input_special_chars.py +0 -0
  120. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/test_invalid_schema.py +0 -0
  121. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/test_rpc.py +0 -0
  122. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/types/recursive_schema.json +0 -0
  123. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/codegen/types/schema.json +0 -0
  124. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/common_handlers.py +0 -0
  125. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/river_fixtures/clientserver.py +0 -0
  126. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/river_fixtures/logging.py +0 -0
  127. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/test_communication.py +0 -0
  128. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/test_handshake.py +0 -0
  129. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/test_message_buffer.py +0 -0
  130. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/test_opentelemetry.py +0 -0
  131. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/test_rate_limiter.py +0 -0
  132. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/test_seq_manager.py +0 -0
  133. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v1/test_timeout.py +0 -0
  134. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/codegen/snapshot/snapshots/test_basic_rpc/__init__.py +0 -0
  135. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/codegen/snapshot/snapshots/test_basic_rpc/test_service/__init__.py +0 -0
  136. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/codegen/snapshot/snapshots/test_basic_rpc/test_service/rpc_method.py +0 -0
  137. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/codegen/snapshot/snapshots/test_basic_stream/__init__.py +0 -0
  138. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/codegen/snapshot/snapshots/test_basic_stream/test_service/__init__.py +0 -0
  139. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/codegen/snapshot/snapshots/test_basic_stream/test_service/emit_error.py +0 -0
  140. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/codegen/snapshot/snapshots/test_basic_stream/test_service/stream_method.py +0 -0
  141. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/datagrams.py +0 -0
  142. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/fixtures/bound_client.py +0 -0
  143. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/fixtures/raw_ws_server.py +0 -0
  144. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/interpreter.py +0 -0
  145. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/test_rpc.schema.json +0 -0
  146. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/test_stream.schema.json +0 -0
  147. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/test_v2_cancellation.py +0 -0
  148. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/test_v2_rpc.py +0 -0
  149. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/test_v2_session_lifecycle.py +0 -0
  150. {replit_river-0.17.13 → replit_river-0.17.15}/tests/v2/test_v2_stream.py +0 -0
  151. {replit_river-0.17.13 → replit_river-0.17.15}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: replit-river
3
- Version: 0.17.13
3
+ Version: 0.17.15
4
4
  Summary: Replit river toolkit for Python
5
5
  Author-email: Replit <eng@replit.com>
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name="replit-river"
3
- version="0.17.13"
3
+ version="0.17.15"
4
4
  description="Replit river toolkit for Python"
5
5
  authors = [{ name = "Replit", email = "eng@replit.com" }]
6
6
  license = { file = "LICENSE" }
@@ -389,7 +389,8 @@ def encode_type(
389
389
  type = original_type
390
390
  any_of: list[TypeExpression] = []
391
391
 
392
- typeddict_encoder = []
392
+ # Collect (type_check, encoder_expr) pairs for building ternary chain
393
+ encoder_parts: list[tuple[str | None, str]] = []
393
394
  for i, t in enumerate(type.anyOf):
394
395
  type_name, _, contents, _ = encode_type(
395
396
  t,
@@ -403,34 +404,65 @@ def encode_type(
403
404
  chunks.extend(contents)
404
405
  if isinstance(t, RiverConcreteType):
405
406
  if t.type == "string":
406
- typeddict_encoder.extend(["x", " if isinstance(x, str) else "])
407
- else:
408
- # TODO(dstewart): This structure changed since we were incorrectly
409
- # leaking ListTypeExprs into codegen. This generated
410
- # code is probably wrong.
407
+ encoder_parts.append(("isinstance(x, str)", "x"))
408
+ elif t.type == "array":
411
409
  match type_name:
412
- case ListTypeExpr(inner_type_name):
413
- typeddict_encoder.append(
414
- f"encode_{render_literal_type(inner_type_name)}(x)"
410
+ case ListTypeExpr(inner_type_name) if isinstance(
411
+ inner_type_name, TypeName
412
+ ):
413
+ # Primitives don't need encoding
414
+ inner_type_str = render_literal_type(inner_type_name)
415
+ if inner_type_str in ("str", "int", "float", "bool", "Any"):
416
+ encoder_parts.append(("isinstance(x, list)", "list(x)"))
417
+ else:
418
+ encoder_parts.append(
419
+ (
420
+ "isinstance(x, list)",
421
+ f"[encode_{inner_type_str}(y) for y in x]",
422
+ )
423
+ )
424
+ case _:
425
+ encoder_parts.append(("isinstance(x, list)", "list(x)"))
426
+ elif t.type == "object":
427
+ match type_name:
428
+ case TypeName(value):
429
+ encoder_parts.append(
430
+ ("isinstance(x, dict)", f"encode_{value}(x)")
415
431
  )
432
+ case _:
433
+ encoder_parts.append(("isinstance(x, dict)", "dict(x)"))
434
+ elif t.type in ("number", "integer"):
435
+ match type_name:
416
436
  case LiteralTypeExpr(const):
417
- typeddict_encoder.append(repr(const))
437
+ encoder_parts.append((f"x == {repr(const)}", repr(const)))
438
+ case _:
439
+ encoder_parts.append(("isinstance(x, (int, float))", "x"))
440
+ elif t.type == "boolean":
441
+ encoder_parts.append(("isinstance(x, bool)", "x"))
442
+ elif t.type == "null" or t.type == "undefined":
443
+ encoder_parts.append(("x is None", "None"))
444
+ else:
445
+ # Fallback for other types
446
+ match type_name:
418
447
  case TypeName(value):
419
- typeddict_encoder.append(f"encode_{value}(x)")
448
+ encoder_parts.append((None, f"encode_{value}(x)"))
449
+ case LiteralTypeExpr(const):
450
+ encoder_parts.append((None, repr(const)))
420
451
  case NoneTypeExpr():
421
- typeddict_encoder.append("None")
422
- case other:
423
- _o2: (
424
- DictTypeExpr
425
- | OpenUnionTypeExpr
426
- | UnionTypeExpr
427
- | LiteralType
428
- ) = other
429
- raise ValueError(
430
- f"What does it mean to have {
431
- render_type_expr(_o2)
432
- } here?"
433
- )
452
+ encoder_parts.append((None, "None"))
453
+ case _:
454
+ encoder_parts.append((None, "x"))
455
+
456
+ # Build the ternary chain from encoder_parts
457
+ typeddict_encoder = list[str]()
458
+ for i, (type_check, encoder_expr) in enumerate(encoder_parts):
459
+ is_last = i == len(encoder_parts) - 1
460
+ if is_last or type_check is None:
461
+ # Last item or no type check - just the expression
462
+ typeddict_encoder.append(encoder_expr)
463
+ else:
464
+ # Add expression with type check
465
+ typeddict_encoder.append(f"{encoder_expr} if {type_check} else")
434
466
  if permit_unknown_members:
435
467
  union = _make_open_union_type_expr(any_of)
436
468
  else:
@@ -603,7 +635,9 @@ def encode_type(
603
635
  f"encode_{render_literal_type(type_name)}"
604
636
  )
605
637
  encoder_names.add(encoder_name)
606
- typeddict_encoder.append(f"{encoder_name}(x[{repr(name)}])")
638
+ typeddict_encoder.append(
639
+ f"{render_literal_type(encoder_name)}(x[{repr(name)}])"
640
+ )
607
641
  elif isinstance(prop, RiverConcreteType):
608
642
  if name == "$kind":
609
643
  safe_name = "kind"
@@ -0,0 +1,13 @@
1
+ # Code generated by river.codegen. DO NOT EDIT.
2
+ from pydantic import BaseModel
3
+ from typing import Literal
4
+
5
+ import replit_river as river
6
+
7
+
8
+ from .test_service import Test_ServiceService
9
+
10
+
11
+ class AnyOfMixedClient:
12
+ def __init__(self, client: river.Client[Literal[None]]):
13
+ self.test_service = Test_ServiceService(client)
@@ -0,0 +1,44 @@
1
+ # Code generated by river.codegen. DO NOT EDIT.
2
+ from collections.abc import AsyncIterable, AsyncIterator
3
+ from typing import Any
4
+ import datetime
5
+
6
+ from pydantic import TypeAdapter
7
+
8
+ from replit_river.error_schema import RiverError, RiverErrorTypeAdapter
9
+ import replit_river as river
10
+
11
+
12
+ from .anyof_mixed_method import (
13
+ Anyof_Mixed_MethodInput,
14
+ Anyof_Mixed_MethodOutput,
15
+ Anyof_Mixed_MethodOutputTypeAdapter,
16
+ encode_Anyof_Mixed_MethodInput,
17
+ encode_Anyof_Mixed_MethodInputNumber_Or_String,
18
+ encode_Anyof_Mixed_MethodInputRun_Command,
19
+ encode_Anyof_Mixed_MethodInputValue_Or_Null,
20
+ )
21
+
22
+
23
+ class Test_ServiceService:
24
+ def __init__(self, client: river.Client[Any]):
25
+ self.client = client
26
+
27
+ async def anyof_mixed_method(
28
+ self,
29
+ input: Anyof_Mixed_MethodInput,
30
+ timeout: datetime.timedelta,
31
+ ) -> Anyof_Mixed_MethodOutput:
32
+ return await self.client.send_rpc(
33
+ "test_service",
34
+ "anyof_mixed_method",
35
+ input,
36
+ encode_Anyof_Mixed_MethodInput,
37
+ lambda x: Anyof_Mixed_MethodOutputTypeAdapter.validate_python(
38
+ x # type: ignore[arg-type]
39
+ ),
40
+ lambda x: RiverErrorTypeAdapter.validate_python(
41
+ x # type: ignore[arg-type]
42
+ ),
43
+ timeout,
44
+ )
@@ -0,0 +1,118 @@
1
+ # Code generated by river.codegen. DO NOT EDIT.
2
+ from collections.abc import AsyncIterable, AsyncIterator
3
+ import datetime
4
+ from typing import (
5
+ Any,
6
+ Literal,
7
+ Mapping,
8
+ NotRequired,
9
+ TypedDict,
10
+ )
11
+ from typing_extensions import Annotated
12
+
13
+ from pydantic import BaseModel, Field, TypeAdapter, WrapValidator
14
+ from replit_river.error_schema import RiverError
15
+ from replit_river.client import (
16
+ RiverUnknownError,
17
+ translate_unknown_error,
18
+ RiverUnknownValue,
19
+ translate_unknown_value,
20
+ )
21
+
22
+ import replit_river as river
23
+
24
+
25
+ Anyof_Mixed_MethodInputNumber_Or_String = float | str
26
+
27
+
28
+ def encode_Anyof_Mixed_MethodInputNumber_Or_String(
29
+ x: "Anyof_Mixed_MethodInputNumber_Or_String",
30
+ ) -> Any:
31
+ return x
32
+
33
+
34
+ def encode_Anyof_Mixed_MethodInputRun_CommandAnyOf_0(
35
+ x: "Anyof_Mixed_MethodInputRun_CommandAnyOf_0",
36
+ ) -> Any:
37
+ return {
38
+ k: v
39
+ for (k, v) in (
40
+ {
41
+ "args": x.get("args"),
42
+ "env": x.get("env"),
43
+ }
44
+ ).items()
45
+ if v is not None
46
+ }
47
+
48
+
49
+ class Anyof_Mixed_MethodInputRun_CommandAnyOf_0(TypedDict):
50
+ args: list[str]
51
+ env: NotRequired[dict[str, str] | None]
52
+
53
+
54
+ Anyof_Mixed_MethodInputRun_Command = (
55
+ Anyof_Mixed_MethodInputRun_CommandAnyOf_0 | str | list[str]
56
+ )
57
+
58
+
59
+ def encode_Anyof_Mixed_MethodInputRun_Command(
60
+ x: "Anyof_Mixed_MethodInputRun_Command",
61
+ ) -> Any:
62
+ return (
63
+ encode_Anyof_Mixed_MethodInputRun_CommandAnyOf_0(x)
64
+ if isinstance(x, dict)
65
+ else x
66
+ if isinstance(x, str)
67
+ else list(x)
68
+ )
69
+
70
+
71
+ Anyof_Mixed_MethodInputValue_Or_Null = str | None
72
+
73
+
74
+ def encode_Anyof_Mixed_MethodInputValue_Or_Null(
75
+ x: "Anyof_Mixed_MethodInputValue_Or_Null",
76
+ ) -> Any:
77
+ return x
78
+
79
+
80
+ def encode_Anyof_Mixed_MethodInput(
81
+ x: "Anyof_Mixed_MethodInput",
82
+ ) -> Any:
83
+ return {
84
+ k: v
85
+ for (k, v) in (
86
+ {
87
+ "number_or_string": encode_Anyof_Mixed_MethodInputNumber_Or_String(
88
+ x["number_or_string"]
89
+ )
90
+ if "number_or_string" in x and x["number_or_string"]
91
+ else None,
92
+ "run_command": encode_Anyof_Mixed_MethodInputRun_Command(
93
+ x["run_command"]
94
+ ),
95
+ "value_or_null": encode_Anyof_Mixed_MethodInputValue_Or_Null(
96
+ x["value_or_null"]
97
+ )
98
+ if "value_or_null" in x and x["value_or_null"]
99
+ else None,
100
+ }
101
+ ).items()
102
+ if v is not None
103
+ }
104
+
105
+
106
+ class Anyof_Mixed_MethodInput(TypedDict):
107
+ number_or_string: NotRequired[Anyof_Mixed_MethodInputNumber_Or_String | None]
108
+ run_command: Anyof_Mixed_MethodInputRun_Command
109
+ value_or_null: NotRequired[Anyof_Mixed_MethodInputValue_Or_Null | None]
110
+
111
+
112
+ class Anyof_Mixed_MethodOutput(BaseModel):
113
+ success: bool
114
+
115
+
116
+ Anyof_Mixed_MethodOutputTypeAdapter: TypeAdapter[Anyof_Mixed_MethodOutput] = (
117
+ TypeAdapter(Anyof_Mixed_MethodOutput)
118
+ )
@@ -0,0 +1,20 @@
1
+ from pytest_snapshot.plugin import Snapshot
2
+
3
+ from tests.fixtures.codegen_snapshot_fixtures import validate_codegen
4
+
5
+
6
+ async def test_anyof_mixed_types(snapshot: Snapshot) -> None:
7
+ """Test codegen for anyOf unions with mixed types (object, string, array).
8
+
9
+ This tests the fix for the bug where non-discriminated anyOf unions
10
+ with mixed types like [object, string, array] would generate malformed
11
+ Python code with broken ternary expressions.
12
+ """
13
+ validate_codegen(
14
+ snapshot=snapshot,
15
+ snapshot_dir="tests/v1/codegen/snapshot/snapshots",
16
+ read_schema=lambda: open("tests/v1/codegen/types/anyof_mixed_schema.json"),
17
+ target_path="test_anyof_mixed_types",
18
+ client_name="AnyOfMixedClient",
19
+ protocol_version="v1.1",
20
+ )
@@ -0,0 +1,85 @@
1
+ {
2
+ "services": {
3
+ "test_service": {
4
+ "procedures": {
5
+ "anyof_mixed_method": {
6
+ "input": {
7
+ "type": "object",
8
+ "properties": {
9
+ "run_command": {
10
+ "description": "Command can be object with args, string, or array of strings",
11
+ "anyOf": [
12
+ {
13
+ "type": "object",
14
+ "properties": {
15
+ "args": {
16
+ "type": "array",
17
+ "items": {
18
+ "type": "string"
19
+ }
20
+ },
21
+ "env": {
22
+ "type": "object",
23
+ "patternProperties": {
24
+ "^(.*)$": {
25
+ "type": "string"
26
+ }
27
+ }
28
+ }
29
+ },
30
+ "required": ["args"]
31
+ },
32
+ {
33
+ "type": "string"
34
+ },
35
+ {
36
+ "type": "array",
37
+ "items": {
38
+ "type": "string"
39
+ }
40
+ }
41
+ ]
42
+ },
43
+ "value_or_null": {
44
+ "description": "Value can be string or null",
45
+ "anyOf": [
46
+ {
47
+ "type": "string"
48
+ },
49
+ {
50
+ "type": "null"
51
+ }
52
+ ]
53
+ },
54
+ "number_or_string": {
55
+ "description": "Can be number or string",
56
+ "anyOf": [
57
+ {
58
+ "type": "number"
59
+ },
60
+ {
61
+ "type": "string"
62
+ }
63
+ ]
64
+ }
65
+ },
66
+ "required": ["run_command"]
67
+ },
68
+ "output": {
69
+ "type": "object",
70
+ "properties": {
71
+ "success": {
72
+ "type": "boolean"
73
+ }
74
+ },
75
+ "required": ["success"]
76
+ },
77
+ "errors": {
78
+ "not": {}
79
+ },
80
+ "type": "rpc"
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes