obographs 0.0.1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Charles Tapley Hoyt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,351 @@
1
+ Metadata-Version: 2.4
2
+ Name: obographs
3
+ Version: 0.0.1
4
+ Summary: A python data model for OBO Graphs
5
+ Keywords: snekpack,cookiecutter
6
+ Author: Charles Tapley Hoyt
7
+ Author-email: Charles Tapley Hoyt <cthoyt@gmail.com>
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 1 - Planning
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Framework :: Pytest
15
+ Classifier: Framework :: tox
16
+ Classifier: Framework :: Sphinx
17
+ Classifier: Natural Language :: English
18
+ Classifier: Programming Language :: Python
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3 :: Only
24
+ Classifier: Typing :: Typed
25
+ Requires-Dist: pydantic
26
+ Requires-Dist: sphinx>=8 ; extra == 'docs'
27
+ Requires-Dist: sphinx-rtd-theme>=3.0 ; extra == 'docs'
28
+ Requires-Dist: sphinx-automodapi ; extra == 'docs'
29
+ Requires-Dist: autodoc-pydantic ; extra == 'docs'
30
+ Requires-Dist: requests ; extra == 'network'
31
+ Requires-Dist: pytest ; extra == 'tests'
32
+ Requires-Dist: coverage[toml] ; extra == 'tests'
33
+ Maintainer: Charles Tapley Hoyt
34
+ Maintainer-email: Charles Tapley Hoyt <cthoyt@gmail.com>
35
+ Requires-Python: >=3.10
36
+ Project-URL: Bug Tracker, https://github.com/cthoyt/obographs/issues
37
+ Project-URL: Documentation, https://obographs.readthedocs.io
38
+ Project-URL: Funding, https://github.com/sponsors/cthoyt
39
+ Project-URL: Homepage, https://github.com/cthoyt/obographs
40
+ Project-URL: Repository, https://github.com/cthoyt/obographs.git
41
+ Provides-Extra: docs
42
+ Provides-Extra: network
43
+ Provides-Extra: tests
44
+ Description-Content-Type: text/markdown
45
+
46
+ <!--
47
+ <p align="center">
48
+ <img src="https://github.com/cthoyt/obographs/raw/main/docs/source/logo.png" height="150">
49
+ </p>
50
+ -->
51
+
52
+ <h1 align="center">
53
+ OBO Graphs
54
+ </h1>
55
+
56
+ <p align="center">
57
+ <a href="https://github.com/cthoyt/obographs/actions/workflows/tests.yml">
58
+ <img alt="Tests" src="https://github.com/cthoyt/obographs/actions/workflows/tests.yml/badge.svg" /></a>
59
+ <a href="https://pypi.org/project/obographs">
60
+ <img alt="PyPI" src="https://img.shields.io/pypi/v/obographs" /></a>
61
+ <a href="https://pypi.org/project/obographs">
62
+ <img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/obographs" /></a>
63
+ <a href="https://github.com/cthoyt/obographs/blob/main/LICENSE">
64
+ <img alt="PyPI - License" src="https://img.shields.io/pypi/l/obographs" /></a>
65
+ <a href='https://obographs.readthedocs.io/en/latest/?badge=latest'>
66
+ <img src='https://readthedocs.org/projects/obographs/badge/?version=latest' alt='Documentation Status' /></a>
67
+ <a href="https://codecov.io/gh/cthoyt/obographs/branch/main">
68
+ <img src="https://codecov.io/gh/cthoyt/obographs/branch/main/graph/badge.svg" alt="Codecov status" /></a>
69
+ <a href="https://github.com/cthoyt/cookiecutter-python-package">
70
+ <img alt="Cookiecutter template from @cthoyt" src="https://img.shields.io/badge/Cookiecutter-snekpack-blue" /></a>
71
+ <a href="https://github.com/astral-sh/ruff">
72
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff" style="max-width:100%;"></a>
73
+ <a href="https://github.com/cthoyt/obographs/blob/main/.github/CODE_OF_CONDUCT.md">
74
+ <img src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg" alt="Contributor Covenant"/></a>
75
+ <!-- uncomment if you archive on zenodo
76
+ <a href="https://zenodo.org/badge/latestdoi/XXXXXX">
77
+ <img src="https://zenodo.org/badge/XXXXXX.svg" alt="DOI"></a>
78
+ -->
79
+ </p>
80
+
81
+ A Python data model for [OBO Graphs](https://github.com/geneontology/obographs).
82
+
83
+ ## 💪 Getting Started
84
+
85
+ This package enables reading a remote OBO Graph JSON file into a Pydantic-backed
86
+ data model.
87
+
88
+ ```python
89
+ import obographs
90
+
91
+ url = "https://raw.githubusercontent.com/geneontology/obographs/refs/heads/master/examples/abox.json"
92
+ graph_document = obographs.read(url)
93
+ ```
94
+
95
+ Note that the OBO Graph JSON schema uses non-pythonic names. The underlying data
96
+ model does not attempt to give better names to the fields.
97
+
98
+ ## 🚀 Installation
99
+
100
+ The most recent release can be installed from
101
+ [PyPI](https://pypi.org/project/obographs/) with uv:
102
+
103
+ ```console
104
+ $ uv pip install obographs
105
+ ```
106
+
107
+ or with pip:
108
+
109
+ ```console
110
+ $ python3 -m pip install obographs
111
+ ```
112
+
113
+ The most recent code and data can be installed directly from GitHub with uv:
114
+
115
+ ```console
116
+ $ uv --preview pip install git+https://github.com/cthoyt/obographs.git
117
+ ```
118
+
119
+ or with pip:
120
+
121
+ ```console
122
+ $ UV_PREVIEW=1 python3 -m pip install git+https://github.com/cthoyt/obographs.git
123
+ ```
124
+
125
+ Note that this requires setting `UV_PREVIEW` mode enabled until the uv build
126
+ backend becomes a stable feature.
127
+
128
+ ## 👐 Contributing
129
+
130
+ Contributions, whether filing an issue, making a pull request, or forking, are
131
+ appreciated. See
132
+ [CONTRIBUTING.md](https://github.com/cthoyt/obographs/blob/master/.github/CONTRIBUTING.md)
133
+ for more information on getting involved.
134
+
135
+ ## 👋 Attribution
136
+
137
+ ### ⚖️ License
138
+
139
+ The code in this package is licensed under the MIT License.
140
+
141
+ <!--
142
+ ### 📖 Citation
143
+
144
+ Citation goes here!
145
+ -->
146
+
147
+ <!--
148
+ ### 🎁 Support
149
+
150
+ This project has been supported by the following organizations (in alphabetical order):
151
+
152
+ - [Biopragmatics Lab](https://biopragmatics.github.io)
153
+
154
+ -->
155
+
156
+ <!--
157
+ ### 💰 Funding
158
+
159
+ This project has been supported by the following grants:
160
+
161
+ | Funding Body | Program | Grant Number |
162
+ |---------------|--------------------------------------------------------------|--------------|
163
+ | Funder | [Grant Name (GRANT-ACRONYM)](https://example.com/grant-link) | ABCXYZ |
164
+ -->
165
+
166
+ ### 🍪 Cookiecutter
167
+
168
+ This package was created with
169
+ [@audreyfeldroy](https://github.com/audreyfeldroy)'s
170
+ [cookiecutter](https://github.com/cookiecutter/cookiecutter) package using
171
+ [@cthoyt](https://github.com/cthoyt)'s
172
+ [cookiecutter-snekpack](https://github.com/cthoyt/cookiecutter-snekpack)
173
+ template.
174
+
175
+ ## 🛠️ For Developers
176
+
177
+ <details>
178
+ <summary>See developer instructions</summary>
179
+
180
+ The final section of the README is for if you want to get involved by making a
181
+ code contribution.
182
+
183
+ ### Development Installation
184
+
185
+ To install in development mode, use the following:
186
+
187
+ ```console
188
+ $ git clone git+https://github.com/cthoyt/obographs.git
189
+ $ cd obographs
190
+ $ uv --preview pip install -e .
191
+ ```
192
+
193
+ Alternatively, install using pip:
194
+
195
+ ```console
196
+ $ UV_PREVIEW=1 python3 -m pip install -e .
197
+ ```
198
+
199
+ Note that this requires setting `UV_PREVIEW` mode enabled until the uv build
200
+ backend becomes a stable feature.
201
+
202
+ ### Updating Package Boilerplate
203
+
204
+ This project uses `cruft` to keep boilerplate (i.e., configuration, contribution
205
+ guidelines, documentation configuration) up-to-date with the upstream
206
+ cookiecutter package. Install cruft with either `uv tool install cruft` or
207
+ `python3 -m pip install cruft` then run:
208
+
209
+ ```console
210
+ $ cruft update
211
+ ```
212
+
213
+ More info on Cruft's update command is available
214
+ [here](https://github.com/cruft/cruft?tab=readme-ov-file#updating-a-project).
215
+
216
+ ### 🥼 Testing
217
+
218
+ After cloning the repository and installing `tox` with
219
+ `uv tool install tox --with tox-uv` or `python3 -m pip install tox tox-uv`, the
220
+ unit tests in the `tests/` folder can be run reproducibly with:
221
+
222
+ ```console
223
+ $ tox -e py
224
+ ```
225
+
226
+ Additionally, these tests are automatically re-run with each commit in a
227
+ [GitHub Action](https://github.com/cthoyt/obographs/actions?query=workflow%3ATests).
228
+
229
+ ### 📖 Building the Documentation
230
+
231
+ The documentation can be built locally using the following:
232
+
233
+ ```console
234
+ $ git clone git+https://github.com/cthoyt/obographs.git
235
+ $ cd obographs
236
+ $ tox -e docs
237
+ $ open docs/build/html/index.html
238
+ ```
239
+
240
+ The documentation automatically installs the package as well as the `docs` extra
241
+ specified in the [`pyproject.toml`](pyproject.toml). `sphinx` plugins like
242
+ `texext` can be added there. Additionally, they need to be added to the
243
+ `extensions` list in [`docs/source/conf.py`](docs/source/conf.py).
244
+
245
+ The documentation can be deployed to [ReadTheDocs](https://readthedocs.io) using
246
+ [this guide](https://docs.readthedocs.io/en/stable/intro/import-guide.html). The
247
+ [`.readthedocs.yml`](.readthedocs.yml) YAML file contains all the configuration
248
+ you'll need. You can also set up continuous integration on GitHub to check not
249
+ only that Sphinx can build the documentation in an isolated environment (i.e.,
250
+ with `tox -e docs-test`) but also that
251
+ [ReadTheDocs can build it too](https://docs.readthedocs.io/en/stable/pull-requests.html).
252
+
253
+ #### Configuring ReadTheDocs
254
+
255
+ 1. Log in to ReadTheDocs with your GitHub account to install the integration at
256
+ https://readthedocs.org/accounts/login/?next=/dashboard/
257
+ 2. Import your project by navigating to https://readthedocs.org/dashboard/import
258
+ then clicking the plus icon next to your repository
259
+ 3. You can rename the repository on the next screen using a more stylized name
260
+ (i.e., with spaces and capital letters)
261
+ 4. Click next, and you're good to go!
262
+
263
+ ### 📦 Making a Release
264
+
265
+ #### Configuring Zenodo
266
+
267
+ [Zenodo](https://zenodo.org) is a long-term archival system that assigns a DOI
268
+ to each release of your package.
269
+
270
+ 1. Log in to Zenodo via GitHub with this link:
271
+ https://zenodo.org/oauth/login/github/?next=%2F. This brings you to a page
272
+ that lists all of your organizations and asks you to approve installing the
273
+ Zenodo app on GitHub. Click "grant" next to any organizations you want to
274
+ enable the integration for, then click the big green "approve" button. This
275
+ step only needs to be done once.
276
+ 2. Navigate to https://zenodo.org/account/settings/github/, which lists all of
277
+ your GitHub repositories (both in your username and any organizations you
278
+ enabled). Click the on/off toggle for any relevant repositories. When you
279
+ make a new repository, you'll have to come back to this
280
+
281
+ After these steps, you're ready to go! After you make "release" on GitHub (steps
282
+ for this are below), you can navigate to
283
+ https://zenodo.org/account/settings/github/repository/cthoyt/obographs to see
284
+ the DOI for the release and link to the Zenodo record for it.
285
+
286
+ #### Registering with the Python Package Index (PyPI)
287
+
288
+ You only have to do the following steps once.
289
+
290
+ 1. Register for an account on the
291
+ [Python Package Index (PyPI)](https://pypi.org/account/register)
292
+ 2. Navigate to https://pypi.org/manage/account and make sure you have verified
293
+ your email address. A verification email might not have been sent by default,
294
+ so you might have to click the "options" dropdown next to your address to get
295
+ to the "re-send verification email" button
296
+ 3. 2-Factor authentication is required for PyPI since the end of 2023 (see this
297
+ [blog post from PyPI](https://blog.pypi.org/posts/2023-05-25-securing-pypi-with-2fa/)).
298
+ This means you have to first issue account recovery codes, then set up
299
+ 2-factor authentication
300
+ 4. Issue an API token from https://pypi.org/manage/account/token
301
+
302
+ #### Configuring your machine's connection to PyPI
303
+
304
+ You have to do the following steps once per machine.
305
+
306
+ ```console
307
+ $ uv tool install keyring
308
+ $ keyring set https://upload.pypi.org/legacy/ __token__
309
+ $ keyring set https://test.pypi.org/legacy/ __token__
310
+ ```
311
+
312
+ Note that this deprecates previous workflows using `.pypirc`.
313
+
314
+ #### Uploading to PyPI
315
+
316
+ After installing the package in development mode and installing `tox` with
317
+ `uv tool install tox --with tox-uv` or `python3 -m pip install tox tox-uv`, run
318
+ the following from the console:
319
+
320
+ ```console
321
+ $ tox -e finish
322
+ ```
323
+
324
+ This script does the following:
325
+
326
+ 1. Uses [bump-my-version](https://github.com/callowayproject/bump-my-version) to
327
+ switch the version number in the `pyproject.toml`, `CITATION.cff`,
328
+ `src/obographs/version.py`, and [`docs/source/conf.py`](docs/source/conf.py)
329
+ to not have the `-dev` suffix
330
+ 2. Packages the code in both a tar archive and a wheel using
331
+ [`uv build`](https://docs.astral.sh/uv/guides/publish/#building-your-package)
332
+ 3. Uploads to PyPI using
333
+ [`uv publish`](https://docs.astral.sh/uv/guides/publish/#publishing-your-package).
334
+ 4. Push to GitHub. You'll need to make a release going with the commit where the
335
+ version was bumped.
336
+ 5. Bump the version to the next patch. If you made big changes and want to bump
337
+ the version by minor, you can use `tox -e bumpversion -- minor` after.
338
+
339
+ #### Releasing on GitHub
340
+
341
+ 1. Navigate to https://github.com/cthoyt/obographs/releases/new to draft a new
342
+ release
343
+ 2. Click the "Choose a Tag" dropdown and select the tag corresponding to the
344
+ release you just made
345
+ 3. Click the "Generate Release Notes" button to get a quick outline of recent
346
+ changes. Modify the title and description as you see fit
347
+ 4. Click the big green "Publish Release" button
348
+
349
+ This will trigger Zenodo to assign a DOI to your release as well.
350
+
351
+ </details>
@@ -0,0 +1,306 @@
1
+ <!--
2
+ <p align="center">
3
+ <img src="https://github.com/cthoyt/obographs/raw/main/docs/source/logo.png" height="150">
4
+ </p>
5
+ -->
6
+
7
+ <h1 align="center">
8
+ OBO Graphs
9
+ </h1>
10
+
11
+ <p align="center">
12
+ <a href="https://github.com/cthoyt/obographs/actions/workflows/tests.yml">
13
+ <img alt="Tests" src="https://github.com/cthoyt/obographs/actions/workflows/tests.yml/badge.svg" /></a>
14
+ <a href="https://pypi.org/project/obographs">
15
+ <img alt="PyPI" src="https://img.shields.io/pypi/v/obographs" /></a>
16
+ <a href="https://pypi.org/project/obographs">
17
+ <img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/obographs" /></a>
18
+ <a href="https://github.com/cthoyt/obographs/blob/main/LICENSE">
19
+ <img alt="PyPI - License" src="https://img.shields.io/pypi/l/obographs" /></a>
20
+ <a href='https://obographs.readthedocs.io/en/latest/?badge=latest'>
21
+ <img src='https://readthedocs.org/projects/obographs/badge/?version=latest' alt='Documentation Status' /></a>
22
+ <a href="https://codecov.io/gh/cthoyt/obographs/branch/main">
23
+ <img src="https://codecov.io/gh/cthoyt/obographs/branch/main/graph/badge.svg" alt="Codecov status" /></a>
24
+ <a href="https://github.com/cthoyt/cookiecutter-python-package">
25
+ <img alt="Cookiecutter template from @cthoyt" src="https://img.shields.io/badge/Cookiecutter-snekpack-blue" /></a>
26
+ <a href="https://github.com/astral-sh/ruff">
27
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff" style="max-width:100%;"></a>
28
+ <a href="https://github.com/cthoyt/obographs/blob/main/.github/CODE_OF_CONDUCT.md">
29
+ <img src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg" alt="Contributor Covenant"/></a>
30
+ <!-- uncomment if you archive on zenodo
31
+ <a href="https://zenodo.org/badge/latestdoi/XXXXXX">
32
+ <img src="https://zenodo.org/badge/XXXXXX.svg" alt="DOI"></a>
33
+ -->
34
+ </p>
35
+
36
+ A Python data model for [OBO Graphs](https://github.com/geneontology/obographs).
37
+
38
+ ## 💪 Getting Started
39
+
40
+ This package enables reading a remote OBO Graph JSON file into a Pydantic-backed
41
+ data model.
42
+
43
+ ```python
44
+ import obographs
45
+
46
+ url = "https://raw.githubusercontent.com/geneontology/obographs/refs/heads/master/examples/abox.json"
47
+ graph_document = obographs.read(url)
48
+ ```
49
+
50
+ Note that the OBO Graph JSON schema uses non-pythonic names. The underlying data
51
+ model does not attempt to give better names to the fields.
52
+
53
+ ## 🚀 Installation
54
+
55
+ The most recent release can be installed from
56
+ [PyPI](https://pypi.org/project/obographs/) with uv:
57
+
58
+ ```console
59
+ $ uv pip install obographs
60
+ ```
61
+
62
+ or with pip:
63
+
64
+ ```console
65
+ $ python3 -m pip install obographs
66
+ ```
67
+
68
+ The most recent code and data can be installed directly from GitHub with uv:
69
+
70
+ ```console
71
+ $ uv --preview pip install git+https://github.com/cthoyt/obographs.git
72
+ ```
73
+
74
+ or with pip:
75
+
76
+ ```console
77
+ $ UV_PREVIEW=1 python3 -m pip install git+https://github.com/cthoyt/obographs.git
78
+ ```
79
+
80
+ Note that this requires setting `UV_PREVIEW` mode enabled until the uv build
81
+ backend becomes a stable feature.
82
+
83
+ ## 👐 Contributing
84
+
85
+ Contributions, whether filing an issue, making a pull request, or forking, are
86
+ appreciated. See
87
+ [CONTRIBUTING.md](https://github.com/cthoyt/obographs/blob/master/.github/CONTRIBUTING.md)
88
+ for more information on getting involved.
89
+
90
+ ## 👋 Attribution
91
+
92
+ ### ⚖️ License
93
+
94
+ The code in this package is licensed under the MIT License.
95
+
96
+ <!--
97
+ ### 📖 Citation
98
+
99
+ Citation goes here!
100
+ -->
101
+
102
+ <!--
103
+ ### 🎁 Support
104
+
105
+ This project has been supported by the following organizations (in alphabetical order):
106
+
107
+ - [Biopragmatics Lab](https://biopragmatics.github.io)
108
+
109
+ -->
110
+
111
+ <!--
112
+ ### 💰 Funding
113
+
114
+ This project has been supported by the following grants:
115
+
116
+ | Funding Body | Program | Grant Number |
117
+ |---------------|--------------------------------------------------------------|--------------|
118
+ | Funder | [Grant Name (GRANT-ACRONYM)](https://example.com/grant-link) | ABCXYZ |
119
+ -->
120
+
121
+ ### 🍪 Cookiecutter
122
+
123
+ This package was created with
124
+ [@audreyfeldroy](https://github.com/audreyfeldroy)'s
125
+ [cookiecutter](https://github.com/cookiecutter/cookiecutter) package using
126
+ [@cthoyt](https://github.com/cthoyt)'s
127
+ [cookiecutter-snekpack](https://github.com/cthoyt/cookiecutter-snekpack)
128
+ template.
129
+
130
+ ## 🛠️ For Developers
131
+
132
+ <details>
133
+ <summary>See developer instructions</summary>
134
+
135
+ The final section of the README is for if you want to get involved by making a
136
+ code contribution.
137
+
138
+ ### Development Installation
139
+
140
+ To install in development mode, use the following:
141
+
142
+ ```console
143
+ $ git clone git+https://github.com/cthoyt/obographs.git
144
+ $ cd obographs
145
+ $ uv --preview pip install -e .
146
+ ```
147
+
148
+ Alternatively, install using pip:
149
+
150
+ ```console
151
+ $ UV_PREVIEW=1 python3 -m pip install -e .
152
+ ```
153
+
154
+ Note that this requires setting `UV_PREVIEW` mode enabled until the uv build
155
+ backend becomes a stable feature.
156
+
157
+ ### Updating Package Boilerplate
158
+
159
+ This project uses `cruft` to keep boilerplate (i.e., configuration, contribution
160
+ guidelines, documentation configuration) up-to-date with the upstream
161
+ cookiecutter package. Install cruft with either `uv tool install cruft` or
162
+ `python3 -m pip install cruft` then run:
163
+
164
+ ```console
165
+ $ cruft update
166
+ ```
167
+
168
+ More info on Cruft's update command is available
169
+ [here](https://github.com/cruft/cruft?tab=readme-ov-file#updating-a-project).
170
+
171
+ ### 🥼 Testing
172
+
173
+ After cloning the repository and installing `tox` with
174
+ `uv tool install tox --with tox-uv` or `python3 -m pip install tox tox-uv`, the
175
+ unit tests in the `tests/` folder can be run reproducibly with:
176
+
177
+ ```console
178
+ $ tox -e py
179
+ ```
180
+
181
+ Additionally, these tests are automatically re-run with each commit in a
182
+ [GitHub Action](https://github.com/cthoyt/obographs/actions?query=workflow%3ATests).
183
+
184
+ ### 📖 Building the Documentation
185
+
186
+ The documentation can be built locally using the following:
187
+
188
+ ```console
189
+ $ git clone git+https://github.com/cthoyt/obographs.git
190
+ $ cd obographs
191
+ $ tox -e docs
192
+ $ open docs/build/html/index.html
193
+ ```
194
+
195
+ The documentation automatically installs the package as well as the `docs` extra
196
+ specified in the [`pyproject.toml`](pyproject.toml). `sphinx` plugins like
197
+ `texext` can be added there. Additionally, they need to be added to the
198
+ `extensions` list in [`docs/source/conf.py`](docs/source/conf.py).
199
+
200
+ The documentation can be deployed to [ReadTheDocs](https://readthedocs.io) using
201
+ [this guide](https://docs.readthedocs.io/en/stable/intro/import-guide.html). The
202
+ [`.readthedocs.yml`](.readthedocs.yml) YAML file contains all the configuration
203
+ you'll need. You can also set up continuous integration on GitHub to check not
204
+ only that Sphinx can build the documentation in an isolated environment (i.e.,
205
+ with `tox -e docs-test`) but also that
206
+ [ReadTheDocs can build it too](https://docs.readthedocs.io/en/stable/pull-requests.html).
207
+
208
+ #### Configuring ReadTheDocs
209
+
210
+ 1. Log in to ReadTheDocs with your GitHub account to install the integration at
211
+ https://readthedocs.org/accounts/login/?next=/dashboard/
212
+ 2. Import your project by navigating to https://readthedocs.org/dashboard/import
213
+ then clicking the plus icon next to your repository
214
+ 3. You can rename the repository on the next screen using a more stylized name
215
+ (i.e., with spaces and capital letters)
216
+ 4. Click next, and you're good to go!
217
+
218
+ ### 📦 Making a Release
219
+
220
+ #### Configuring Zenodo
221
+
222
+ [Zenodo](https://zenodo.org) is a long-term archival system that assigns a DOI
223
+ to each release of your package.
224
+
225
+ 1. Log in to Zenodo via GitHub with this link:
226
+ https://zenodo.org/oauth/login/github/?next=%2F. This brings you to a page
227
+ that lists all of your organizations and asks you to approve installing the
228
+ Zenodo app on GitHub. Click "grant" next to any organizations you want to
229
+ enable the integration for, then click the big green "approve" button. This
230
+ step only needs to be done once.
231
+ 2. Navigate to https://zenodo.org/account/settings/github/, which lists all of
232
+ your GitHub repositories (both in your username and any organizations you
233
+ enabled). Click the on/off toggle for any relevant repositories. When you
234
+ make a new repository, you'll have to come back to this
235
+
236
+ After these steps, you're ready to go! After you make "release" on GitHub (steps
237
+ for this are below), you can navigate to
238
+ https://zenodo.org/account/settings/github/repository/cthoyt/obographs to see
239
+ the DOI for the release and link to the Zenodo record for it.
240
+
241
+ #### Registering with the Python Package Index (PyPI)
242
+
243
+ You only have to do the following steps once.
244
+
245
+ 1. Register for an account on the
246
+ [Python Package Index (PyPI)](https://pypi.org/account/register)
247
+ 2. Navigate to https://pypi.org/manage/account and make sure you have verified
248
+ your email address. A verification email might not have been sent by default,
249
+ so you might have to click the "options" dropdown next to your address to get
250
+ to the "re-send verification email" button
251
+ 3. 2-Factor authentication is required for PyPI since the end of 2023 (see this
252
+ [blog post from PyPI](https://blog.pypi.org/posts/2023-05-25-securing-pypi-with-2fa/)).
253
+ This means you have to first issue account recovery codes, then set up
254
+ 2-factor authentication
255
+ 4. Issue an API token from https://pypi.org/manage/account/token
256
+
257
+ #### Configuring your machine's connection to PyPI
258
+
259
+ You have to do the following steps once per machine.
260
+
261
+ ```console
262
+ $ uv tool install keyring
263
+ $ keyring set https://upload.pypi.org/legacy/ __token__
264
+ $ keyring set https://test.pypi.org/legacy/ __token__
265
+ ```
266
+
267
+ Note that this deprecates previous workflows using `.pypirc`.
268
+
269
+ #### Uploading to PyPI
270
+
271
+ After installing the package in development mode and installing `tox` with
272
+ `uv tool install tox --with tox-uv` or `python3 -m pip install tox tox-uv`, run
273
+ the following from the console:
274
+
275
+ ```console
276
+ $ tox -e finish
277
+ ```
278
+
279
+ This script does the following:
280
+
281
+ 1. Uses [bump-my-version](https://github.com/callowayproject/bump-my-version) to
282
+ switch the version number in the `pyproject.toml`, `CITATION.cff`,
283
+ `src/obographs/version.py`, and [`docs/source/conf.py`](docs/source/conf.py)
284
+ to not have the `-dev` suffix
285
+ 2. Packages the code in both a tar archive and a wheel using
286
+ [`uv build`](https://docs.astral.sh/uv/guides/publish/#building-your-package)
287
+ 3. Uploads to PyPI using
288
+ [`uv publish`](https://docs.astral.sh/uv/guides/publish/#publishing-your-package).
289
+ 4. Push to GitHub. You'll need to make a release going with the commit where the
290
+ version was bumped.
291
+ 5. Bump the version to the next patch. If you made big changes and want to bump
292
+ the version by minor, you can use `tox -e bumpversion -- minor` after.
293
+
294
+ #### Releasing on GitHub
295
+
296
+ 1. Navigate to https://github.com/cthoyt/obographs/releases/new to draft a new
297
+ release
298
+ 2. Click the "Choose a Tag" dropdown and select the tag corresponding to the
299
+ release you just made
300
+ 3. Click the "Generate Release Notes" button to get a quick outline of recent
301
+ changes. Modify the title and description as you see fit
302
+ 4. Click the big green "Publish Release" button
303
+
304
+ This will trigger Zenodo to assign a DOI to your release as well.
305
+
306
+ </details>
@@ -0,0 +1,222 @@
1
+ [build-system]
2
+ requires = ["uv>=0.5.13,<0.6.0"]
3
+ # The uv backend entered preview mode in https://github.com/astral-sh/uv/pull/8886/files
4
+ # with the 0.5.0 release. See also https://github.com/astral-sh/uv/issues/3957 for tracking.
5
+ build-backend = "uv"
6
+
7
+ [project]
8
+ name = "obographs"
9
+ version = "0.0.1"
10
+ description = "A python data model for OBO Graphs"
11
+ readme = "README.md"
12
+ authors = [
13
+ { name = "Charles Tapley Hoyt", email = "cthoyt@gmail.com" }
14
+ ]
15
+ maintainers = [
16
+ { name = "Charles Tapley Hoyt", email = "cthoyt@gmail.com" }
17
+ ]
18
+
19
+ # See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#classifiers
20
+ # Search tags using the controlled vocabulary at https://pypi.org/classifiers
21
+ classifiers = [
22
+ "Development Status :: 1 - Planning",
23
+ "Environment :: Console",
24
+ "Intended Audience :: Developers",
25
+ "License :: OSI Approved :: MIT License",
26
+ "Operating System :: OS Independent",
27
+ "Framework :: Pytest",
28
+ "Framework :: tox",
29
+ "Framework :: Sphinx",
30
+ "Natural Language :: English",
31
+ "Programming Language :: Python",
32
+ "Programming Language :: Python :: 3.10",
33
+ "Programming Language :: Python :: 3.11",
34
+ "Programming Language :: Python :: 3.12",
35
+ "Programming Language :: Python :: 3.13",
36
+ "Programming Language :: Python :: 3 :: Only",
37
+ "Typing :: Typed",
38
+ # TODO add your topics from the Trove controlled vocabulary (see https://pypi.org/classifiers)
39
+ ]
40
+ keywords = [
41
+ "snekpack", # please keep this keyword to credit the cookiecutter-snekpack template
42
+ "cookiecutter",
43
+ # TODO add your own free-text keywords
44
+ ]
45
+
46
+ # License Information.
47
+ # See PEP-639 at https://peps.python.org/pep-0639/#add-license-files-key
48
+ license-files = [
49
+ "LICENSE",
50
+ ]
51
+
52
+ requires-python = ">=3.10"
53
+ dependencies = [
54
+ "pydantic",
55
+ ]
56
+
57
+ [project.optional-dependencies]
58
+ tests = [
59
+ "pytest",
60
+ "coverage[toml]",
61
+ ]
62
+ docs = [
63
+ "sphinx>=8",
64
+ "sphinx-rtd-theme>=3.0",
65
+ "sphinx_automodapi",
66
+ "autodoc_pydantic",
67
+ ]
68
+ network = [
69
+ "requests",
70
+ ]
71
+
72
+ # See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#urls
73
+ # and also https://packaging.python.org/en/latest/specifications/well-known-project-urls/
74
+ [project.urls]
75
+ "Bug Tracker" = "https://github.com/cthoyt/obographs/issues"
76
+ Homepage = "https://github.com/cthoyt/obographs"
77
+ Repository = "https://github.com/cthoyt/obographs.git"
78
+ Documentation = "https://obographs.readthedocs.io"
79
+ Funding = "https://github.com/sponsors/cthoyt"
80
+
81
+ [project.scripts]
82
+ obographs = "obographs.cli:main"
83
+
84
+ [tool.cruft]
85
+ skip = [
86
+ "**/__init__.py",
87
+ "tests/*"
88
+ ]
89
+
90
+ # MyPy, see https://mypy.readthedocs.io/en/stable/config_file.html
91
+ [tool.mypy]
92
+ plugins = [
93
+ "pydantic.mypy",
94
+ ]
95
+
96
+ # Doc8, see https://doc8.readthedocs.io/en/stable/readme.html#ini-file-usage
97
+ [tool.doc8]
98
+ max-line-length = 120
99
+
100
+ # Pytest, see https://docs.pytest.org/en/stable/reference/customize.html#pyproject-toml
101
+ [tool.pytest.ini_options]
102
+ markers = [
103
+ "slow: marks tests as slow (deselect with '-m \"not slow\"')",
104
+ ]
105
+
106
+ # Coverage, see https://coverage.readthedocs.io/en/latest/config.html
107
+ [tool.coverage.run]
108
+ branch = true
109
+ source = [
110
+ "obographs",
111
+ ]
112
+ omit = [
113
+ "tests/*",
114
+ "docs/*",
115
+ ]
116
+
117
+ [tool.coverage.paths]
118
+ source = [
119
+ "src/obographs",
120
+ ".tox/*/lib/python*/site-packages/obographs",
121
+ ]
122
+
123
+ [tool.coverage.report]
124
+ show_missing = true
125
+ exclude_lines = [
126
+ "pragma: no cover",
127
+ "raise NotImplementedError",
128
+ "if __name__ == \"__main__\":",
129
+ "if TYPE_CHECKING:",
130
+ "def __str__",
131
+ "def __repr__",
132
+ ]
133
+
134
+ [tool.ruff]
135
+ line-length = 100
136
+ extend-include = ["*.ipynb"]
137
+
138
+ [tool.ruff.lint]
139
+ # See https://docs.astral.sh/ruff/rules
140
+ extend-select = [
141
+ "F", # pyflakes
142
+ "E", # pycodestyle errors
143
+ "W", # pycodestyle warnings
144
+ "C90", # mccabe
145
+ "I", # isort
146
+ "UP", # pyupgrade
147
+ "D", # pydocstyle
148
+ "DOC", # pydoclint
149
+ "B", # bugbear
150
+ "S", # bandit
151
+ "T20", # print
152
+ "N", # pep8 naming
153
+ "ERA", # eradicate commented out code
154
+ "NPY", # numpy checks
155
+ "RUF", # ruff rules
156
+ "C4", # comprehensions
157
+ ]
158
+ ignore = [
159
+ "D105", # Missing docstring in magic method
160
+ "E203", # Black conflicts with the following
161
+ ]
162
+
163
+ # See https://docs.astral.sh/ruff/settings/#per-file-ignores
164
+ [tool.ruff.lint.per-file-ignores]
165
+ # Ignore security issues in the version.py, which are inconsistent
166
+ "src/obographs/version.py" = ["S603", "S607"]
167
+ # Ignore commented out code in Sphinx configuration file
168
+ "docs/source/conf.py" = ["ERA001"]
169
+ # Prints are okay in notebooks
170
+ "notebooks/**/*.ipynb" = ["T201"]
171
+
172
+ [tool.ruff.lint.pydocstyle]
173
+ convention = "pep257"
174
+
175
+ [tool.ruff.lint.isort]
176
+ relative-imports-order = "closest-to-furthest"
177
+ known-third-party = [
178
+ "tqdm",
179
+ ]
180
+ known-first-party = [
181
+ "obographs",
182
+ "tests",
183
+ ]
184
+
185
+ [tool.ruff.format]
186
+ # see https://docs.astral.sh/ruff/settings/#format_docstring-code-format
187
+ docstring-code-format = true
188
+
189
+ [tool.bumpversion]
190
+ current_version = "0.0.1"
191
+ parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(?:-(?P<release>[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+(?P<build>[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?"
192
+ serialize = [
193
+ "{major}.{minor}.{patch}-{release}+{build}",
194
+ "{major}.{minor}.{patch}+{build}",
195
+ "{major}.{minor}.{patch}-{release}",
196
+ "{major}.{minor}.{patch}",
197
+ ]
198
+ commit = true
199
+ tag = false
200
+
201
+ [tool.bumpversion.parts.release]
202
+ optional_value = "production"
203
+ first_value = "dev"
204
+ values = [
205
+ "dev",
206
+ "production",
207
+ ]
208
+
209
+ [[tool.bumpversion.files]]
210
+ filename = "pyproject.toml"
211
+ search = "version = \"{current_version}\""
212
+ replace = "version = \"{new_version}\""
213
+
214
+ [[tool.bumpversion.files]]
215
+ filename = "docs/source/conf.py"
216
+ search = "release = \"{current_version}\""
217
+ replace = "release = \"{new_version}\""
218
+
219
+ [[tool.bumpversion.files]]
220
+ filename = "src/obographs/version.py"
221
+ search = "VERSION = \"{current_version}\""
222
+ replace = "VERSION = \"{new_version}\""
@@ -0,0 +1,14 @@
1
+ """A python data model for OBO Graphs."""
2
+
3
+ from .model import Graph, GraphDocument, Meta, Node, Property, Synonym, Xref, read
4
+
5
+ __all__ = [
6
+ "Graph",
7
+ "GraphDocument",
8
+ "Meta",
9
+ "Node",
10
+ "Property",
11
+ "Synonym",
12
+ "Xref",
13
+ "read",
14
+ ]
@@ -0,0 +1,212 @@
1
+ """Data structures for representing OBO Graphs.
2
+
3
+ .. seealso::
4
+
5
+ - the defining repository https://github.com/geneontology/obographs
6
+ - the JSON schema
7
+ https://github.com/geneontology/obographs/blob/master/schema/obographs-schema.json
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import logging
14
+ from collections import defaultdict
15
+ from pathlib import Path
16
+ from typing import Any, Literal, TypeAlias, overload
17
+
18
+ from pydantic import BaseModel, Field
19
+
20
+ __all__ = [
21
+ "Definition",
22
+ "Edge",
23
+ "Graph",
24
+ "GraphDocument",
25
+ "Meta",
26
+ "Node",
27
+ "Property",
28
+ "Synonym",
29
+ "Xref",
30
+ "read",
31
+ ]
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ OBO_URI_PREFIX = "http://purl.obolibrary.org/obo/"
36
+ OBO_URI_PREFIX_LEN = len(OBO_URI_PREFIX)
37
+
38
+ SynonymPredicate: TypeAlias = Literal[
39
+ "hasExactSynonym",
40
+ "hasBroadSynonym",
41
+ "hasNarrowSynonym",
42
+ "hasRelatedSynonym",
43
+ ]
44
+ NodeType: TypeAlias = Literal["CLASS", "PROPERTY", "INDIVIDUAL"]
45
+
46
+ TimeoutHint = int | float | None
47
+
48
+ #: A mapping from OBO flat file format internal synonym types to OBO in OWL vocabulary
49
+ #: identifiers. See https://owlcollab.github.io/oboformat/doc/GO.format.obo-1_4.html
50
+ OBO_SYNONYM_TO_OIO: dict[str, SynonymPredicate] = {
51
+ "EXACT": "hasExactSynonym",
52
+ "BROAD": "hasBroadSynonym",
53
+ "NARROW": "hasNarrowSynonym",
54
+ "RELATED": "hasRelatedSynonym",
55
+ }
56
+
57
+
58
+ class Property(BaseModel):
59
+ """Represent a property inside a metadata element."""
60
+
61
+ pred: str = Field(...)
62
+ val: str = Field(
63
+ ...,
64
+ )
65
+
66
+
67
+ class Definition(BaseModel):
68
+ """Represents a definition for a node."""
69
+
70
+ val: str | None = Field(default=None)
71
+ xrefs: list[str] | None = Field(default=None) # Just a list of CURIEs/IRIs
72
+
73
+
74
+ class Xref(BaseModel):
75
+ """Represents a cross-reference."""
76
+
77
+ val: str = Field(...)
78
+
79
+
80
+ class Synonym(BaseModel):
81
+ """Represents a synonym inside an object meta."""
82
+
83
+ val: str | None = Field(default=None)
84
+ pred: str = Field(default="hasExactSynonym")
85
+ synonymType: str | None = Field(examples=["OMO:0003000"]) # noqa:N815
86
+ xrefs: list[str] = Field(
87
+ default_factory=list,
88
+ description="A list of CURIEs/IRIs for provenance for the synonym",
89
+ )
90
+
91
+
92
+ class Meta(BaseModel):
93
+ """Represents the metadata about a node or ontology."""
94
+
95
+ definition: Definition | None = None
96
+ subsets: list[str] | None = None
97
+ xrefs: list[Xref] | None = None
98
+ synonyms: list[Synonym] | None = None
99
+ comments: list[str] | None = None
100
+ version: str | None = None
101
+ basicPropertyValues: list[Property] | None = Field(None) # noqa:N815
102
+ deprecated: bool = False
103
+
104
+
105
+ class Edge(BaseModel):
106
+ """Represents an edge in an OBO Graph."""
107
+
108
+ sub: str = Field(..., examples=["http://purl.obolibrary.org/obo/CHEBI_99998"])
109
+ pred: str = Field(..., examples=["is_a"])
110
+ obj: str = Field(..., examples=["http://purl.obolibrary.org/obo/CHEBI_24995"])
111
+ meta: Meta | None = None
112
+
113
+
114
+ class Node(BaseModel):
115
+ """Represents a node in an OBO Graph."""
116
+
117
+ id: str = Field(..., description="The IRI for the node")
118
+ lbl: str | None = Field(None, description="The name of the node")
119
+ meta: Meta | None = None
120
+ type: NodeType = Field(..., description="Type of node")
121
+
122
+
123
+ class Graph(BaseModel):
124
+ """A graph corresponds to an ontology."""
125
+
126
+ id: str | None = None
127
+ meta: Meta | None = None
128
+ nodes: list[Node] = Field(default_factory=list)
129
+ edges: list[Edge] = Field(default_factory=list)
130
+ equivalentNodesSets: list[Any] = Field(default_factory=list) # noqa:N815
131
+ logicalDefinitionAxioms: list[Any] = Field(default_factory=list) # noqa:N815
132
+ domainRangeAxioms: list[Any] = Field(default_factory=list) # noqa:N815
133
+ propertyChainAxioms: list[Any] = Field(default_factory=list) # noqa:N815
134
+
135
+
136
+ class GraphDocument(BaseModel):
137
+ """Represents a list of OBO graphs."""
138
+
139
+ graphs: list[Graph]
140
+
141
+
142
+ def get_id_to_node(graph: Graph) -> dict[str, Node]:
143
+ """Get a dictionary from node ID to nodes."""
144
+ return {node.id: node for node in graph.nodes or []}
145
+
146
+
147
+ def get_id_to_edges(graph: Graph) -> dict[str, list[tuple[str, str]]]:
148
+ """Get a dictionary from node ID to nodes."""
149
+ dd = defaultdict(set)
150
+ for edge in graph.edges or []:
151
+ dd[edge.sub].add((edge.pred, edge.obj))
152
+ return {node_id: list(predicate_object_pairs) for node_id, predicate_object_pairs in dd.items()}
153
+
154
+
155
+ # docstr-coverage:excused `overload`
156
+ @overload
157
+ def read(
158
+ source: str, *, timeout: TimeoutHint = ..., squeeze: Literal[False] = ...
159
+ ) -> GraphDocument: ...
160
+
161
+
162
+ # docstr-coverage:excused `overload`
163
+ @overload
164
+ def read(source: str, *, timeout: TimeoutHint = ..., squeeze: Literal[True] = ...) -> Graph: ...
165
+
166
+
167
+ def read(
168
+ source: str, *, timeout: TimeoutHint = None, squeeze: bool = True
169
+ ) -> Graph | GraphDocument:
170
+ """Read an OBO Graph document.
171
+
172
+ :param source: A file path or URL to an OBO Graph JSON
173
+ :param timeout: The timeout for getting a URL
174
+ :param squeeze: By default, will unpack the first graph from a graph document that
175
+ only has a single graph and return a :class:`Graph` object. If `true` and
176
+ multiple graphs are received, will raise an error. Set this to `false` to return
177
+ a GraphDocument containing all graphs.
178
+
179
+ :returns: A graph or graph document
180
+
181
+ :raises ValueError: If squeeze is set to true and multiple graphs are received
182
+ """
183
+ if (isinstance(source, str) and source.startswith("https://")) or source.startswith("http://"):
184
+ import requests
185
+
186
+ if source.endswith(".gz"):
187
+ raise NotImplementedError
188
+ else:
189
+ res = requests.get(source, timeout=timeout)
190
+ res_json = res.json()
191
+ graph_document = GraphDocument.model_validate(res_json)
192
+
193
+ elif isinstance(source, str | Path):
194
+ path = Path(source).expanduser().resolve()
195
+ if path.is_file():
196
+ if path.suffix.endswith(".gz"):
197
+ raise NotImplementedError
198
+ else:
199
+ with path.open() as file:
200
+ graph_document = GraphDocument.model_validate(json.load(file))
201
+ else:
202
+ raise TypeError(f"Unhandled source: {source}")
203
+
204
+ if not squeeze:
205
+ return graph_document
206
+ elif len(graph_document.graphs) != 1:
207
+ raise ValueError(
208
+ f"graph document has {len(graph_document.graphs)} graphs, "
209
+ f"so can not squeeze. set squeeze=False"
210
+ )
211
+ else:
212
+ return graph_document.graphs[0]
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,39 @@
1
+ """Version information for :mod:`obographs`.
2
+
3
+ Run with ``python -m obographs.version``
4
+ """
5
+
6
+ import os
7
+ from subprocess import CalledProcessError, check_output
8
+
9
+ __all__ = [
10
+ "VERSION",
11
+ "get_git_hash",
12
+ "get_version",
13
+ ]
14
+
15
+ VERSION = "0.0.1"
16
+
17
+
18
+ def get_git_hash() -> str:
19
+ """Get the :mod:`obographs` git hash."""
20
+ with open(os.devnull, "w") as devnull:
21
+ try:
22
+ ret = check_output(
23
+ ["git", "rev-parse", "HEAD"],
24
+ cwd=os.path.dirname(__file__),
25
+ stderr=devnull,
26
+ )
27
+ except CalledProcessError:
28
+ return "UNHASHED"
29
+ else:
30
+ return ret.strip().decode("utf-8")[:8]
31
+
32
+
33
+ def get_version(with_git_hash: bool = False) -> str:
34
+ """Get the :mod:`obographs` version string, including a git hash."""
35
+ return f"{VERSION}-{get_git_hash()}" if with_git_hash else VERSION
36
+
37
+
38
+ if __name__ == "__main__":
39
+ print(get_version(with_git_hash=True)) # noqa:T201