├── .bumpversion.cfg ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── workflows │ └── tests.yml ├── .gitignore ├── .readthedocs.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs └── source │ ├── cli.rst │ ├── conf.py │ ├── index.rst │ └── usage.rst ├── pyproject.toml ├── setup.cfg ├── src └── ols_client │ ├── __init__.py │ ├── __main__.py │ ├── cli.py │ ├── client.py │ ├── py.typed │ └── version.py ├── tests ├── __init__.py ├── cases.py ├── test_ols_client.py └── test_version.py └── tox.ini /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.5-dev 3 | commit = True 4 | tag = False 5 | parse = (?P\d+)\.(?P\d+)\.(?P\d+)(?:-(?P[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+(?P[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))? 6 | serialize = 7 | {major}.{minor}.{patch}-{release}+{build} 8 | {major}.{minor}.{patch}+{build} 9 | {major}.{minor}.{patch}-{release} 10 | {major}.{minor}.{patch} 11 | 12 | [bumpversion:part:release] 13 | optional_value = production 14 | first_value = dev 15 | values = 16 | dev 17 | production 18 | 19 | [bumpverion:part:build] 20 | values = [0-9A-Za-z-]+ 21 | 22 | [bumpversion:file:setup.cfg] 23 | search = version = {current_version} 24 | replace = version = {new_version} 25 | 26 | [bumpversion:file:docs/source/conf.py] 27 | search = release = "{current_version}" 28 | replace = release = "{new_version}" 29 | 30 | [bumpversion:file:src/ols_client/version.py] 31 | search = VERSION = "{current_version}" 32 | replace = VERSION = "{new_version}" 33 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | cthoyt@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions to this repository are welcomed and encouraged. 4 | 5 | ## Code Contribution 6 | 7 | This project uses the [GitHub Flow](https://guides.github.com/introduction/flow) 8 | model for code contributions. Follow these steps: 9 | 10 | 1. [Create a fork](https://help.github.com/articles/fork-a-repo) of the upstream 11 | repository at [`cthoyt/ols-client`](https://github.com/cthoyt/ols-client) 12 | on your GitHub account (or in one of your organizations) 13 | 2. [Clone your fork](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) 14 | with `git clone https://github.com//ols-client.git` 15 | 3. Make and commit changes to your fork with `git commit` 16 | 4. Push changes to your fork with `git push` 17 | 5. Repeat steps 3 and 4 as needed 18 | 6. Submit a pull request back to the upstream repository 19 | 20 | ### Merge Model 21 | 22 | This repository uses [squash merges](https://docs.github.com/en/github/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-pull-request-commits) 23 | to group all related commits in a given pull request into a single commit upon 24 | acceptance and merge into the main branch. This has several benefits: 25 | 26 | 1. Keeps the commit history on the main branch focused on high-level narrative 27 | 2. Enables people to make lots of small commits without worrying about muddying 28 | up the commit history 29 | 3. Commits correspond 1-to-1 with pull requests 30 | 31 | ### Code Style 32 | 33 | This project encourages the use of optional static typing. It 34 | uses [`mypy`](http://mypy-lang.org/) as a type checker 35 | and [`sphinx_autodoc_typehints`](https://github.com/agronholm/sphinx-autodoc-typehints) 36 | to automatically generate documentation based on type hints. You can check if 37 | your code passes `mypy` with `tox -e mypy`. 38 | 39 | This project uses [`black`](https://github.com/psf/black) to automatically 40 | enforce a consistent code style. You can apply `black` and other pre-configured 41 | linters with `tox -e lint`. 42 | 43 | This project uses [`flake8`](https://flake8.pycqa.org) and several plugins for 44 | additional checks of documentation style, security issues, good variable 45 | nomenclature, and more ( 46 | see [`tox.ini`](tox.ini) for a list of flake8 plugins). You can check if your 47 | code passes `flake8` with `tox -e flake8`. 48 | 49 | Each of these checks are run on each commit using GitHub Actions as a continuous 50 | integration service. Passing all of them is required for accepting a 51 | contribution. If you're unsure how to address the feedback from one of these 52 | tools, please say so either in the description of your pull request or in a 53 | comment, and we will help you. 54 | 55 | ### Logging 56 | 57 | Python's builtin `print()` should not be used (except when writing to files), 58 | it's checked by the 59 | [`flake8-print`](https://github.com/jbkahn/flake8-print) plugin to `flake8`. If 60 | you're in a command line setting or `main()` function for a module, you can use 61 | `click.echo()`. Otherwise, you can use the builtin `logging` module by adding 62 | `logger = logging.getLogger(__name__)` below the imports at the top of your 63 | file. 64 | 65 | ### Documentation 66 | 67 | All public functions (i.e., not starting with an underscore `_`) must be 68 | documented using the [sphinx documentation format](https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html#the-sphinx-docstring-format). 69 | The [`darglint`](https://github.com/terrencepreilly/darglint) plugin to `flake8` 70 | reports on functions that are not fully documented. 71 | 72 | This project uses [`sphinx`](https://www.sphinx-doc.org) to automatically build 73 | documentation into a narrative structure. You can check that the documentation 74 | builds properly in an isolated environment with `tox -e docs-test` and actually 75 | build it locally with `tox -e docs`. 76 | 77 | ### Testing 78 | 79 | Functions in this repository should be unit tested. These can either be written 80 | using the `unittest` framework in the `tests/` directory or as embedded 81 | doctests. You can check that the unit tests pass with `tox -e py` and that the 82 | doctests pass with `tox -e doctests`. These tests are required to pass for 83 | accepting a contribution. 84 | 85 | ### Syncing your fork 86 | 87 | If other code is updated before your contribution gets merged, you might need to 88 | resolve conflicts against the main branch. After cloning, you should add the 89 | upstream repository with 90 | 91 | ```shell 92 | $ git remote add cthoyt https://github.com/cthoyt/ols-client.git 93 | ``` 94 | 95 | Then, you can merge upstream code into your branch. You can also use the GitHub 96 | UI to do this by following [this tutorial](https://docs.github.com/en/github/collaborating-with-pull-requests/working-with-forks/syncing-a-fork). 97 | 98 | ### Python Version Compatibility 99 | 100 | This project aims to support all versions of Python that have not passed their 101 | end-of-life dates. After end-of-life, the version will be removed from the Trove 102 | qualifiers in the [`setup.cfg`](setup.cfg) and from the GitHub Actions testing 103 | configuration. 104 | 105 | See https://endoflife.date/python for a timeline of Python release and 106 | end-of-life dates. 107 | 108 | ## Acknowledgements 109 | 110 | These code contribution guidelines are derived from the [cthoyt/cookiecutter-snekpack](https://github.com/cthoyt/cookiecutter-snekpack) 111 | Python package template. They're free to reuse and modify as long as they're properly acknowledged. 112 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | lint: 11 | name: Lint 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: [ "3.7", "3.10" ] 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install dependencies 23 | run: pip install tox 24 | - name: Check manifest 25 | run: tox -e manifest 26 | - name: Check code quality with flake8 27 | run: tox -e flake8 28 | - name: Check package metadata with Pyroma 29 | run: tox -e pyroma 30 | - name: Check static typing with MyPy 31 | run: tox -e mypy 32 | docs: 33 | name: Documentation 34 | runs-on: ubuntu-latest 35 | strategy: 36 | matrix: 37 | python-version: [ "3.7", "3.10" ] 38 | steps: 39 | - uses: actions/checkout@v2 40 | - name: Set up Python ${{ matrix.python-version }} 41 | uses: actions/setup-python@v2 42 | with: 43 | python-version: ${{ matrix.python-version }} 44 | - name: Install dependencies 45 | run: | 46 | pip install tox 47 | sudo apt-get install graphviz 48 | - name: Check RST conformity with doc8 49 | run: tox -e doc8 50 | - name: Check docstring coverage 51 | run: tox -e docstr-coverage 52 | - name: Check documentation build with Sphinx 53 | run: tox -e docs-test 54 | tests: 55 | name: Tests 56 | runs-on: ${{ matrix.os }} 57 | strategy: 58 | matrix: 59 | os: [ ubuntu-latest ] 60 | python-version: [ "3.7", "3.10" ] 61 | steps: 62 | - uses: actions/checkout@v2 63 | - name: Set up Python ${{ matrix.python-version }} 64 | uses: actions/setup-python@v2 65 | with: 66 | python-version: ${{ matrix.python-version }} 67 | - name: Install dependencies 68 | run: pip install tox 69 | - name: Test with pytest and generate coverage file 70 | run: 71 | tox -e py 72 | - name: Upload coverage report to codecov 73 | uses: codecov/codecov-action@v1 74 | if: success() 75 | with: 76 | file: coverage.xml 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/macos,linux,windows,python,jupyternotebooks,jetbrains,pycharm,vim,emacs,visualstudiocode,visualstudio 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,linux,windows,python,jupyternotebooks,jetbrains,pycharm,vim,emacs,visualstudiocode,visualstudio 3 | 4 | ### Emacs ### 5 | # -*- mode: gitignore; -*- 6 | *~ 7 | \#*\# 8 | /.emacs.desktop 9 | /.emacs.desktop.lock 10 | *.elc 11 | auto-save-list 12 | tramp 13 | .\#* 14 | 15 | # Org-mode 16 | .org-id-locations 17 | *_archive 18 | 19 | # flymake-mode 20 | *_flymake.* 21 | 22 | # eshell files 23 | /eshell/history 24 | /eshell/lastdir 25 | 26 | # elpa packages 27 | /elpa/ 28 | 29 | # reftex files 30 | *.rel 31 | 32 | # AUCTeX auto folder 33 | /auto/ 34 | 35 | # cask packages 36 | .cask/ 37 | dist/ 38 | 39 | # Flycheck 40 | flycheck_*.el 41 | 42 | # server auth directory 43 | /server/ 44 | 45 | # projectiles files 46 | .projectile 47 | 48 | # directory configuration 49 | .dir-locals.el 50 | 51 | # network security 52 | /network-security.data 53 | 54 | 55 | ### JetBrains ### 56 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 57 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 58 | 59 | # User-specific stuff 60 | .idea/**/workspace.xml 61 | .idea/**/tasks.xml 62 | .idea/**/usage.statistics.xml 63 | .idea/**/dictionaries 64 | .idea/**/shelf 65 | 66 | # AWS User-specific 67 | .idea/**/aws.xml 68 | 69 | # Generated files 70 | .idea/**/contentModel.xml 71 | 72 | # Sensitive or high-churn files 73 | .idea/**/dataSources/ 74 | .idea/**/dataSources.ids 75 | .idea/**/dataSources.local.xml 76 | .idea/**/sqlDataSources.xml 77 | .idea/**/dynamic.xml 78 | .idea/**/uiDesigner.xml 79 | .idea/**/dbnavigator.xml 80 | 81 | # Gradle 82 | .idea/**/gradle.xml 83 | .idea/**/libraries 84 | 85 | # Gradle and Maven with auto-import 86 | # When using Gradle or Maven with auto-import, you should exclude module files, 87 | # since they will be recreated, and may cause churn. Uncomment if using 88 | # auto-import. 89 | # .idea/artifacts 90 | # .idea/compiler.xml 91 | # .idea/jarRepositories.xml 92 | # .idea/modules.xml 93 | # .idea/*.iml 94 | # .idea/modules 95 | # *.iml 96 | # *.ipr 97 | 98 | # CMake 99 | cmake-build-*/ 100 | 101 | # Mongo Explorer plugin 102 | .idea/**/mongoSettings.xml 103 | 104 | # File-based project format 105 | *.iws 106 | 107 | # IntelliJ 108 | out/ 109 | 110 | # mpeltonen/sbt-idea plugin 111 | .idea_modules/ 112 | 113 | # JIRA plugin 114 | atlassian-ide-plugin.xml 115 | 116 | # Cursive Clojure plugin 117 | .idea/replstate.xml 118 | 119 | # SonarLint plugin 120 | .idea/sonarlint/ 121 | 122 | # Crashlytics plugin (for Android Studio and IntelliJ) 123 | com_crashlytics_export_strings.xml 124 | crashlytics.properties 125 | crashlytics-build.properties 126 | fabric.properties 127 | 128 | # Editor-based Rest Client 129 | .idea/httpRequests 130 | 131 | # Android studio 3.1+ serialized cache file 132 | .idea/caches/build_file_checksums.ser 133 | 134 | ### JetBrains Patch ### 135 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 136 | 137 | # *.iml 138 | # modules.xml 139 | # .idea/misc.xml 140 | # *.ipr 141 | 142 | # Sonarlint plugin 143 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 144 | .idea/**/sonarlint/ 145 | 146 | # SonarQube Plugin 147 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 148 | .idea/**/sonarIssues.xml 149 | 150 | # Markdown Navigator plugin 151 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 152 | .idea/**/markdown-navigator.xml 153 | .idea/**/markdown-navigator-enh.xml 154 | .idea/**/markdown-navigator/ 155 | 156 | # Cache file creation bug 157 | # See https://youtrack.jetbrains.com/issue/JBR-2257 158 | .idea/$CACHE_FILE$ 159 | 160 | # CodeStream plugin 161 | # https://plugins.jetbrains.com/plugin/12206-codestream 162 | .idea/codestream.xml 163 | 164 | ### JupyterNotebooks ### 165 | # gitignore template for Jupyter Notebooks 166 | # website: http://jupyter.org/ 167 | 168 | .ipynb_checkpoints 169 | */.ipynb_checkpoints/* 170 | 171 | # IPython 172 | profile_default/ 173 | ipython_config.py 174 | 175 | # Remove previous ipynb_checkpoints 176 | # git rm -r .ipynb_checkpoints/ 177 | 178 | ### Linux ### 179 | 180 | # temporary files which can be created if a process still has a handle open of a deleted file 181 | .fuse_hidden* 182 | 183 | # KDE directory preferences 184 | .directory 185 | 186 | # Linux trash folder which might appear on any partition or disk 187 | .Trash-* 188 | 189 | # .nfs files are created when an open file is removed but is still being accessed 190 | .nfs* 191 | 192 | ### macOS ### 193 | # General 194 | .DS_Store 195 | .AppleDouble 196 | .LSOverride 197 | 198 | # Icon must end with two \r 199 | Icon 200 | 201 | 202 | # Thumbnails 203 | ._* 204 | 205 | # Files that might appear in the root of a volume 206 | .DocumentRevisions-V100 207 | .fseventsd 208 | .Spotlight-V100 209 | .TemporaryItems 210 | .Trashes 211 | .VolumeIcon.icns 212 | .com.apple.timemachine.donotpresent 213 | 214 | # Directories potentially created on remote AFP share 215 | .AppleDB 216 | .AppleDesktop 217 | Network Trash Folder 218 | Temporary Items 219 | .apdisk 220 | 221 | ### PyCharm ### 222 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 223 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 224 | 225 | # User-specific stuff 226 | 227 | # AWS User-specific 228 | 229 | # Generated files 230 | 231 | # Sensitive or high-churn files 232 | 233 | # Gradle 234 | 235 | # Gradle and Maven with auto-import 236 | # When using Gradle or Maven with auto-import, you should exclude module files, 237 | # since they will be recreated, and may cause churn. Uncomment if using 238 | # auto-import. 239 | # .idea/artifacts 240 | # .idea/compiler.xml 241 | # .idea/jarRepositories.xml 242 | # .idea/modules.xml 243 | # .idea/*.iml 244 | # .idea/modules 245 | # *.iml 246 | # *.ipr 247 | 248 | # CMake 249 | 250 | # Mongo Explorer plugin 251 | 252 | # File-based project format 253 | 254 | # IntelliJ 255 | 256 | # mpeltonen/sbt-idea plugin 257 | 258 | # JIRA plugin 259 | 260 | # Cursive Clojure plugin 261 | 262 | # SonarLint plugin 263 | 264 | # Crashlytics plugin (for Android Studio and IntelliJ) 265 | 266 | # Editor-based Rest Client 267 | 268 | # Android studio 3.1+ serialized cache file 269 | 270 | ### PyCharm Patch ### 271 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 272 | 273 | # *.iml 274 | # modules.xml 275 | # .idea/misc.xml 276 | # *.ipr 277 | 278 | # Sonarlint plugin 279 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 280 | 281 | # SonarQube Plugin 282 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 283 | 284 | # Markdown Navigator plugin 285 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 286 | 287 | # Cache file creation bug 288 | # See https://youtrack.jetbrains.com/issue/JBR-2257 289 | 290 | # CodeStream plugin 291 | # https://plugins.jetbrains.com/plugin/12206-codestream 292 | 293 | ### Python ### 294 | # Byte-compiled / optimized / DLL files 295 | __pycache__/ 296 | *.py[cod] 297 | *$py.class 298 | 299 | # C extensions 300 | *.so 301 | 302 | # Distribution / packaging 303 | .Python 304 | build/ 305 | develop-eggs/ 306 | downloads/ 307 | eggs/ 308 | .eggs/ 309 | lib/ 310 | lib64/ 311 | parts/ 312 | sdist/ 313 | var/ 314 | wheels/ 315 | share/python-wheels/ 316 | *.egg-info/ 317 | .installed.cfg 318 | *.egg 319 | MANIFEST 320 | 321 | # PyInstaller 322 | # Usually these files are written by a python script from a template 323 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 324 | *.manifest 325 | *.spec 326 | 327 | # Installer logs 328 | pip-log.txt 329 | pip-delete-this-directory.txt 330 | 331 | # Unit test / coverage reports 332 | htmlcov/ 333 | .tox/ 334 | .nox/ 335 | .coverage 336 | .coverage.* 337 | .cache 338 | nosetests.xml 339 | coverage.xml 340 | *.cover 341 | *.py,cover 342 | .hypothesis/ 343 | .pytest_cache/ 344 | cover/ 345 | 346 | # Translations 347 | *.mo 348 | *.pot 349 | 350 | # Django stuff: 351 | *.log 352 | local_settings.py 353 | db.sqlite3 354 | db.sqlite3-journal 355 | 356 | # Flask stuff: 357 | instance/ 358 | .webassets-cache 359 | 360 | # Scrapy stuff: 361 | .scrapy 362 | 363 | # Sphinx documentation 364 | docs/_build/ 365 | docs/build 366 | docs/source/api 367 | 368 | # PyBuilder 369 | .pybuilder/ 370 | target/ 371 | 372 | # Jupyter Notebook 373 | 374 | # IPython 375 | 376 | # pyenv 377 | # For a library or package, you might want to ignore these files since the code is 378 | # intended to run in multiple environments; otherwise, check them in: 379 | # .python-version 380 | 381 | # pipenv 382 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 383 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 384 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 385 | # install all needed dependencies. 386 | #Pipfile.lock 387 | 388 | # poetry 389 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 390 | # This is especially recommended for binary packages to ensure reproducibility, and is more 391 | # commonly ignored for libraries. 392 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 393 | #poetry.lock 394 | 395 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 396 | __pypackages__/ 397 | 398 | # Celery stuff 399 | celerybeat-schedule 400 | celerybeat.pid 401 | 402 | # SageMath parsed files 403 | *.sage.py 404 | 405 | # Environments 406 | .env 407 | .venv 408 | env/ 409 | venv/ 410 | ENV/ 411 | env.bak/ 412 | venv.bak/ 413 | 414 | # Spyder project settings 415 | .spyderproject 416 | .spyproject 417 | 418 | # Rope project settings 419 | .ropeproject 420 | 421 | # mkdocs documentation 422 | /site 423 | 424 | # mypy 425 | .mypy_cache/ 426 | .dmypy.json 427 | dmypy.json 428 | 429 | # Pyre type checker 430 | .pyre/ 431 | 432 | # pytype static type analyzer 433 | .pytype/ 434 | 435 | # Cython debug symbols 436 | cython_debug/ 437 | 438 | # PyCharm 439 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can 440 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 441 | # and can be added to the global gitignore or merged into this file. For a more nuclear 442 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 443 | #.idea/ 444 | 445 | ### Vim ### 446 | # Swap 447 | [._]*.s[a-v][a-z] 448 | !*.svg # comment out if you don't need vector files 449 | [._]*.sw[a-p] 450 | [._]s[a-rt-v][a-z] 451 | [._]ss[a-gi-z] 452 | [._]sw[a-p] 453 | 454 | # Session 455 | Session.vim 456 | Sessionx.vim 457 | 458 | # Temporary 459 | .netrwhist 460 | # Auto-generated tag files 461 | tags 462 | # Persistent undo 463 | [._]*.un~ 464 | 465 | ### VisualStudioCode ### 466 | .vscode/* 467 | !.vscode/settings.json 468 | !.vscode/tasks.json 469 | !.vscode/launch.json 470 | !.vscode/extensions.json 471 | !.vscode/*.code-snippets 472 | 473 | # Local History for Visual Studio Code 474 | .history/ 475 | 476 | # Built Visual Studio Code Extensions 477 | *.vsix 478 | 479 | ### VisualStudioCode Patch ### 480 | # Ignore all local history of files 481 | .history 482 | .ionide 483 | 484 | # Support for Project snippet scope 485 | 486 | ### Windows ### 487 | # Windows thumbnail cache files 488 | Thumbs.db 489 | Thumbs.db:encryptable 490 | ehthumbs.db 491 | ehthumbs_vista.db 492 | 493 | # Dump file 494 | *.stackdump 495 | 496 | # Folder config file 497 | [Dd]esktop.ini 498 | 499 | # Recycle Bin used on file shares 500 | $RECYCLE.BIN/ 501 | 502 | # Windows Installer files 503 | *.cab 504 | *.msi 505 | *.msix 506 | *.msm 507 | *.msp 508 | 509 | # Windows shortcuts 510 | *.lnk 511 | 512 | ### VisualStudio ### 513 | ## Ignore Visual Studio temporary files, build results, and 514 | ## files generated by popular Visual Studio add-ons. 515 | ## 516 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 517 | 518 | # User-specific files 519 | *.rsuser 520 | *.suo 521 | *.user 522 | *.userosscache 523 | *.sln.docstates 524 | 525 | # User-specific files (MonoDevelop/Xamarin Studio) 526 | *.userprefs 527 | 528 | # Mono auto generated files 529 | mono_crash.* 530 | 531 | # Build results 532 | [Dd]ebug/ 533 | [Dd]ebugPublic/ 534 | [Rr]elease/ 535 | [Rr]eleases/ 536 | x64/ 537 | x86/ 538 | [Ww][Ii][Nn]32/ 539 | [Aa][Rr][Mm]/ 540 | [Aa][Rr][Mm]64/ 541 | bld/ 542 | [Bb]in/ 543 | [Oo]bj/ 544 | [Ll]og/ 545 | [Ll]ogs/ 546 | 547 | # Visual Studio 2015/2017 cache/options directory 548 | .vs/ 549 | # Uncomment if you have tasks that create the project's static files in wwwroot 550 | #wwwroot/ 551 | 552 | # Visual Studio 2017 auto generated files 553 | Generated\ Files/ 554 | 555 | # MSTest test Results 556 | [Tt]est[Rr]esult*/ 557 | [Bb]uild[Ll]og.* 558 | 559 | # NUnit 560 | *.VisualState.xml 561 | TestResult.xml 562 | nunit-*.xml 563 | 564 | # Build Results of an ATL Project 565 | [Dd]ebugPS/ 566 | [Rr]eleasePS/ 567 | dlldata.c 568 | 569 | # Benchmark Results 570 | BenchmarkDotNet.Artifacts/ 571 | 572 | # .NET Core 573 | project.lock.json 574 | project.fragment.lock.json 575 | artifacts/ 576 | 577 | # ASP.NET Scaffolding 578 | ScaffoldingReadMe.txt 579 | 580 | # StyleCop 581 | StyleCopReport.xml 582 | 583 | # Files built by Visual Studio 584 | *_i.c 585 | *_p.c 586 | *_h.h 587 | *.ilk 588 | *.meta 589 | *.obj 590 | *.iobj 591 | *.pch 592 | *.pdb 593 | *.ipdb 594 | *.pgc 595 | *.pgd 596 | *.rsp 597 | *.sbr 598 | *.tlb 599 | *.tli 600 | *.tlh 601 | *.tmp 602 | *.tmp_proj 603 | *_wpftmp.csproj 604 | *.tlog 605 | *.vspscc 606 | *.vssscc 607 | .builds 608 | *.pidb 609 | *.svclog 610 | *.scc 611 | 612 | # Chutzpah Test files 613 | _Chutzpah* 614 | 615 | # Visual C++ cache files 616 | ipch/ 617 | *.aps 618 | *.ncb 619 | *.opendb 620 | *.opensdf 621 | *.sdf 622 | *.cachefile 623 | *.VC.db 624 | *.VC.VC.opendb 625 | 626 | # Visual Studio profiler 627 | *.psess 628 | *.vsp 629 | *.vspx 630 | *.sap 631 | 632 | # Visual Studio Trace Files 633 | *.e2e 634 | 635 | # TFS 2012 Local Workspace 636 | $tf/ 637 | 638 | # Guidance Automation Toolkit 639 | *.gpState 640 | 641 | # ReSharper is a .NET coding add-in 642 | _ReSharper*/ 643 | *.[Rr]e[Ss]harper 644 | *.DotSettings.user 645 | 646 | # TeamCity is a build add-in 647 | _TeamCity* 648 | 649 | # DotCover is a Code Coverage Tool 650 | *.dotCover 651 | 652 | # AxoCover is a Code Coverage Tool 653 | .axoCover/* 654 | !.axoCover/settings.json 655 | 656 | # Coverlet is a free, cross platform Code Coverage Tool 657 | coverage*.json 658 | coverage*.xml 659 | coverage*.info 660 | 661 | # Visual Studio code coverage results 662 | *.coverage 663 | *.coveragexml 664 | 665 | # NCrunch 666 | _NCrunch_* 667 | .*crunch*.local.xml 668 | nCrunchTemp_* 669 | 670 | # MightyMoose 671 | *.mm.* 672 | AutoTest.Net/ 673 | 674 | # Web workbench (sass) 675 | .sass-cache/ 676 | 677 | # Installshield output folder 678 | [Ee]xpress/ 679 | 680 | # DocProject is a documentation generator add-in 681 | DocProject/buildhelp/ 682 | DocProject/Help/*.HxT 683 | DocProject/Help/*.HxC 684 | DocProject/Help/*.hhc 685 | DocProject/Help/*.hhk 686 | DocProject/Help/*.hhp 687 | DocProject/Help/Html2 688 | DocProject/Help/html 689 | 690 | # Click-Once directory 691 | publish/ 692 | 693 | # Publish Web Output 694 | *.[Pp]ublish.xml 695 | *.azurePubxml 696 | # Note: Comment the next line if you want to checkin your web deploy settings, 697 | # but database connection strings (with potential passwords) will be unencrypted 698 | *.pubxml 699 | *.publishproj 700 | 701 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 702 | # checkin your Azure Web App publish settings, but sensitive information contained 703 | # in these scripts will be unencrypted 704 | PublishScripts/ 705 | 706 | # NuGet Packages 707 | *.nupkg 708 | # NuGet Symbol Packages 709 | *.snupkg 710 | # The packages folder can be ignored because of Package Restore 711 | **/[Pp]ackages/* 712 | # except build/, which is used as an MSBuild target. 713 | !**/[Pp]ackages/build/ 714 | # Uncomment if necessary however generally it will be regenerated when needed 715 | #!**/[Pp]ackages/repositories.config 716 | # NuGet v3's project.json files produces more ignorable files 717 | *.nuget.props 718 | *.nuget.targets 719 | 720 | # Microsoft Azure Build Output 721 | csx/ 722 | *.build.csdef 723 | 724 | # Microsoft Azure Emulator 725 | ecf/ 726 | rcf/ 727 | 728 | # Windows Store app package directories and files 729 | AppPackages/ 730 | BundleArtifacts/ 731 | Package.StoreAssociation.xml 732 | _pkginfo.txt 733 | *.appx 734 | *.appxbundle 735 | *.appxupload 736 | 737 | # Visual Studio cache files 738 | # files ending in .cache can be ignored 739 | *.[Cc]ache 740 | # but keep track of directories ending in .cache 741 | !?*.[Cc]ache/ 742 | 743 | # Others 744 | ClientBin/ 745 | ~$* 746 | *.dbmdl 747 | *.dbproj.schemaview 748 | *.jfm 749 | *.pfx 750 | *.publishsettings 751 | orleans.codegen.cs 752 | 753 | # Including strong name files can present a security risk 754 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 755 | #*.snk 756 | 757 | # Since there are multiple workflows, uncomment next line to ignore bower_components 758 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 759 | #bower_components/ 760 | 761 | # RIA/Silverlight projects 762 | Generated_Code/ 763 | 764 | # Backup & report files from converting an old project file 765 | # to a newer Visual Studio version. Backup files are not needed, 766 | # because we have git ;-) 767 | _UpgradeReport_Files/ 768 | Backup*/ 769 | UpgradeLog*.XML 770 | UpgradeLog*.htm 771 | ServiceFabricBackup/ 772 | *.rptproj.bak 773 | 774 | # SQL Server files 775 | *.mdf 776 | *.ldf 777 | *.ndf 778 | 779 | # Business Intelligence projects 780 | *.rdl.data 781 | *.bim.layout 782 | *.bim_*.settings 783 | *.rptproj.rsuser 784 | *- [Bb]ackup.rdl 785 | *- [Bb]ackup ([0-9]).rdl 786 | *- [Bb]ackup ([0-9][0-9]).rdl 787 | 788 | # Microsoft Fakes 789 | FakesAssemblies/ 790 | 791 | # GhostDoc plugin setting file 792 | *.GhostDoc.xml 793 | 794 | # Node.js Tools for Visual Studio 795 | .ntvs_analysis.dat 796 | node_modules/ 797 | 798 | # Visual Studio 6 build log 799 | *.plg 800 | 801 | # Visual Studio 6 workspace options file 802 | *.opt 803 | 804 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 805 | *.vbw 806 | 807 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 808 | *.vbp 809 | 810 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 811 | *.dsw 812 | *.dsp 813 | 814 | # Visual Studio 6 technical files 815 | 816 | # Visual Studio LightSwitch build output 817 | **/*.HTMLClient/GeneratedArtifacts 818 | **/*.DesktopClient/GeneratedArtifacts 819 | **/*.DesktopClient/ModelManifest.xml 820 | **/*.Server/GeneratedArtifacts 821 | **/*.Server/ModelManifest.xml 822 | _Pvt_Extensions 823 | 824 | # Paket dependency manager 825 | .paket/paket.exe 826 | paket-files/ 827 | 828 | # FAKE - F# Make 829 | .fake/ 830 | 831 | # CodeRush personal settings 832 | .cr/personal 833 | 834 | # Python Tools for Visual Studio (PTVS) 835 | *.pyc 836 | 837 | # Cake - Uncomment if you are using it 838 | # tools/** 839 | # !tools/packages.config 840 | 841 | # Tabs Studio 842 | *.tss 843 | 844 | # Telerik's JustMock configuration file 845 | *.jmconfig 846 | 847 | # BizTalk build output 848 | *.btp.cs 849 | *.btm.cs 850 | *.odx.cs 851 | *.xsd.cs 852 | 853 | # OpenCover UI analysis results 854 | OpenCover/ 855 | 856 | # Azure Stream Analytics local run output 857 | ASALocalRun/ 858 | 859 | # MSBuild Binary and Structured Log 860 | *.binlog 861 | 862 | # NVidia Nsight GPU debugger configuration file 863 | *.nvuser 864 | 865 | # MFractors (Xamarin productivity tool) working folder 866 | .mfractor/ 867 | 868 | # Local History for Visual Studio 869 | .localhistory/ 870 | 871 | # Visual Studio History (VSHistory) files 872 | .vshistory/ 873 | 874 | # BeatPulse healthcheck temp database 875 | healthchecksdb 876 | 877 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 878 | MigrationBackup/ 879 | 880 | # Ionide (cross platform F# VS Code tools) working folder 881 | .ionide/ 882 | 883 | # Fody - auto-generated XML schema 884 | FodyWeavers.xsd 885 | 886 | # VS Code files for those working on multiple tools 887 | *.code-workspace 888 | 889 | # Local History for Visual Studio Code 890 | 891 | # Windows Installer files from build outputs 892 | 893 | # JetBrains Rider 894 | *.sln.iml 895 | 896 | ### VisualStudio Patch ### 897 | # Additional files built by Visual Studio 898 | 899 | # End of https://www.toptal.com/developers/gitignore/api/macos,linux,windows,python,jupyternotebooks,jetbrains,pycharm,vim,emacs,visualstudiocode,visualstudio 900 | 901 | scratch/ 902 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | version: 2 6 | 7 | # Set the version of Python and other tools you might need 8 | build: 9 | os: ubuntu-20.04 10 | tools: 11 | python: "3.9" 12 | 13 | python: 14 | install: 15 | - method: pip 16 | path: . 17 | extra_requirements: 18 | - docs 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 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. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft src 2 | graft tests 3 | prune scripts 4 | prune notebooks 5 | prune tests/.pytest_cache 6 | 7 | prune docs/build 8 | prune docs/source/api 9 | 10 | recursive-include docs/source *.py 11 | recursive-include docs/source *.rst 12 | recursive-include docs/source *.png 13 | 14 | global-exclude *.py[cod] __pycache__ *.so *.dylib .DS_Store *.gpickle 15 | 16 | include README.md LICENSE 17 | exclude tox.ini .flake8 .bumpversion.cfg .readthedocs.yml 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | OLS Client 3 |

4 | 5 |

6 | 7 | Tests 8 | 9 | 10 | PyPI 11 | 12 | 13 | PyPI - Python Version 14 | 15 | 16 | PyPI - License 17 | 18 | 19 | Documentation Status 20 | 21 | 22 | Codecov status 23 | 24 | 25 | Cookiecutter template from @cthoyt 26 | 27 | 28 | Code style: black 29 | 30 | 31 | Contributor Covenant 32 | 33 | 34 | DOI 35 | 36 |

37 | 38 | A client to the EBI Ontology Lookup Service (OLS) and other instances. 39 | 40 | ## 💪 Getting Started 41 | 42 | One of several OLS clients can be instantiated and accessed with the same API: 43 | 44 | ```python 45 | from ols_client import EBIClient 46 | 47 | ebi_client = EBIClient() 48 | ontologies = ebi_client.get_ontologies() 49 | ``` 50 | 51 | Further documentation can be found at 52 | [ols-client.readthedocs.io](https://ols-client.readthedocs.io). 53 | 54 | ## 🚀 Installation 55 | 56 | The most recent release can be installed from 57 | [PyPI](https://pypi.org/project/ols_client/) with: 58 | 59 | ```bash 60 | $ pip install ols_client 61 | ``` 62 | 63 | The most recent code and data can be installed directly from GitHub with: 64 | 65 | ```bash 66 | $ pip install git+https://github.com/cthoyt/ols-client.git 67 | ``` 68 | 69 | ## 👐 Contributing 70 | 71 | Contributions, whether filing an issue, making a pull request, or forking, are appreciated. See 72 | [CONTRIBUTING.md](https://github.com/cthoyt/ols-client/blob/master/.github/CONTRIBUTING.md) for more information on getting involved. 73 | 74 | ## 👋 Attribution 75 | 76 | ### ⚖️ License 77 | 78 | The code in this package is licensed under the MIT License. 79 | 80 | ### Related 81 | 82 | Since the creation of this repository, the EBI has also generated their 83 | own client that can be found at https://github.com/Ensembl/ols-client and 84 | on PyPI as `ebi-ols-client`. 85 | 86 | ### 🍪 Cookiecutter 87 | 88 | This package was created with [@audreyfeldroy](https://github.com/audreyfeldroy)'s 89 | [cookiecutter](https://github.com/cookiecutter/cookiecutter) package using [@cthoyt](https://github.com/cthoyt)'s 90 | [cookiecutter-snekpack](https://github.com/cthoyt/cookiecutter-snekpack) template. 91 | 92 | ## 🛠️ For Developers 93 | 94 |
95 | See developer instructions 96 | 97 | 98 | The final section of the README is for if you want to get involved by making a code contribution. 99 | 100 | ### Development Installation 101 | 102 | To install in development mode, use the following: 103 | 104 | ```bash 105 | $ git clone git+https://github.com/cthoyt/ols-client.git 106 | $ cd ols-client 107 | $ pip install -e . 108 | ``` 109 | 110 | ### 🥼 Testing 111 | 112 | After cloning the repository and installing `tox` with `pip install tox`, the unit tests in the `tests/` folder can be 113 | run reproducibly with: 114 | 115 | ```shell 116 | $ tox 117 | ``` 118 | 119 | Additionally, these tests are automatically re-run with each commit in a [GitHub Action](https://github.com/cthoyt/ols-client/actions?query=workflow%3ATests). 120 | 121 | ### 📖 Building the Documentation 122 | 123 | The documentation can be built locally using the following: 124 | 125 | ```shell 126 | $ git clone git+https://github.com/cthoyt/ols-client.git 127 | $ cd ols-client 128 | $ tox -e docs 129 | $ open docs/build/html/index.html 130 | ``` 131 | 132 | The documentation automatically installs the package as well as the `docs` 133 | extra specified in the [`setup.cfg`](setup.cfg). `sphinx` plugins 134 | like `texext` can be added there. Additionally, they need to be added to the 135 | `extensions` list in [`docs/source/conf.py`](docs/source/conf.py). 136 | 137 | ### 📦 Making a Release 138 | 139 | After installing the package in development mode and installing 140 | `tox` with `pip install tox`, the commands for making a new release are contained within the `finish` environment 141 | in `tox.ini`. Run the following from the shell: 142 | 143 | ```shell 144 | $ tox -e finish 145 | ``` 146 | 147 | This script does the following: 148 | 149 | 1. Uses [Bump2Version](https://github.com/c4urself/bump2version) to switch the version number in the `setup.cfg`, 150 | `src/ols_client/version.py`, and [`docs/source/conf.py`](docs/source/conf.py) to not have the `-dev` suffix 151 | 2. Packages the code in both a tar archive and a wheel using [`build`](https://github.com/pypa/build) 152 | 3. Uploads to PyPI using [`twine`](https://github.com/pypa/twine). Be sure to have a `.pypirc` file configured to avoid the need for manual input at this 153 | step 154 | 4. Push to GitHub. You'll need to make a release going with the commit where the version was bumped. 155 | 5. Bump the version to the next patch. If you made big changes and want to bump the version by minor, you can 156 | use `tox -e bumpversion minor` after. 157 |
158 | -------------------------------------------------------------------------------- /docs/source/cli.rst: -------------------------------------------------------------------------------- 1 | Command Line Interface 2 | ====================== 3 | ols_client automatically installs the command :code:`ols_client`. See 4 | :code:`ols_client --help` for usage details. 5 | 6 | .. click:: ols_client.cli:main 7 | :prog: ols_client 8 | :show-nested: 9 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | 16 | import os 17 | import re 18 | import sys 19 | from datetime import date 20 | 21 | sys.path.insert(0, os.path.abspath("../../src")) 22 | 23 | # -- Project information ----------------------------------------------------- 24 | 25 | project = "ols_client" 26 | copyright = f"{date.today().year}, Charles Tapley Hoyt" 27 | author = "Charles Tapley Hoyt" 28 | 29 | # The full version, including alpha/beta/rc tags. 30 | release = "0.1.5-dev" 31 | 32 | # The short X.Y version. 33 | parsed_version = re.match( 34 | "(?P\d+)\.(?P\d+)\.(?P\d+)(?:-(?P[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+(?P[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?", 35 | release, 36 | ) 37 | version = parsed_version.expand("\g.\g.\g") 38 | 39 | if parsed_version.group("release"): 40 | tags.add("prerelease") 41 | 42 | # -- General configuration --------------------------------------------------- 43 | 44 | # If your documentation needs a minimal Sphinx version, state it here. 45 | # 46 | # needs_sphinx = '1.0' 47 | 48 | # If true, the current module name will be prepended to all description 49 | # unit titles (such as .. function::). 50 | add_module_names = False 51 | 52 | # A list of prefixes that are ignored when creating the module index. (new in Sphinx 0.6) 53 | modindex_common_prefix = ["ols_client."] 54 | 55 | # Add any Sphinx extension module names here, as strings. They can be 56 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 57 | # ones. 58 | extensions = [ 59 | "sphinx.ext.autosummary", 60 | "sphinx.ext.autodoc", 61 | "sphinx.ext.coverage", 62 | "sphinx.ext.intersphinx", 63 | "sphinx.ext.todo", 64 | "sphinx.ext.mathjax", 65 | "sphinx.ext.viewcode", 66 | "sphinx_autodoc_typehints", 67 | "sphinx_automodapi.automodapi", 68 | "sphinx_automodapi.smart_resolver", 69 | # 'texext', 70 | ] 71 | 72 | 73 | extensions.append("sphinx_click.ext") 74 | 75 | 76 | # generate autosummary pages 77 | autosummary_generate = True 78 | 79 | # Add any paths that contain templates here, relative to this directory. 80 | templates_path = ["_templates"] 81 | 82 | # The suffix(es) of source filenames. 83 | # You can specify multiple suffix as a list of string: 84 | # 85 | # source_suffix = ['.rst', '.md'] 86 | source_suffix = ".rst" 87 | 88 | # The master toctree document. 89 | master_doc = "index" 90 | 91 | # The language for content autogenerated by Sphinx. Refer to documentation 92 | # for a list of supported languages. 93 | # 94 | # This is also used if you do content translation via gettext catalogs. 95 | # Usually you set "language" from the command line for these cases. 96 | language = "en" 97 | 98 | # List of patterns, relative to source directory, that match files and 99 | # directories to ignore when looking for source files. 100 | # This pattern also affects html_static_path and html_extra_path. 101 | exclude_patterns = [] 102 | 103 | # The name of the Pygments (syntax highlighting) style to use. 104 | pygments_style = "sphinx" 105 | 106 | # -- Options for HTML output ------------------------------------------------- 107 | 108 | # The theme to use for HTML and HTML Help pages. See the documentation for 109 | # a list of builtin themes. 110 | # 111 | html_theme = "sphinx_rtd_theme" 112 | 113 | # Theme options are theme-specific and customize the look and feel of a theme 114 | # further. For a list of options available for each theme, see the 115 | # documentation. 116 | # 117 | # html_theme_options = {} 118 | 119 | # Add any paths that contain custom static files (such as style sheets) here, 120 | # relative to this directory. They are copied after the builtin static files, 121 | # so a file named "default.css" will overwrite the builtin "default.css". 122 | # html_static_path = ['_static'] 123 | 124 | # Custom sidebar templates, must be a dictionary that maps document names 125 | # to template names. 126 | # 127 | # The default sidebars (for documents that don't match any pattern) are 128 | # defined by theme itself. Builtin themes are using these templates by 129 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 130 | # 'searchbox.html']``. 131 | # 132 | # html_sidebars = {} 133 | 134 | # The name of an image file (relative to this directory) to place at the top 135 | # of the sidebar. 136 | # 137 | if os.path.exists("logo.png"): 138 | html_logo = "logo.png" 139 | 140 | # -- Options for HTMLHelp output --------------------------------------------- 141 | 142 | # Output file base name for HTML help builder. 143 | htmlhelp_basename = "OLS Clientdoc" 144 | 145 | # -- Options for LaTeX output ------------------------------------------------ 146 | 147 | # latex_elements = { 148 | # The paper size ('letterpaper' or 'a4paper'). 149 | # 150 | # 'papersize': 'letterpaper', 151 | # 152 | # The font size ('10pt', '11pt' or '12pt'). 153 | # 154 | # 'pointsize': '10pt', 155 | # 156 | # Additional stuff for the LaTeX preamble. 157 | # 158 | # 'preamble': '', 159 | # 160 | # Latex figure (float) alignment 161 | # 162 | # 'figure_align': 'htbp', 163 | # } 164 | 165 | # Grouping the document tree into LaTeX files. List of tuples 166 | # (source start file, target name, title, 167 | # author, documentclass [howto, manual, or own class]). 168 | # latex_documents = [ 169 | # ( 170 | # master_doc, 171 | # 'ols_client.tex', 172 | # 'OLS Client Documentation', 173 | # author, 174 | # 'manual', 175 | # ), 176 | # ] 177 | 178 | # -- Options for manual page output ------------------------------------------ 179 | 180 | # One entry per manual page. List of tuples 181 | # (source start file, name, description, authors, manual section). 182 | man_pages = [ 183 | ( 184 | master_doc, 185 | "ols_client", 186 | "OLS Client Documentation", 187 | [author], 188 | 1, 189 | ), 190 | ] 191 | 192 | # -- Options for Texinfo output ---------------------------------------------- 193 | 194 | # Grouping the document tree into Texinfo files. List of tuples 195 | # (source start file, target name, title, author, 196 | # dir menu entry, description, category) 197 | texinfo_documents = [ 198 | ( 199 | master_doc, 200 | "ols_client", 201 | "OLS Client Documentation", 202 | author, 203 | "Charles Tapley Hoyt", 204 | "A client to the EBI Ontology Lookup Service", 205 | "Miscellaneous", 206 | ), 207 | ] 208 | 209 | # -- Options for Epub output ------------------------------------------------- 210 | 211 | # Bibliographic Dublin Core info. 212 | # epub_title = project 213 | 214 | # The unique identifier of the text. This can be a ISBN number 215 | # or the project homepage. 216 | # 217 | # epub_identifier = '' 218 | 219 | # A unique identification for the text. 220 | # 221 | # epub_uid = '' 222 | 223 | # A list of files that should not be packed into the epub file. 224 | # epub_exclude_files = ['search.html'] 225 | 226 | # -- Extension configuration ------------------------------------------------- 227 | 228 | # -- Options for intersphinx extension --------------------------------------- 229 | 230 | # Example configuration for intersphinx: refer to the Python standard library. 231 | intersphinx_mapping = { 232 | "python": ("https://docs.python.org/3", None), 233 | } 234 | 235 | autoclass_content = "both" 236 | 237 | # Don't sort alphabetically, explained at: 238 | # https://stackoverflow.com/questions/37209921/python-how-not-to-sort-sphinx-output-in-alphabetical-order 239 | autodoc_member_order = "bysource" 240 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | OLS Client |release| Documentation 2 | ================================== 3 | Installation 4 | ------------ 5 | The most recent release can be installed from 6 | `PyPI `_ with: 7 | 8 | .. code-block:: shell 9 | 10 | $ pip install ols_client 11 | 12 | The most recent code and data can be installed directly from GitHub with: 13 | 14 | .. code-block:: shell 15 | 16 | $ pip install git+https://github.com/cthoyt/ols-client.git 17 | 18 | To install in development mode, use the following: 19 | 20 | .. code-block:: shell 21 | 22 | $ git clone git+https://github.com/cthoyt/ols-client.git 23 | $ cd ols-client 24 | $ pip install -e . 25 | 26 | 27 | Table of Contents 28 | ----------------- 29 | .. toctree:: 30 | :maxdepth: 2 31 | :caption: Getting Started 32 | :name: start 33 | 34 | usage 35 | cli 36 | 37 | Indices and Tables 38 | ------------------ 39 | * :ref:`genindex` 40 | * :ref:`modindex` 41 | * :ref:`search` 42 | -------------------------------------------------------------------------------- /docs/source/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | .. automodule:: ols_client.client 4 | :members: 5 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # See https://setuptools.readthedocs.io/en/latest/build_meta.html 2 | [build-system] 3 | requires = ["setuptools", "wheel"] 4 | build-backend = "setuptools.build_meta:__legacy__" 5 | 6 | [tool.black] 7 | line-length = 100 8 | target-version = ["py37", "py38", "py39", "py310"] 9 | 10 | [tool.isort] 11 | profile = "black" 12 | multi_line_output = 3 13 | line_length = 100 14 | include_trailing_comma = true 15 | reverse_relative = true 16 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | ########################## 2 | # Setup.py Configuration # 3 | ########################## 4 | [metadata] 5 | name = ols_client 6 | version = 0.1.5-dev 7 | description = A client to the EBI Ontology Lookup Service 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | 11 | # URLs associated with the project 12 | url = https://github.com/cthoyt/ols-client 13 | download_url = https://github.com/cthoyt/ols-client/releases 14 | project_urls = 15 | Bug Tracker = https://github.com/cthoyt/ols-client/issues 16 | Source Code = https://github.com/cthoyt/ols-client 17 | 18 | # Author information 19 | author = Charles Tapley Hoyt 20 | author_email = cthoyt@gmail.com 21 | maintainer = Charles Tapley Hoyt 22 | maintainer_email = cthoyt@gmail.com 23 | 24 | # License Information 25 | license = MIT 26 | license_files = 27 | LICENSE 28 | 29 | # Search tags 30 | classifiers = 31 | Development Status :: 1 - Planning 32 | Environment :: Console 33 | Intended Audience :: Developers 34 | License :: OSI Approved :: MIT License 35 | Operating System :: OS Independent 36 | Framework :: Pytest 37 | Framework :: tox 38 | Framework :: Sphinx 39 | Programming Language :: Python 40 | Programming Language :: Python :: 3.7 41 | Programming Language :: Python :: 3.8 42 | Programming Language :: Python :: 3.9 43 | Programming Language :: Python :: 3.10 44 | Programming Language :: Python :: 3 :: Only 45 | keywords = 46 | snekpack 47 | cookiecutter 48 | 49 | [options] 50 | install_requires = 51 | pystow 52 | requests 53 | click 54 | more_click 55 | class-resolver 56 | 57 | # Random options 58 | zip_safe = false 59 | include_package_data = True 60 | python_requires = >=3.7 61 | 62 | # Where is my code 63 | packages = find: 64 | package_dir = 65 | = src 66 | 67 | [options.packages.find] 68 | where = src 69 | 70 | [options.extras_require] 71 | tests = 72 | pytest 73 | coverage 74 | docs = 75 | sphinx 76 | sphinx-rtd-theme 77 | sphinx-click 78 | sphinx-autodoc-typehints 79 | sphinx_automodapi 80 | 81 | [options.entry_points] 82 | console_scripts = 83 | ols_client = ols_client.cli:main 84 | 85 | ###################### 86 | # Doc8 Configuration # 87 | # (doc8.ini) # 88 | ###################### 89 | [doc8] 90 | max-line-length = 120 91 | 92 | ########################## 93 | # Coverage Configuration # 94 | # (.coveragerc) # 95 | ########################## 96 | [coverage:run] 97 | branch = True 98 | source = ols_client 99 | omit = 100 | tests/* 101 | docs/* 102 | 103 | [coverage:paths] 104 | source = 105 | src/ols_client 106 | .tox/*/lib/python*/site-packages/ols_client 107 | 108 | [coverage:report] 109 | show_missing = True 110 | exclude_lines = 111 | pragma: no cover 112 | raise NotImplementedError 113 | if __name__ == __main__: 114 | if TYPE_CHECKING: 115 | def __str__ 116 | def __repr__ 117 | 118 | ########################## 119 | # Darglint Configuration # 120 | ########################## 121 | [darglint] 122 | docstring_style = sphinx 123 | strictness = short 124 | 125 | ######################### 126 | # Flake8 Configuration # 127 | # (.flake8) # 128 | ######################### 129 | [flake8] 130 | ignore = 131 | S301 # pickle 132 | S403 # pickle 133 | S404 134 | S603 135 | W503 # Line break before binary operator (flake8 is wrong) 136 | E203 # whitespace before ':' 137 | S113 138 | exclude = 139 | .tox, 140 | .git, 141 | __pycache__, 142 | docs/source/conf.py, 143 | build, 144 | dist, 145 | tests/fixtures/*, 146 | *.pyc, 147 | *.egg-info, 148 | .cache, 149 | .eggs, 150 | data 151 | max-line-length = 120 152 | max-complexity = 20 153 | import-order-style = pycharm 154 | application-import-names = 155 | ols_client 156 | tests 157 | -------------------------------------------------------------------------------- /src/ols_client/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """A client to the EBI Ontology Lookup Service.""" 4 | 5 | from class_resolver import ClassResolver 6 | 7 | from .client import ( 8 | Client, 9 | EBIClient, 10 | FraunhoferClient, 11 | MonarchClient, 12 | TIBClient, 13 | ZBMedClient, 14 | ) 15 | 16 | __all__ = [ 17 | "client_resolver", 18 | # Base class 19 | "Client", 20 | # Concrete classes 21 | "EBIClient", 22 | "TIBClient", 23 | "ZBMedClient", 24 | "FraunhoferClient", 25 | "MonarchClient", 26 | ] 27 | 28 | client_resolver = ClassResolver.from_subclasses(Client) 29 | -------------------------------------------------------------------------------- /src/ols_client/__main__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Entrypoint module, in case you use `python -m ols_client`. 4 | 5 | Why does this file exist, and why ``__main__``? For more info, read: 6 | 7 | - https://www.python.org/dev/peps/pep-0338/ 8 | - https://docs.python.org/3/using/cmdline.html#cmdoption-m 9 | """ 10 | 11 | from .cli import main 12 | 13 | if __name__ == "__main__": 14 | main() 15 | -------------------------------------------------------------------------------- /src/ols_client/cli.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """CLI for the OLS client.""" 4 | 5 | import sys 6 | from typing import Optional 7 | 8 | import click 9 | 10 | from .client import Client 11 | 12 | 13 | @click.group() 14 | def main(): 15 | """Run the OLS Client Command Line Interface.""" 16 | 17 | 18 | ontology_argument = click.argument("ontology") 19 | iri_option = click.option("--iri", required=True) 20 | output_option = click.option("-o", "--output", type=click.File("w"), default=sys.stdout) 21 | base_url_option = click.option( 22 | "-b", "--base-url", default="http://www.ebi.ac.uk/ols", show_default=True 23 | ) 24 | 25 | 26 | def _echo_via_pager(x): 27 | click.echo_via_pager((term + "\n" for term in x)) 28 | 29 | 30 | @main.command() 31 | @ontology_argument 32 | @base_url_option 33 | def labels(ontology: str, base_url: str): 34 | """Output the names to the given file.""" 35 | client = Client(base_url) 36 | _echo_via_pager(client.iter_labels(ontology)) 37 | 38 | 39 | @main.command() 40 | @ontology_argument 41 | @iri_option 42 | @base_url_option 43 | def ancestors(ontology: str, iri: str, base_url: str): 44 | """Output the ancestors of the given term.""" 45 | client = Client(base_url) 46 | _echo_via_pager(client.iter_ancestors_labels(ontology=ontology, iri=iri)) 47 | 48 | 49 | @main.command() 50 | @click.argument("query") 51 | @base_url_option 52 | def search(query: str, base_url: str): 53 | """Search the OLS with the given query.""" 54 | client = Client(base_url) 55 | _echo_via_pager(client.search(query=query)) 56 | 57 | 58 | @main.command() 59 | @click.argument("query") 60 | @click.option("--ontology") 61 | @base_url_option 62 | def suggest(query: str, ontology: Optional[str], base_url: str): 63 | """Suggest a term based on th given query.""" 64 | client = Client(base_url) 65 | click.echo_via_pager((term + "\n" for term in client.suggest(query=query, ontology=ontology))) 66 | 67 | 68 | if __name__ == "__main__": 69 | main() 70 | -------------------------------------------------------------------------------- /src/ols_client/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Client classes for the OLS.""" 4 | 5 | import logging 6 | import time 7 | from typing import Any, Dict, Iterable, List, Optional, Tuple, Union 8 | from urllib.parse import quote 9 | 10 | import requests 11 | 12 | __all__ = [ 13 | # Base client 14 | "Client", 15 | # Concrete 16 | "EBIClient", 17 | "TIBClient", 18 | "ZBMedClient", 19 | "MonarchClient", 20 | "FraunhoferClient", 21 | ] 22 | 23 | logger = logging.getLogger(__name__) 24 | 25 | 26 | def _iterate_response_terms(response): 27 | """Iterate over the terms in the given response.""" 28 | yield from response["_embedded"]["terms"] 29 | 30 | 31 | def _quote(iri): 32 | # must be double encoded https://www.ebi.ac.uk/ols/docs/api 33 | iri = quote(iri, safe="") 34 | iri = quote(iri, safe="") 35 | return iri 36 | 37 | 38 | def _help_iterate_labels(term_iterator): 39 | for term in term_iterator: 40 | yield term["label"] 41 | 42 | 43 | class Client: 44 | """Wraps the functions to query the Ontology Lookup Service such that alternative base URL's can be used.""" 45 | 46 | def __init__(self, base_url: str): 47 | """Initialize the client. 48 | 49 | :param base_url: An optional, custom URL for the OLS API. 50 | """ 51 | base_url = base_url.rstrip("/") 52 | if not base_url.endswith("/api"): 53 | base_url = f"{base_url}/api" 54 | self.base_url = base_url 55 | 56 | def get_json( 57 | self, 58 | path: str, 59 | params: Optional[Dict[str, Any]] = None, 60 | raise_for_status: bool = True, 61 | **kwargs, 62 | ): 63 | """Get the response JSON.""" 64 | return self.get_response( 65 | path=path, params=params, raise_for_status=raise_for_status, **kwargs 66 | ).json() 67 | 68 | def get_response( 69 | self, 70 | path: str, 71 | params: Optional[Dict[str, Any]] = None, 72 | raise_for_status: bool = True, 73 | **kwargs, 74 | ) -> requests.Response: 75 | """Send a GET request the given endpoint. 76 | 77 | :param path: The path to query following the base URL, e.g., ``/ontologies``. 78 | If this starts with the base URL, it gets stripped. 79 | :param params: Parameters to pass through to :func:`requests.get` 80 | :param raise_for_status: If true and the status code isn't 200, raise an exception 81 | :param kwargs: Keyword arguments to pass through to :func:`requests.get` 82 | :returns: The response from :func:`requests.get` 83 | """ 84 | if not params: 85 | params = {} 86 | if path.startswith(self.base_url): 87 | path = path[len(self.base_url) :] 88 | url = self.base_url + "/" + path.lstrip("/") 89 | res = requests.get(url, params=params, **kwargs) 90 | if raise_for_status: 91 | res.raise_for_status() 92 | return res 93 | 94 | def get_paged( 95 | self, 96 | path: str, 97 | key: Optional[str] = None, 98 | size: Optional[int] = None, 99 | sleep: Optional[int] = None, 100 | ) -> Iterable: 101 | """Iterate over all terms, lazily with paging. 102 | 103 | :param path: The url to query 104 | :param key: The key to slice from the _embedded field 105 | :param size: The size of each page. Defaults to 500, which is the maximum allowed by the EBI. 106 | :param sleep: The amount of time to sleep between pages. Defaults to none. 107 | :yields: A terms in an ontology 108 | :raises ValueError: if an invalid size is given 109 | """ 110 | if size is None: 111 | size = 500 112 | elif size > 500: 113 | raise ValueError(f"Maximum size is 500. Given: {size}") 114 | 115 | res_json = self.get_json(path, params={"size": size}) 116 | yv = res_json["_embedded"] 117 | if key: 118 | yv = yv[key] 119 | yield from yv 120 | next_href = (res_json.get("_links") or {}).get("href") 121 | while next_href: 122 | if sleep is not None: 123 | time.sleep(sleep) 124 | loop_res_json = requests.get(next_href).json() 125 | yv = loop_res_json["_embedded"] 126 | if key: 127 | yv = yv[key] 128 | yield from yv 129 | next_href = (loop_res_json.get("_links") or {}).get("href") 130 | 131 | def get_ontologies(self): 132 | """Get all ontologies.""" 133 | return self.get_paged("/ontologies", key="ontologies") 134 | 135 | def get_ontology(self, ontology: str): 136 | """Get the metadata for a given ontology. 137 | 138 | :param ontology: The name of the ontology 139 | :return: The dictionary representing the JSON from the OLS 140 | """ 141 | return self.get_json(f"/ontologies/{ontology}") 142 | 143 | def get_term(self, ontology: str, iri: str): 144 | """Get the data for a given term. 145 | 146 | :param ontology: The name of the ontology 147 | :param iri: The IRI of a term 148 | :returns: Results about the term 149 | """ 150 | return self.get_json(f"/ontologies/{ontology}/terms", params={"iri": iri}) 151 | 152 | def search(self, query: str, query_fields: Optional[Iterable[str]] = None, params=None): 153 | """Search the OLS with the given term. 154 | 155 | :param query: The query to search 156 | :param query_fields: Fields to query 157 | :param params: Additional params to pass through to :func:`get_json` 158 | :return: dict 159 | :returns: A list of search results 160 | """ 161 | params = dict(params or {}) 162 | params["q"] = query 163 | if query_fields: 164 | params["queryFields"] = ",".join(query_fields) 165 | return self.get_json("/search", params=params)["response"]["docs"] 166 | 167 | def suggest(self, query: str, ontology: Union[None, str, List[str]] = None): 168 | """Suggest terms from an optional list of ontologies. 169 | 170 | :param query: The query to suggest 171 | :param ontology: The ontology or list of ontologies 172 | :returns: A list of suggestion results 173 | 174 | .. seealso:: https://www.ebi.ac.uk/ols/docs/api#_suggest_term 175 | """ 176 | params = {"q": query} 177 | if ontology: 178 | params["ontology"] = ",".join(ontology) if isinstance(ontology, list) else ontology 179 | return self.get_json("/suggest", params=params) 180 | 181 | def iter_terms(self, ontology: str, size: Optional[int] = None, sleep: Optional[int] = None): 182 | """Iterate over all terms, lazily with paging. 183 | 184 | :param ontology: The name of the ontology 185 | :param size: The size of each page. Defaults to 500, which is the maximum allowed by the EBI. 186 | :param sleep: The amount of time to sleep between pages. Defaults to 0 seconds. 187 | :rtype: iter[dict] 188 | :yields: Terms in the ontology 189 | """ 190 | yield from self.get_paged( 191 | f"/ontologies/{ontology}/terms", key="terms", size=size, sleep=sleep 192 | ) 193 | 194 | def iter_ancestors( 195 | self, 196 | ontology: str, 197 | iri: str, 198 | size: Optional[int] = None, 199 | sleep: Optional[int] = None, 200 | ): 201 | """Iterate over the ancestors of a given term. 202 | 203 | :param ontology: The name of the ontology 204 | :param iri: The IRI of a term 205 | :param size: The size of each page. Defaults to 500, which is the maximum allowed by the EBI. 206 | :param sleep: The amount of time to sleep between pages. Defaults to 0 seconds. 207 | :rtype: iter[dict] 208 | :yields: the descendants of the given term 209 | """ 210 | yield from self.get_paged( 211 | f"ontologies/{ontology}/terms/{_quote(iri)}/ancestors", 212 | key="terms", 213 | size=size, 214 | sleep=sleep, 215 | ) 216 | 217 | def iter_hierarchical_ancestors( 218 | self, 219 | ontology: str, 220 | iri: str, 221 | size: Optional[int] = None, 222 | sleep: Optional[int] = None, 223 | ): 224 | """Iterate over the hierarchical of a given term. 225 | 226 | :param ontology: The name of the ontology 227 | :param iri: The IRI of a term 228 | :param size: The size of each page. Defaults to 500, which is the maximum allowed by the EBI. 229 | :param sleep: The amount of time to sleep between pages. Defaults to 0 seconds. 230 | :rtype: iter[dict] 231 | :yields: the descendants of the given term 232 | """ 233 | yield from self.get_paged( 234 | f"ontologies/{ontology}/terms/{_quote(iri)}/hierarchicalAncestors", 235 | key="terms", 236 | size=size, 237 | sleep=sleep, 238 | ) 239 | 240 | def iter_ancestors_labels( 241 | self, ontology: str, iri: str, size: Optional[int] = None, sleep: Optional[int] = None 242 | ) -> Iterable[str]: 243 | """Iterate over the labels for the descendants of a given term. 244 | 245 | :param ontology: The name of the ontology 246 | :param iri: The IRI of a term 247 | :param size: The size of each page. Defaults to 500, which is the maximum allowed by the EBI. 248 | :param sleep: The amount of time to sleep between pages. Defaults to 0 seconds. 249 | :yields: labels of the descendants of the given term 250 | """ 251 | yield from _help_iterate_labels(self.iter_ancestors(ontology, iri, size=size, sleep=sleep)) 252 | 253 | def iter_labels( 254 | self, ontology: str, size: Optional[int] = None, sleep: Optional[int] = None 255 | ) -> Iterable[str]: 256 | """Iterate over the labels of terms in the ontology. Automatically wraps the pager returned by the OLS. 257 | 258 | :param ontology: The name of the ontology 259 | :param size: The size of each page. Defaults to 500, which is the maximum allowed by the EBI. 260 | :param sleep: The amount of time to sleep between pages. Defaults to 0 seconds. 261 | :yields: labels of terms in the ontology 262 | """ 263 | yield from _help_iterate_labels(self.iter_terms(ontology=ontology, size=size, sleep=sleep)) 264 | 265 | def iter_hierarchy( 266 | self, ontology: str, size: Optional[int] = None, sleep: Optional[int] = None 267 | ) -> Iterable[Tuple[str, str]]: 268 | """Iterate over parent-child relation labels. 269 | 270 | :param ontology: The name of the ontology 271 | :param size: The size of each page. Defaults to 500, which is the maximum allowed by the EBI. 272 | :param sleep: The amount of time to sleep between pages. Defaults to 0 seconds. 273 | :yields: pairs of parent/child labels 274 | """ 275 | for term in self.iter_terms(ontology=ontology, size=size, sleep=sleep): 276 | try: 277 | hierarchy_children_link = term["_links"]["hierarchicalChildren"]["href"] 278 | except KeyError: # there's no children for this one 279 | continue 280 | 281 | response = requests.get(hierarchy_children_link).json() 282 | 283 | for child_term in response["_embedded"]["terms"]: 284 | yield term["label"], child_term["label"] # TODO handle different relation types 285 | 286 | def get_description(self, ontology: str) -> Optional[str]: 287 | """Get the description of a given ontology. 288 | 289 | :param ontology: The name of the ontology 290 | :returns: The description of the ontology. 291 | """ 292 | response = self.get_ontology(ontology) 293 | return response["config"].get("description") 294 | 295 | 296 | class EBIClient(Client): 297 | """The first-party instance of the OLS. 298 | 299 | .. seealso:: https://www.ebi.ac.uk/ols4 300 | """ 301 | 302 | def __init__(self): 303 | """Initialize the client.""" 304 | super().__init__(base_url="https://www.ebi.ac.uk/ols4") 305 | 306 | 307 | class TIBClient(Client): 308 | """The TIB instance of the OLS. 309 | 310 | With its new Terminology Service, TIB – Leibniz Information Centre 311 | for Science and Technology and University Library provides a single 312 | point of access to terminology from domains such as architecture, 313 | chemistry, computer science, mathematics and physics. 314 | 315 | .. seealso:: https://service.tib.eu/ts4tib/ 316 | """ 317 | 318 | def __init__(self): 319 | """Initialize the client.""" 320 | super().__init__(base_url="https://service.tib.eu/ts4tib") 321 | 322 | 323 | class ZBMedClient(Client): 324 | """The ZB Med instance of the OLS. 325 | 326 | .. seealso:: https://semanticlookup.zbmed.de/ols 327 | """ 328 | 329 | def __init__(self): 330 | """Initialize the client.""" 331 | super().__init__(base_url="https://semanticlookup.zbmed.de/ols") 332 | 333 | 334 | class MonarchClient(Client): 335 | """The Monarch Initiative instance of the OLS. 336 | 337 | .. seealso:: https://ols.monarchinitiative.org/ 338 | """ 339 | 340 | def __init__(self): 341 | """Initialize the client.""" 342 | super().__init__(base_url="https://ols.monarchinitiative.org/") 343 | 344 | 345 | class FraunhoferClient(Client): 346 | """The Fraunhofer SCAI instance of the OLS. 347 | 348 | .. warning:: Fraunhofer SCAI resources are typically not maintained, do not rely on this. 349 | 350 | .. seealso:: https://rohan.scai.fraunhofer.de 351 | """ 352 | 353 | def __init__(self): 354 | """Initialize the client.""" 355 | super().__init__(base_url="https://rohan.scai.fraunhofer.de") 356 | -------------------------------------------------------------------------------- /src/ols_client/py.typed: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/ols_client/version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Version information for :mod:`ols_client`. 4 | 5 | Run with ``python -m ols_client.version`` 6 | """ 7 | 8 | import os 9 | from subprocess import CalledProcessError, check_output # noqa: S404 10 | 11 | __all__ = [ 12 | "VERSION", 13 | "get_version", 14 | "get_git_hash", 15 | ] 16 | 17 | VERSION = "0.1.5-dev" 18 | 19 | 20 | def get_git_hash() -> str: 21 | """Get the :mod:`ols_client` git hash.""" 22 | with open(os.devnull, "w") as devnull: 23 | try: 24 | ret = check_output( # noqa: S603,S607 25 | ["git", "rev-parse", "HEAD"], 26 | cwd=os.path.dirname(__file__), 27 | stderr=devnull, 28 | ) 29 | except CalledProcessError: 30 | return "UNHASHED" 31 | else: 32 | return ret.strip().decode("utf-8")[:8] 33 | 34 | 35 | def get_version(with_git_hash: bool = False): 36 | """Get the :mod:`ols_client` version string, including a git hash.""" 37 | return f"{VERSION}-{get_git_hash()}" if with_git_hash else VERSION 38 | 39 | 40 | if __name__ == "__main__": 41 | print(get_version(with_git_hash=True)) # noqa:T201 42 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Tests for :mod:`ols_client`.""" 4 | -------------------------------------------------------------------------------- /tests/cases.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Tests for the client.""" 4 | 5 | import unittest 6 | from typing import ClassVar, Type 7 | 8 | from ols_client.client import Client 9 | 10 | __all__ = [ 11 | "TestClient", 12 | ] 13 | 14 | 15 | class TestClient(unittest.TestCase): 16 | """Test the OLS client.""" 17 | 18 | client_cls: ClassVar[Type[Client]] 19 | test_ontology: ClassVar[str] 20 | test_label: ClassVar[str] 21 | 22 | def setUp(self) -> None: 23 | """Set up the test case.""" 24 | self.client = self.client_cls() 25 | 26 | def test_iter_labels(self): 27 | """Test getting labels.""" 28 | labels = set(self.client.iter_labels(self.test_ontology)) 29 | self.assertIn(self.test_label, labels) 30 | -------------------------------------------------------------------------------- /tests/test_ols_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Tests for the client.""" 4 | 5 | from ols_client.client import EBIClient 6 | from tests import cases 7 | 8 | 9 | class TestEbi(cases.TestClient): 10 | """Tests for the EBI client.""" 11 | 12 | client_cls = EBIClient 13 | test_ontology = "aro" 14 | test_label = "tetracycline-resistant ribosomal protection protein" 15 | 16 | def test_get_term(self): 17 | """Test getting a term.""" 18 | iri = "http://biomodels.net/SBO/SBO_0000150" 19 | res_json = self.client.get_term("sbo", iri) 20 | terms = res_json["_embedded"]["terms"] 21 | self.assertEqual(1, len(terms)) 22 | term = terms[0] 23 | self.assertEqual(iri, term["iri"]) 24 | -------------------------------------------------------------------------------- /tests/test_version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Trivial version test.""" 4 | 5 | import unittest 6 | 7 | from ols_client.version import get_version 8 | 9 | 10 | class TestVersion(unittest.TestCase): 11 | """Trivially test a version.""" 12 | 13 | def test_version_type(self): 14 | """Test the version is a string. 15 | 16 | This is only meant to be an example test. 17 | """ 18 | version = get_version() 19 | self.assertIsInstance(version, str) 20 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | # To use a PEP 517 build-backend you are required to configure tox to use an isolated_build: 8 | # https://tox.readthedocs.io/en/latest/example/package.html 9 | isolated_build = True 10 | 11 | # These environments are run in order if you just use `tox`: 12 | envlist = 13 | # always keep coverage-clean first 14 | # coverage-clean 15 | # code linters/stylers 16 | lint 17 | manifest 18 | pyroma 19 | flake8 20 | mypy 21 | # documentation linters/checkers 22 | doc8 23 | docstr-coverage 24 | docs-test 25 | # the actual tests 26 | py 27 | # always keep coverage-report last 28 | # coverage-report 29 | 30 | [testenv] 31 | # Runs on the "tests" directory by default, or passes the positional 32 | # arguments from `tox -e py ... 33 | commands = 34 | coverage run -p -m pytest --durations=20 {posargs:tests} 35 | coverage combine 36 | coverage xml 37 | extras = 38 | # See the [options.extras_require] entry in setup.cfg for "tests" 39 | tests 40 | 41 | [testenv:doctests] 42 | commands = 43 | xdoctest -m src 44 | deps = 45 | xdoctest 46 | pygments 47 | 48 | [testenv:coverage-clean] 49 | deps = coverage 50 | skip_install = true 51 | commands = coverage erase 52 | 53 | [testenv:lint] 54 | deps = 55 | black[jupyter] 56 | isort 57 | skip_install = true 58 | commands = 59 | black . 60 | isort . 61 | description = Run linters. 62 | 63 | [testenv:doclint] 64 | deps = 65 | rstfmt 66 | skip_install = true 67 | commands = 68 | rstfmt docs/source/ 69 | description = Run documentation linters. 70 | 71 | [testenv:manifest] 72 | deps = check-manifest 73 | skip_install = true 74 | commands = check-manifest 75 | description = Check that the MANIFEST.in is written properly and give feedback on how to fix it. 76 | 77 | [testenv:flake8] 78 | skip_install = true 79 | deps = 80 | darglint 81 | flake8<5.0.0 82 | flake8-black 83 | flake8-bandit 84 | flake8-bugbear 85 | flake8-colors 86 | flake8-docstrings 87 | flake8-isort 88 | flake8-print 89 | pep8-naming 90 | pydocstyle 91 | commands = 92 | flake8 src/ tests/ 93 | description = Run the flake8 tool with several plugins (bandit, docstrings, import order, pep8 naming). See https://cthoyt.com/2020/04/25/how-to-code-with-me-flake8.html for more information. 94 | 95 | [testenv:pyroma] 96 | deps = 97 | pygments 98 | pyroma 99 | skip_install = true 100 | commands = pyroma --min=10 . 101 | description = Run the pyroma tool to check the package friendliness of the project. 102 | 103 | [testenv:mypy] 104 | deps = mypy 105 | skip_install = true 106 | commands = mypy --install-types --non-interactive --ignore-missing-imports src/ 107 | description = Run the mypy tool to check static typing on the project. 108 | 109 | [testenv:doc8] 110 | skip_install = true 111 | deps = 112 | sphinx 113 | doc8 114 | commands = 115 | doc8 docs/source/ 116 | description = Run the doc8 tool to check the style of the RST files in the project docs. 117 | 118 | [testenv:docstr-coverage] 119 | skip_install = true 120 | deps = 121 | docstr-coverage 122 | commands = 123 | docstr-coverage src/ tests/ --skip-private --skip-magic 124 | description = Run the docstr-coverage tool to check documentation coverage 125 | 126 | [testenv:docs] 127 | description = Build the documentation locally. 128 | extras = 129 | # See the [options.extras_require] entry in setup.cfg for "docs" 130 | docs 131 | # You might need to add additional extras if your documentation covers it 132 | commands = 133 | python -m sphinx -W -b html -d docs/build/doctrees docs/source docs/build/html 134 | 135 | [testenv:docs-test] 136 | description = Test building the documentation in an isolated environment. 137 | changedir = docs 138 | extras = 139 | {[testenv:docs]extras} 140 | commands = 141 | mkdir -p {envtmpdir} 142 | cp -r source {envtmpdir}/source 143 | python -m sphinx -W -b html -d {envtmpdir}/build/doctrees {envtmpdir}/source {envtmpdir}/build/html 144 | python -m sphinx -W -b coverage -d {envtmpdir}/build/doctrees {envtmpdir}/source {envtmpdir}/build/coverage 145 | cat {envtmpdir}/build/coverage/c.txt 146 | cat {envtmpdir}/build/coverage/python.txt 147 | allowlist_externals = 148 | /bin/cp 149 | /bin/cat 150 | /bin/mkdir 151 | # for compatibility on GitHub actions 152 | /usr/bin/cp 153 | /usr/bin/cat 154 | /usr/bin/mkdir 155 | 156 | [testenv:coverage-report] 157 | deps = coverage 158 | skip_install = true 159 | commands = 160 | coverage combine 161 | coverage report 162 | 163 | #################### 164 | # Deployment tools # 165 | #################### 166 | 167 | [testenv:bumpversion] 168 | commands = bumpversion {posargs} 169 | skip_install = true 170 | passenv = HOME 171 | deps = 172 | bumpversion 173 | 174 | [testenv:build] 175 | skip_install = true 176 | deps = 177 | wheel 178 | build 179 | commands = 180 | python -m build --sdist --wheel --no-isolation 181 | 182 | [testenv:release] 183 | description = Release the code to PyPI so users can pip install it 184 | skip_install = true 185 | deps = 186 | {[testenv:build]deps} 187 | twine >= 1.5.0 188 | commands = 189 | {[testenv:build]commands} 190 | twine upload --skip-existing dist/* 191 | 192 | [testenv:testrelease] 193 | description = Release the code to the test PyPI site 194 | skip_install = true 195 | deps = 196 | {[testenv:build]deps} 197 | twine >= 1.5.0 198 | commands = 199 | {[testenv:build]commands} 200 | twine upload --skip-existing --repository-url https://test.pypi.org/simple/ dist/* 201 | 202 | [testenv:finish] 203 | skip_install = true 204 | passenv = 205 | HOME 206 | TWINE_USERNAME 207 | TWINE_PASSWORD 208 | deps = 209 | {[testenv:release]deps} 210 | bump2version 211 | commands = 212 | bump2version release --tag 213 | {[testenv:release]commands} 214 | git push --tags 215 | bump2version patch 216 | git push 217 | allowlist_externals = 218 | /usr/bin/git 219 | --------------------------------------------------------------------------------