We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
DOCKER: git rev-parse --short HEAD --` returns a non-zero exit status 128. web container won't load
### Evidence / Screenshot (if possible)
![image](https://github.com/internetarchive/openlibrary/assets/126498180/a75ff91b-4477-4a98-96b4-4b78c12bb202)

### Steps to Reproduce

1. Follow the instructions from openlibrary/docker/README.md to build a new docker setup
2. Run docker compose up

* Actual:
The errors shown on the screen capture are thrown, and the _web_ container fails to get up.
* Expected:
_web_ container successfully brought up

### Proposal & Constraints
The issue seems to be on the `get_software_version()` function called on `openlibrary/plugins/openlibrary/status.py`, when `git rev-parse --short HEAD --` returns a non-zero exit status 128.

I put the function call between quotation marks as a test, so that "Software version" becomes a hardcoded string, and after that everything seems to load and work just fine.



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `openlibrary/utils/__init__.py`
Content:
```
1 """Generic utilities"""
2 
3 from enum import Enum
4 import re
5 from subprocess import run
6 from typing import TypeVar, Literal, Optional
7 from collections.abc import Iterable, Callable
8 
9 to_drop = set(''';/?:@&=+$,<>#%"{}|\\^[]`\n\r''')
10 
11 
12 def str_to_key(s: str) -> str:
13     """
14     >>> str_to_key("?H$e##l{o}[0] -world!")
15     'helo0_-world!'
16     >>> str_to_key("".join(to_drop))
17     ''
18     >>> str_to_key("")
19     ''
20     """
21     return ''.join(c if c != ' ' else '_' for c in s.lower() if c not in to_drop)
22 
23 
24 def finddict(dicts, **filters):
25     """Find a dictionary that matches given filter conditions.
26 
27     >>> dicts = [{"x": 1, "y": 2}, {"x": 3, "y": 4}]
28     >>> sorted(finddict(dicts, x=1).items())
29     [('x', 1), ('y', 2)]
30     """
31     for d in dicts:
32         if all(d.get(k) == v for k, v in filters.items()):
33             return d
34 
35 
36 T = TypeVar('T')
37 
38 
39 def uniq(values: Iterable[T], key=None) -> list[T]:
40     """Returns the unique entries from the given values in the original order.
41 
42     The value of the optional `key` parameter should be a function that takes
43     a single argument and returns a key to test the uniqueness.
44     TODO: Moved this to core/utils.py
45 
46     >>> uniq("abcbcddefefg")
47     ['a', 'b', 'c', 'd', 'e', 'f', 'g']
48     >>> uniq("011223344556677889")
49     ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
50     """
51     key = key or (lambda x: x)
52     s = set()
53     result = []
54     for v in values:
55         k = key(v)
56         if k not in s:
57             s.add(k)
58             result.append(v)
59     return result
60 
61 
62 def take_best(
63     items: list[T],
64     optimization: Literal["min", "max"],
65     scoring_fn: Callable[[T], float],
66 ) -> list[T]:
67     """
68     >>> take_best([], 'min', lambda x: x)
69     []
70     >>> take_best([3, 2, 1], 'min', lambda x: x)
71     [1]
72     >>> take_best([3, 4, 5], 'max', lambda x: x)
73     [5]
74     >>> take_best([4, 1, -1, -1], 'min', lambda x: x)
75     [-1, -1]
76     """
77     best_score = float("-inf") if optimization == "max" else float("inf")
78     besties = []
79     for item in items:
80         score = scoring_fn(item)
81         if (optimization == "max" and score > best_score) or (
82             optimization == "min" and score < best_score
83         ):
84             best_score = score
85             besties = [item]
86         elif score == best_score:
87             besties.append(item)
88         else:
89             continue
90     return besties
91 
92 
93 def multisort_best(
94     items: list[T], specs: list[tuple[Literal["min", "max"], Callable[[T], float]]]
95 ) -> Optional[T]:
96     """
97     Takes the best item, taking into account the multisorts
98 
99     >>> multisort_best([], [])
100 
101     >>> multisort_best([3,4,5], [('max', lambda x: x)])
102     5
103 
104     >>> multisort_best([
105     ...     {'provider': 'ia', 'size': 4},
106     ...     {'provider': 'ia', 'size': 12},
107     ...     {'provider': None, 'size': 42},
108     ... ], [
109     ...     ('min', lambda x: 0 if x['provider'] == 'ia' else 1),
110     ...     ('max', lambda x: x['size']),
111     ... ])
112     {'provider': 'ia', 'size': 12}
113     """
114     if not items:
115         return None
116     pool = items
117     for optimization, fn in specs:
118         # Shrink the pool down each time
119         pool = take_best(pool, optimization, fn)
120     return pool[0]
121 
122 
123 def dicthash(d):
124     """Dictionaries are not hashable. This function converts dictionary into nested
125     tuples, so that it can hashed.
126     """
127     if isinstance(d, dict):
128         return tuple((k, dicthash(d[k])) for k in sorted(d))
129     elif isinstance(d, list):
130         return tuple(dicthash(v) for v in d)
131     else:
132         return d
133 
134 
135 olid_re = re.compile(r'OL\d+[A-Z]', re.IGNORECASE)
136 
137 
138 def find_olid_in_string(s: str, olid_suffix: str | None = None) -> str | None:
139     """
140     >>> find_olid_in_string("ol123w")
141     'OL123W'
142     >>> find_olid_in_string("/authors/OL123A/DAVIE_BOWIE")
143     'OL123A'
144     >>> find_olid_in_string("/authors/OL123A/DAVIE_BOWIE", "W")
145     >>> find_olid_in_string("some random string")
146     """
147     found = re.search(olid_re, s)
148     if not found:
149         return None
150     olid = found.group(0).upper()
151 
152     if olid_suffix and not olid.endswith(olid_suffix):
153         return None
154 
155     return olid
156 
157 
158 def olid_to_key(olid: str) -> str:
159     """
160     >>> olid_to_key('OL123W')
161     '/works/OL123W'
162     >>> olid_to_key('OL123A')
163     '/authors/OL123A'
164     >>> olid_to_key('OL123M')
165     '/books/OL123M'
166     >>> olid_to_key("OL123L")
167     '/lists/OL123L'
168     """
169     typ = {
170         'A': 'authors',
171         'W': 'works',
172         'M': 'books',
173         'L': 'lists',
174     }[olid[-1]]
175     if not typ:
176         raise ValueError(f"Invalid olid: {olid}")
177     return f"/{typ}/{olid}"
178 
179 
180 def extract_numeric_id_from_olid(olid):
181     """
182     >>> extract_numeric_id_from_olid("OL123W")
183     '123'
184     >>> extract_numeric_id_from_olid("/authors/OL123A")
185     '123'
186     """
187     if '/' in olid:
188         olid = olid.split('/')[-1]
189     if olid.lower().startswith('ol'):
190         olid = olid[2:]
191     if not is_number(olid[-1].lower()):
192         olid = olid[:-1]
193     return olid
194 
195 
196 def is_number(s):
197     """
198     >>> all(is_number(n) for n in (1234, "1234", -1234, "-1234", 123.4, -123.4))
199     True
200     >>> not any(is_number(n) for n in ("123.4", "-123.4", "123a", "--1234"))
201     True
202     """
203     try:
204         int(s)
205         return True
206     except ValueError:
207         return False
208 
209 
210 def get_software_version() -> str:
211     """
212     assert get_software_version()  # Should never return a falsy value
213     """
214     cmd = "git rev-parse --short HEAD --".split()
215     return run(cmd, capture_output=True, text=True, check=True).stdout.strip()
216 
217 
218 # See https://docs.python.org/3/library/enum.html#orderedenum
219 class OrderedEnum(Enum):
220     def __ge__(self, other):
221         if self.__class__ is other.__class__:
222             return self.value >= other.value
223         return NotImplemented
224 
225     def __gt__(self, other):
226         if self.__class__ is other.__class__:
227             return self.value > other.value
228         return NotImplemented
229 
230     def __le__(self, other):
231         if self.__class__ is other.__class__:
232             return self.value <= other.value
233         return NotImplemented
234 
235     def __lt__(self, other):
236         if self.__class__ is other.__class__:
237             return self.value < other.value
238         return NotImplemented
239
```
--- 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/openlibrary/utils/__init__.py b/openlibrary/utils/__init__.py
--- a/openlibrary/utils/__init__.py
+++ b/openlibrary/utils/__init__.py
@@ -2,7 +2,7 @@
 
 from enum import Enum
 import re
-from subprocess import run
+from subprocess import CalledProcessError, run
 from typing import TypeVar, Literal, Optional
 from collections.abc import Iterable, Callable
 
@@ -212,7 +212,10 @@
     assert get_software_version()  # Should never return a falsy value
     """
     cmd = "git rev-parse --short HEAD --".split()
-    return run(cmd, capture_output=True, text=True, check=True).stdout.strip()
+    try:
+        return run(cmd, capture_output=True, text=True, check=True).stdout.strip()
+    except CalledProcessError:
+        return "unknown"
 
 
 # See https://docs.python.org/3/library/enum.html#orderedenum

{"golden_diff": "diff --git a/openlibrary/utils/__init__.py b/openlibrary/utils/__init__.py\n--- a/openlibrary/utils/__init__.py\n+++ b/openlibrary/utils/__init__.py\n@@ -2,7 +2,7 @@\n \n from enum import Enum\n import re\n-from subprocess import run\n+from subprocess import CalledProcessError, run\n from typing import TypeVar, Literal, Optional\n from collections.abc import Iterable, Callable\n \n@@ -212,7 +212,10 @@\n     assert get_software_version()  # Should never return a falsy value\n     \"\"\"\n     cmd = \"git rev-parse --short HEAD --\".split()\n-    return run(cmd, capture_output=True, text=True, check=True).stdout.strip()\n+    try:\n+        return run(cmd, capture_output=True, text=True, check=True).stdout.strip()\n+    except CalledProcessError:\n+        return \"unknown\"\n \n \n # See https://docs.python.org/3/library/enum.html#orderedenum\n", "issue": "DOCKER: git rev-parse --short HEAD --` returns a non-zero exit status 128. web container won't load\n### Evidence / Screenshot (if possible)\r\n![image](https://github.com/internetarchive/openlibrary/assets/126498180/a75ff91b-4477-4a98-96b4-4b78c12bb202)\r\n\r\n### Steps to Reproduce\r\n\r\n1. Follow the instructions from openlibrary/docker/README.md to build a new docker setup\r\n2. Run docker compose up\r\n\r\n* Actual:\r\nThe errors shown on the screen capture are thrown, and the _web_ container fails to get up.\r\n* Expected:\r\n_web_ container successfully brought up\r\n\r\n### Proposal & Constraints\r\nThe issue seems to be on the `get_software_version()` function called on `openlibrary/plugins/openlibrary/status.py`, when `git rev-parse --short HEAD --` returns a non-zero exit status 128.\r\n\r\nI put the function call between quotation marks as a test, so that \"Software version\" becomes a hardcoded string, and after that everything seems to load and work just fine.\r\n\r\n\n", "before_files": [{"content": "\"\"\"Generic utilities\"\"\"\n\nfrom enum import Enum\nimport re\nfrom subprocess import run\nfrom typing import TypeVar, Literal, Optional\nfrom collections.abc import Iterable, Callable\n\nto_drop = set(''';/?:@&=+$,<>#%\"{}|\\\\^[]`\\n\\r''')\n\n\ndef str_to_key(s: str) -> str:\n    \"\"\"\n    >>> str_to_key(\"?H$e##l{o}[0] -world!\")\n    'helo0_-world!'\n    >>> str_to_key(\"\".join(to_drop))\n    ''\n    >>> str_to_key(\"\")\n    ''\n    \"\"\"\n    return ''.join(c if c != ' ' else '_' for c in s.lower() if c not in to_drop)\n\n\ndef finddict(dicts, **filters):\n    \"\"\"Find a dictionary that matches given filter conditions.\n\n    >>> dicts = [{\"x\": 1, \"y\": 2}, {\"x\": 3, \"y\": 4}]\n    >>> sorted(finddict(dicts, x=1).items())\n    [('x', 1), ('y', 2)]\n    \"\"\"\n    for d in dicts:\n        if all(d.get(k) == v for k, v in filters.items()):\n            return d\n\n\nT = TypeVar('T')\n\n\ndef uniq(values: Iterable[T], key=None) -> list[T]:\n    \"\"\"Returns the unique entries from the given values in the original order.\n\n    The value of the optional `key` parameter should be a function that takes\n    a single argument and returns a key to test the uniqueness.\n    TODO: Moved this to core/utils.py\n\n    >>> uniq(\"abcbcddefefg\")\n    ['a', 'b', 'c', 'd', 'e', 'f', 'g']\n    >>> uniq(\"011223344556677889\")\n    ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']\n    \"\"\"\n    key = key or (lambda x: x)\n    s = set()\n    result = []\n    for v in values:\n        k = key(v)\n        if k not in s:\n            s.add(k)\n            result.append(v)\n    return result\n\n\ndef take_best(\n    items: list[T],\n    optimization: Literal[\"min\", \"max\"],\n    scoring_fn: Callable[[T], float],\n) -> list[T]:\n    \"\"\"\n    >>> take_best([], 'min', lambda x: x)\n    []\n    >>> take_best([3, 2, 1], 'min', lambda x: x)\n    [1]\n    >>> take_best([3, 4, 5], 'max', lambda x: x)\n    [5]\n    >>> take_best([4, 1, -1, -1], 'min', lambda x: x)\n    [-1, -1]\n    \"\"\"\n    best_score = float(\"-inf\") if optimization == \"max\" else float(\"inf\")\n    besties = []\n    for item in items:\n        score = scoring_fn(item)\n        if (optimization == \"max\" and score > best_score) or (\n            optimization == \"min\" and score < best_score\n        ):\n            best_score = score\n            besties = [item]\n        elif score == best_score:\n            besties.append(item)\n        else:\n            continue\n    return besties\n\n\ndef multisort_best(\n    items: list[T], specs: list[tuple[Literal[\"min\", \"max\"], Callable[[T], float]]]\n) -> Optional[T]:\n    \"\"\"\n    Takes the best item, taking into account the multisorts\n\n    >>> multisort_best([], [])\n\n    >>> multisort_best([3,4,5], [('max', lambda x: x)])\n    5\n\n    >>> multisort_best([\n    ...     {'provider': 'ia', 'size': 4},\n    ...     {'provider': 'ia', 'size': 12},\n    ...     {'provider': None, 'size': 42},\n    ... ], [\n    ...     ('min', lambda x: 0 if x['provider'] == 'ia' else 1),\n    ...     ('max', lambda x: x['size']),\n    ... ])\n    {'provider': 'ia', 'size': 12}\n    \"\"\"\n    if not items:\n        return None\n    pool = items\n    for optimization, fn in specs:\n        # Shrink the pool down each time\n        pool = take_best(pool, optimization, fn)\n    return pool[0]\n\n\ndef dicthash(d):\n    \"\"\"Dictionaries are not hashable. This function converts dictionary into nested\n    tuples, so that it can hashed.\n    \"\"\"\n    if isinstance(d, dict):\n        return tuple((k, dicthash(d[k])) for k in sorted(d))\n    elif isinstance(d, list):\n        return tuple(dicthash(v) for v in d)\n    else:\n        return d\n\n\nolid_re = re.compile(r'OL\\d+[A-Z]', re.IGNORECASE)\n\n\ndef find_olid_in_string(s: str, olid_suffix: str | None = None) -> str | None:\n    \"\"\"\n    >>> find_olid_in_string(\"ol123w\")\n    'OL123W'\n    >>> find_olid_in_string(\"/authors/OL123A/DAVIE_BOWIE\")\n    'OL123A'\n    >>> find_olid_in_string(\"/authors/OL123A/DAVIE_BOWIE\", \"W\")\n    >>> find_olid_in_string(\"some random string\")\n    \"\"\"\n    found = re.search(olid_re, s)\n    if not found:\n        return None\n    olid = found.group(0).upper()\n\n    if olid_suffix and not olid.endswith(olid_suffix):\n        return None\n\n    return olid\n\n\ndef olid_to_key(olid: str) -> str:\n    \"\"\"\n    >>> olid_to_key('OL123W')\n    '/works/OL123W'\n    >>> olid_to_key('OL123A')\n    '/authors/OL123A'\n    >>> olid_to_key('OL123M')\n    '/books/OL123M'\n    >>> olid_to_key(\"OL123L\")\n    '/lists/OL123L'\n    \"\"\"\n    typ = {\n        'A': 'authors',\n        'W': 'works',\n        'M': 'books',\n        'L': 'lists',\n    }[olid[-1]]\n    if not typ:\n        raise ValueError(f\"Invalid olid: {olid}\")\n    return f\"/{typ}/{olid}\"\n\n\ndef extract_numeric_id_from_olid(olid):\n    \"\"\"\n    >>> extract_numeric_id_from_olid(\"OL123W\")\n    '123'\n    >>> extract_numeric_id_from_olid(\"/authors/OL123A\")\n    '123'\n    \"\"\"\n    if '/' in olid:\n        olid = olid.split('/')[-1]\n    if olid.lower().startswith('ol'):\n        olid = olid[2:]\n    if not is_number(olid[-1].lower()):\n        olid = olid[:-1]\n    return olid\n\n\ndef is_number(s):\n    \"\"\"\n    >>> all(is_number(n) for n in (1234, \"1234\", -1234, \"-1234\", 123.4, -123.4))\n    True\n    >>> not any(is_number(n) for n in (\"123.4\", \"-123.4\", \"123a\", \"--1234\"))\n    True\n    \"\"\"\n    try:\n        int(s)\n        return True\n    except ValueError:\n        return False\n\n\ndef get_software_version() -> str:\n    \"\"\"\n    assert get_software_version()  # Should never return a falsy value\n    \"\"\"\n    cmd = \"git rev-parse --short HEAD --\".split()\n    return run(cmd, capture_output=True, text=True, check=True).stdout.strip()\n\n\n# See https://docs.python.org/3/library/enum.html#orderedenum\nclass OrderedEnum(Enum):\n    def __ge__(self, other):\n        if self.__class__ is other.__class__:\n            return self.value >= other.value\n        return NotImplemented\n\n    def __gt__(self, other):\n        if self.__class__ is other.__class__:\n            return self.value > other.value\n        return NotImplemented\n\n    def __le__(self, other):\n        if self.__class__ is other.__class__:\n            return self.value <= other.value\n        return NotImplemented\n\n    def __lt__(self, other):\n        if self.__class__ is other.__class__:\n            return self.value < other.value\n        return NotImplemented\n", "path": "openlibrary/utils/__init__.py"}], "after_files": [{"content": "\"\"\"Generic utilities\"\"\"\n\nfrom enum import Enum\nimport re\nfrom subprocess import CalledProcessError, run\nfrom typing import TypeVar, Literal, Optional\nfrom collections.abc import Iterable, Callable\n\nto_drop = set(''';/?:@&=+$,<>#%\"{}|\\\\^[]`\\n\\r''')\n\n\ndef str_to_key(s: str) -> str:\n    \"\"\"\n    >>> str_to_key(\"?H$e##l{o}[0] -world!\")\n    'helo0_-world!'\n    >>> str_to_key(\"\".join(to_drop))\n    ''\n    >>> str_to_key(\"\")\n    ''\n    \"\"\"\n    return ''.join(c if c != ' ' else '_' for c in s.lower() if c not in to_drop)\n\n\ndef finddict(dicts, **filters):\n    \"\"\"Find a dictionary that matches given filter conditions.\n\n    >>> dicts = [{\"x\": 1, \"y\": 2}, {\"x\": 3, \"y\": 4}]\n    >>> sorted(finddict(dicts, x=1).items())\n    [('x', 1), ('y', 2)]\n    \"\"\"\n    for d in dicts:\n        if all(d.get(k) == v for k, v in filters.items()):\n            return d\n\n\nT = TypeVar('T')\n\n\ndef uniq(values: Iterable[T], key=None) -> list[T]:\n    \"\"\"Returns the unique entries from the given values in the original order.\n\n    The value of the optional `key` parameter should be a function that takes\n    a single argument and returns a key to test the uniqueness.\n    TODO: Moved this to core/utils.py\n\n    >>> uniq(\"abcbcddefefg\")\n    ['a', 'b', 'c', 'd', 'e', 'f', 'g']\n    >>> uniq(\"011223344556677889\")\n    ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']\n    \"\"\"\n    key = key or (lambda x: x)\n    s = set()\n    result = []\n    for v in values:\n        k = key(v)\n        if k not in s:\n            s.add(k)\n            result.append(v)\n    return result\n\n\ndef take_best(\n    items: list[T],\n    optimization: Literal[\"min\", \"max\"],\n    scoring_fn: Callable[[T], float],\n) -> list[T]:\n    \"\"\"\n    >>> take_best([], 'min', lambda x: x)\n    []\n    >>> take_best([3, 2, 1], 'min', lambda x: x)\n    [1]\n    >>> take_best([3, 4, 5], 'max', lambda x: x)\n    [5]\n    >>> take_best([4, 1, -1, -1], 'min', lambda x: x)\n    [-1, -1]\n    \"\"\"\n    best_score = float(\"-inf\") if optimization == \"max\" else float(\"inf\")\n    besties = []\n    for item in items:\n        score = scoring_fn(item)\n        if (optimization == \"max\" and score > best_score) or (\n            optimization == \"min\" and score < best_score\n        ):\n            best_score = score\n            besties = [item]\n        elif score == best_score:\n            besties.append(item)\n        else:\n            continue\n    return besties\n\n\ndef multisort_best(\n    items: list[T], specs: list[tuple[Literal[\"min\", \"max\"], Callable[[T], float]]]\n) -> Optional[T]:\n    \"\"\"\n    Takes the best item, taking into account the multisorts\n\n    >>> multisort_best([], [])\n\n    >>> multisort_best([3,4,5], [('max', lambda x: x)])\n    5\n\n    >>> multisort_best([\n    ...     {'provider': 'ia', 'size': 4},\n    ...     {'provider': 'ia', 'size': 12},\n    ...     {'provider': None, 'size': 42},\n    ... ], [\n    ...     ('min', lambda x: 0 if x['provider'] == 'ia' else 1),\n    ...     ('max', lambda x: x['size']),\n    ... ])\n    {'provider': 'ia', 'size': 12}\n    \"\"\"\n    if not items:\n        return None\n    pool = items\n    for optimization, fn in specs:\n        # Shrink the pool down each time\n        pool = take_best(pool, optimization, fn)\n    return pool[0]\n\n\ndef dicthash(d):\n    \"\"\"Dictionaries are not hashable. This function converts dictionary into nested\n    tuples, so that it can hashed.\n    \"\"\"\n    if isinstance(d, dict):\n        return tuple((k, dicthash(d[k])) for k in sorted(d))\n    elif isinstance(d, list):\n        return tuple(dicthash(v) for v in d)\n    else:\n        return d\n\n\nolid_re = re.compile(r'OL\\d+[A-Z]', re.IGNORECASE)\n\n\ndef find_olid_in_string(s: str, olid_suffix: str | None = None) -> str | None:\n    \"\"\"\n    >>> find_olid_in_string(\"ol123w\")\n    'OL123W'\n    >>> find_olid_in_string(\"/authors/OL123A/DAVIE_BOWIE\")\n    'OL123A'\n    >>> find_olid_in_string(\"/authors/OL123A/DAVIE_BOWIE\", \"W\")\n    >>> find_olid_in_string(\"some random string\")\n    \"\"\"\n    found = re.search(olid_re, s)\n    if not found:\n        return None\n    olid = found.group(0).upper()\n\n    if olid_suffix and not olid.endswith(olid_suffix):\n        return None\n\n    return olid\n\n\ndef olid_to_key(olid: str) -> str:\n    \"\"\"\n    >>> olid_to_key('OL123W')\n    '/works/OL123W'\n    >>> olid_to_key('OL123A')\n    '/authors/OL123A'\n    >>> olid_to_key('OL123M')\n    '/books/OL123M'\n    >>> olid_to_key(\"OL123L\")\n    '/lists/OL123L'\n    \"\"\"\n    typ = {\n        'A': 'authors',\n        'W': 'works',\n        'M': 'books',\n        'L': 'lists',\n    }[olid[-1]]\n    if not typ:\n        raise ValueError(f\"Invalid olid: {olid}\")\n    return f\"/{typ}/{olid}\"\n\n\ndef extract_numeric_id_from_olid(olid):\n    \"\"\"\n    >>> extract_numeric_id_from_olid(\"OL123W\")\n    '123'\n    >>> extract_numeric_id_from_olid(\"/authors/OL123A\")\n    '123'\n    \"\"\"\n    if '/' in olid:\n        olid = olid.split('/')[-1]\n    if olid.lower().startswith('ol'):\n        olid = olid[2:]\n    if not is_number(olid[-1].lower()):\n        olid = olid[:-1]\n    return olid\n\n\ndef is_number(s):\n    \"\"\"\n    >>> all(is_number(n) for n in (1234, \"1234\", -1234, \"-1234\", 123.4, -123.4))\n    True\n    >>> not any(is_number(n) for n in (\"123.4\", \"-123.4\", \"123a\", \"--1234\"))\n    True\n    \"\"\"\n    try:\n        int(s)\n        return True\n    except ValueError:\n        return False\n\n\ndef get_software_version() -> str:\n    \"\"\"\n    assert get_software_version()  # Should never return a falsy value\n    \"\"\"\n    cmd = \"git rev-parse --short HEAD --\".split()\n    try:\n        return run(cmd, capture_output=True, text=True, check=True).stdout.strip()\n    except CalledProcessError:\n        return \"unknown\"\n\n\n# See https://docs.python.org/3/library/enum.html#orderedenum\nclass OrderedEnum(Enum):\n    def __ge__(self, other):\n        if self.__class__ is other.__class__:\n            return self.value >= other.value\n        return NotImplemented\n\n    def __gt__(self, other):\n        if self.__class__ is other.__class__:\n            return self.value > other.value\n        return NotImplemented\n\n    def __le__(self, other):\n        if self.__class__ is other.__class__:\n            return self.value <= other.value\n        return NotImplemented\n\n    def __lt__(self, other):\n        if self.__class__ is other.__class__:\n            return self.value < other.value\n        return NotImplemented\n", "path": "openlibrary/utils/__init__.py"}]}