texas2step 2.2.2__tar.gz → 2.2.3__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 (66) hide show
  1. {texas2step-2.2.2 → texas2step-2.2.3}/PKG-INFO +1 -1
  2. {texas2step-2.2.2 → texas2step-2.2.3}/docs/access.md +6 -2
  3. {texas2step-2.2.2 → texas2step-2.2.3}/docs/configuration.md +3 -2
  4. {texas2step-2.2.2 → texas2step-2.2.3}/docs/roadmap.md +5 -0
  5. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/__init__.py +1 -1
  6. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/api/access.py +4 -1
  7. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/api/measure.py +2 -1
  8. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/api/results.py +1 -1
  9. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/cli.py +12 -1
  10. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/config.py +19 -1
  11. {texas2step-2.2.2 → texas2step-2.2.3}/.gitignore +0 -0
  12. {texas2step-2.2.2 → texas2step-2.2.3}/LICENSE.md +0 -0
  13. {texas2step-2.2.2 → texas2step-2.2.3}/README.md +0 -0
  14. {texas2step-2.2.2 → texas2step-2.2.3}/deploy/nginx/texas2step-http.conf +0 -0
  15. {texas2step-2.2.2 → texas2step-2.2.3}/deploy/nginx/texas2step-log.conf +0 -0
  16. {texas2step-2.2.2 → texas2step-2.2.3}/deploy/nginx/texas2step-speedtest.conf +0 -0
  17. {texas2step-2.2.2 → texas2step-2.2.3}/deploy/nginx/texas2step.conf +0 -0
  18. {texas2step-2.2.2 → texas2step-2.2.3}/deploy/texas2step-update.service +0 -0
  19. {texas2step-2.2.2 → texas2step-2.2.3}/deploy/texas2step-update.timer +0 -0
  20. {texas2step-2.2.2 → texas2step-2.2.3}/deploy/texas2step.default +0 -0
  21. {texas2step-2.2.2 → texas2step-2.2.3}/deploy/texas2step.service +0 -0
  22. {texas2step-2.2.2 → texas2step-2.2.3}/docs/api.md +0 -0
  23. {texas2step-2.2.2 → texas2step-2.2.3}/docs/development.md +0 -0
  24. {texas2step-2.2.2 → texas2step-2.2.3}/docs/experience.md +0 -0
  25. {texas2step-2.2.2 → texas2step-2.2.3}/docs/images/hero-dark.jpg +0 -0
  26. {texas2step-2.2.2 → texas2step-2.2.3}/docs/images/results-dark.jpg +0 -0
  27. {texas2step-2.2.2 → texas2step-2.2.3}/docs/images/results-light.jpg +0 -0
  28. {texas2step-2.2.2 → texas2step-2.2.3}/docs/images/upload-light.jpg +0 -0
  29. {texas2step-2.2.2 → texas2step-2.2.3}/docs/install.md +0 -0
  30. {texas2step-2.2.2 → texas2step-2.2.3}/docs/methodology.md +0 -0
  31. {texas2step-2.2.2 → texas2step-2.2.3}/docs/security.md +0 -0
  32. {texas2step-2.2.2 → texas2step-2.2.3}/docs/traffic.md +0 -0
  33. {texas2step-2.2.2 → texas2step-2.2.3}/legacy/README.md +0 -0
  34. {texas2step-2.2.2 → texas2step-2.2.3}/pyproject.toml +0 -0
  35. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/__main__.py +0 -0
  36. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/access.py +0 -0
  37. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/api/__init__.py +0 -0
  38. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/api/meta.py +0 -0
  39. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/api/wire.py +0 -0
  40. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/app.py +0 -0
  41. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/cluster.py +0 -0
  42. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/config_edit.py +0 -0
  43. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/config_merge.py +0 -0
  44. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/config_template.toml +0 -0
  45. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/engine.py +0 -0
  46. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/iface.py +0 -0
  47. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/lockouts.py +0 -0
  48. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/logs.py +0 -0
  49. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/page.py +0 -0
  50. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/payload.py +0 -0
  51. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/quality.py +0 -0
  52. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/rtc.py +0 -0
  53. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/static/assets/app.css +0 -0
  54. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/static/assets/app.js +0 -0
  55. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/static/assets/swagger/LICENSE +0 -0
  56. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/static/assets/swagger/NOTICE +0 -0
  57. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/static/assets/swagger/swagger-ui-bundle.js +0 -0
  58. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/static/assets/swagger/swagger-ui-bundle.js.LICENSE.txt +0 -0
  59. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/static/assets/swagger/swagger-ui.css +0 -0
  60. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/static/favicon.svg +0 -0
  61. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/static/index.html +0 -0
  62. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/stats.py +0 -0
  63. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/store.py +0 -0
  64. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/traffic.py +0 -0
  65. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/update.py +0 -0
  66. {texas2step-2.2.2 → texas2step-2.2.3}/src/texas2step/words.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: texas2step
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Summary: Web-centric throughput, latency, jitter and packet-loss measurement between endpoints.
5
5
  Project-URL: Homepage, https://github.com/robert-patrick-texas/texas2step
6
6
  Project-URL: Documentation, https://robert-patrick-texas.github.io/texas2step/
@@ -49,10 +49,14 @@ networks, as addresses or CIDR blocks. Include the corporate addresses the desk'
49
49
  browsers reach the servers from.
50
50
 
51
51
  ```bash
52
- sudo texas2step config set access_code=daily internal_networks=10.0.0.0/8,198.51.100.0/24 --write
52
+ sudo texas2step config set access_code=daily internal_networks=10.0.0.0/8,2001:db8::/32,198.51.100.0/24 --write
53
53
  sudo systemctl restart texas2step
54
54
  ```
55
55
 
56
+ On the command line, separate the networks with commas. In `config.toml` they are a list
57
+ of quoted strings: `internal_networks = ["10.0.0.0/8", "2001:db8::/32", "198.51.100.0/24"]`.
58
+ One string of networks separated by spaces is refused.
59
+
56
60
  **3. Check** that the servers agree. On each:
57
61
 
58
62
  ```bash
@@ -87,7 +91,7 @@ To turn codes off again: `sudo texas2step config set access_code=off --write`, t
87
91
  | `access_pass_hours` | `12` | How long an entered code lets a browser keep testing |
88
92
  | `access_key_file` | `/etc/texas2step/access.key` | The key |
89
93
  | `access_phrases_file` | `/etc/texas2step/access-phrases.toml` | [Standing phrases](#standing-phrases), if the file exists |
90
- | `internal_networks` | `[]` | Your own networks: no code, and the desk's tools |
94
+ | `internal_networks` | `[]` | Your own networks: no code, and the desk's tools. A list of IPv4 and IPv6 addresses and networks in CIDR form, mixed freely: `["192.168.0.0/16", "2001:db8::/32", "203.0.113.7"]`. An address alone is a single host. Bits past the prefix are ignored, so `10.2.1.30/24` means all of `10.2.1.0/24`. Give the addresses as the server sees them: staff who come in through your firewall's NAT arrive from its public address. |
91
95
 
92
96
  **Built-in words.** A code is two words and the digits: `maple.river.42`. The words come
93
97
  from the EFF's short word list of easily spelled words (credited in the package's
@@ -7,8 +7,9 @@ Settings come from, in increasing priority:
7
7
  3. environment variables `TEXAS2STEP_<SETTING>` (upper case).
8
8
 
9
9
  Lists in environment variables are comma separated. Booleans accept `1/0`,
10
- `true/false`, `yes/no` and `on/off`. Unknown keys in the TOML file are an error, so
11
- typos don't fail silently. The package installs a fully commented file listing every
10
+ `true/false`, `yes/no` and `on/off`. Spaces around any value, or around each item of a
11
+ list, are ignored, wherever the setting comes from. Unknown keys in the TOML file are an
12
+ error, so typos don't fail silently. The package installs a fully commented file listing every
12
13
  setting and its default as `/etc/texas2step/config.toml` (source:
13
14
  [`src/texas2step/config_template.toml`](../src/texas2step/config_template.toml)); restart
14
15
  the service after editing it. After an upgrade, `texas2step config merge` brings your
@@ -116,6 +116,11 @@ links and footer credit. Three places still say texas2step:
116
116
 
117
117
  Newest first. Each release's notes have the details and its upgrade steps.
118
118
 
119
+ ### [2.2.3](https://github.com/robert-patrick-texas/texas2step/releases/tag/v2.2.3) (2026-10-24)
120
+
121
+ - [x] Spaces around any setting or typed value are ignored; a stray one in
122
+ `internal_networks` no longer breaks every request.
123
+
119
124
  ### [2.2.2](https://github.com/robert-patrick-texas/texas2step/releases/tag/v2.2.2) (2026-10-24)
120
125
 
121
126
  - [x] UDP works in Firefox: Monitor, Diag and the Test page's packet loss no longer fall
@@ -1,3 +1,3 @@
1
1
  """texas2step: web-centric throughput, latency, jitter and packet-loss measurement."""
2
2
 
3
- __version__ = "2.2.2"
3
+ __version__ = "2.2.3"
@@ -9,7 +9,7 @@ from datetime import UTC, datetime
9
9
  from typing import Any, Literal
10
10
 
11
11
  from fastapi import APIRouter, HTTPException, Query, Request, Response
12
- from pydantic import BaseModel, Field, ValidationError
12
+ from pydantic import BaseModel, ConfigDict, Field, ValidationError
13
13
 
14
14
  from .. import cluster
15
15
  from ..access import Codes, is_internal
@@ -24,10 +24,12 @@ PASS_COOKIE = "texas2step_pass" # the pass again, for loading this server's own
24
24
 
25
25
 
26
26
  class CodeIn(BaseModel):
27
+ model_config = ConfigDict(str_strip_whitespace=True) # as typed or pasted
27
28
  code: str = Field(max_length=200, description="The code, with any case and separators")
28
29
 
29
30
 
30
31
  class BlockIn(BaseModel):
32
+ model_config = ConfigDict(str_strip_whitespace=True)
31
33
  network: str = Field(
32
34
  max_length=64,
33
35
  description="An address or network outside internal_networks, e.g. 203.0.113.0/24",
@@ -261,6 +263,7 @@ async def unblock(
261
263
  ),
262
264
  ) -> dict[str, Any]:
263
265
  ip = _desk_client(request)
266
+ network = network.strip()
264
267
  try:
265
268
  target = None if network == "all" else str(parse_network(network))
266
269
  except ValueError as exc:
@@ -15,7 +15,7 @@ from typing import Any
15
15
 
16
16
  import httpx
17
17
  from fastapi import APIRouter, HTTPException, Request
18
- from pydantic import BaseModel, Field
18
+ from pydantic import BaseModel, ConfigDict, Field
19
19
 
20
20
  from .. import engine
21
21
  from ..store import TRANSFERS
@@ -27,6 +27,7 @@ _slots = asyncio.Semaphore(2)
27
27
 
28
28
 
29
29
  class Target(BaseModel):
30
+ model_config = ConfigDict(str_strip_whitespace=True)
30
31
  target: str = Field(description="Base URL of a texas2step server, e.g. https://peer.example")
31
32
 
32
33
 
@@ -50,7 +50,7 @@ async def recent(
50
50
 
51
51
  @router.get("/{rid}", summary="A saved result, in full")
52
52
  async def one(rid: str, request: Request) -> dict[str, Any]:
53
- result = await run_in_threadpool(_store(request).get, rid)
53
+ result = await run_in_threadpool(_store(request).get, rid.strip())
54
54
  if result is None:
55
55
  raise HTTPException(404, "no such result")
56
56
  return result
@@ -1361,6 +1361,17 @@ def build_parser() -> argparse.ArgumentParser:
1361
1361
  return parser
1362
1362
 
1363
1363
 
1364
+ def trimmed(args: argparse.Namespace) -> argparse.Namespace:
1365
+ """The arguments without spaces around their text: pasted values often bring them, with
1366
+ an address, a URL or a network."""
1367
+ for name, value in vars(args).items():
1368
+ if isinstance(value, str):
1369
+ setattr(args, name, value.strip())
1370
+ elif isinstance(value, list):
1371
+ setattr(args, name, [v.strip() if isinstance(v, str) else v for v in value])
1372
+ return args
1373
+
1374
+
1364
1375
  def main(argv: list[str] | None = None) -> None:
1365
- args = build_parser().parse_args(argv)
1376
+ args = trimmed(build_parser().parse_args(argv))
1366
1377
  args.func(args)
@@ -190,6 +190,11 @@ class Settings:
190
190
  brand_credit: str = "link"
191
191
 
192
192
  def __post_init__(self) -> None:
193
+ # However a setting was given (file, environment, `config set`), spaces around its
194
+ # text are no part of it: a stray space inside quotes would otherwise pass here and
195
+ # fail later, as in an address network that every request is checked against.
196
+ for f in dataclasses.fields(self):
197
+ setattr(self, f.name, _trimmed(getattr(self, f.name)))
193
198
  ports = (self.rtc_port_min, self.rtc_port_max)
194
199
  if ports != (0, 0) and not 1 <= ports[0] <= ports[1] <= 65535:
195
200
  raise ValueError(
@@ -317,6 +322,19 @@ class Settings:
317
322
  )
318
323
 
319
324
 
325
+ def _trimmed(value: Any) -> Any:
326
+ """A setting with the whitespace around each piece of its text taken off: in lists, and
327
+ in the fields of [[locations]] and links too."""
328
+ if isinstance(value, str):
329
+ return value.strip()
330
+ if isinstance(value, list):
331
+ return [_trimmed(v) for v in value]
332
+ if dataclasses.is_dataclass(value) and not isinstance(value, type):
333
+ fields = {f.name: _trimmed(getattr(value, f.name)) for f in dataclasses.fields(value)}
334
+ return dataclasses.replace(value, **fields)
335
+ return value
336
+
337
+
320
338
  def _coerce(raw: Any, hint: Any, name: str) -> Any:
321
339
  origin = typing.get_origin(hint)
322
340
  if origin in (typing.Union, types.UnionType):
@@ -336,7 +354,7 @@ def _coerce(raw: Any, hint: Any, name: str) -> Any:
336
354
  if hint is float:
337
355
  return float(raw)
338
356
  if hint is str:
339
- return str(raw)
357
+ return str(raw).strip()
340
358
  if origin is list:
341
359
  (item,) = typing.get_args(hint)
342
360
  if isinstance(raw, str):
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