We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
JSON-LD context does not expand as intended
https://purl.obolibrary.org/meta/obo_context.jsonld

We can't have trailing underscores on expansions (and have it behave as expected). Sorry, I don't make the rules

More context here:

- https://github.com/w3c/json-ld-syntax/issues/329

These would all have to be modified to be nested

```json
      "RO": {
         "@id": "http://purl.obolibrary.org/obo/RO_",
         "@prefix": true
      }
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `util/processor.py`
Content:
```
1 #!/usr/bin/env python3
2 
3 import argparse
4 import datetime
5 import logging
6 import sys
7 import time
8 from contextlib import closing
9 from json import dumps
10 
11 import requests
12 import yaml
13 from SPARQLWrapper import JSON, SPARQLWrapper
14 
15 __author__ = "cjm"
16 
17 
18 def main():
19     parser = argparse.ArgumentParser(
20         description="Helper utils for OBO",
21         formatter_class=argparse.RawTextHelpFormatter,
22     )
23     parser.add_argument(
24         "-i", "--input", type=str, required=False, help="Input metadata file"
25     )
26     parser.add_argument(
27         "-v",
28         "--verbosity",
29         default=0,
30         action="count",
31         help="Increase output verbosity (min.: 0, max. 2)",
32     )
33     subparsers = parser.add_subparsers(dest="subcommand", help="sub-command help")
34 
35     # SUBCOMMAND
36     parser_n = subparsers.add_parser("check-urls", help="Ensure PURLs resolve")
37     parser_n.set_defaults(function=check_urls)
38 
39     parser_n = subparsers.add_parser(
40         "sparql-compare",
41         help="Run SPARQL commands against the db to generate a " "consistency report",
42     )
43     parser_n.set_defaults(function=sparql_compare_all)
44 
45     parser_n = subparsers.add_parser("extract-context", help="Extracts JSON-LD context")
46     parser_n.set_defaults(function=extract_context)
47 
48     parser_n = subparsers.add_parser(
49         "extract-contributors",
50         help="Queries github API for metadata about contributors",
51     )
52     parser_n.set_defaults(function=write_all_contributors)
53 
54     args = parser.parse_args()
55     if args.verbosity >= 2:
56         logging.basicConfig(level=logging.DEBUG)
57     elif args.verbosity == 1:
58         logging.basicConfig(level=logging.INFO)
59     else:
60         logging.basicConfig(level=logging.WARNING)
61 
62     with open(args.input, "r") as f:
63         obj = yaml.load(f, Loader=yaml.SafeLoader)
64         ontologies = obj["ontologies"]
65 
66     func = args.function
67     func(ontologies, args)
68 
69 
70 def check_urls(ontologies, args):
71     """
72     Ensure PURLs resolve
73     """
74 
75     def test_url(url):
76         try:
77             with closing(requests.get(url, stream=False)) as resp:
78                 return resp.status_code == 200
79         except requests.exceptions.InvalidSchema as e:
80             # TODO: requests lib doesn't handle ftp. For now simply return True in that case.
81             if not format(e).startswith("No connection adapters were found for 'ftp:"):
82                 raise
83             return True
84 
85     failed_ids = []
86     for ont in ontologies:
87         for p in ont.get("products", []):
88             pid = p["id"]
89             if not test_url(p.get("ontology_purl")):
90                 failed_ids.append(pid)
91     if len(failed_ids) > 0:
92         print("FAILURES:")
93         for pid in failed_ids:
94             print(pid, file=sys.stderr)
95         exit(1)
96 
97 
98 def extract_context(ontologies, args):
99     """
100     Writes to STDOUT a sorted JSON map from ontology prefixes to PURLs
101     """
102 
103     def has_obo_prefix(obj):
104         return ("uri_prefix" not in obj) or (
105             obj["uri_prefix"] == "http://purl.obolibrary.org/obo/"
106         )
107 
108     prefix_map = {}
109     for obj in ontologies:
110         if has_obo_prefix(obj):
111             prefix = obj.get("preferredPrefix") or obj["id"].upper()
112             prefix_map[prefix] = "http://purl.obolibrary.org/obo/" + prefix + "_"
113 
114     print(
115         dumps(
116             {"@context": prefix_map}, sort_keys=True, indent=4, separators=(",", ": ")
117         )
118     )
119 
120 
121 def write_all_contributors(ontologies, args):
122     """
123     Query github API for all contributors to an ontology,
124     write results as json
125     """
126     results = []
127     for ont_obj in ontologies:
128         id = ont_obj["id"]
129         logging.info("Getting info for {}".format(id))
130         repo_path = get_repo_path(ont_obj)
131         if repo_path is not None:
132             contribs = list(get_ontology_contributors(repo_path))
133             print("CONTRIBS({})=={}".format(id, contribs))
134             for c in contribs:
135                 print("#{}\t{}\n".format(id, c["login"]))
136             results.append(dict(id=id, contributors=contribs))
137         else:
138             logging.warn("No repo_path declared for {}".format(id))
139     print(dumps(results, sort_keys=True, indent=4, separators=(",", ": ")))
140 
141 
142 def get_ontology_contributors(repo_path):
143     """
144     Get individual contributors to a org/repo_path
145     repo_path is a string "org/repo"
146     """
147     url = "https://api.github.com/repos/{}/contributors".format(repo_path)
148     # TODO: allow use of oauth token;
149     # GH has a quota for non-logged in API calls
150     time.sleep(3)
151     with closing(requests.get(url, stream=False)) as resp:
152         ok = resp.status_code == 200
153         if ok:
154             results = resp.json()
155             logging.info("RESP={}".format(results))
156             return results
157         else:
158             logging.error("Failed: {}".format(url))
159             return []
160 
161 
162 def get_repo_path(ont_obj):
163     """
164     Extract the repository path for the given object
165     """
166     repo_path = None
167     if "repository" in ont_obj:
168         repo_path = ont_obj["repository"]
169     elif "tracker" in ont_obj:
170         tracker = ont_obj["tracker"]
171         if tracker is not None and "github" in tracker:
172             repo_path = tracker.replace("/issues", "")
173 
174     if repo_path is not None:
175         repo_path = repo_path.replace("https://github.com/", "")
176         if repo_path.endswith("/"):
177             repo_path = repo_path[:-1]
178         return repo_path
179     else:
180         logging.warn("Could not get gh repo_path for ".format(ont_obj))
181         return None
182 
183 
184 def run_sparql(obj, p, expected_value, q):
185     """
186     Generate a SPARQL statement using query q and parameter p, and expect 'expected_value' as the
187     result. Print out a message indicating whether the there is or is not a match for the given object
188     """
189     sparql = SPARQLWrapper("http://sparql.hegroup.org/sparql")
190     sparql.setQuery(q)
191     sparql.setReturnFormat(JSON)
192     results = sparql.query().convert()
193 
194     id = obj["id"]
195     got_value = False
196     is_match = False
197     vs = []
198 
199     for result in results["results"]["bindings"]:
200         got_value = True
201         v = result[p]["value"]
202         vs.append(str(v))
203         if v == expected_value:
204             is_match = True
205 
206     if got_value and is_match:
207         msg = "CONSISTENT"
208     elif got_value and not is_match:
209         if expected_value == "":
210             msg = "UNDECLARED_LOCAL: REMOTE:" + ",".join(vs)
211         else:
212             msg = "INCONSISTENT: REMOTE:" + ",".join(vs) + " != LOCAL:" + expected_value
213     else:
214         msg = "UNDECLARED_REMOTE"
215     print(id + " " + p + " " + msg)
216 
217 
218 def sparql_compare_ont(obj):
219     """
220     Some ontologies will directly declare some subset of the OBO metadata
221     directly in the ontology header. In the majority of cases we should
222     yield to the provider. However, we reserve the right to override. For
223     example, OBO may have particular guidelines about the length of the title,
224     required for coherency within the registry. All differences should be
225     discussed with the provider and an accomodation reached
226     """
227     if "ontology_purl" not in obj:
228         return
229 
230     purl = obj["ontology_purl"]
231     # this could be made more declarative, or driven by the context.jsonld mapping;
232     # however, for now this is relatively simple and easy to understand:
233     run_sparql(
234         obj,
235         "license",
236         obj["license"]["url"] if "license" in obj else "",
237         "SELECT DISTINCT ?license WHERE {<"
238         + purl
239         + "> <http://purl.org/dc/elements/1.1/license> ?license}",
240     )
241     run_sparql(
242         obj,
243         "title",
244         obj["title"] if "title" in obj else "",
245         "SELECT DISTINCT ?title WHERE {<"
246         + purl
247         + "> <http://purl.org/dc/elements/1.1/title> ?title}",
248     )
249     run_sparql(
250         obj,
251         "description",
252         obj["description"] if "description" in obj else "",
253         "SELECT DISTINCT ?description WHERE {<"
254         + purl
255         + "> <http://purl.org/dc/elements/1.1/description> ?description}",
256     )
257     run_sparql(
258         obj,
259         "homepage",
260         obj["homepage"] if "homepage" in obj else "",
261         "SELECT DISTINCT ?homepage WHERE {<"
262         + purl
263         + "> <http://xmlns.com/foaf/0.1/homepage> ?homepage}",
264     )
265 
266 
267 def sparql_compare_all(ontologies, args):
268     """
269     Run sparql_compare_ont() on all the given ontologies.
270     """
271     # The `args` parameter is not used here but it is convenient to have it in our definition, since
272     # whether this function or one of the other main `subcommands` of this script is called is
273     # determine dynamically, and we want all of the subcommands to have a consistent signature.
274     for obj in ontologies:
275         sparql_compare_ont(obj)
276 
277 
278 if __name__ == "__main__":
279     main()
280
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/util/processor.py b/util/processor.py
--- a/util/processor.py
+++ b/util/processor.py
@@ -109,7 +109,10 @@
     for obj in ontologies:
         if has_obo_prefix(obj):
             prefix = obj.get("preferredPrefix") or obj["id"].upper()
-            prefix_map[prefix] = "http://purl.obolibrary.org/obo/" + prefix + "_"
+            prefix_map[prefix] = {
+                "@id": "http://purl.obolibrary.org/obo/" + prefix + "_",
+                "@prefix": True,
+            }
 
     print(
         dumps(

{"golden_diff": "diff --git a/util/processor.py b/util/processor.py\n--- a/util/processor.py\n+++ b/util/processor.py\n@@ -109,7 +109,10 @@\n     for obj in ontologies:\n         if has_obo_prefix(obj):\n             prefix = obj.get(\"preferredPrefix\") or obj[\"id\"].upper()\n-            prefix_map[prefix] = \"http://purl.obolibrary.org/obo/\" + prefix + \"_\"\n+            prefix_map[prefix] = {\n+                \"@id\": \"http://purl.obolibrary.org/obo/\" + prefix + \"_\",\n+                \"@prefix\": True,\n+            }\n \n     print(\n         dumps(\n", "issue": "JSON-LD context does not expand as intended\nhttps://purl.obolibrary.org/meta/obo_context.jsonld\r\n\r\nWe can't have trailing underscores on expansions (and have it behave as expected). Sorry, I don't make the rules\r\n\r\nMore context here:\r\n\r\n- https://github.com/w3c/json-ld-syntax/issues/329\r\n\r\nThese would all have to be modified to be nested\r\n\r\n```json\r\n      \"RO\": {\r\n         \"@id\": \"http://purl.obolibrary.org/obo/RO_\",\r\n         \"@prefix\": true\r\n      }\r\n```\n", "before_files": [{"content": "#!/usr/bin/env python3\n\nimport argparse\nimport datetime\nimport logging\nimport sys\nimport time\nfrom contextlib import closing\nfrom json import dumps\n\nimport requests\nimport yaml\nfrom SPARQLWrapper import JSON, SPARQLWrapper\n\n__author__ = \"cjm\"\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        description=\"Helper utils for OBO\",\n        formatter_class=argparse.RawTextHelpFormatter,\n    )\n    parser.add_argument(\n        \"-i\", \"--input\", type=str, required=False, help=\"Input metadata file\"\n    )\n    parser.add_argument(\n        \"-v\",\n        \"--verbosity\",\n        default=0,\n        action=\"count\",\n        help=\"Increase output verbosity (min.: 0, max. 2)\",\n    )\n    subparsers = parser.add_subparsers(dest=\"subcommand\", help=\"sub-command help\")\n\n    # SUBCOMMAND\n    parser_n = subparsers.add_parser(\"check-urls\", help=\"Ensure PURLs resolve\")\n    parser_n.set_defaults(function=check_urls)\n\n    parser_n = subparsers.add_parser(\n        \"sparql-compare\",\n        help=\"Run SPARQL commands against the db to generate a \" \"consistency report\",\n    )\n    parser_n.set_defaults(function=sparql_compare_all)\n\n    parser_n = subparsers.add_parser(\"extract-context\", help=\"Extracts JSON-LD context\")\n    parser_n.set_defaults(function=extract_context)\n\n    parser_n = subparsers.add_parser(\n        \"extract-contributors\",\n        help=\"Queries github API for metadata about contributors\",\n    )\n    parser_n.set_defaults(function=write_all_contributors)\n\n    args = parser.parse_args()\n    if args.verbosity >= 2:\n        logging.basicConfig(level=logging.DEBUG)\n    elif args.verbosity == 1:\n        logging.basicConfig(level=logging.INFO)\n    else:\n        logging.basicConfig(level=logging.WARNING)\n\n    with open(args.input, \"r\") as f:\n        obj = yaml.load(f, Loader=yaml.SafeLoader)\n        ontologies = obj[\"ontologies\"]\n\n    func = args.function\n    func(ontologies, args)\n\n\ndef check_urls(ontologies, args):\n    \"\"\"\n    Ensure PURLs resolve\n    \"\"\"\n\n    def test_url(url):\n        try:\n            with closing(requests.get(url, stream=False)) as resp:\n                return resp.status_code == 200\n        except requests.exceptions.InvalidSchema as e:\n            # TODO: requests lib doesn't handle ftp. For now simply return True in that case.\n            if not format(e).startswith(\"No connection adapters were found for 'ftp:\"):\n                raise\n            return True\n\n    failed_ids = []\n    for ont in ontologies:\n        for p in ont.get(\"products\", []):\n            pid = p[\"id\"]\n            if not test_url(p.get(\"ontology_purl\")):\n                failed_ids.append(pid)\n    if len(failed_ids) > 0:\n        print(\"FAILURES:\")\n        for pid in failed_ids:\n            print(pid, file=sys.stderr)\n        exit(1)\n\n\ndef extract_context(ontologies, args):\n    \"\"\"\n    Writes to STDOUT a sorted JSON map from ontology prefixes to PURLs\n    \"\"\"\n\n    def has_obo_prefix(obj):\n        return (\"uri_prefix\" not in obj) or (\n            obj[\"uri_prefix\"] == \"http://purl.obolibrary.org/obo/\"\n        )\n\n    prefix_map = {}\n    for obj in ontologies:\n        if has_obo_prefix(obj):\n            prefix = obj.get(\"preferredPrefix\") or obj[\"id\"].upper()\n            prefix_map[prefix] = \"http://purl.obolibrary.org/obo/\" + prefix + \"_\"\n\n    print(\n        dumps(\n            {\"@context\": prefix_map}, sort_keys=True, indent=4, separators=(\",\", \": \")\n        )\n    )\n\n\ndef write_all_contributors(ontologies, args):\n    \"\"\"\n    Query github API for all contributors to an ontology,\n    write results as json\n    \"\"\"\n    results = []\n    for ont_obj in ontologies:\n        id = ont_obj[\"id\"]\n        logging.info(\"Getting info for {}\".format(id))\n        repo_path = get_repo_path(ont_obj)\n        if repo_path is not None:\n            contribs = list(get_ontology_contributors(repo_path))\n            print(\"CONTRIBS({})=={}\".format(id, contribs))\n            for c in contribs:\n                print(\"#{}\\t{}\\n\".format(id, c[\"login\"]))\n            results.append(dict(id=id, contributors=contribs))\n        else:\n            logging.warn(\"No repo_path declared for {}\".format(id))\n    print(dumps(results, sort_keys=True, indent=4, separators=(\",\", \": \")))\n\n\ndef get_ontology_contributors(repo_path):\n    \"\"\"\n    Get individual contributors to a org/repo_path\n    repo_path is a string \"org/repo\"\n    \"\"\"\n    url = \"https://api.github.com/repos/{}/contributors\".format(repo_path)\n    # TODO: allow use of oauth token;\n    # GH has a quota for non-logged in API calls\n    time.sleep(3)\n    with closing(requests.get(url, stream=False)) as resp:\n        ok = resp.status_code == 200\n        if ok:\n            results = resp.json()\n            logging.info(\"RESP={}\".format(results))\n            return results\n        else:\n            logging.error(\"Failed: {}\".format(url))\n            return []\n\n\ndef get_repo_path(ont_obj):\n    \"\"\"\n    Extract the repository path for the given object\n    \"\"\"\n    repo_path = None\n    if \"repository\" in ont_obj:\n        repo_path = ont_obj[\"repository\"]\n    elif \"tracker\" in ont_obj:\n        tracker = ont_obj[\"tracker\"]\n        if tracker is not None and \"github\" in tracker:\n            repo_path = tracker.replace(\"/issues\", \"\")\n\n    if repo_path is not None:\n        repo_path = repo_path.replace(\"https://github.com/\", \"\")\n        if repo_path.endswith(\"/\"):\n            repo_path = repo_path[:-1]\n        return repo_path\n    else:\n        logging.warn(\"Could not get gh repo_path for \".format(ont_obj))\n        return None\n\n\ndef run_sparql(obj, p, expected_value, q):\n    \"\"\"\n    Generate a SPARQL statement using query q and parameter p, and expect 'expected_value' as the\n    result. Print out a message indicating whether the there is or is not a match for the given object\n    \"\"\"\n    sparql = SPARQLWrapper(\"http://sparql.hegroup.org/sparql\")\n    sparql.setQuery(q)\n    sparql.setReturnFormat(JSON)\n    results = sparql.query().convert()\n\n    id = obj[\"id\"]\n    got_value = False\n    is_match = False\n    vs = []\n\n    for result in results[\"results\"][\"bindings\"]:\n        got_value = True\n        v = result[p][\"value\"]\n        vs.append(str(v))\n        if v == expected_value:\n            is_match = True\n\n    if got_value and is_match:\n        msg = \"CONSISTENT\"\n    elif got_value and not is_match:\n        if expected_value == \"\":\n            msg = \"UNDECLARED_LOCAL: REMOTE:\" + \",\".join(vs)\n        else:\n            msg = \"INCONSISTENT: REMOTE:\" + \",\".join(vs) + \" != LOCAL:\" + expected_value\n    else:\n        msg = \"UNDECLARED_REMOTE\"\n    print(id + \" \" + p + \" \" + msg)\n\n\ndef sparql_compare_ont(obj):\n    \"\"\"\n    Some ontologies will directly declare some subset of the OBO metadata\n    directly in the ontology header. In the majority of cases we should\n    yield to the provider. However, we reserve the right to override. For\n    example, OBO may have particular guidelines about the length of the title,\n    required for coherency within the registry. All differences should be\n    discussed with the provider and an accomodation reached\n    \"\"\"\n    if \"ontology_purl\" not in obj:\n        return\n\n    purl = obj[\"ontology_purl\"]\n    # this could be made more declarative, or driven by the context.jsonld mapping;\n    # however, for now this is relatively simple and easy to understand:\n    run_sparql(\n        obj,\n        \"license\",\n        obj[\"license\"][\"url\"] if \"license\" in obj else \"\",\n        \"SELECT DISTINCT ?license WHERE {<\"\n        + purl\n        + \"> <http://purl.org/dc/elements/1.1/license> ?license}\",\n    )\n    run_sparql(\n        obj,\n        \"title\",\n        obj[\"title\"] if \"title\" in obj else \"\",\n        \"SELECT DISTINCT ?title WHERE {<\"\n        + purl\n        + \"> <http://purl.org/dc/elements/1.1/title> ?title}\",\n    )\n    run_sparql(\n        obj,\n        \"description\",\n        obj[\"description\"] if \"description\" in obj else \"\",\n        \"SELECT DISTINCT ?description WHERE {<\"\n        + purl\n        + \"> <http://purl.org/dc/elements/1.1/description> ?description}\",\n    )\n    run_sparql(\n        obj,\n        \"homepage\",\n        obj[\"homepage\"] if \"homepage\" in obj else \"\",\n        \"SELECT DISTINCT ?homepage WHERE {<\"\n        + purl\n        + \"> <http://xmlns.com/foaf/0.1/homepage> ?homepage}\",\n    )\n\n\ndef sparql_compare_all(ontologies, args):\n    \"\"\"\n    Run sparql_compare_ont() on all the given ontologies.\n    \"\"\"\n    # The `args` parameter is not used here but it is convenient to have it in our definition, since\n    # whether this function or one of the other main `subcommands` of this script is called is\n    # determine dynamically, and we want all of the subcommands to have a consistent signature.\n    for obj in ontologies:\n        sparql_compare_ont(obj)\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "util/processor.py"}], "after_files": [{"content": "#!/usr/bin/env python3\n\nimport argparse\nimport datetime\nimport logging\nimport sys\nimport time\nfrom contextlib import closing\nfrom json import dumps\n\nimport requests\nimport yaml\nfrom SPARQLWrapper import JSON, SPARQLWrapper\n\n__author__ = \"cjm\"\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        description=\"Helper utils for OBO\",\n        formatter_class=argparse.RawTextHelpFormatter,\n    )\n    parser.add_argument(\n        \"-i\", \"--input\", type=str, required=False, help=\"Input metadata file\"\n    )\n    parser.add_argument(\n        \"-v\",\n        \"--verbosity\",\n        default=0,\n        action=\"count\",\n        help=\"Increase output verbosity (min.: 0, max. 2)\",\n    )\n    subparsers = parser.add_subparsers(dest=\"subcommand\", help=\"sub-command help\")\n\n    # SUBCOMMAND\n    parser_n = subparsers.add_parser(\"check-urls\", help=\"Ensure PURLs resolve\")\n    parser_n.set_defaults(function=check_urls)\n\n    parser_n = subparsers.add_parser(\n        \"sparql-compare\",\n        help=\"Run SPARQL commands against the db to generate a \" \"consistency report\",\n    )\n    parser_n.set_defaults(function=sparql_compare_all)\n\n    parser_n = subparsers.add_parser(\"extract-context\", help=\"Extracts JSON-LD context\")\n    parser_n.set_defaults(function=extract_context)\n\n    parser_n = subparsers.add_parser(\n        \"extract-contributors\",\n        help=\"Queries github API for metadata about contributors\",\n    )\n    parser_n.set_defaults(function=write_all_contributors)\n\n    args = parser.parse_args()\n    if args.verbosity >= 2:\n        logging.basicConfig(level=logging.DEBUG)\n    elif args.verbosity == 1:\n        logging.basicConfig(level=logging.INFO)\n    else:\n        logging.basicConfig(level=logging.WARNING)\n\n    with open(args.input, \"r\") as f:\n        obj = yaml.load(f, Loader=yaml.SafeLoader)\n        ontologies = obj[\"ontologies\"]\n\n    func = args.function\n    func(ontologies, args)\n\n\ndef check_urls(ontologies, args):\n    \"\"\"\n    Ensure PURLs resolve\n    \"\"\"\n\n    def test_url(url):\n        try:\n            with closing(requests.get(url, stream=False)) as resp:\n                return resp.status_code == 200\n        except requests.exceptions.InvalidSchema as e:\n            # TODO: requests lib doesn't handle ftp. For now simply return True in that case.\n            if not format(e).startswith(\"No connection adapters were found for 'ftp:\"):\n                raise\n            return True\n\n    failed_ids = []\n    for ont in ontologies:\n        for p in ont.get(\"products\", []):\n            pid = p[\"id\"]\n            if not test_url(p.get(\"ontology_purl\")):\n                failed_ids.append(pid)\n    if len(failed_ids) > 0:\n        print(\"FAILURES:\")\n        for pid in failed_ids:\n            print(pid, file=sys.stderr)\n        exit(1)\n\n\ndef extract_context(ontologies, args):\n    \"\"\"\n    Writes to STDOUT a sorted JSON map from ontology prefixes to PURLs\n    \"\"\"\n\n    def has_obo_prefix(obj):\n        return (\"uri_prefix\" not in obj) or (\n            obj[\"uri_prefix\"] == \"http://purl.obolibrary.org/obo/\"\n        )\n\n    prefix_map = {}\n    for obj in ontologies:\n        if has_obo_prefix(obj):\n            prefix = obj.get(\"preferredPrefix\") or obj[\"id\"].upper()\n            prefix_map[prefix] = {\n                \"@id\": \"http://purl.obolibrary.org/obo/\" + prefix + \"_\",\n                \"@prefix\": True,\n            }\n\n    print(\n        dumps(\n            {\"@context\": prefix_map}, sort_keys=True, indent=4, separators=(\",\", \": \")\n        )\n    )\n\n\ndef write_all_contributors(ontologies, args):\n    \"\"\"\n    Query github API for all contributors to an ontology,\n    write results as json\n    \"\"\"\n    results = []\n    for ont_obj in ontologies:\n        id = ont_obj[\"id\"]\n        logging.info(\"Getting info for {}\".format(id))\n        repo_path = get_repo_path(ont_obj)\n        if repo_path is not None:\n            contribs = list(get_ontology_contributors(repo_path))\n            print(\"CONTRIBS({})=={}\".format(id, contribs))\n            for c in contribs:\n                print(\"#{}\\t{}\\n\".format(id, c[\"login\"]))\n            results.append(dict(id=id, contributors=contribs))\n        else:\n            logging.warn(\"No repo_path declared for {}\".format(id))\n    print(dumps(results, sort_keys=True, indent=4, separators=(\",\", \": \")))\n\n\ndef get_ontology_contributors(repo_path):\n    \"\"\"\n    Get individual contributors to a org/repo_path\n    repo_path is a string \"org/repo\"\n    \"\"\"\n    url = \"https://api.github.com/repos/{}/contributors\".format(repo_path)\n    # TODO: allow use of oauth token;\n    # GH has a quota for non-logged in API calls\n    time.sleep(3)\n    with closing(requests.get(url, stream=False)) as resp:\n        ok = resp.status_code == 200\n        if ok:\n            results = resp.json()\n            logging.info(\"RESP={}\".format(results))\n            return results\n        else:\n            logging.error(\"Failed: {}\".format(url))\n            return []\n\n\ndef get_repo_path(ont_obj):\n    \"\"\"\n    Extract the repository path for the given object\n    \"\"\"\n    repo_path = None\n    if \"repository\" in ont_obj:\n        repo_path = ont_obj[\"repository\"]\n    elif \"tracker\" in ont_obj:\n        tracker = ont_obj[\"tracker\"]\n        if tracker is not None and \"github\" in tracker:\n            repo_path = tracker.replace(\"/issues\", \"\")\n\n    if repo_path is not None:\n        repo_path = repo_path.replace(\"https://github.com/\", \"\")\n        if repo_path.endswith(\"/\"):\n            repo_path = repo_path[:-1]\n        return repo_path\n    else:\n        logging.warn(\"Could not get gh repo_path for \".format(ont_obj))\n        return None\n\n\ndef run_sparql(obj, p, expected_value, q):\n    \"\"\"\n    Generate a SPARQL statement using query q and parameter p, and expect 'expected_value' as the\n    result. Print out a message indicating whether the there is or is not a match for the given object\n    \"\"\"\n    sparql = SPARQLWrapper(\"http://sparql.hegroup.org/sparql\")\n    sparql.setQuery(q)\n    sparql.setReturnFormat(JSON)\n    results = sparql.query().convert()\n\n    id = obj[\"id\"]\n    got_value = False\n    is_match = False\n    vs = []\n\n    for result in results[\"results\"][\"bindings\"]:\n        got_value = True\n        v = result[p][\"value\"]\n        vs.append(str(v))\n        if v == expected_value:\n            is_match = True\n\n    if got_value and is_match:\n        msg = \"CONSISTENT\"\n    elif got_value and not is_match:\n        if expected_value == \"\":\n            msg = \"UNDECLARED_LOCAL: REMOTE:\" + \",\".join(vs)\n        else:\n            msg = \"INCONSISTENT: REMOTE:\" + \",\".join(vs) + \" != LOCAL:\" + expected_value\n    else:\n        msg = \"UNDECLARED_REMOTE\"\n    print(id + \" \" + p + \" \" + msg)\n\n\ndef sparql_compare_ont(obj):\n    \"\"\"\n    Some ontologies will directly declare some subset of the OBO metadata\n    directly in the ontology header. In the majority of cases we should\n    yield to the provider. However, we reserve the right to override. For\n    example, OBO may have particular guidelines about the length of the title,\n    required for coherency within the registry. All differences should be\n    discussed with the provider and an accomodation reached\n    \"\"\"\n    if \"ontology_purl\" not in obj:\n        return\n\n    purl = obj[\"ontology_purl\"]\n    # this could be made more declarative, or driven by the context.jsonld mapping;\n    # however, for now this is relatively simple and easy to understand:\n    run_sparql(\n        obj,\n        \"license\",\n        obj[\"license\"][\"url\"] if \"license\" in obj else \"\",\n        \"SELECT DISTINCT ?license WHERE {<\"\n        + purl\n        + \"> <http://purl.org/dc/elements/1.1/license> ?license}\",\n    )\n    run_sparql(\n        obj,\n        \"title\",\n        obj[\"title\"] if \"title\" in obj else \"\",\n        \"SELECT DISTINCT ?title WHERE {<\"\n        + purl\n        + \"> <http://purl.org/dc/elements/1.1/title> ?title}\",\n    )\n    run_sparql(\n        obj,\n        \"description\",\n        obj[\"description\"] if \"description\" in obj else \"\",\n        \"SELECT DISTINCT ?description WHERE {<\"\n        + purl\n        + \"> <http://purl.org/dc/elements/1.1/description> ?description}\",\n    )\n    run_sparql(\n        obj,\n        \"homepage\",\n        obj[\"homepage\"] if \"homepage\" in obj else \"\",\n        \"SELECT DISTINCT ?homepage WHERE {<\"\n        + purl\n        + \"> <http://xmlns.com/foaf/0.1/homepage> ?homepage}\",\n    )\n\n\ndef sparql_compare_all(ontologies, args):\n    \"\"\"\n    Run sparql_compare_ont() on all the given ontologies.\n    \"\"\"\n    # The `args` parameter is not used here but it is convenient to have it in our definition, since\n    # whether this function or one of the other main `subcommands` of this script is called is\n    # determine dynamically, and we want all of the subcommands to have a consistent signature.\n    for obj in ontologies:\n        sparql_compare_ont(obj)\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "util/processor.py"}]}