We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add pyhf.utils.citation to the python API


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/pyhf/utils.py`
Content:
```
1 import json
2 import jsonschema
3 import pkg_resources
4 from pathlib import Path
5 import yaml
6 import click
7 import hashlib
8 
9 from .exceptions import InvalidSpecification
10 
11 SCHEMA_CACHE = {}
12 SCHEMA_BASE = "https://scikit-hep.org/pyhf/schemas/"
13 SCHEMA_VERSION = '1.0.0'
14 
15 
16 def load_schema(schema_id, version=None):
17     global SCHEMA_CACHE
18     if not version:
19         version = SCHEMA_VERSION
20     try:
21         return SCHEMA_CACHE[f'{SCHEMA_BASE}{Path(version).joinpath(schema_id)}']
22     except KeyError:
23         pass
24 
25     path = pkg_resources.resource_filename(
26         __name__, str(Path('schemas').joinpath(version, schema_id))
27     )
28     with open(path) as json_schema:
29         schema = json.load(json_schema)
30         SCHEMA_CACHE[schema['$id']] = schema
31     return SCHEMA_CACHE[schema['$id']]
32 
33 
34 # load the defs.json as it is included by $ref
35 load_schema('defs.json')
36 
37 
38 def validate(spec, schema_name, version=None):
39     schema = load_schema(schema_name, version=version)
40     try:
41         resolver = jsonschema.RefResolver(
42             base_uri=f"file://{pkg_resources.resource_filename(__name__, 'schemas/'):s}",
43             referrer=schema_name,
44             store=SCHEMA_CACHE,
45         )
46         validator = jsonschema.Draft6Validator(
47             schema, resolver=resolver, format_checker=None
48         )
49         return validator.validate(spec)
50     except jsonschema.ValidationError as err:
51         raise InvalidSpecification(err, schema_name)
52 
53 
54 def options_from_eqdelimstring(opts):
55     document = '\n'.join(
56         f"{opt.split('=', 1)[0]}: {opt.split('=', 1)[1]}" for opt in opts
57     )
58     return yaml.safe_load(document)
59 
60 
61 class EqDelimStringParamType(click.ParamType):
62     name = 'equal-delimited option'
63 
64     def convert(self, value, param, ctx):
65         try:
66             return options_from_eqdelimstring([value])
67         except IndexError:
68             self.fail(f'{value:s} is not a valid equal-delimited string', param, ctx)
69 
70 
71 def digest(obj, algorithm='sha256'):
72     """
73     Get the digest for the provided object. Note: object must be JSON-serializable.
74 
75     The hashing algorithms supported are in :mod:`hashlib`, part of Python's Standard Libraries.
76 
77     Example:
78 
79         >>> import pyhf
80         >>> obj = {'a': 2.0, 'b': 3.0, 'c': 1.0}
81         >>> pyhf.utils.digest(obj)
82         'a38f6093800189b79bc22ef677baf90c75705af2cfc7ff594159eca54eaa7928'
83         >>> pyhf.utils.digest(obj, algorithm='md5')
84         '2c0633f242928eb55c3672fed5ba8612'
85         >>> pyhf.utils.digest(obj, algorithm='sha1')
86         '49a27f499e763766c9545b294880df277be6f545'
87 
88     Raises:
89         ValueError: If the object is not JSON-serializable or if the algorithm is not supported.
90 
91     Args:
92         obj (:obj:`jsonable`): A JSON-serializable object to compute the digest of. Usually a :class:`~pyhf.workspace.Workspace` object.
93         algorithm (:obj:`str`): The hashing algorithm to use.
94 
95     Returns:
96         digest (:obj:`str`): The digest for the JSON-serialized object provided and hash algorithm specified.
97     """
98 
99     try:
100         stringified = json.dumps(obj, sort_keys=True, ensure_ascii=False).encode('utf8')
101     except TypeError:
102         raise ValueError(
103             "The supplied object is not JSON-serializable for calculating a hash."
104         )
105     try:
106         hash_alg = getattr(hashlib, algorithm)
107     except AttributeError:
108         raise ValueError(
109             f"{algorithm} is not an algorithm provided by Python's hashlib library."
110         )
111     return hash_alg(stringified).hexdigest()
112 
113 
114 def remove_prefix(text, prefix):
115     """
116     Remove a prefix from the beginning of the provided text.
117 
118     Example:
119 
120         >>> import pyhf
121         >>> pyhf.utils.remove_prefix("alpha_syst1", "alpha_")
122         'syst1'
123 
124     Args:
125         text (:obj:`str`): A provided input to manipulate.
126         prefix (:obj:`str`): A prefix to remove from provided input, if it exists.
127 
128     Returns:
129         stripped_text (:obj:`str`): Text with the prefix removed.
130     """
131     # NB: python3.9 can be `return text.removeprefix(prefix)`
132     if text.startswith(prefix):
133         return text[len(prefix) :]
134     return text
135 
136 
137 def citation(oneline=False):
138     """
139     Get the bibtex citation for pyhf
140 
141     Example:
142 
143         >>> import pyhf
144         >>> pyhf.utils.citation(True)
145         '@software{pyhf,  author = {Lukas Heinrich and Matthew Feickert and Giordon Stark},  title = "{pyhf: v0.6.1}",  version = {0.6.1},  doi = {10.5281/zenodo.1169739},  url = {https://github.com/scikit-hep/pyhf},}@article{pyhf_joss,  doi = {10.21105/joss.02823},  url = {https://doi.org/10.21105/joss.02823},  year = {2021},  publisher = {The Open Journal},  volume = {6},  number = {58},  pages = {2823},  author = {Lukas Heinrich and Matthew Feickert and Giordon Stark and Kyle Cranmer},  title = {pyhf: pure-Python implementation of HistFactory statistical models},  journal = {Journal of Open Source Software}}'
146 
147     Keyword Args:
148         oneline (:obj:`bool`): Whether to provide citation with new lines (default) or as a one-liner.
149 
150     Returns:
151         citation (:obj:`str`): The citation for this software
152     """
153     path = Path(
154         pkg_resources.resource_filename(
155             __name__, str(Path('data').joinpath('citation.bib'))
156         )
157     )
158     with path.open() as fp:
159         # remove end-of-file newline if there is one
160         data = fp.read().strip()
161 
162     if oneline:
163         data = ''.join(data.splitlines())
164     return data
165
```
--- 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/src/pyhf/utils.py b/src/pyhf/utils.py
--- a/src/pyhf/utils.py
+++ b/src/pyhf/utils.py
@@ -141,7 +141,7 @@
     Example:
 
         >>> import pyhf
-        >>> pyhf.utils.citation(True)
+        >>> pyhf.utils.citation(oneline=True)
         '@software{pyhf,  author = {Lukas Heinrich and Matthew Feickert and Giordon Stark},  title = "{pyhf: v0.6.1}",  version = {0.6.1},  doi = {10.5281/zenodo.1169739},  url = {https://github.com/scikit-hep/pyhf},}@article{pyhf_joss,  doi = {10.21105/joss.02823},  url = {https://doi.org/10.21105/joss.02823},  year = {2021},  publisher = {The Open Journal},  volume = {6},  number = {58},  pages = {2823},  author = {Lukas Heinrich and Matthew Feickert and Giordon Stark and Kyle Cranmer},  title = {pyhf: pure-Python implementation of HistFactory statistical models},  journal = {Journal of Open Source Software}}'
 
     Keyword Args:

{"golden_diff": "diff --git a/src/pyhf/utils.py b/src/pyhf/utils.py\n--- a/src/pyhf/utils.py\n+++ b/src/pyhf/utils.py\n@@ -141,7 +141,7 @@\n     Example:\n \n         >>> import pyhf\n-        >>> pyhf.utils.citation(True)\n+        >>> pyhf.utils.citation(oneline=True)\n         '@software{pyhf,  author = {Lukas Heinrich and Matthew Feickert and Giordon Stark},  title = \"{pyhf: v0.6.1}\",  version = {0.6.1},  doi = {10.5281/zenodo.1169739},  url = {https://github.com/scikit-hep/pyhf},}@article{pyhf_joss,  doi = {10.21105/joss.02823},  url = {https://doi.org/10.21105/joss.02823},  year = {2021},  publisher = {The Open Journal},  volume = {6},  number = {58},  pages = {2823},  author = {Lukas Heinrich and Matthew Feickert and Giordon Stark and Kyle Cranmer},  title = {pyhf: pure-Python implementation of HistFactory statistical models},  journal = {Journal of Open Source Software}}'\n \n     Keyword Args:\n", "issue": "Add pyhf.utils.citation to the python API\n\n", "before_files": [{"content": "import json\nimport jsonschema\nimport pkg_resources\nfrom pathlib import Path\nimport yaml\nimport click\nimport hashlib\n\nfrom .exceptions import InvalidSpecification\n\nSCHEMA_CACHE = {}\nSCHEMA_BASE = \"https://scikit-hep.org/pyhf/schemas/\"\nSCHEMA_VERSION = '1.0.0'\n\n\ndef load_schema(schema_id, version=None):\n    global SCHEMA_CACHE\n    if not version:\n        version = SCHEMA_VERSION\n    try:\n        return SCHEMA_CACHE[f'{SCHEMA_BASE}{Path(version).joinpath(schema_id)}']\n    except KeyError:\n        pass\n\n    path = pkg_resources.resource_filename(\n        __name__, str(Path('schemas').joinpath(version, schema_id))\n    )\n    with open(path) as json_schema:\n        schema = json.load(json_schema)\n        SCHEMA_CACHE[schema['$id']] = schema\n    return SCHEMA_CACHE[schema['$id']]\n\n\n# load the defs.json as it is included by $ref\nload_schema('defs.json')\n\n\ndef validate(spec, schema_name, version=None):\n    schema = load_schema(schema_name, version=version)\n    try:\n        resolver = jsonschema.RefResolver(\n            base_uri=f\"file://{pkg_resources.resource_filename(__name__, 'schemas/'):s}\",\n            referrer=schema_name,\n            store=SCHEMA_CACHE,\n        )\n        validator = jsonschema.Draft6Validator(\n            schema, resolver=resolver, format_checker=None\n        )\n        return validator.validate(spec)\n    except jsonschema.ValidationError as err:\n        raise InvalidSpecification(err, schema_name)\n\n\ndef options_from_eqdelimstring(opts):\n    document = '\\n'.join(\n        f\"{opt.split('=', 1)[0]}: {opt.split('=', 1)[1]}\" for opt in opts\n    )\n    return yaml.safe_load(document)\n\n\nclass EqDelimStringParamType(click.ParamType):\n    name = 'equal-delimited option'\n\n    def convert(self, value, param, ctx):\n        try:\n            return options_from_eqdelimstring([value])\n        except IndexError:\n            self.fail(f'{value:s} is not a valid equal-delimited string', param, ctx)\n\n\ndef digest(obj, algorithm='sha256'):\n    \"\"\"\n    Get the digest for the provided object. Note: object must be JSON-serializable.\n\n    The hashing algorithms supported are in :mod:`hashlib`, part of Python's Standard Libraries.\n\n    Example:\n\n        >>> import pyhf\n        >>> obj = {'a': 2.0, 'b': 3.0, 'c': 1.0}\n        >>> pyhf.utils.digest(obj)\n        'a38f6093800189b79bc22ef677baf90c75705af2cfc7ff594159eca54eaa7928'\n        >>> pyhf.utils.digest(obj, algorithm='md5')\n        '2c0633f242928eb55c3672fed5ba8612'\n        >>> pyhf.utils.digest(obj, algorithm='sha1')\n        '49a27f499e763766c9545b294880df277be6f545'\n\n    Raises:\n        ValueError: If the object is not JSON-serializable or if the algorithm is not supported.\n\n    Args:\n        obj (:obj:`jsonable`): A JSON-serializable object to compute the digest of. Usually a :class:`~pyhf.workspace.Workspace` object.\n        algorithm (:obj:`str`): The hashing algorithm to use.\n\n    Returns:\n        digest (:obj:`str`): The digest for the JSON-serialized object provided and hash algorithm specified.\n    \"\"\"\n\n    try:\n        stringified = json.dumps(obj, sort_keys=True, ensure_ascii=False).encode('utf8')\n    except TypeError:\n        raise ValueError(\n            \"The supplied object is not JSON-serializable for calculating a hash.\"\n        )\n    try:\n        hash_alg = getattr(hashlib, algorithm)\n    except AttributeError:\n        raise ValueError(\n            f\"{algorithm} is not an algorithm provided by Python's hashlib library.\"\n        )\n    return hash_alg(stringified).hexdigest()\n\n\ndef remove_prefix(text, prefix):\n    \"\"\"\n    Remove a prefix from the beginning of the provided text.\n\n    Example:\n\n        >>> import pyhf\n        >>> pyhf.utils.remove_prefix(\"alpha_syst1\", \"alpha_\")\n        'syst1'\n\n    Args:\n        text (:obj:`str`): A provided input to manipulate.\n        prefix (:obj:`str`): A prefix to remove from provided input, if it exists.\n\n    Returns:\n        stripped_text (:obj:`str`): Text with the prefix removed.\n    \"\"\"\n    # NB: python3.9 can be `return text.removeprefix(prefix)`\n    if text.startswith(prefix):\n        return text[len(prefix) :]\n    return text\n\n\ndef citation(oneline=False):\n    \"\"\"\n    Get the bibtex citation for pyhf\n\n    Example:\n\n        >>> import pyhf\n        >>> pyhf.utils.citation(True)\n        '@software{pyhf,  author = {Lukas Heinrich and Matthew Feickert and Giordon Stark},  title = \"{pyhf: v0.6.1}\",  version = {0.6.1},  doi = {10.5281/zenodo.1169739},  url = {https://github.com/scikit-hep/pyhf},}@article{pyhf_joss,  doi = {10.21105/joss.02823},  url = {https://doi.org/10.21105/joss.02823},  year = {2021},  publisher = {The Open Journal},  volume = {6},  number = {58},  pages = {2823},  author = {Lukas Heinrich and Matthew Feickert and Giordon Stark and Kyle Cranmer},  title = {pyhf: pure-Python implementation of HistFactory statistical models},  journal = {Journal of Open Source Software}}'\n\n    Keyword Args:\n        oneline (:obj:`bool`): Whether to provide citation with new lines (default) or as a one-liner.\n\n    Returns:\n        citation (:obj:`str`): The citation for this software\n    \"\"\"\n    path = Path(\n        pkg_resources.resource_filename(\n            __name__, str(Path('data').joinpath('citation.bib'))\n        )\n    )\n    with path.open() as fp:\n        # remove end-of-file newline if there is one\n        data = fp.read().strip()\n\n    if oneline:\n        data = ''.join(data.splitlines())\n    return data\n", "path": "src/pyhf/utils.py"}], "after_files": [{"content": "import json\nimport jsonschema\nimport pkg_resources\nfrom pathlib import Path\nimport yaml\nimport click\nimport hashlib\n\nfrom .exceptions import InvalidSpecification\n\nSCHEMA_CACHE = {}\nSCHEMA_BASE = \"https://scikit-hep.org/pyhf/schemas/\"\nSCHEMA_VERSION = '1.0.0'\n\n\ndef load_schema(schema_id, version=None):\n    global SCHEMA_CACHE\n    if not version:\n        version = SCHEMA_VERSION\n    try:\n        return SCHEMA_CACHE[f'{SCHEMA_BASE}{Path(version).joinpath(schema_id)}']\n    except KeyError:\n        pass\n\n    path = pkg_resources.resource_filename(\n        __name__, str(Path('schemas').joinpath(version, schema_id))\n    )\n    with open(path) as json_schema:\n        schema = json.load(json_schema)\n        SCHEMA_CACHE[schema['$id']] = schema\n    return SCHEMA_CACHE[schema['$id']]\n\n\n# load the defs.json as it is included by $ref\nload_schema('defs.json')\n\n\ndef validate(spec, schema_name, version=None):\n    schema = load_schema(schema_name, version=version)\n    try:\n        resolver = jsonschema.RefResolver(\n            base_uri=f\"file://{pkg_resources.resource_filename(__name__, 'schemas/'):s}\",\n            referrer=schema_name,\n            store=SCHEMA_CACHE,\n        )\n        validator = jsonschema.Draft6Validator(\n            schema, resolver=resolver, format_checker=None\n        )\n        return validator.validate(spec)\n    except jsonschema.ValidationError as err:\n        raise InvalidSpecification(err, schema_name)\n\n\ndef options_from_eqdelimstring(opts):\n    document = '\\n'.join(\n        f\"{opt.split('=', 1)[0]}: {opt.split('=', 1)[1]}\" for opt in opts\n    )\n    return yaml.safe_load(document)\n\n\nclass EqDelimStringParamType(click.ParamType):\n    name = 'equal-delimited option'\n\n    def convert(self, value, param, ctx):\n        try:\n            return options_from_eqdelimstring([value])\n        except IndexError:\n            self.fail(f'{value:s} is not a valid equal-delimited string', param, ctx)\n\n\ndef digest(obj, algorithm='sha256'):\n    \"\"\"\n    Get the digest for the provided object. Note: object must be JSON-serializable.\n\n    The hashing algorithms supported are in :mod:`hashlib`, part of Python's Standard Libraries.\n\n    Example:\n\n        >>> import pyhf\n        >>> obj = {'a': 2.0, 'b': 3.0, 'c': 1.0}\n        >>> pyhf.utils.digest(obj)\n        'a38f6093800189b79bc22ef677baf90c75705af2cfc7ff594159eca54eaa7928'\n        >>> pyhf.utils.digest(obj, algorithm='md5')\n        '2c0633f242928eb55c3672fed5ba8612'\n        >>> pyhf.utils.digest(obj, algorithm='sha1')\n        '49a27f499e763766c9545b294880df277be6f545'\n\n    Raises:\n        ValueError: If the object is not JSON-serializable or if the algorithm is not supported.\n\n    Args:\n        obj (:obj:`jsonable`): A JSON-serializable object to compute the digest of. Usually a :class:`~pyhf.workspace.Workspace` object.\n        algorithm (:obj:`str`): The hashing algorithm to use.\n\n    Returns:\n        digest (:obj:`str`): The digest for the JSON-serialized object provided and hash algorithm specified.\n    \"\"\"\n\n    try:\n        stringified = json.dumps(obj, sort_keys=True, ensure_ascii=False).encode('utf8')\n    except TypeError:\n        raise ValueError(\n            \"The supplied object is not JSON-serializable for calculating a hash.\"\n        )\n    try:\n        hash_alg = getattr(hashlib, algorithm)\n    except AttributeError:\n        raise ValueError(\n            f\"{algorithm} is not an algorithm provided by Python's hashlib library.\"\n        )\n    return hash_alg(stringified).hexdigest()\n\n\ndef remove_prefix(text, prefix):\n    \"\"\"\n    Remove a prefix from the beginning of the provided text.\n\n    Example:\n\n        >>> import pyhf\n        >>> pyhf.utils.remove_prefix(\"alpha_syst1\", \"alpha_\")\n        'syst1'\n\n    Args:\n        text (:obj:`str`): A provided input to manipulate.\n        prefix (:obj:`str`): A prefix to remove from provided input, if it exists.\n\n    Returns:\n        stripped_text (:obj:`str`): Text with the prefix removed.\n    \"\"\"\n    # NB: python3.9 can be `return text.removeprefix(prefix)`\n    if text.startswith(prefix):\n        return text[len(prefix) :]\n    return text\n\n\ndef citation(oneline=False):\n    \"\"\"\n    Get the bibtex citation for pyhf\n\n    Example:\n\n        >>> import pyhf\n        >>> pyhf.utils.citation(oneline=True)\n        '@software{pyhf,  author = {Lukas Heinrich and Matthew Feickert and Giordon Stark},  title = \"{pyhf: v0.6.1}\",  version = {0.6.1},  doi = {10.5281/zenodo.1169739},  url = {https://github.com/scikit-hep/pyhf},}@article{pyhf_joss,  doi = {10.21105/joss.02823},  url = {https://doi.org/10.21105/joss.02823},  year = {2021},  publisher = {The Open Journal},  volume = {6},  number = {58},  pages = {2823},  author = {Lukas Heinrich and Matthew Feickert and Giordon Stark and Kyle Cranmer},  title = {pyhf: pure-Python implementation of HistFactory statistical models},  journal = {Journal of Open Source Software}}'\n\n    Keyword Args:\n        oneline (:obj:`bool`): Whether to provide citation with new lines (default) or as a one-liner.\n\n    Returns:\n        citation (:obj:`str`): The citation for this software\n    \"\"\"\n    path = Path(\n        pkg_resources.resource_filename(\n            __name__, str(Path('data').joinpath('citation.bib'))\n        )\n    )\n    with path.open() as fp:\n        # remove end-of-file newline if there is one\n        data = fp.read().strip()\n\n    if oneline:\n        data = ''.join(data.splitlines())\n    return data\n", "path": "src/pyhf/utils.py"}]}