jsonrpcserver 5.0.10__tar.gz → 5.1.0__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 (110) hide show
  1. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/CHANGELOG.md +16 -0
  2. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/PKG-INFO +3 -3
  3. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/README.md +2 -2
  4. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/RELEASING.md +6 -4
  5. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/errors.md +11 -8
  6. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/methods.md +12 -5
  7. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/migration.md +10 -6
  8. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/roadmap.md +2 -1
  9. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/__init__.py +1 -1
  10. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/async_dispatcher.py +4 -4
  11. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/dispatcher.py +31 -13
  12. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/methods.py +2 -1
  13. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/requirements/examples.txt +3 -3
  14. jsonrpcserver-5.1.0/requirements/lint.txt +7 -0
  15. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_async_dispatcher.py +14 -6
  16. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_dispatcher.py +42 -13
  17. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_robustness.py +5 -3
  18. jsonrpcserver-5.0.10/requirements/lint.txt +0 -7
  19. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/CODE_OF_CONDUCT.md +0 -0
  20. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/CONTRIBUTING.md +0 -0
  21. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/LICENSE +0 -0
  22. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/SECURITY.md +0 -0
  23. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/assets/a11y.js +0 -0
  24. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/assets/apple-touch-icon.png +0 -0
  25. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/assets/copy-without-prompts.js +0 -0
  26. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/assets/extra.css +0 -0
  27. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/assets/favicon.png +0 -0
  28. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/assets/logo.png +0 -0
  29. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/assets/social-card.png +0 -0
  30. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/async.md +0 -0
  31. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/batches.md +0 -0
  32. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/changelog.md +0 -0
  33. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/context.md +0 -0
  34. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/contributing.md +0 -0
  35. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/dispatch.md +0 -0
  36. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/aiohttp_server.py +0 -0
  37. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/check_examples.py +0 -0
  38. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/django_server.py +0 -0
  39. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/fastapi_server.py +0 -0
  40. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/flask_server.py +0 -0
  41. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/http_server.py +0 -0
  42. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/quickstart.py +0 -0
  43. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/sanic_server.py +0 -0
  44. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/socketio_server.py +0 -0
  45. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/tornado_server.py +0 -0
  46. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/websockets_server.py +0 -0
  47. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/werkzeug_server.py +0 -0
  48. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/zeromq_async_server.py +0 -0
  49. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples/zeromq_server.py +0 -0
  50. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/examples.md +0 -0
  51. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/faq.md +0 -0
  52. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/frameworks/aiohttp.md +0 -0
  53. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/frameworks/django.md +0 -0
  54. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/frameworks/fastapi.md +0 -0
  55. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/frameworks/flask.md +0 -0
  56. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/frameworks/http-server.md +0 -0
  57. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/frameworks/sanic.md +0 -0
  58. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/frameworks/socketio.md +0 -0
  59. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/frameworks/tornado.md +0 -0
  60. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/frameworks/websockets.md +0 -0
  61. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/frameworks/werkzeug.md +0 -0
  62. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/frameworks/zeromq.md +0 -0
  63. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/index.md +0 -0
  64. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/license.md +0 -0
  65. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/reference.md +0 -0
  66. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/security-policy.md +0 -0
  67. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/security.md +0 -0
  68. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/testing.md +0 -0
  69. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/threads.md +0 -0
  70. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/typing.md +0 -0
  71. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/docs/validation.md +0 -0
  72. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/async_main.py +0 -0
  73. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/codes.py +0 -0
  74. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/exceptions.py +0 -0
  75. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/main.py +0 -0
  76. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/py.typed +0 -0
  77. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/request-schema.json +0 -0
  78. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/request.py +0 -0
  79. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/response.py +0 -0
  80. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/result.py +0 -0
  81. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/sentinels.py +0 -0
  82. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/server.py +0 -0
  83. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/jsonrpcserver/utils.py +0 -0
  84. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/mkdocs.yml +0 -0
  85. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/overrides/404.html +0 -0
  86. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/overrides/main.html +0 -0
  87. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/pyproject.toml +0 -0
  88. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/requirements/build.txt +0 -0
  89. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/requirements/docs-constraints.txt +0 -0
  90. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/requirements/docs.txt +0 -0
  91. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/requirements/site.txt +0 -0
  92. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/requirements/test.txt +0 -0
  93. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/__init__.py +0 -0
  94. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/doc_examples.py +0 -0
  95. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_async_main.py +0 -0
  96. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_batch_limit.py +0 -0
  97. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_docs.py +0 -0
  98. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_docstrings.py +0 -0
  99. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_main.py +0 -0
  100. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_methods.py +0 -0
  101. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_request.py +0 -0
  102. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_response.py +0 -0
  103. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_result.py +0 -0
  104. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_sentinels.py +0 -0
  105. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_server.py +0 -0
  106. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_threading.py +0 -0
  107. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_typing.py +0 -0
  108. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_version.py +0 -0
  109. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tests/test_warnings.py +0 -0
  110. {jsonrpcserver-5.0.10 → jsonrpcserver-5.1.0}/tox.ini +0 -0
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.0 (2026-11-06)
4
+
5
+ ### Added
6
+
7
+ - A method can return a plain value. `return "pong"` sends the same response as
8
+ `return Success("pong")`, as in 4.x (#285). Before, a plain value gave the
9
+ client an Internal error. `Success` and `Error` work as before.
10
+
11
+ ### Changed
12
+
13
+ - A method that returns an oslash `Left` or `Right` not made by `Success` or
14
+ `Error`, or a bare `SuccessResult` or `ErrorResult`, still gets an Internal
15
+ error. Its log line and `InvalidResultError` message now say "Return a value,
16
+ Success(value) or Error(code, message)", and the log line links the Methods
17
+ page.
18
+
3
19
  ## 5.0.10 (2026-11-06)
4
20
 
5
21
  The first release since the project moved to
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: jsonrpcserver
3
- Version: 5.0.10
3
+ Version: 5.1.0
4
4
  Summary: Process incoming JSON-RPC 2.0 requests in Python
5
5
  Keywords: json-rpc,jsonrpc,json-rpc-2.0,rpc,server
6
6
  Author-email: Beau Barker <beau@explodinglabs.com>
@@ -143,8 +143,8 @@ explains why.
143
143
  framework examples and the
144
144
  [API reference](https://bensynapse.github.io/jsonrpcserver/reference/).
145
145
  - [Migrating from 4.x](https://bensynapse.github.io/jsonrpcserver/migration/):
146
- methods must now return `Success(value)`. A 4.x method that returns a plain
147
- value still runs, but the client gets an Internal error.
146
+ methods return `Success(value)` or `Error(...)`. From 5.1.0 a method can also
147
+ return a plain value, as in 4.x.
148
148
  - [Contributing](https://github.com/bensynapse/jsonrpcserver/blob/main/CONTRIBUTING.md)
149
149
  and the
150
150
  [security policy](https://github.com/bensynapse/jsonrpcserver/blob/main/SECURITY.md).
@@ -101,8 +101,8 @@ explains why.
101
101
  framework examples and the
102
102
  [API reference](https://bensynapse.github.io/jsonrpcserver/reference/).
103
103
  - [Migrating from 4.x](https://bensynapse.github.io/jsonrpcserver/migration/):
104
- methods must now return `Success(value)`. A 4.x method that returns a plain
105
- value still runs, but the client gets an Internal error.
104
+ methods return `Success(value)` or `Error(...)`. From 5.1.0 a method can also
105
+ return a plain value, as in 4.x.
106
106
  - [Contributing](https://github.com/bensynapse/jsonrpcserver/blob/main/CONTRIBUTING.md)
107
107
  and the
108
108
  [security policy](https://github.com/bensynapse/jsonrpcserver/blob/main/SECURITY.md).
@@ -35,7 +35,8 @@ Settings → Environments → pypi.
35
35
  yet": `## 5.0.10 (2026-10-20)`.
36
36
  - The `unreleased` and `pypi_version` lines under `extra` in mkdocs.yml are
37
37
  deleted. They show the "not on PyPI yet" banner on every docs page.
38
- Merging this redeploys the docs, so merge it just before you tag.
38
+ The docs deploy when you push the tag, so merging this changes nothing
39
+ users see until then.
39
40
  - The paragraph under "Install" in README.md that says the version isn't
40
41
  released yet is deleted. The README becomes the PyPI page.
41
42
  - For 5.0.10 only: shorten the "If you are on 5.0.9" section of
@@ -89,7 +90,8 @@ whose version reached PyPI.
89
90
 
90
91
  ## Docs
91
92
 
92
- The docs site deploys itself from main through `.github/workflows/docs.yml`,
93
- so it changes when the docs change, not at release time. While main is ahead
94
- of PyPI, the `unreleased` setting in mkdocs.yml shows a banner that says so.
93
+ The docs site deploys from `.github/workflows/docs.yml` when a release tag is
94
+ pushed, so it always describes the version on PyPI. To publish a docs-only fix
95
+ between releases, run the Docs workflow by hand on main. If main is ahead of
96
+ PyPI by then, set `unreleased` in mkdocs.yml first, so a banner says so.
95
97
  The docs also mark features with "New in" or "Changed in" notes.
@@ -153,19 +153,22 @@ logging.basicConfig(
153
153
  To change only jsonrpcserver's messages, set the level or add a handler on
154
154
  the parent logger: `logging.getLogger("jsonrpcserver")`.
155
155
 
156
- This is what a method that returns a plain value, as it would in 4.x, looks
157
- like in the log:
156
+ A method can return a plain value, a `Success` or an `Error`. What it can't
157
+ return is a result object that `Success` or `Error` didn't make, such as a bare
158
+ `SuccessResult`. This is what that looks like in the log:
158
159
 
159
160
  ```python
160
161
  import sys
161
162
 
163
+ from jsonrpcserver.result import SuccessResult
164
+
162
165
  handler = logging.StreamHandler(sys.stdout)
163
166
  handler.setFormatter(logging.Formatter("%(name)s %(levelname)s: %(message)s"))
164
167
  logging.getLogger("jsonrpcserver").addHandler(handler)
165
168
 
166
169
 
167
- def ping() -> str:
168
- return "pong" # Should be Success("pong")
170
+ def ping() -> SuccessResult:
171
+ return SuccessResult("pong") # Should be Success("pong"), or just "pong"
169
172
 
170
173
 
171
174
  dispatch('{"jsonrpc": "2.0", "method": "ping", "id": 1}', {"ping": ping})
@@ -173,11 +176,11 @@ logging.getLogger("jsonrpcserver").removeHandler(handler)
173
176
  ```
174
177
 
175
178
  ```text title="Output"
176
- jsonrpcserver.dispatcher ERROR: Method 'ping' returned 'pong', which is not a Result, so the client got an Internal error. Return Success(value) or Error(code, message). Since 5.0 a plain return value is not enough: https://bensynapse.github.io/jsonrpcserver/migration/
179
+ jsonrpcserver.dispatcher ERROR: Method 'ping' returned SuccessResult('pong'), which is not a valid Result, so the client got an Internal error. Return a value, Success(value) or Error(code, message): https://bensynapse.github.io/jsonrpcserver/methods/
177
180
  ```
178
181
 
179
- !!! info "New in 5.0.10"
182
+ !!! info "New in 5.0.10, changed in 5.1.0"
180
183
  The `jsonrpcserver.main` messages and this one. Before 5.0.10, a
181
- serializer failure raised out of `dispatch`. A plain return value was
184
+ serializer failure raised out of `dispatch`. An invalid return value was
182
185
  logged with a traceback, and its explanation was also sent to the client
183
- in `data`.
186
+ in `data`. Before 5.1.0, a plain return value was invalid too.
@@ -47,11 +47,18 @@ A method returns `Success` or `Error`. They are the `result` and `error` parts
47
47
  of a [JSON-RPC response](https://www.jsonrpc.org/specification#response_object).
48
48
  jsonrpcserver adds the `jsonrpc` and `id` parts.
49
49
 
50
- !!! warning "Return `Success(value)`, not the value"
51
- In 4.x a method returned its result directly. In 5.x, `return "pong"`
52
- sends the client a -32603 "Internal error". From 5.0.10 the response
53
- has no details, and the log says what went wrong. See
54
- [Migration](migration.md).
50
+ A method can also return its value directly. `return "pong"` sends the same
51
+ response as `return Success("pong")`:
52
+
53
+ ```pycon
54
+ >>> dispatch('{"jsonrpc": "2.0", "method": "count", "id": 1}', {"count": lambda: 3})
55
+ '{"jsonrpc": "2.0", "result": 3, "id": 1}'
56
+ ```
57
+
58
+ !!! info "New in 5.1.0"
59
+ Plain return values. From 5.0.0 to 5.0.10, `return "pong"` sent the client
60
+ a -32603 "Internal error". If your code has to run on those versions too,
61
+ return `Success(value)`. See [Migration](migration.md).
55
62
 
56
63
  `Success` takes the result value. If there's nothing to return, call it with
57
64
  no argument and the result is `null`:
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Upgrade jsonrpcserver from 4.x to 5.x, and from 5.0.9 to 5.0.10. Side-by-side examples, removed options, and the plain return value that silently becomes an Internal error.
2
+ description: Upgrade jsonrpcserver from 4.x to 5.x, from 5.0.9 to 5.0.10, and to 5.1.0. Side-by-side examples, removed options, and plain return values, which work again from 5.1.0.
3
3
  ---
4
4
 
5
5
  # Migration
@@ -9,9 +9,10 @@ description: Upgrade jsonrpcserver from 4.x to 5.x, and from 5.0.9 to 5.0.10. Si
9
9
  Version 5 changed how methods report their result. Most other code needs only
10
10
  small changes.
11
11
 
12
- !!! danger "Old methods fail with no details"
13
- In 4.x a method returned its result directly. In 5.x it must return
14
- `Success(result)`. A 4.x method such as
12
+ !!! danger "Old methods fail with no details before 5.1.0"
13
+ In 4.x a method returned its result directly. In 5.0.x it must return
14
+ `Success(result)`. From 5.1.0 a plain return value works again, and
15
+ becomes `Success(value)`. On 5.0.x, a 4.x method such as
15
16
 
16
17
  <!-- skip: 4.x code -->
17
18
  ```python
@@ -28,6 +29,9 @@ small changes.
28
29
  Method 'ping' returned 'pong', which is not a Result, so the client got an Internal error. Return Success(value) or Error(code, message). ...
29
30
  ```
30
31
 
32
+ That's the 5.0.10 message. Upgrading to 5.1.0 fixes these methods
33
+ without a code change.
34
+
31
35
  If you don't see that line, configure logging (see
32
36
  [Errors and logging](errors.md#logging)). Then search your code for
33
37
  `return` statements in methods. mypy and pyright catch them too, once
@@ -85,7 +89,7 @@ print(response, 200 if response else 204)
85
89
 
86
90
  | 4.x | 5.x |
87
91
  |---|---|
88
- | `return value` | `return Success(value)` |
92
+ | `return value` | `return Success(value)`. From 5.1.0 `return value` works too |
89
93
  | `raise ApiError(message, code=1, data=...)` | `return Error(code, message, data)`, or `raise JsonRpcError(code, message, data)`. Note the order: code first. |
90
94
  | `raise InvalidParamsError(...)` | `return InvalidParams(data)` |
91
95
  | `raise MethodNotFoundError` | there's no equivalent. jsonrpcserver sends "Method not found" itself |
@@ -103,7 +107,7 @@ print(response, 200 if response else 204)
103
107
  Code written for 4.x that 5.x can't run gives clear errors in most cases. The
104
108
  removed keywords raise `TypeError`, and `response.wanted` raises
105
109
  `AttributeError: 'str' object has no attribute 'wanted'`. The plain return
106
- value above is the one that fails quietly, and `str(response)` still works but
110
+ value above is the one that fails quietly before 5.1.0, and `str(response)` still works but
107
111
  is no longer needed.
108
112
 
109
113
  The 4.x documentation is no longer online. The [changelog](changelog.md)
@@ -6,6 +6,8 @@ description: The plan for jsonrpcserver 6.0. Results without oslash, safer defau
6
6
 
7
7
  This is the plan for the next major version. Nothing here is released yet, and
8
8
  it may change. 5.x keeps its current API, and gets bug and security fixes.
9
+ 5.1.0 took one idea from this list early: a method can return a plain value
10
+ (#285).
9
11
 
10
12
  Discussion happens in the
11
13
  [issues](https://github.com/bensynapse/jsonrpcserver/issues) and
@@ -51,7 +53,6 @@ five request fields would be faster and lighter.
51
53
 
52
54
  **Ideas under discussion.**
53
55
 
54
- - Let a method return a plain value instead of `Success(value)` (#285).
55
56
  - Middleware around method calls (#126). Decorators on your own functions
56
57
  already cover most uses.
57
58
  - OpenRPC schema generation (#156), probably as a separate package.
@@ -38,5 +38,5 @@ from .methods import method
38
38
  from .result import Error, InvalidParams, Result, Success
39
39
  from .server import serve
40
40
 
41
- __version__ = "5.0.10"
41
+ __version__ = "5.1.0"
42
42
  """The version of jsonrpcserver, as a string. Added in 5.0.10."""
@@ -25,9 +25,9 @@ from .dispatcher import (
25
25
  member_id,
26
26
  not_notification,
27
27
  to_response,
28
+ to_result,
28
29
  validate_args,
29
30
  validate_request,
30
- validate_result,
31
31
  )
32
32
  from .exceptions import JsonRpcError
33
33
  from .methods import AnyMethod, MethodsArgument
@@ -51,7 +51,8 @@ async def call(
51
51
  # Plain (sync) methods work too. Their result is used as it is.
52
52
  if isawaitable(result):
53
53
  result = await result
54
- validate_result(result)
54
+ # A plain value becomes Success(value) (5.1.0).
55
+ result = to_result(result)
55
56
  except JsonRpcError as exc:
56
57
  return Left(ErrorResult(code=exc.code, message=exc.message, data=exc.data))
57
58
  except InvalidResultError as exc:
@@ -61,8 +62,7 @@ async def call(
61
62
  # Other error inside method - Internal error
62
63
  logger.exception("Method %r raised an exception", request.method)
63
64
  return Left(InternalErrorResult(exception_data(exc, debug)))
64
- # validate_result has checked it.
65
- return cast(Result, result)
65
+ return result
66
66
 
67
67
 
68
68
  async def dispatch_request(
@@ -32,6 +32,7 @@ from .result import (
32
32
  InvalidParamsResult,
33
33
  MethodNotFoundResult,
34
34
  Result,
35
+ Success,
35
36
  SuccessResult,
36
37
  )
37
38
  from .sentinels import NOCONTEXT, NODATA, NOID, Sentinel
@@ -114,12 +115,13 @@ def extract_kwargs(request: Request) -> Dict[str, Any]:
114
115
 
115
116
 
116
117
  class InvalidResultError(AssertionError):
117
- """A method returned something other than Success(...) or Error(...)."""
118
+ """A method returned an oslash `Left` or `Right`, or a result object, that isn't a
119
+ Result made by Success(...) or Error(...)."""
118
120
 
119
121
  def __init__(self, result: object):
120
122
  super().__init__(
121
123
  f"The method did not return a valid Result (returned {result!r}). "
122
- "Return Success(value) or Error(code, message)."
124
+ "Return a value, Success(value) or Error(code, message)."
123
125
  )
124
126
  self.result = result
125
127
 
@@ -143,19 +145,36 @@ def validate_result(result: object) -> None:
143
145
  raise InvalidResultError(returned)
144
146
 
145
147
 
148
+ def to_result(result: object) -> Result:
149
+ """The Result for what a method returned.
150
+
151
+ A plain value becomes `Success(value)`, so `return "pong"` and
152
+ `return Success("pong")` send the same response. A Result made by `Success` or
153
+ `Error` is used as it is. An oslash `Left` or `Right` that holds something else,
154
+ or a bare `SuccessResult` or `ErrorResult`, raises InvalidResultError, because
155
+ it is nearly always a mistake, not a value meant for the client.
156
+
157
+ New in 5.1.0. Before it, any plain value gave the client an Internal error.
158
+ """
159
+ returned = result # Not narrowed: oslash's Left and Right are generic.
160
+ if isinstance(result, (Left, Right, SuccessResult, ErrorResult)):
161
+ validate_result(returned)
162
+ return cast(Result, returned)
163
+ return Success(result)
164
+
165
+
146
166
  def log_invalid_result(
147
167
  method_name: str, exc: InvalidResultError, log: logging.Logger
148
168
  ) -> None:
149
- """Log a method that returned a plain value, which 4.x allowed, without a traceback.
169
+ """Log a method that returned an invalid Result, without a traceback.
150
170
 
151
171
  The traceback would only point into jsonrpcserver, so the message says what to
152
172
  change instead.
153
173
  """
154
174
  log.error(
155
- "Method %r returned %s, which is not a Result, so the client got an "
156
- "Internal error. Return Success(value) or Error(code, message). Since 5.0 a "
157
- "plain return value is not enough: "
158
- "https://bensynapse.github.io/jsonrpcserver/migration/",
175
+ "Method %r returned %s, which is not a valid Result, so the client got an "
176
+ "Internal error. Return a value, Success(value) or Error(code, message): "
177
+ "https://bensynapse.github.io/jsonrpcserver/methods/",
159
178
  method_name,
160
179
  reprlib.repr(exc.result),
161
180
  )
@@ -188,10 +207,10 @@ def call(
188
207
  f"Method {request.method!r} is async. Use async_dispatch to call "
189
208
  "async methods."
190
209
  )
191
- # validate_result raises AssertionError if the return value is not a valid
192
- # Result, which should respond with Internal Error because its a problem in the
193
- # method.
194
- validate_result(result)
210
+ # A plain value becomes Success(value). to_result raises InvalidResultError,
211
+ # an AssertionError, for an invalid Result, which gets an Internal error
212
+ # because it's a problem in the method.
213
+ result = to_result(result)
195
214
  # Raising JsonRpcError inside the method is an alternative way of returning an error
196
215
  # response.
197
216
  except JsonRpcError as exc:
@@ -203,8 +222,7 @@ def call(
203
222
  except Exception as exc:
204
223
  logger.exception("Method %r raised an exception", request.method)
205
224
  return Left(InternalErrorResult(exception_data(exc, debug)))
206
- # validate_result has checked it.
207
- return cast(Result, result)
225
+ return result
208
226
 
209
227
 
210
228
  def validate_args(
@@ -7,7 +7,8 @@ your own dict instead, pass it as the methods argument:
7
7
  dispatch(request) # the functions registered with @method
8
8
  dispatch(request, methods={"ping": ping}) # only the functions in this dict
9
9
 
10
- Either way, a method returns Success(...) or Error(...), not a plain value.
10
+ Either way, a method returns Success(...) or Error(...). From 5.1.0 it can also
11
+ return a plain value, which becomes Success(value).
11
12
 
12
13
  A request's params are either a list (positional arguments) or an object (named
13
14
  arguments), not both. That's a JSON-RPC rule.
@@ -3,19 +3,19 @@ aiohttp==3.14.5
3
3
  django==6.1.2
4
4
  fastapi==0.162.0
5
5
  flask==3.1.3
6
- flask-socketio==5.7.0
6
+ flask-socketio==5.7.2
7
7
  # FastAPI's test client, used on the Context page.
8
8
  httpx2==2.16.0
9
9
  # The quickstart calls the server with jsonrpcclient and requests.
10
10
  jsonrpcclient==4.0.3
11
11
  # The FAQ's orjson example.
12
12
  orjson==3.13.0
13
- python-socketio[client]==5.17.0
13
+ python-socketio[client]==5.18.0
14
14
  pyzmq==27.2.0
15
15
  requests==2.34.2
16
16
  sanic==25.12.1
17
17
  tornado==6.6
18
18
  ujson==6.0.0
19
19
  uvicorn==0.54.0
20
- websockets==17.3
20
+ websockets==17.4
21
21
  werkzeug==3.1.9
@@ -0,0 +1,7 @@
1
+ hypothesis==6.170.14
2
+ mypy==2.4.1
3
+ pyright==1.1.414
4
+ pytest==9.2.0
5
+ pytest-asyncio==1.4.1.post1
6
+ ruff==0.17.3
7
+ types-jsonschema==4.26.0.20261006
@@ -95,21 +95,29 @@ async def test_call_raising_exception_is_logged(
95
95
 
96
96
 
97
97
  @pytest.mark.asyncio
98
- async def test_plain_return_value_is_logged_with_a_hint(
99
- caplog: pytest.LogCaptureFixture,
100
- ) -> None:
98
+ async def test_plain_return_value_becomes_success() -> None:
101
99
  async def method() -> str:
102
100
  return "pong"
103
101
 
102
+ assert await call(Request("ping", [], 1), NOCONTEXT, method) == Right(
103
+ SuccessResult("pong")
104
+ )
105
+
106
+
107
+ @pytest.mark.asyncio
108
+ async def test_invalid_result_is_logged_with_a_hint(
109
+ caplog: pytest.LogCaptureFixture,
110
+ ) -> None:
111
+ async def method() -> object:
112
+ return Right[str, Any]("pong")
113
+
104
114
  assert await call(Request("ping", [], 1), NOCONTEXT, method) == Left(
105
115
  ErrorResult(ERROR_INTERNAL_ERROR, "Internal error")
106
116
  )
107
117
  (record,) = caplog.records
108
118
  assert record.name == "jsonrpcserver.async_dispatcher"
109
119
  assert record.exc_info is None
110
- assert record.getMessage().startswith(
111
- "Method 'ping' returned 'pong', which is not a Result"
112
- )
120
+ assert "which is not a valid Result" in record.getMessage()
113
121
 
114
122
 
115
123
  @pytest.mark.asyncio
@@ -49,6 +49,7 @@ from jsonrpcserver.response import (
49
49
  )
50
50
  from jsonrpcserver.result import (
51
51
  ErrorResult,
52
+ InternalErrorResult,
52
53
  InvalidParams,
53
54
  Result,
54
55
  Success,
@@ -512,10 +513,11 @@ def test_dispatch_to_response_pure_server_error(
512
513
 
513
514
 
514
515
  def test_dispatch_to_response_pure_invalid_result() -> None:
515
- """Methods should return a Result, otherwise we get an Internal Error response."""
516
+ """A bare SuccessResult, not wrapped by Success(), gets an Internal Error
517
+ response. A plain value is fine from 5.1.0."""
516
518
 
517
- def not_a_result() -> None:
518
- return None
519
+ def not_a_result() -> object:
520
+ return SuccessResult(None)
519
521
 
520
522
  assert dispatch_to_response_pure(
521
523
  deserializer=default_deserializer,
@@ -529,21 +531,36 @@ def test_dispatch_to_response_pure_invalid_result() -> None:
529
531
  ErrorResponse(
530
532
  ERROR_INTERNAL_ERROR,
531
533
  "Internal error",
532
- "The method did not return a valid Result (returned None). "
533
- "Return Success(value) or Error(code, message).",
534
+ "The method did not return a valid Result "
535
+ "(returned SuccessResult(None)). "
536
+ "Return a value, Success(value) or Error(code, message).",
534
537
  1,
535
538
  )
536
539
  )
537
540
 
538
541
 
539
- def test_plain_return_value_is_logged_with_a_hint(
542
+ @pytest.mark.parametrize("value", ["pong", None, 0, [1, 2], {"a": 1}, False])
543
+ def test_plain_return_value_becomes_success(value: object) -> None:
544
+ """A method can return its value directly (5.1.0, #285)."""
545
+ response = dispatch_to_response_pure(
546
+ deserializer=default_deserializer,
547
+ validator=default_validator,
548
+ post_process=identity,
549
+ context=NOCONTEXT,
550
+ methods={"ping": lambda: value},
551
+ request='{"jsonrpc": "2.0", "method": "ping", "id": 1}',
552
+ )
553
+ assert response == Right(SuccessResponse(value, 1))
554
+
555
+
556
+ def test_invalid_result_is_logged_with_a_hint(
540
557
  caplog: pytest.LogCaptureFixture,
541
558
  ) -> None:
542
- """A 4.x-style method that returns a plain value gets a log line saying what to do,
543
- not a traceback into jsonrpcserver."""
559
+ """An oslash Right that doesn't hold a SuccessResult is a mistake, not a value. It
560
+ gets a log line saying what to do, not a traceback into jsonrpcserver."""
544
561
 
545
- def ping() -> str:
546
- return "pong"
562
+ def ping() -> object:
563
+ return Right[str, Any]("pong")
547
564
 
548
565
  response = dispatch_to_response_pure(
549
566
  deserializer=default_deserializer,
@@ -559,9 +576,21 @@ def test_plain_return_value_is_logged_with_a_hint(
559
576
  (record,) = caplog.records
560
577
  assert record.levelname == "ERROR"
561
578
  assert record.exc_info is None
562
- assert record.getMessage().startswith(
563
- "Method 'ping' returned 'pong', which is not a Result, so the client got an "
564
- "Internal error. Return Success(value) or Error(code, message)."
579
+ assert "which is not a valid Result, so the client got an Internal error. " in (
580
+ record.getMessage()
581
+ )
582
+ assert "Return a value, Success(value) or Error(code, message)" in (
583
+ record.getMessage()
584
+ )
585
+
586
+
587
+ @pytest.mark.parametrize(
588
+ "returned",
589
+ [SuccessResult("pong"), ErrorResult(1, "x"), Left[Any, str]("oops")],
590
+ )
591
+ def test_bare_result_objects_are_invalid(returned: object) -> None:
592
+ assert call(Request("ping", [], 1), NOCONTEXT, lambda: returned) == Left(
593
+ InternalErrorResult(NODATA)
565
594
  )
566
595
 
567
596
 
@@ -158,10 +158,11 @@ def test_serializer_that_always_fails() -> None:
158
158
 
159
159
 
160
160
  def test_builtin_without_signature() -> None:
161
- """max has no signature inspect can read. It used to collapse the batch."""
161
+ """max has no signature inspect can read. It used to collapse the batch. From
162
+ 5.1.0 its plain return value is the result."""
162
163
  batch = [{**request("max", 1), "params": [1, 2]}, request("ping", 2)]
163
164
  assert json.loads(dispatch(json.dumps(batch), METHODS)) == [
164
- {"jsonrpc": "2.0", "error": INTERNAL_ERROR, "id": 1},
165
+ {"jsonrpc": "2.0", "result": 2, "id": 1},
165
166
  {"jsonrpc": "2.0", "result": "pong", "id": 2},
166
167
  ]
167
168
 
@@ -290,9 +291,10 @@ def test_invalid_result_under_optimize_flag() -> None:
290
291
  "import json, logging\n"
291
292
  "logging.disable(logging.CRITICAL)\n"
292
293
  "from jsonrpcserver import dispatch, Success\n"
294
+ "from oslash.either import Right\n"
293
295
  "batch = [{'jsonrpc': '2.0', 'method': 'none', 'id': 1},"
294
296
  " {'jsonrpc': '2.0', 'method': 'ping', 'id': 2}]\n"
295
- "methods = {'none': lambda: None, 'ping': lambda: Success('pong')}\n"
297
+ "methods = {'none': lambda: Right(None), 'ping': lambda: Success('pong')}\n"
296
298
  "print(dispatch(json.dumps(batch), methods))\n"
297
299
  )
298
300
  output = subprocess.run(
@@ -1,7 +0,0 @@
1
- hypothesis==6.170.12
2
- mypy==2.4.0
3
- pyright==1.1.414
4
- pytest==9.1.1
5
- pytest-asyncio==1.4.1
6
- ruff==0.17.3
7
- types-jsonschema==4.26.0.20261006
File without changes
File without changes
File without changes