openbadgeslib 3.8.0__tar.gz → 3.10.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 (134) hide show
  1. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/Changelog.txt +129 -0
  2. {openbadgeslib-3.8.0/openbadgeslib.egg-info → openbadgeslib-3.10.0}/PKG-INFO +1 -1
  3. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/__init__.py +24 -9
  4. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/badge.py +4 -4
  5. openbadgeslib-3.10.0/openbadgeslib/issue.py +516 -0
  6. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob1/__init__.py +15 -14
  7. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/__init__.py +10 -2
  8. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/credential.py +344 -15
  9. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/verifier.py +99 -1
  10. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/openbadges_publish.py +74 -6
  11. openbadgeslib-3.10.0/openbadgeslib/openbadges_signer.py +473 -0
  12. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/openbadges_verifier.py +33 -3
  13. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/signer.py +4 -4
  14. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/util.py +1 -1
  15. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/verifier.py +4 -4
  16. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0/openbadgeslib.egg-info}/PKG-INFO +1 -1
  17. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib.egg-info/SOURCES.txt +11 -0
  18. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/pyproject.toml +5 -3
  19. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/conformance/test_official_validators.py +71 -0
  20. openbadgeslib-3.10.0/tests/test_baking.py +104 -0
  21. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_cli_json.py +35 -0
  22. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_cli_smoke.py +2 -1
  23. openbadgeslib-3.10.0/tests/test_examples.py +31 -0
  24. openbadgeslib-3.10.0/tests/test_issue.py +252 -0
  25. openbadgeslib-3.10.0/tests/test_keys.py +70 -0
  26. openbadgeslib-3.10.0/tests/test_mail.py +85 -0
  27. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob1_deprecation.py +14 -13
  28. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob1_signer.py +1 -1
  29. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob2_cli.py +46 -6
  30. openbadgeslib-3.10.0/tests/test_ob2_error_branches.py +173 -0
  31. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_conformance_schema.py +31 -0
  32. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_credential.py +222 -0
  33. openbadgeslib-3.10.0/tests/test_ob3_endorsement.py +188 -0
  34. openbadgeslib-3.10.0/tests/test_ob3_ldp_errors.py +123 -0
  35. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_publish_cli.py +82 -0
  36. openbadgeslib-3.10.0/tests/test_signer_batch.py +182 -0
  37. openbadgeslib-3.10.0/tests/test_verifier_cli_coverage.py +284 -0
  38. openbadgeslib-3.8.0/openbadgeslib/openbadges_signer.py +0 -500
  39. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/LICENSE.txt +0 -0
  40. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/MANIFEST.in +0 -0
  41. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/README.md +0 -0
  42. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/SECURITY.md +0 -0
  43. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/docs/README.md +0 -0
  44. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/_jws/__init__.py +0 -0
  45. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/_jws/exceptions.py +0 -0
  46. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/_jws/utils.py +0 -0
  47. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/baking.py +0 -0
  48. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/config.ini.example +0 -0
  49. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/confparser.py +0 -0
  50. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/errors.py +0 -0
  51. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/keys.py +0 -0
  52. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/logs.py +0 -0
  53. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/mail.py +0 -0
  54. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob1/badge.py +0 -0
  55. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob1/signer.py +0 -0
  56. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob1/verifier.py +0 -0
  57. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob2/__init__.py +0 -0
  58. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob2/models.py +0 -0
  59. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob2/signer.py +0 -0
  60. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob2/verifier.py +0 -0
  61. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/contexts/__init__.py +0 -0
  62. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/contexts/credentials-v2.json +0 -0
  63. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.1.json +0 -0
  64. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.2.json +0 -0
  65. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.3.json +0 -0
  66. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/contexts/security-data-integrity-v2.json +0 -0
  67. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/contexts/security-multikey-v1.json +0 -0
  68. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/did.py +0 -0
  69. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/eudi.py +0 -0
  70. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/ldp.py +0 -0
  71. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/signer.py +0 -0
  72. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/status.py +0 -0
  73. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/status_list.py +0 -0
  74. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/ob3/status_registry.py +0 -0
  75. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/openbadges_init.py +0 -0
  76. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/openbadges_keygenerator.py +0 -0
  77. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib/py.typed +0 -0
  78. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib.egg-info/dependency_links.txt +0 -0
  79. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib.egg-info/entry_points.txt +0 -0
  80. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib.egg-info/requires.txt +0 -0
  81. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/openbadgeslib.egg-info/top_level.txt +0 -0
  82. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/setup.cfg +0 -0
  83. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/config1.ini +0 -0
  84. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/conformance/Dockerfile.ob3 +0 -0
  85. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/conformance/README.md +0 -0
  86. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/conformance/conftest.py +0 -0
  87. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/conformance/docker-compose.yml +0 -0
  88. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/conftest.py +0 -0
  89. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/fixtures/ob_schemas/README.md +0 -0
  90. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/fixtures/ob_schemas/ob_v3p0_achievementcredential_schema.json +0 -0
  91. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/fixtures/ob_schemas/ob_v3p0_profile_schema.json +0 -0
  92. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/fixtures/ob_schemas/refresh.sh +0 -0
  93. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/fixtures/vc_di_eddsa/README.md +0 -0
  94. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/fixtures/vc_di_eddsa/credentials-examples-v2.json +0 -0
  95. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/fixtures/vc_di_eddsa/doc-hash.txt +0 -0
  96. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/fixtures/vc_di_eddsa/key-pair.json +0 -0
  97. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/fixtures/vc_di_eddsa/proof-config-hash.txt +0 -0
  98. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/fixtures/vc_di_eddsa/signed-credential.json +0 -0
  99. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/images/sample1.png +0 -0
  100. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/images/sample1.svg +0 -0
  101. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/images/userimage01.svg +0 -0
  102. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/ldp_helpers.py +0 -0
  103. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/logo Python Espan/314/203a.svg" +0 -0
  104. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/logo Python Espa/303/261a.svg" +0 -0
  105. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_confparser.py +0 -0
  106. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_docs.py +0 -0
  107. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_eddsa.py +0 -0
  108. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_jws.py +0 -0
  109. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_key_operation.py +0 -0
  110. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob1_badge_io.py +0 -0
  111. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob1_verifier.py +0 -0
  112. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob2_models.py +0 -0
  113. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob2_signer.py +0 -0
  114. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob2_verifier.py +0 -0
  115. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_contexts.py +0 -0
  116. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_did.py +0 -0
  117. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_did_doc.py +0 -0
  118. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_eudi_sd_jwt.py +0 -0
  119. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_ldp.py +0 -0
  120. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_ldp_cli.py +0 -0
  121. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_ldp_sign.py +0 -0
  122. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_signer.py +0 -0
  123. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_signer_cli_ldp.py +0 -0
  124. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_status.py +0 -0
  125. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_status_list.py +0 -0
  126. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_status_registry.py +0 -0
  127. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_ob3_verifier.py +0 -0
  128. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_sign_ecc.pem +0 -0
  129. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_sign_rsa.pem +0 -0
  130. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_util.py +0 -0
  131. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_verify_ecc.pem +0 -0
  132. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/test_verify_rsa.pem +0 -0
  133. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/withoutxmlheader.svg +0 -0
  134. {openbadgeslib-3.8.0 → openbadgeslib-3.10.0}/tests/withxmlheader.svg +0 -0
@@ -4,6 +4,135 @@ OpenBadgesLib - Changelog
4
4
  Newest first. Dates are ISO 8601 (YYYY-MM-DD).
5
5
 
6
6
 
7
+ * v3.10.0 - 2026-07-08
8
+
9
+ - feat(publish): `openbadges-publish -V 3 --check-live` completes #164. After
10
+ (re)publishing, it downloads each written artifact — `did.json`, the signed
11
+ status lists, `verify.pem` — from `publish_url` and byte-compares it against
12
+ the local copy, turning the "re-upload so the change takes effect" reminder
13
+ into a verifiable guarantee. Exit 2 (`--json`, listed in `live_check.stale`)
14
+ or 1 (human) if any artifact is stale or missing on the server. The rest of
15
+ #164 (status-list `validUntil` + opt-in list-proof verification + issuer
16
+ binding) shipped in v3.8.0. (Closes #164)
17
+
18
+ - docs(examples): #168 library integration tutorial + runnable examples. New
19
+ `examples/` scripts — issue+verify an OB 3.0 JWT-VC, issue with a Data
20
+ Integrity (LDP) proof, and issue from a config section via the high-level API
21
+ (single + batch) — each self-contained and executed in CI (`test_examples.py`,
22
+ anti-drift, same philosophy as `test_docs.py`). Two new wiki pages: the
23
+ "Library Integration Tutorial" narrating them end to end (issue → sign →
24
+ publish → revoke → verify) and a "Certification Cookbook" on passing the
25
+ official 1EdTech OB 3.0 conformance tests without the paid membership.
26
+ (Closes #168)
27
+
28
+ - test(coverage): #169 targeted-coverage round. New dedicated suites for the
29
+ shared token carrier (`test_baking.py` — embed/extract round-trips per
30
+ format, compression and malformed-iTXt edges), mail (`test_mail.py` — SMTP
31
+ mocked) and the key helpers (`test_keys.py`), plus error-branch coverage for
32
+ the OB2 signer/verifier, the OB3 Data Integrity (LDP) path and the verifier
33
+ CLI (`openbadges_verifier.py` 74% → 95%). Total coverage crosses 93% and the
34
+ enforced `fail_under` floor is raised from 91 to 93. An opt-in OB2
35
+ **SignedBadge** conformance case joins the existing hosted one (the
36
+ crypto-sensitive path, Docker-gated). (Closes #169)
37
+
38
+ - docs(ob1): OpenBadges 1.0 is reclassified from "deprecated, removal in
39
+ 4.0.0" to a **supported legacy surface with no removal planned**. Removing a
40
+ working, dependency-free legacy path (OB1 has carried no unique dependencies
41
+ since #167) was judged excessive; OB1 stays fully functional and maintained,
42
+ simply superseded by OB 2.0/3.0 for new work. The library
43
+ `DeprecationWarning`s and the `-V 1` CLI notices are reworded to steer new
44
+ work to OB2/OB3 without promising removal, and the "OpenBadges 1.0
45
+ lifecycle" wiki page records the policy — including why a `[legacy]`/`[ob1]`
46
+ install extra stays rejected: a Python extra gates *dependencies*, not code,
47
+ and OB1 has none to gate (it would install the same bytes). Reverses the
48
+ removal side of #159/#170.
49
+
50
+ - feat(cli): batch issuance — `openbadges-signer` now issues to many recipients
51
+ in one run. Repeat `-r EMAIL`, or pass `--recipients-file FILE` (one email per
52
+ line or comma-separated; blank lines and `#`-comments ignored); a single `-r`
53
+ keeps the historical single-badge behaviour byte-for-byte. For a revocable OB3
54
+ badge every status-list index is allocated in a SINGLE registry transaction
55
+ (load once, allocate N, save once) rather than N load/save cycles — the piece
56
+ that makes thousands-scale issuance practical. An already-existing output file
57
+ is skipped (batch) or aborts (single) unless the new `--force`/`--overwrite`
58
+ overwrites it, and one bad recipient does not abort the rest. `--json` emits a
59
+ per-recipient summary `{signed, skipped, failed}` under the shared exit
60
+ contract (0 all signed, 2 some skipped/failed, 1 a batch-level error). The
61
+ library entry point is `openbadgeslib.issue.issue_batch_from_conf`, returning
62
+ one `BatchResult` per recipient. (Closes #165)
63
+
64
+ - feat(api): extract the issuance orchestration from the CLI into a reusable
65
+ library API — the single biggest adoption gap. `openbadgeslib.issue` exposes
66
+ `issue_from_conf(conf, badge, recipient, ob_version, ...)` (also re-exported
67
+ as `openbadgeslib.issue_from_conf`), returning a `SignResult` dataclass with
68
+ the signed badge bytes and metadata and doing no user-facing I/O — no
69
+ prints, no sys.exit, no file write; a config or policy problem raises
70
+ `IssuanceError`. The ~230 lines of business logic that lived in
71
+ openbadges-signer (credential construction from config, salt generation, the
72
+ OB2 hosted-vs-signed decision, the OB3 status-registry→sign transactional
73
+ order and the Data Integrity verificationMethod policy — did:web trusted vs
74
+ a derived self-asserted did:key) now live there and are unit-tested
75
+ directly, not only reachable by driving the CLI. The signer entrypoints are
76
+ reduced to flag parsing, I/O and display over that API; behaviour — including
77
+ the historical per-version exit conventions — is unchanged. Prerequisite for
78
+ batch signing (#165) and any unified CLI. OpenBadges 1.0 issuance stays
79
+ CLI-only (it is deprecated). (Closes #160)
80
+
81
+ - feat(ob3): complete the broadened OB 3.0 credential model started in
82
+ v3.9.0 — closing the remaining data-model gap versus the spec. New `Result`
83
+ (a measured outcome: value, status, achievedLevel and the linked
84
+ resultDescription), `ResultDescription` (an achievement's possible results:
85
+ resultType, allowedValue, valueMin/valueMax, requiredValue/requiredLevel)
86
+ and `IdentityObject` (a hashed or plaintext `credentialSubject.identifier`,
87
+ the alternative to `credentialSubject.id`) dataclasses, plus `creditsEarned`
88
+ on the subject and a `resultDescription` list on `Achievement`. All follow
89
+ the house style (dataclasses + mypy, no Pydantic), parse from a verified
90
+ credential, round-trip through `to_vc`, and satisfy the offline conformance
91
+ schema — a fully-populated credential (identifier-only subject, result +
92
+ linked resultDescription, alignment, credits) validates against 1EdTech's
93
+ AchievementCredential schema. `openbadges-verifier` now surfaces the
94
+ broadened fields — achievementType, creditsAvailable/creditsEarned and the
95
+ alignment/result/identifier counts — in both `--json` and `--show`. Two
96
+ round-trip fixes fall out: a subject conveying identity solely through
97
+ `identifier` now re-serialises with it (the identifier was silently dropped
98
+ on parse before), and a lone `alignment` object not wrapped in an array is
99
+ no longer discarded. (Closes #162)
100
+
101
+ - fix(cli): `openbadges-publish -V 1/-V 2` now exits with a clear message when
102
+ the `[issuer]` section is missing `publish_url` or `revocationList`, instead
103
+ of raising a raw `KeyError` traceback partway through — after the output
104
+ directory had already been created. The OB3 path already validated this; the
105
+ hosted OB1/OB2 paths now share the same guard. Last item of the quick-fix
106
+ round. (Closes #156)
107
+
108
+
109
+ * v3.9.0 - 2026-07-08
110
+
111
+ - feat(ob3): broaden the credential data model toward the OB 3.0 spec — the
112
+ gap versus the spec was the model, not the crypto. New `Evidence` (the full
113
+ `evidence` array: narrative, name, description, genre, audience — beyond the
114
+ single `evidence_url`) and `Alignment` (competency-framework mappings, what
115
+ LMSes read to map a badge to a competency) dataclasses, plus
116
+ `achievementType`, `creditsAvailable` and an `alignments` list on
117
+ `Achievement`. All are validated in the house style (dataclasses + mypy, no
118
+ Pydantic), parse from a verified credential, round-trip through `to_vc`, and
119
+ still satisfy the offline conformance schema. (Refs #162 —
120
+ Result/ResultDescription, IdentityObject and creditsEarned follow.)
121
+
122
+ - feat(ob3): support OpenBadges 3.0 `endorsementJwt` (spec errata v1.6, added
123
+ to context 3.0.3). Third-party endorsement JWTs were passing through as
124
+ opaque, unmapped fields; the credential model now exposes them at the three
125
+ levels the spec allows — `credential.endorsement_jwts`,
126
+ `issuer.endorsement_jwts`, `achievement.endorsement_jwts`, gathered by
127
+ `credential.all_endorsement_jwts()` — and round-trips them through `to_vc`.
128
+ A new `ob3.verify_endorsement_jwt(token, download=None,
129
+ endorser_pubkey_pem=None)` verifies an EndorsementCredential JWT under the
130
+ *endorser's* key (resolved from its own issuer did:web/did:key, or an
131
+ explicit key when the endorser is not a DID), checking the
132
+ EndorsementCredential type and its validFrom/validUntil window.
133
+ `openbadges-verifier --json` now reports the `endorsements` count. (#161)
134
+
135
+
7
136
  * v3.8.0 - 2026-07-08
8
137
 
9
138
  - security(ob3): status lists gain replay protection and opt-in proof
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openbadgeslib
3
- Version: 3.8.0
3
+ Version: 3.10.0
4
4
  Summary: Production-ready Python library & CLI for Open Badges 3.0 (W3C Verifiable Credentials): issue, verify, and revoke JWT-VC credentials with Bitstring Status Lists and did:web — plus strict OB 2.0 and legacy OB 1.0.
5
5
  Author-email: Luis González Fernández <luisgf@luisgf.es>, Jesús Cea Avión <jcea@jcea.es>
6
6
  License: LGPLv3
@@ -39,13 +39,23 @@ from .keys import KeyFactory, KeyRSA, KeyECC # noqa: F401
39
39
  from .util import __version__ # noqa: F401
40
40
 
41
41
 
42
+ # ── Issuance API ─────────────────────────────────────────────────────────────
43
+ # "Issue badge X to Y per config" as a library call — the orchestration the CLI
44
+ # wraps, returning a SignResult instead of writing files (openbadgeslib.issue).
45
+ # Resolved lazily (PEP 562, below) because openbadgeslib.issue pulls in the
46
+ # shared Badge model from the ob1 leaf module; a bare `import openbadgeslib`
47
+ # must not drag that in. `from openbadgeslib.issue import ...` works directly.
48
+ _ISSUE_API = ('IssuanceError', 'SignResult', 'issue_from_conf')
49
+
50
+
42
51
  # ── OpenBadges 1.0 (legacy) ──────────────────────────────────────────────────
43
52
  # The unprefixed OB1 names (Signer, Verifier, Badge, …) stay importable from
44
- # the top-level package for backward compatibility, but they are the legacy
45
- # OpenBadges 1.0 surface: accessing one now emits a DeprecationWarning
46
- # (removal in 4.0.0). Modern code uses openbadgeslib.ob2 / openbadgeslib.ob3.
47
- # They are resolved lazily from the ob1 leaf modules (PEP 562), so a bare
48
- # `import openbadgeslib` neither warns nor drags in the ob1 package.
53
+ # the top-level package for backward compatibility. They are the legacy
54
+ # OpenBadges 1.0 surface: accessing one emits a DeprecationWarning steering new
55
+ # work to openbadgeslib.ob2 / openbadgeslib.ob3. OB 1.0 itself remains supported
56
+ # (no removal planned). They are resolved lazily from the ob1 leaf modules (PEP
57
+ # 562), so a bare `import openbadgeslib` neither warns nor drags in the ob1
58
+ # package.
49
59
  _OB1_API = {
50
60
  'Signer': 'signer', 'Verifier': 'verifier', 'VerifyInfo': 'verifier',
51
61
  'Badge': 'badge', 'BadgeSigned': 'badge', 'Assertion': 'badge',
@@ -55,6 +65,11 @@ _OB1_API = {
55
65
 
56
66
 
57
67
  def __getattr__(name: str) -> Any:
68
+ if name in _ISSUE_API:
69
+ # The modern issuance API — lazy so a bare import stays ob1-free, but
70
+ # warning-free (unlike the OB1 names below).
71
+ import importlib
72
+ return getattr(importlib.import_module('.issue', __name__), name)
58
73
  module = _OB1_API.get(name)
59
74
  if module is None:
60
75
  raise AttributeError(
@@ -62,9 +77,9 @@ def __getattr__(name: str) -> Any:
62
77
  import importlib
63
78
  import warnings
64
79
  warnings.warn(
65
- 'openbadgeslib.%s is the legacy OpenBadges 1.0 API and is deprecated; '
66
- 'it will be removed in openbadgeslib 4.0.0. Use openbadgeslib.ob2 '
67
- '(strict OB 2.0) or openbadgeslib.ob3 (OB 3.0). See the "OpenBadges '
68
- '1.0 lifecycle" wiki page.' % name,
80
+ 'openbadgeslib.%s is the legacy OpenBadges 1.0 API; prefer '
81
+ 'openbadgeslib.ob2 (strict OB 2.0) or openbadgeslib.ob3 (OB 3.0) for '
82
+ 'new work. OpenBadges 1.0 remains supported (no removal planned). See '
83
+ 'the "OpenBadges 1.0 lifecycle" wiki page.' % name,
69
84
  DeprecationWarning, stacklevel=2)
70
85
  return getattr(importlib.import_module('.ob1.' + module, __name__), name)
@@ -8,10 +8,10 @@ from .ob1.badge import (
8
8
  )
9
9
 
10
10
  warnings.warn(
11
- 'openbadgeslib.badge is a legacy OpenBadges 1.0 compatibility shim and is '
12
- 'deprecated; it will be removed in openbadgeslib 4.0.0. Import from '
13
- 'openbadgeslib.ob2 (strict OB 2.0) or openbadgeslib.ob3 (OB 3.0) instead. '
14
- 'See the "OpenBadges 1.0 lifecycle" wiki page.',
11
+ 'openbadgeslib.badge is a legacy OpenBadges 1.0 compatibility shim; prefer '
12
+ 'openbadgeslib.ob2 (strict OB 2.0) or openbadgeslib.ob3 (OB 3.0) for new '
13
+ 'work. OpenBadges 1.0 remains supported (no removal planned). See the '
14
+ '"OpenBadges 1.0 lifecycle" wiki page.',
15
15
  DeprecationWarning, stacklevel=2)
16
16
 
17
17
  __all__ = [