sci-protocol 0.2.0__py3-none-any.whl

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 (80) hide show
  1. pysci/__init__.py +77 -0
  2. pysci/__main__.py +8 -0
  3. pysci/_cli/__init__.py +5 -0
  4. pysci/_cli/checks.py +173 -0
  5. pysci/_cli/clients/__init__.py +0 -0
  6. pysci/_cli/clients/archive.py +456 -0
  7. pysci/_cli/clients/credentials.py +54 -0
  8. pysci/_cli/clients/github.py +734 -0
  9. pysci/_cli/clients/hosting.py +238 -0
  10. pysci/_cli/clients/http.py +481 -0
  11. pysci/_cli/clients/huggingface.py +492 -0
  12. pysci/_cli/clients/index.py +435 -0
  13. pysci/_cli/clients/tsa.py +223 -0
  14. pysci/_cli/commands/__init__.py +44 -0
  15. pysci/_cli/commands/check.py +115 -0
  16. pysci/_cli/commands/claim.py +179 -0
  17. pysci/_cli/commands/common.py +91 -0
  18. pysci/_cli/commands/folders.py +83 -0
  19. pysci/_cli/commands/index.py +51 -0
  20. pysci/_cli/commands/keygen.py +58 -0
  21. pysci/_cli/commands/license.py +75 -0
  22. pysci/_cli/commands/protocol.py +72 -0
  23. pysci/_cli/commands/publish.py +282 -0
  24. pysci/_cli/commands/pull.py +337 -0
  25. pysci/_cli/commands/read.py +364 -0
  26. pysci/_cli/commands/revoke.py +207 -0
  27. pysci/_cli/commands/search.py +400 -0
  28. pysci/_cli/commands/sign.py +284 -0
  29. pysci/_cli/commands/submit.py +172 -0
  30. pysci/_cli/commands/timestamp.py +196 -0
  31. pysci/_cli/commands/verify.py +163 -0
  32. pysci/_cli/config.py +233 -0
  33. pysci/_cli/context.py +79 -0
  34. pysci/_cli/destinations.py +163 -0
  35. pysci/_cli/errors.py +168 -0
  36. pysci/_cli/main.py +229 -0
  37. pysci/_cli/output.py +80 -0
  38. pysci/_cli/retrieval.py +51 -0
  39. pysci/_cli/spec.py +113 -0
  40. pysci/_cli/storage/__init__.py +0 -0
  41. pysci/_cli/storage/catalog.py +371 -0
  42. pysci/_cli/storage/files.py +252 -0
  43. pysci/_cli/storage/inputs.py +205 -0
  44. pysci/_cli/storage/keys.py +132 -0
  45. pysci/_cli/storage/local_publish.py +88 -0
  46. pysci/accounts.py +135 -0
  47. pysci/baseline.py +142 -0
  48. pysci/bundle.py +878 -0
  49. pysci/citations.py +233 -0
  50. pysci/data/freetsa-cacert.pem +45 -0
  51. pysci/data/sci-baseline-LICENSE.txt +457 -0
  52. pysci/data/sci-protocol.md +613 -0
  53. pysci/der.py +146 -0
  54. pysci/discovery.py +461 -0
  55. pysci/draft.py +374 -0
  56. pysci/ed25519.py +153 -0
  57. pysci/errors.py +204 -0
  58. pysci/ids.py +61 -0
  59. pysci/jsonstrict.py +225 -0
  60. pysci/keys.py +247 -0
  61. pysci/manifest.py +302 -0
  62. pysci/metadata.py +406 -0
  63. pysci/paths.py +136 -0
  64. pysci/png.py +217 -0
  65. pysci/py.typed +0 -0
  66. pysci/records.py +384 -0
  67. pysci/rfc3339.py +66 -0
  68. pysci/schemas.py +75 -0
  69. pysci/source.py +166 -0
  70. pysci/specification.py +58 -0
  71. pysci/submission.py +225 -0
  72. pysci/templates.py +129 -0
  73. pysci/text.py +226 -0
  74. pysci/timestamps.py +867 -0
  75. pysci/zipformat.py +400 -0
  76. sci_protocol-0.2.0.dist-info/METADATA +112 -0
  77. sci_protocol-0.2.0.dist-info/RECORD +80 -0
  78. sci_protocol-0.2.0.dist-info/WHEEL +4 -0
  79. sci_protocol-0.2.0.dist-info/entry_points.txt +2 -0
  80. sci_protocol-0.2.0.dist-info/licenses/LICENSE +202 -0
pysci/baseline.py ADDED
@@ -0,0 +1,142 @@
1
+ """Baseline publications (protocol §2.1)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass, field
8
+ from functools import cache
9
+ from importlib import resources
10
+ from typing import Final
11
+
12
+ from .errors import Issue
13
+
14
+ __all__ = [
15
+ "APPROVED_LICENSE_SHA256",
16
+ "BASELINE_LICENSE_SHA256",
17
+ "BaselineLimits",
18
+ "BaselineReport",
19
+ "baseline_license_bytes",
20
+ "evaluate_baseline",
21
+ ]
22
+
23
+ BASELINE_LICENSE_SHA256: Final = "8ccfd5fdaa93e5e71dc8812bf11e0a7198bd0a534fa64b8970fd2de58e6c2478"
24
+ """SHA-256 of the SCI baseline license 0.1."""
25
+
26
+ APPROVED_LICENSE_SHA256: Final = frozenset({BASELINE_LICENSE_SHA256})
27
+ """Hashes of every approved baseline license."""
28
+
29
+
30
+ @dataclass(frozen=True, slots=True)
31
+ class BaselineLimits:
32
+ """The §2.1 maxima."""
33
+
34
+ zip_bytes: int = 64 * 1024 * 1024
35
+ files: int = 1000
36
+ display_items: int = 3
37
+ manifest_bytes: int = 256 * 1024
38
+ metadata_bytes: int = 64 * 1024
39
+ readme_bytes: int = 1024 * 1024
40
+
41
+
42
+ BASELINE_LIMITS: Final = BaselineLimits()
43
+
44
+
45
+ @cache
46
+ def baseline_license_bytes() -> bytes:
47
+ """Exact bytes of the approved SCI baseline license 0.1 (bundled package data).
48
+
49
+ Raises:
50
+ RuntimeError: the packaged file is damaged (hash mismatch).
51
+ """
52
+ data = resources.files("pysci").joinpath("data/sci-baseline-LICENSE.txt").read_bytes()
53
+ if hashlib.sha256(data).hexdigest() != BASELINE_LICENSE_SHA256:
54
+ raise RuntimeError("bundled baseline license does not match its approved hash")
55
+ return data
56
+
57
+
58
+ @dataclass(frozen=True, slots=True)
59
+ class BaselineReport:
60
+ """Result of evaluating the baseline requirements.
61
+
62
+ ``eligible`` is True only when every limit holds and the license is an
63
+ approved one. ``reasons`` lists each unmet requirement as an issue.
64
+ """
65
+
66
+ eligible: bool
67
+ zip_bytes: int
68
+ reasons: tuple[Issue, ...] = field(default_factory=tuple)
69
+
70
+
71
+ def evaluate_baseline(
72
+ *,
73
+ zip_bytes: int,
74
+ file_count: int,
75
+ display_items: int,
76
+ manifest_bytes: int,
77
+ metadata_bytes: int,
78
+ readme_bytes: int,
79
+ license_sha256: str | None,
80
+ display_problems: Mapping[str, str],
81
+ limits: BaselineLimits = BASELINE_LIMITS,
82
+ ) -> BaselineReport:
83
+ """Evaluate §2.1 for an otherwise valid publication (or a draft plan).
84
+
85
+ ``file_count`` counts the manifest ``files`` entries (metadata, README, and
86
+ LICENSE included; manifest and signature excluded). ``license_sha256`` is
87
+ the SHA-256 of the ``LICENSE`` bytes, or None if there is no LICENSE.
88
+ ``display_problems`` maps each display file outside the §2.1 display
89
+ requirements to the reason (see :func:`pysci.png.display_problem`).
90
+ """
91
+ reasons: list[Issue] = []
92
+
93
+ def check(value: int, maximum: int, code: str, what: str) -> None:
94
+ if value > maximum:
95
+ reasons.append(
96
+ Issue(
97
+ code,
98
+ f"baseline allows at most {maximum:,} {what}",
99
+ section="2.1",
100
+ observed=f"{value:,}",
101
+ )
102
+ )
103
+
104
+ check(zip_bytes, limits.zip_bytes, "baseline_zip_bytes", "bytes in the complete ZIP")
105
+ check(file_count, limits.files, "baseline_file_count", "entries in the manifest files object")
106
+ check(display_items, limits.display_items, "baseline_display_items", "display items")
107
+ check(
108
+ manifest_bytes, limits.manifest_bytes, "baseline_manifest_bytes", "bytes of manifest.json"
109
+ )
110
+ check(metadata_bytes, limits.metadata_bytes, "baseline_metadata_bytes", "bytes of sci.json")
111
+ check(readme_bytes, limits.readme_bytes, "baseline_readme_bytes", "bytes of README.md")
112
+ for path, problem in display_problems.items():
113
+ reasons.append(
114
+ Issue(
115
+ "baseline_display_file",
116
+ "display files must be static PNGs named .png: at most 2 MiB, 8,000,000 pixels, "
117
+ "and 8,192 pixels per side; convert the file or remove it from display",
118
+ section="2.1",
119
+ file=path,
120
+ observed=problem,
121
+ )
122
+ )
123
+ if license_sha256 is None:
124
+ reasons.append(
125
+ Issue(
126
+ "baseline_license_missing",
127
+ "baseline requires LICENSE with the exact bytes of an approved license",
128
+ section="2.1",
129
+ file="LICENSE",
130
+ )
131
+ )
132
+ elif license_sha256 not in APPROVED_LICENSE_SHA256:
133
+ reasons.append(
134
+ Issue(
135
+ "baseline_license_not_approved",
136
+ "LICENSE must be byte-identical to an approved baseline license",
137
+ section="2.1",
138
+ file="LICENSE",
139
+ observed=license_sha256,
140
+ )
141
+ )
142
+ return BaselineReport(eligible=not reasons, zip_bytes=zip_bytes, reasons=tuple(reasons))