asciichem 0.31.14 → 0.31.15

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 380b64ab12e3ccc18a554a5d1dc7793046bfa7ea5ba80d51330bb5d713ea618f
4
- data.tar.gz: 4b3ec1535d5854626b5e2976adb060b4432bfb7f9d20331e79717ae0db4cdf73
3
+ metadata.gz: 3246a177690e1019834ab1e177da12f75aee9a7a75cdaa6caecaa3e7a721b227
4
+ data.tar.gz: e2194852b615ef5e61ddea0726e8b279543c6cbad326991a6238adf791e8d87c
5
5
  SHA512:
6
- metadata.gz: 49ca402649027d0091acb25b77848e46a9ffaa5bf876e16512f14378d507a3bf32a6dbbb6245967bc54d1eb6728f0672a530db0e92c8db476ca9bfbdb87497f6
7
- data.tar.gz: 1741914b553ebe4f1828fa3a294c3ed0249a22072dd90f57185d92aafea717a97eaded37236c97ac3b406032f2919cd449818ba6cf7a6094fdd37a81c7f5c44e
6
+ metadata.gz: 9fb5e64c26d57111cb9a742388228e225a99da2965d5ac7793cf8ec2366367a3c3b4ed24c14aab1c173f03d324309bd0f34919c52d29a0ee569cb34de88d1012
7
+ data.tar.gz: cc604167f949bac08b2f47c1dd6ec69e76d1b85ec9d2158ed7d0004a1c0f60c368ec5c1c5a6544fec68f6cc9f71ffec197d1fc2eb165c7da3ea6062e039db7f1
data/CHANGELOG.md CHANGED
@@ -3,6 +3,19 @@
3
3
  All notable changes to AsciiChem are documented here.
4
4
  This project follows [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [0.31.15] - 2026-11-12
7
+
8
+ ### Fixed
9
+ - Common Chemistry speaks its real key contract (CAS's access
10
+ email): `GET /direct-api/detail` with the token as the
11
+ `X-API-KEY` header plus `x-origin` and `accept` headers — not
12
+ the `apikey` query parameter 0.31.14 guessed. The key resolves
13
+ from `COMMON_CHEMISTRY_API_KEY` or the token's stored location
14
+ (`~/.config/cas-common-chemistry-api.key`); explicit nil means
15
+ no key. Verified live: formaldehyde resolves from production.
16
+ - The fetch contract grows `get(url, headers = {})`; redirects
17
+ keep the headers.
18
+
6
19
  ## [0.31.14] - 2026-11-12
7
20
 
8
21
  ### Added
data/README.adoc CHANGED
@@ -104,8 +104,10 @@ AsciiChem::Resolver.resolve(value: "50-78-2", convention: "cas",
104
104
  ----
105
105
 
106
106
  CAS key-gated the Common Chemistry API: live lookups there need a
107
- key (request one at cas.org/services/commonchemistry-api) exported
108
- as `COMMON_CHEMISTRY_API_KEY`. Without it, that source answers 401
107
+ personal token (request one at cas.org/services/commonchemistry-api,
108
+ valid 12 months). Export it as `COMMON_CHEMISTRY_API_KEY` or store
109
+ it at `~/.config/cas-common-chemistry-api.key` — the adapter sends
110
+ it as the `X-API-KEY` header. Without a key, that source answers 401
109
111
  and is skipped — PubChem and OPSIN keep resolving.
110
112
 
111
113
  === Citations: dataset bibitems with provenance
@@ -78,18 +78,19 @@ module AsciiChem
78
78
 
79
79
  # Minimal stdlib HTTP transport. No runtime gem dependencies.
80
80
  class NetFetch
81
- def get(url, limit = 3)
81
+ def get(url, headers = {}, limit = 3)
82
82
  raise Error, "too many redirects" if limit.zero?
83
83
 
84
84
  uri = URI(url)
85
+ merged = { "User-Agent" => "asciichem-resolver" }.merge(headers)
85
86
  response = Net::HTTP.start(uri.host, uri.port, use_ssl: true,
86
87
  open_timeout: 5, read_timeout: 10) do |http|
87
- http.request(Net::HTTP::Get.new(uri.request_uri, "User-Agent" => "asciichem-resolver"))
88
+ http.request(Net::HTTP::Get.new(uri.request_uri, merged))
88
89
  end
89
90
 
90
91
  case response
91
92
  when Net::HTTPSuccess then response.body
92
- when Net::HTTPRedirection then get(URI.join(uri, response["location"]).to_s, limit - 1)
93
+ when Net::HTTPRedirection then get(URI.join(uri, response["location"]).to_s, headers, limit - 1)
93
94
  when Net::HTTPNotFound then nil
94
95
  else
95
96
  raise Error, "HTTP #{response.code}: #{response.message}"
@@ -16,30 +16,40 @@ module AsciiChem
16
16
  # `Resolver.resolve(sources:)`.
17
17
  #
18
18
  # API access: CAS key-gated the API (401 without a key; request
19
- # one at cas.org/services/commonchemistry-api — the exact contract
20
- # arrives with the access email). The adapter sends the key as the
21
- # `apikey` query parameter; set it per instance (`api_key:`) or
22
- # through COMMON_CHEMISTRY_API_KEY, which the resolver's own
23
- # instantiation picks up. Without a key, live lookups answer 401
24
- # and the resolver skips this source (the other sources still
25
- # resolve; seeded caches keep working).
19
+ # one at cas.org/services/commonchemistry-api). The access email
20
+ # defines the contract: GET /direct-api/detail with the token as
21
+ # the X-API-KEY header plus x-origin and accept headers. The key
22
+ # comes per instance (`api_key:`), from
23
+ # COMMON_CHEMISTRY_API_KEY, or from
24
+ # ~/.config/cas-common-chemistry-api.key (the token's stored
25
+ # location). Tokens are personal and expire after 12 months
26
+ # (CAS emails a renewal notice). Without a key, live lookups
27
+ # answer 401 and the resolver skips this source; seeded caches
28
+ # keep working.
26
29
  class CommonChemistry < Adapter
27
30
  source_name :common_chemistry
28
31
  supports "cas"
29
32
 
30
- BASE = "https://commonchemistry.cas.org/api/detail"
33
+ BASE = "https://commonchemistry.cas.org/direct-api/detail"
34
+ ORIGIN = "https://commonchemistry.cas.org/api-overview"
35
+ KEY_FILE = File.expand_path("~/.config/cas-common-chemistry-api.key")
31
36
 
32
37
  attr_reader :api_key
33
38
 
34
- def initialize(api_key: ENV.fetch("COMMON_CHEMISTRY_API_KEY", nil))
39
+ # Explicit nil means no key; the env/file lookup runs only when
40
+ # the argument is omitted.
41
+ def initialize(api_key: default_api_key)
35
42
  super()
36
43
  @api_key = api_key
37
44
  end
38
45
 
39
46
  def fetch_record(value:, convention:, fetch:)
40
- url = "#{BASE}?cas_rn=#{CGI.escape(value.to_s)}"
41
- url = "#{url}&apikey=#{CGI.escape(api_key)}" if api_key
42
- body = fetch.get(url)
47
+ headers = {
48
+ "Accept" => "application/json",
49
+ "x-origin" => ORIGIN
50
+ }
51
+ headers["X-API-KEY"] = api_key if api_key
52
+ body = fetch.get("#{BASE}?cas_rn=#{CGI.escape(value.to_s)}", headers)
43
53
  return nil unless body
44
54
 
45
55
  data = JSON.parse(body)
@@ -56,6 +66,11 @@ module AsciiChem
56
66
 
57
67
  private
58
68
 
69
+ def default_api_key
70
+ ENV.fetch("COMMON_CHEMISTRY_API_KEY", nil) ||
71
+ (File.read(KEY_FILE).strip if File.readable?(KEY_FILE))
72
+ end
73
+
59
74
  def substance_from(data)
60
75
  provenance = default_provenance
61
76
  identifiers = [Identifier.new(value: data["rn"], convention: "cas",
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AsciiChem
4
- VERSION = "0.31.14"
4
+ VERSION = "0.31.15"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asciichem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.31.14
4
+ version: 0.31.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.