jsonrpcserver 5.0.10__tar.gz → 5.2.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 (111) hide show
  1. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/CHANGELOG.md +34 -0
  2. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/PKG-INFO +3 -3
  3. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/README.md +2 -2
  4. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/RELEASING.md +6 -4
  5. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/errors.md +11 -8
  6. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/methods.md +12 -5
  7. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/migration.md +10 -6
  8. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/roadmap.md +2 -1
  9. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/__init__.py +1 -1
  10. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/async_dispatcher.py +6 -4
  11. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/dispatcher.py +44 -13
  12. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/main.py +39 -2
  13. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/methods.py +2 -1
  14. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/server.py +17 -5
  15. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/requirements/examples.txt +3 -3
  16. jsonrpcserver-5.2.0/requirements/lint.txt +7 -0
  17. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_async_dispatcher.py +14 -6
  18. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_batch_limit.py +3 -1
  19. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_dispatcher.py +42 -13
  20. jsonrpcserver-5.2.0/tests/test_future_warnings.py +81 -0
  21. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_robustness.py +9 -4
  22. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_server.py +3 -2
  23. jsonrpcserver-5.0.10/requirements/lint.txt +0 -7
  24. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/CODE_OF_CONDUCT.md +0 -0
  25. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/CONTRIBUTING.md +0 -0
  26. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/LICENSE +0 -0
  27. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/SECURITY.md +0 -0
  28. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/assets/a11y.js +0 -0
  29. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/assets/apple-touch-icon.png +0 -0
  30. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/assets/copy-without-prompts.js +0 -0
  31. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/assets/extra.css +0 -0
  32. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/assets/favicon.png +0 -0
  33. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/assets/logo.png +0 -0
  34. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/assets/social-card.png +0 -0
  35. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/async.md +0 -0
  36. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/batches.md +0 -0
  37. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/changelog.md +0 -0
  38. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/context.md +0 -0
  39. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/contributing.md +0 -0
  40. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/dispatch.md +0 -0
  41. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/aiohttp_server.py +0 -0
  42. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/check_examples.py +0 -0
  43. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/django_server.py +0 -0
  44. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/fastapi_server.py +0 -0
  45. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/flask_server.py +0 -0
  46. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/http_server.py +0 -0
  47. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/quickstart.py +0 -0
  48. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/sanic_server.py +0 -0
  49. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/socketio_server.py +0 -0
  50. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/tornado_server.py +0 -0
  51. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/websockets_server.py +0 -0
  52. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/werkzeug_server.py +0 -0
  53. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/zeromq_async_server.py +0 -0
  54. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples/zeromq_server.py +0 -0
  55. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/examples.md +0 -0
  56. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/faq.md +0 -0
  57. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/frameworks/aiohttp.md +0 -0
  58. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/frameworks/django.md +0 -0
  59. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/frameworks/fastapi.md +0 -0
  60. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/frameworks/flask.md +0 -0
  61. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/frameworks/http-server.md +0 -0
  62. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/frameworks/sanic.md +0 -0
  63. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/frameworks/socketio.md +0 -0
  64. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/frameworks/tornado.md +0 -0
  65. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/frameworks/websockets.md +0 -0
  66. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/frameworks/werkzeug.md +0 -0
  67. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/frameworks/zeromq.md +0 -0
  68. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/index.md +0 -0
  69. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/license.md +0 -0
  70. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/reference.md +0 -0
  71. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/security-policy.md +0 -0
  72. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/security.md +0 -0
  73. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/testing.md +0 -0
  74. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/threads.md +0 -0
  75. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/typing.md +0 -0
  76. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/docs/validation.md +0 -0
  77. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/async_main.py +0 -0
  78. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/codes.py +0 -0
  79. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/exceptions.py +0 -0
  80. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/py.typed +0 -0
  81. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/request-schema.json +0 -0
  82. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/request.py +0 -0
  83. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/response.py +0 -0
  84. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/result.py +0 -0
  85. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/sentinels.py +0 -0
  86. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/jsonrpcserver/utils.py +0 -0
  87. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/mkdocs.yml +0 -0
  88. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/overrides/404.html +0 -0
  89. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/overrides/main.html +0 -0
  90. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/pyproject.toml +0 -0
  91. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/requirements/build.txt +0 -0
  92. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/requirements/docs-constraints.txt +0 -0
  93. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/requirements/docs.txt +0 -0
  94. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/requirements/site.txt +0 -0
  95. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/requirements/test.txt +0 -0
  96. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/__init__.py +0 -0
  97. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/doc_examples.py +0 -0
  98. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_async_main.py +0 -0
  99. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_docs.py +0 -0
  100. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_docstrings.py +0 -0
  101. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_main.py +0 -0
  102. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_methods.py +0 -0
  103. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_request.py +0 -0
  104. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_response.py +0 -0
  105. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_result.py +0 -0
  106. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_sentinels.py +0 -0
  107. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_threading.py +0 -0
  108. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_typing.py +0 -0
  109. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_version.py +0 -0
  110. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tests/test_warnings.py +0 -0
  111. {jsonrpcserver-5.0.10 → jsonrpcserver-5.2.0}/tox.ini +0 -0
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.2.0 (2026-11-06)
4
+
5
+ Warnings about what 6.0 changes, so an upgrade doesn't surprise you. Nothing
6
+ else changes. Each is a `FutureWarning`, which Python shows by default.
7
+
8
+ - `serve()` without a host warns. From 6.0 it listens on localhost only.
9
+ `serve("", port)` keeps every network interface, quietly.
10
+ - A request with `NaN`, `Infinity`, `-Infinity` or a number too big for a float
11
+ warns. 6.0 rejects them with a Parse error. `deserializer=json.loads` accepts
12
+ them quietly. `dispatch_to_response` now uses the same default deserializer
13
+ as the other dispatch functions.
14
+ - A batch of more than 100 requests without `max_batch_size` warns. 6.0 limits
15
+ batches to 100 by default. Passing `max_batch_size` turns the warning off.
16
+
17
+ The [6.0 plan](https://github.com/bensynapse/jsonrpcserver/blob/main/plans/6.0.md)
18
+ lists everything else that changes, such as results and responses without
19
+ oslash.
20
+
21
+ ## 5.1.0 (2026-11-06)
22
+
23
+ ### Added
24
+
25
+ - A method can return a plain value. `return "pong"` sends the same response as
26
+ `return Success("pong")`, as in 4.x (#285). Before, a plain value gave the
27
+ client an Internal error. `Success` and `Error` work as before.
28
+
29
+ ### Changed
30
+
31
+ - A method that returns an oslash `Left` or `Right` not made by `Success` or
32
+ `Error`, or a bare `SuccessResult` or `ErrorResult`, still gets an Internal
33
+ error. Its log line and `InvalidResultError` message now say "Return a value,
34
+ Success(value) or Error(code, message)", and the log line links the Methods
35
+ page.
36
+
3
37
  ## 5.0.10 (2026-11-06)
4
38
 
5
39
  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.2.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.2.0"
42
42
  """The version of jsonrpcserver, as a string. Added in 5.0.10."""
@@ -25,9 +25,10 @@ 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
+ warn_if_over_6_0_batch_limit,
31
32
  )
32
33
  from .exceptions import JsonRpcError
33
34
  from .methods import AnyMethod, MethodsArgument
@@ -51,7 +52,8 @@ async def call(
51
52
  # Plain (sync) methods work too. Their result is used as it is.
52
53
  if isawaitable(result):
53
54
  result = await result
54
- validate_result(result)
55
+ # A plain value becomes Success(value) (5.1.0).
56
+ result = to_result(result)
55
57
  except JsonRpcError as exc:
56
58
  return Left(ErrorResult(code=exc.code, message=exc.message, data=exc.data))
57
59
  except InvalidResultError as exc:
@@ -61,8 +63,7 @@ async def call(
61
63
  # Other error inside method - Internal error
62
64
  logger.exception("Method %r raised an exception", request.method)
63
65
  return Left(InternalErrorResult(exception_data(exc, debug)))
64
- # validate_result has checked it.
65
- return cast(Result, result)
66
+ return result
66
67
 
67
68
 
68
69
  async def dispatch_request(
@@ -161,6 +162,7 @@ async def dispatch_to_response_pure(
161
162
  return post_process(Left(parsed._error))
162
163
  deserialized = unwrap(parsed)
163
164
  if isinstance(deserialized, list) and deserialized:
165
+ warn_if_over_6_0_batch_limit(len(deserialized), max_batch_size)
164
166
  if max_batch_size is not None and len(deserialized) > max_batch_size:
165
167
  return post_process(
166
168
  Left(BatchTooLargeResponse(len(deserialized), max_batch_size))
@@ -4,6 +4,7 @@ requests, providing responses.
4
4
 
5
5
  import logging
6
6
  import reprlib
7
+ import warnings
7
8
  from functools import partial
8
9
  from inspect import iscoroutine, signature
9
10
  from itertools import starmap
@@ -32,6 +33,7 @@ from .result import (
32
33
  InvalidParamsResult,
33
34
  MethodNotFoundResult,
34
35
  Result,
36
+ Success,
35
37
  SuccessResult,
36
38
  )
37
39
  from .sentinels import NOCONTEXT, NODATA, NOID, Sentinel
@@ -114,12 +116,13 @@ def extract_kwargs(request: Request) -> Dict[str, Any]:
114
116
 
115
117
 
116
118
  class InvalidResultError(AssertionError):
117
- """A method returned something other than Success(...) or Error(...)."""
119
+ """A method returned an oslash `Left` or `Right`, or a result object, that isn't a
120
+ Result made by Success(...) or Error(...)."""
118
121
 
119
122
  def __init__(self, result: object):
120
123
  super().__init__(
121
124
  f"The method did not return a valid Result (returned {result!r}). "
122
- "Return Success(value) or Error(code, message)."
125
+ "Return a value, Success(value) or Error(code, message)."
123
126
  )
124
127
  self.result = result
125
128
 
@@ -143,19 +146,36 @@ def validate_result(result: object) -> None:
143
146
  raise InvalidResultError(returned)
144
147
 
145
148
 
149
+ def to_result(result: object) -> Result:
150
+ """The Result for what a method returned.
151
+
152
+ A plain value becomes `Success(value)`, so `return "pong"` and
153
+ `return Success("pong")` send the same response. A Result made by `Success` or
154
+ `Error` is used as it is. An oslash `Left` or `Right` that holds something else,
155
+ or a bare `SuccessResult` or `ErrorResult`, raises InvalidResultError, because
156
+ it is nearly always a mistake, not a value meant for the client.
157
+
158
+ New in 5.1.0. Before it, any plain value gave the client an Internal error.
159
+ """
160
+ returned = result # Not narrowed: oslash's Left and Right are generic.
161
+ if isinstance(result, (Left, Right, SuccessResult, ErrorResult)):
162
+ validate_result(returned)
163
+ return cast(Result, returned)
164
+ return Success(result)
165
+
166
+
146
167
  def log_invalid_result(
147
168
  method_name: str, exc: InvalidResultError, log: logging.Logger
148
169
  ) -> None:
149
- """Log a method that returned a plain value, which 4.x allowed, without a traceback.
170
+ """Log a method that returned an invalid Result, without a traceback.
150
171
 
151
172
  The traceback would only point into jsonrpcserver, so the message says what to
152
173
  change instead.
153
174
  """
154
175
  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/",
176
+ "Method %r returned %s, which is not a valid Result, so the client got an "
177
+ "Internal error. Return a value, Success(value) or Error(code, message): "
178
+ "https://bensynapse.github.io/jsonrpcserver/methods/",
159
179
  method_name,
160
180
  reprlib.repr(exc.result),
161
181
  )
@@ -188,10 +208,10 @@ def call(
188
208
  f"Method {request.method!r} is async. Use async_dispatch to call "
189
209
  "async methods."
190
210
  )
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)
211
+ # A plain value becomes Success(value). to_result raises InvalidResultError,
212
+ # an AssertionError, for an invalid Result, which gets an Internal error
213
+ # because it's a problem in the method.
214
+ result = to_result(result)
195
215
  # Raising JsonRpcError inside the method is an alternative way of returning an error
196
216
  # response.
197
217
  except JsonRpcError as exc:
@@ -203,8 +223,7 @@ def call(
203
223
  except Exception as exc:
204
224
  logger.exception("Method %r raised an exception", request.method)
205
225
  return Left(InternalErrorResult(exception_data(exc, debug)))
206
- # validate_result has checked it.
207
- return cast(Result, result)
226
+ return result
208
227
 
209
228
 
210
229
  def validate_args(
@@ -369,6 +388,17 @@ def check_max_batch_size(max_batch_size: object) -> None:
369
388
  )
370
389
 
371
390
 
391
+ def warn_if_over_6_0_batch_limit(size: int, max_batch_size: Optional[int]) -> None:
392
+ """Warn when a batch would get an error under 6.0's default limit of 100."""
393
+ if max_batch_size is None and size > 100:
394
+ warnings.warn(
395
+ f"This batch has {size} requests. 6.0 limits batches to 100 by default. "
396
+ "Pass max_batch_size=None to keep no limit, or set the limit you want.",
397
+ FutureWarning,
398
+ stacklevel=2,
399
+ )
400
+
401
+
372
402
  def member_id(member: Any) -> Any:
373
403
  """The id of a batch member, or None if it has none we can use.
374
404
 
@@ -435,6 +465,7 @@ def dispatch_to_response_pure(
435
465
  return post_process(Left(parsed._error))
436
466
  deserialized = unwrap(parsed)
437
467
  if isinstance(deserialized, list) and deserialized:
468
+ warn_if_over_6_0_batch_limit(len(deserialized), max_batch_size)
438
469
  if max_batch_size is not None and len(deserialized) > max_batch_size:
439
470
  return post_process(
440
471
  Left(BatchTooLargeResponse(len(deserialized), max_batch_size))
@@ -12,7 +12,9 @@ They differ only in the form of the response:
12
12
 
13
13
  import json
14
14
  import logging
15
+ import math
15
16
  import pkgutil
17
+ import warnings
16
18
  from typing import Any, Callable, Dict, List, Optional, Union, cast
17
19
 
18
20
  from jsonschema.validators import validator_for
@@ -41,7 +43,42 @@ from .utils import identity
41
43
 
42
44
  logger = logging.getLogger(__name__)
43
45
 
44
- default_deserializer = json.loads
46
+ _CONSTANTS = {"NaN": math.nan, "Infinity": math.inf, "-Infinity": -math.inf}
47
+
48
+
49
+ def _future_constant(constant: str) -> float:
50
+ warnings.warn(
51
+ f"{constant} in a request isn't valid JSON. 5.x accepts it, but 6.0 will "
52
+ "reject it with a Parse error.",
53
+ FutureWarning,
54
+ stacklevel=2,
55
+ )
56
+ return _CONSTANTS[constant]
57
+
58
+
59
+ def _future_float(text: str) -> float:
60
+ number = float(text)
61
+ if math.isinf(number):
62
+ warnings.warn(
63
+ f"{text} in a request is too big for a float. 5.x reads it as infinity, "
64
+ "but 6.0 will reject it with a Parse error.",
65
+ FutureWarning,
66
+ stacklevel=2,
67
+ )
68
+ return number
69
+
70
+
71
+ def default_deserializer(request: str) -> Any:
72
+ """json.loads, which warns about input 6.0 will reject.
73
+
74
+ `NaN`, `Infinity`, `-Infinity` and numbers too big for a float are accepted, as
75
+ in every 5.x release, with a `FutureWarning`: 6.0's default rejects them with a
76
+ Parse error. Pass `deserializer=json.loads` to accept them without a warning.
77
+ New in 5.2.0.
78
+ """
79
+ return json.loads(
80
+ request, parse_constant=_future_constant, parse_float=_future_float
81
+ )
45
82
 
46
83
 
47
84
  def default_serializer(response: Any) -> str:
@@ -121,7 +158,7 @@ def dispatch_to_response(
121
158
  methods: Optional[MethodsArgument] = None,
122
159
  *,
123
160
  context: Any = NOCONTEXT,
124
- deserializer: Callable[[str], Deserialized] = json.loads,
161
+ deserializer: Callable[[str], Deserialized] = default_deserializer,
125
162
  validator: Callable[[Deserialized], object] = default_validator,
126
163
  post_process: Callable[[Response], Any] = identity,
127
164
  debug: bool = False,
@@ -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.
@@ -7,8 +7,9 @@ server or framework.
7
7
  import json
8
8
  import logging
9
9
  import sys
10
+ import warnings
10
11
  from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
11
- from typing import Any
12
+ from typing import Any, Optional
12
13
 
13
14
  from .main import dispatch
14
15
  from .response import ParseErrorResponse, to_error_dict
@@ -71,7 +72,7 @@ def listening_message(name: str, port: int) -> str:
71
72
  return f" * Listening on http://{name}:{port}/. This is a development server."
72
73
 
73
74
 
74
- def serve(name: str = "", port: int = 5000) -> None:
75
+ def serve(name: Optional[str] = None, port: int = 5000) -> None:
75
76
  """Serve the methods registered with `@method` over HTTP. For development only.
76
77
 
77
78
  It answers POST requests on any path with `dispatch`, sends 204 No Content for a
@@ -87,9 +88,11 @@ def serve(name: str = "", port: int = 5000) -> None:
87
88
  production.
88
89
 
89
90
  Args:
90
- name: The host name or address to listen on. The default, "", listens on
91
- every network interface, so other machines can connect. Pass "localhost"
92
- to accept only local connections.
91
+ name: The host name or address to listen on. Without one, it listens on
92
+ every network interface, so other machines can connect, and gives a
93
+ `FutureWarning` (5.2.0): 6.0 listens on localhost by default. Pass ""
94
+ to keep every interface, or "localhost" to accept only local
95
+ connections.
93
96
  port: The port to listen on.
94
97
 
95
98
  Example:
@@ -103,6 +106,15 @@ def serve(name: str = "", port: int = 5000) -> None:
103
106
  serve("localhost", 8000)
104
107
  ```
105
108
  """
109
+ if name is None:
110
+ warnings.warn(
111
+ "serve() without a host listens on every network interface. From 6.0 it "
112
+ 'will listen on localhost only. Pass serve("", port) to keep listening '
113
+ 'on every interface, or serve("localhost", port).',
114
+ FutureWarning,
115
+ stacklevel=2,
116
+ )
117
+ name = ""
106
118
  httpd = ThreadingHTTPServer((name, port), RequestHandler)
107
119
  try:
108
120
  message = listening_message(name, port)
@@ -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
@@ -47,7 +47,9 @@ def reset_calls() -> None:
47
47
 
48
48
 
49
49
  def test_no_limit_by_default() -> None:
50
- assert len(json.loads(dispatch(batch(500), {"ping": ping}))) == 500
50
+ """5.x has no limit. From 5.2.0 a batch over 6.0's default of 100 warns."""
51
+ with pytest.warns(FutureWarning, match="6.0 limits batches to 100 by default"):
52
+ assert len(json.loads(dispatch(batch(500), {"ping": ping}))) == 500
51
53
 
52
54
 
53
55
  def test_at_the_limit() -> None:
@@ -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
 
@@ -0,0 +1,81 @@
1
+ """5.2.0 warns about the 5.x behaviour that 6.0 changes (plans/6.0.md, D12)."""
2
+
3
+ import json
4
+ import warnings
5
+ from unittest.mock import Mock, patch
6
+
7
+ import pytest
8
+
9
+ from jsonrpcserver import Result, Success, dispatch, serve
10
+
11
+ METHODS = {"ping": lambda: Success("pong")}
12
+
13
+
14
+ def take_one(x: float) -> Result:
15
+ return Success()
16
+
17
+
18
+ def take_two(a: float, b: float) -> Result:
19
+ return Success()
20
+
21
+
22
+ def batch(size: int) -> str:
23
+ return json.dumps(
24
+ [{"jsonrpc": "2.0", "method": "ping", "id": n} for n in range(size)]
25
+ )
26
+
27
+
28
+ @pytest.mark.parametrize("constant", ["NaN", "Infinity", "-Infinity"])
29
+ def test_non_json_constants_warn(constant: str) -> None:
30
+ request = (
31
+ '{"jsonrpc": "2.0", "method": "ping", "params": [' + constant + '], "id": 1}'
32
+ )
33
+ with pytest.warns(FutureWarning, match=f"{constant} in a request isn't valid JSON"):
34
+ dispatch(request, {"ping": take_one})
35
+
36
+
37
+ def test_json_loads_accepts_them_quietly() -> None:
38
+ request = '{"jsonrpc": "2.0", "method": "ping", "params": [NaN], "id": 1}'
39
+ with warnings.catch_warnings():
40
+ warnings.simplefilter("error")
41
+ dispatch(request, {"ping": take_one}, deserializer=json.loads)
42
+
43
+
44
+ def test_ordinary_numbers_dont_warn() -> None:
45
+ request = '{"jsonrpc": "2.0", "method": "ping", "params": [1.5, 1e300], "id": 1}'
46
+ with warnings.catch_warnings():
47
+ warnings.simplefilter("error")
48
+ dispatch(request, {"ping": take_two})
49
+
50
+
51
+ def test_batch_of_100_doesnt_warn() -> None:
52
+ with warnings.catch_warnings():
53
+ warnings.simplefilter("error")
54
+ dispatch(batch(100), METHODS)
55
+
56
+
57
+ def test_batch_of_101_warns() -> None:
58
+ with pytest.warns(FutureWarning, match="This batch has 101 requests"):
59
+ dispatch(batch(101), METHODS)
60
+
61
+
62
+ @pytest.mark.asyncio
63
+ async def test_async_batch_of_101_warns() -> None:
64
+ from jsonrpcserver import async_dispatch
65
+
66
+ with pytest.warns(FutureWarning, match="This batch has 101 requests"):
67
+ await async_dispatch(batch(101), METHODS)
68
+
69
+
70
+ def test_explicit_limit_doesnt_warn() -> None:
71
+ with warnings.catch_warnings():
72
+ warnings.simplefilter("error")
73
+ dispatch(batch(101), METHODS, max_batch_size=200)
74
+
75
+
76
+ @patch("jsonrpcserver.server.ThreadingHTTPServer")
77
+ def test_serve_with_a_host_doesnt_warn(server: Mock) -> None:
78
+ with warnings.catch_warnings():
79
+ warnings.simplefilter("error")
80
+ serve("", 5000)
81
+ serve("localhost", 5000)
@@ -134,7 +134,10 @@ def test_nan_allowed_with_plain_json_dumps() -> None:
134
134
 
135
135
 
136
136
  def test_infinite_id_gets_null_id() -> None:
137
- response = dispatch('{"jsonrpc": "2.0", "method": "ping", "id": 1e400}', METHODS)
137
+ with pytest.warns(FutureWarning, match="too big for a float"):
138
+ response = dispatch(
139
+ '{"jsonrpc": "2.0", "method": "ping", "id": 1e400}', METHODS
140
+ )
138
141
  assert json.loads(response) == {
139
142
  "jsonrpc": "2.0",
140
143
  "error": INTERNAL_ERROR,
@@ -158,10 +161,11 @@ def test_serializer_that_always_fails() -> None:
158
161
 
159
162
 
160
163
  def test_builtin_without_signature() -> None:
161
- """max has no signature inspect can read. It used to collapse the batch."""
164
+ """max has no signature inspect can read. It used to collapse the batch. From
165
+ 5.1.0 its plain return value is the result."""
162
166
  batch = [{**request("max", 1), "params": [1, 2]}, request("ping", 2)]
163
167
  assert json.loads(dispatch(json.dumps(batch), METHODS)) == [
164
- {"jsonrpc": "2.0", "error": INTERNAL_ERROR, "id": 1},
168
+ {"jsonrpc": "2.0", "result": 2, "id": 1},
165
169
  {"jsonrpc": "2.0", "result": "pong", "id": 2},
166
170
  ]
167
171
 
@@ -290,9 +294,10 @@ def test_invalid_result_under_optimize_flag() -> None:
290
294
  "import json, logging\n"
291
295
  "logging.disable(logging.CRITICAL)\n"
292
296
  "from jsonrpcserver import dispatch, Success\n"
297
+ "from oslash.either import Right\n"
293
298
  "batch = [{'jsonrpc': '2.0', 'method': 'none', 'id': 1},"
294
299
  " {'jsonrpc': '2.0', 'method': 'ping', 'id': 2}]\n"
295
- "methods = {'none': lambda: None, 'ping': lambda: Success('pong')}\n"
300
+ "methods = {'none': lambda: Right(None), 'ping': lambda: Success('pong')}\n"
296
301
  "print(dispatch(json.dumps(batch), methods))\n"
297
302
  )
298
303
  output = subprocess.run(
@@ -18,7 +18,8 @@ from jsonrpcserver.server import RequestHandler, listening_message, serve
18
18
 
19
19
  @patch("jsonrpcserver.server.ThreadingHTTPServer")
20
20
  def test_serve(server: Mock) -> None:
21
- serve()
21
+ with pytest.warns(FutureWarning, match="From 6.0 it will listen on localhost"):
22
+ serve()
22
23
  server.assert_called_once_with(("", 5000), RequestHandler)
23
24
  server.return_value.serve_forever.assert_called_once_with()
24
25
  server.return_value.server_close.assert_called_once_with()
@@ -34,7 +35,7 @@ def test_serve_name_and_port(server: Mock) -> None:
34
35
  def test_serve_closes_on_interrupt(server: Mock) -> None:
35
36
  server.return_value.serve_forever.side_effect = KeyboardInterrupt
36
37
  with pytest.raises(KeyboardInterrupt):
37
- serve()
38
+ serve("", 5000)
38
39
  server.return_value.server_close.assert_called_once_with()
39
40
 
40
41
 
@@ -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