pytypehintweb 1.2.3__tar.gz → 1.3.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 (32) hide show
  1. {pytypehintweb-1.2.3/src/pytypehintweb.egg-info → pytypehintweb-1.3.0}/PKG-INFO +2 -2
  2. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/pyproject.toml +1 -1
  3. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/__init__.py +1 -1
  4. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/plan.py +42 -2
  5. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/static/contract.js +8 -0
  6. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/static/inputs.js +11 -48
  7. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0/src/pytypehintweb.egg-info}/PKG-INFO +2 -2
  8. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb.egg-info/requires.txt +1 -1
  9. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/LICENSE +0 -0
  10. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/README.md +0 -0
  11. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/setup.cfg +0 -0
  12. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/decode.py +0 -0
  13. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/demo/__init__.py +0 -0
  14. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/demo/__main__.py +0 -0
  15. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/demo/app.py +0 -0
  16. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/demo/examples.py +0 -0
  17. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/py.typed +0 -0
  18. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/static/defaults.js +0 -0
  19. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/static/fields.js +0 -0
  20. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/static/form.js +0 -0
  21. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/static/icons/select-arrow-dark.svg +0 -0
  22. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/static/icons/select-arrow-light.svg +0 -0
  23. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/static/icons/trash.svg +0 -0
  24. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/static/iso.js +0 -0
  25. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/static/normalize.js +0 -0
  26. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/static/slider.js +0 -0
  27. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/static/widgets.css +0 -0
  28. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb/types.py +0 -0
  29. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb.egg-info/SOURCES.txt +0 -0
  30. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb.egg-info/dependency_links.txt +0 -0
  31. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb.egg-info/entry_points.txt +0 -0
  32. {pytypehintweb-1.2.3 → pytypehintweb-1.3.0}/src/pytypehintweb.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytypehintweb
3
- Version: 1.2.3
3
+ Version: 1.3.0
4
4
  Summary: Framework-free browser form layer for pytypehint
5
5
  Author: Beltran Offerrall
6
6
  License-Expression: MIT
@@ -23,7 +23,7 @@ Classifier: Typing :: Typed
23
23
  Requires-Python: >=3.11
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
- Requires-Dist: pytypehint==1.0.0
26
+ Requires-Dist: pytypehint==1.3.0
27
27
  Provides-Extra: demo
28
28
  Requires-Dist: fastapi; extra == "demo"
29
29
  Requires-Dist: uvicorn; extra == "demo"
@@ -11,7 +11,7 @@ license = "MIT"
11
11
  license-files = ["LICENSE"]
12
12
  authors = [{name = "Beltran Offerrall"}]
13
13
  requires-python = ">=3.11"
14
- dependencies = ["pytypehint==1.0.0"]
14
+ dependencies = ["pytypehint==1.3.0"]
15
15
  keywords = ["forms", "type-hints", "dataclasses", "schema", "web", "html"]
16
16
  classifiers = [
17
17
  "Development Status :: 5 - Production/Stable",
@@ -6,7 +6,7 @@ from pytypehintweb.types import COLOR_PATTERN, EMAIL_PATTERN, Color, Email
6
6
 
7
7
  STATIC = Path(__file__).parent / "static"
8
8
 
9
- __version__ = "1.2.3"
9
+ __version__ = "1.3.0"
10
10
 
11
11
  __all__ = [
12
12
  "plan_of",
@@ -480,6 +480,19 @@ def _bound(bound, delta, path: str, what: str):
480
480
  return inclusive
481
481
 
482
482
 
483
+ def _writable_float(value: float, path: str) -> float:
484
+ # JavaScript writes a number below 1e-6 or from 1e21 up in exponent notation,
485
+ # text the float control's own grammar rejects, so the default could never
486
+ # be shown as the value it is.
487
+ if value != 0 and not 1e-6 <= abs(value) < 1e21:
488
+ raise _error(
489
+ path,
490
+ f"Float default: {value!r} is valid in pytypehint but JavaScript "
491
+ f"writes it in exponent notation, which a float control cannot hold")
492
+
493
+ return value
494
+
495
+
483
496
  def _file_size(bound, path: str, what: str):
484
497
  # A plain int on FileHint, already checked there for being a non-negative
485
498
  # int and for min <= max. All that is left is the one thing the core has no
@@ -1059,6 +1072,11 @@ def _plain_value(shape, value, path: str):
1059
1072
  _check_slider_default(shape, safe, path)
1060
1073
  return safe
1061
1074
 
1075
+ # A select names its choices and writes no number, so only a text control
1076
+ # is bound by what JavaScript can write.
1077
+ if type(shape) is Float and shape.choices is None:
1078
+ return _writable_float(value, path)
1079
+
1062
1080
  # The plan is JSON, so a date/time default travels as canonical ISO text and
1063
1081
  # an enum member as its name — the transport their choices already take.
1064
1082
  if type(shape) is Date or type(shape) is Time:
@@ -1140,10 +1158,32 @@ def _field_node(field, config, path: str) -> dict:
1140
1158
  return node
1141
1159
 
1142
1160
 
1143
- def _form_node(name, description, fields, config) -> dict:
1161
+ def _reject_recursion(shapes, path: str, open_structs: tuple) -> None:
1162
+ # A plan is fully expanded, so a dataclass that contains itself has no plan.
1163
+ # Name the field instead of descending until the interpreter's stack ends.
1164
+ for shape in shapes:
1165
+ if type(shape) is List:
1166
+ _reject_recursion(shape.item, f"{path}.item", open_structs)
1167
+ elif type(shape) is Struct:
1168
+ if any(shape is opened for opened in open_structs):
1169
+ raise _error(
1170
+ path,
1171
+ f"{shape.cls.__name__} contains itself; a plan is fully "
1172
+ f"expanded and cannot represent a recursive shape")
1173
+
1174
+ for f in shape.fields:
1175
+ _reject_recursion(
1176
+ f.shape, f"{path}.{f.name}", (*open_structs, shape))
1177
+
1178
+
1179
+ def _form_node(name, description, fields, config, root=None) -> dict:
1144
1180
  plan = {"v": _PLAN_VERSION, "kind": "form", "name": name,
1145
1181
  "description": description}
1146
1182
 
1183
+ for f in fields:
1184
+ _reject_recursion(
1185
+ f.shape, f"fields.{f.name}", () if root is None else (root,))
1186
+
1147
1187
  plan["fields"] = [_field_node(f, config, "fields") for f in fields]
1148
1188
 
1149
1189
  return plan
@@ -1157,7 +1197,7 @@ def plan_of(obj, *, config: WebConfig | None = None) -> dict:
1157
1197
  f"config must be WebConfig or None, got {type(config).__name__}")
1158
1198
 
1159
1199
  if type(obj) is Struct:
1160
- return _form_node(obj.cls.__name__, None, obj.fields, config)
1200
+ return _form_node(obj.cls.__name__, None, obj.fields, config, obj)
1161
1201
 
1162
1202
  if type(obj) is Signature:
1163
1203
  return _form_node(obj.name, obj.doc, obj.params, config)
@@ -623,7 +623,15 @@ function checkFileDefault(options, value, path) {
623
623
  }
624
624
 
625
625
 
626
+ const WRITABLE_FLOAT = /^-?\d+(\.\d+)?$/;
627
+
628
+
626
629
  function checkFloatDefault(options, value, path) {
630
+ if (options.choices === null && !WRITABLE_FLOAT.test(String(value))) {
631
+ fail(path, "expected a number JavaScript writes as a simple decimal, "
632
+ + "not exponent notation");
633
+ }
634
+
627
635
  if (options.min !== null) {
628
636
  const below = options.minExclusive ? value <= options.min : value < options.min;
629
637
 
@@ -20,6 +20,8 @@ const ISO_MINUTES = /^([01]\d|2[0-3]):[0-5]\d$/;
20
20
 
21
21
  const FLOAT = /^-?\d+(\.\d+)?$/;
22
22
 
23
+ const FREE_EXTENSION = /\.[a-z0-9]{1,16}$/;
24
+
23
25
  function isWritableFloat(value) {
24
26
  return typeof value === "number"
25
27
  && Number.isFinite(value)
@@ -53,7 +55,7 @@ function setAttributes(el, attributes) {
53
55
 
54
56
 
55
57
  function withValue(template, value) {
56
- return template.replace("{value}", String(value));
58
+ return template.replace("{value}", () => String(value));
57
59
  }
58
60
 
59
61
 
@@ -443,21 +445,7 @@ export class FileWidget extends Widget {
443
445
  }
444
446
 
445
447
  setValue(value) {
446
- if (value === null) {
447
- this._clear();
448
- return;
449
- }
450
-
451
- this._current = this._asReferences(value);
452
- this._files = [];
453
- this._refs = [];
454
- this._invalid = false;
455
- this._stashedCurrent = null;
456
- this.input.value = "";
457
-
458
- this._renderCurrent();
459
- this._showMessage();
460
- this._emitChange();
448
+ this._reset(value === null ? null : this._asReferences(value), null);
461
449
  }
462
450
 
463
451
  error() {
@@ -531,13 +519,13 @@ export class FileWidget extends Widget {
531
519
  return this.extensions.some((extension) => lower.endsWith(extension));
532
520
  }
533
521
 
534
- _clear() {
522
+ _reset(current, stashed) {
523
+ this._current = current;
524
+ this._stashedCurrent = stashed;
535
525
  this._files = [];
536
526
  this._refs = [];
537
527
  this._invalid = false;
538
528
  this._oversized = null;
539
- this._current = null;
540
- this._stashedCurrent = null;
541
529
  this.input.value = "";
542
530
 
543
531
  this._renderCurrent();
@@ -547,37 +535,13 @@ export class FileWidget extends Widget {
547
535
  }
548
536
 
549
537
  _beginReplace() {
550
- this._stashedCurrent = this._current;
551
- this._current = null;
552
- this._files = [];
553
- this._refs = [];
554
- this._invalid = false;
555
- this._oversized = null;
556
- this.input.value = "";
557
-
558
- this._renderCurrent();
559
- this._renderList();
560
- this._showMessage();
561
- this._emitChange();
538
+ this._reset(null, this._current);
562
539
  }
563
540
 
564
541
  _restore() {
565
- if (this._stashedCurrent === null) {
566
- return;
542
+ if (this._stashedCurrent !== null) {
543
+ this._reset(this._stashedCurrent, null);
567
544
  }
568
-
569
- this._current = this._stashedCurrent;
570
- this._stashedCurrent = null;
571
- this._files = [];
572
- this._refs = [];
573
- this._invalid = false;
574
- this._oversized = null;
575
- this.input.value = "";
576
-
577
- this._renderCurrent();
578
- this._renderList();
579
- this._showMessage();
580
- this._emitChange();
581
545
  }
582
546
 
583
547
  _sizeError(file) {
@@ -608,8 +572,7 @@ export class FileWidget extends Widget {
608
572
  const lower = name.toLowerCase();
609
573
 
610
574
  if (this.extensions.length === 0) {
611
- const dot = lower.lastIndexOf(".");
612
- return dot === -1 ? "" : lower.slice(dot);
575
+ return FREE_EXTENSION.exec(lower)?.[0] ?? "";
613
576
  }
614
577
 
615
578
  return this.extensions.find((extension) => lower.endsWith(extension))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytypehintweb
3
- Version: 1.2.3
3
+ Version: 1.3.0
4
4
  Summary: Framework-free browser form layer for pytypehint
5
5
  Author: Beltran Offerrall
6
6
  License-Expression: MIT
@@ -23,7 +23,7 @@ Classifier: Typing :: Typed
23
23
  Requires-Python: >=3.11
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
- Requires-Dist: pytypehint==1.0.0
26
+ Requires-Dist: pytypehint==1.3.0
27
27
  Provides-Extra: demo
28
28
  Requires-Dist: fastapi; extra == "demo"
29
29
  Requires-Dist: uvicorn; extra == "demo"
@@ -1,4 +1,4 @@
1
- pytypehint==1.0.0
1
+ pytypehint==1.3.0
2
2
 
3
3
  [demo]
4
4
  fastapi
File without changes
File without changes
File without changes