From 97c806a681790003ca7807766a3a539f5a9807cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Semid=C3=A1n=20Robaina=20Est=C3=A9vez?= Date: Mon, 11 Sep 2023 09:17:03 +0100 Subject: [PATCH 01/10] refactor --- envs/brendapyrser-dev.yml | 19 ++++++++ pyproject.toml | 46 +++++++++++++++++++ setup.py | 24 ---------- .../brendapyrser}/__init__.py | 0 .../brendapyrser}/constants.py | 0 {brendapyrser => src/brendapyrser}/parser.py | 0 {brendapyrser => tests}/tests.py | 0 7 files changed, 65 insertions(+), 24 deletions(-) create mode 100644 envs/brendapyrser-dev.yml create mode 100644 pyproject.toml delete mode 100644 setup.py rename {brendapyrser => src/brendapyrser}/__init__.py (100%) rename {brendapyrser => src/brendapyrser}/constants.py (100%) rename {brendapyrser => src/brendapyrser}/parser.py (100%) rename {brendapyrser => tests}/tests.py (100%) diff --git a/envs/brendapyrser-dev.yml b/envs/brendapyrser-dev.yml new file mode 100644 index 0000000..cb33277 --- /dev/null +++ b/envs/brendapyrser-dev.yml @@ -0,0 +1,19 @@ +name: brendapyrser-dev +channels: + - defaults + - bioconda + - conda-forge +dependencies: + - python >= 3.8 + - poetry >= 1.3 + - pip + - pip: + - mkdocs + - mkdocs-gen-files + - pymdown-extensions + - mkdocs-jupyter + - mkdocstrings[python] + - ruff + - black + - argmark + - coverage diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1bedee9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,46 @@ +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +name = "brendapyrser" +version = "0.0.4" +description = "Tools to parse the BRENDA database" +license = "Apache-2.0" +authors = ["Semidán Robaina Estévez "] +maintainers = ["Semidán Robaina Estévez "] +readme = "README.md" +homepage = "https://github.com/robaina/BRENDApyrser" +repository = "https://github.com/robaina/BRENDApyrser" +documentation = "https://robaina.github.io/BRENDApyrser" +keywords = ["BRENDA", "metabolism", "enzymes", "bioinformatics"] +classifiers = [ + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3", + "Topic :: Scientific/Engineering :: Bio-Informatics", + "Natural Language :: English", +] +packages = [{ include = "brendapyrser", from = "src" }] +[tool.poetry.dependencies] +python = "^3.8" +numpy = "^1.20.2" +pandas = "^1.2.4" +importlib-metadata = "^4.0.1" + +[tool.ruff] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort +] +ignore = [ + "E501", # line too long, handled by black + "B008", # do not perform function calls in argument defaults + "C901", # mccabe complexity + "E999", # match statement is not yet supported + "W605", # ASCII art, verbatim text +] + +[tool.ruff.isort] +known-first-party = ["brendapyrser"] diff --git a/setup.py b/setup.py deleted file mode 100644 index 8c683e4..0000000 --- a/setup.py +++ /dev/null @@ -1,24 +0,0 @@ -from setuptools import setup -from os import path - -this_directory = path.abspath(path.dirname(__file__)) -with open(path.join(this_directory, 'README.md'), 'r', encoding='utf-8') as f: - long_description = f.read() - - -setup( - name='brendapyrser', - version='0.0.2', - description='Tools to parse the BRENDA database', - long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/robaina/BRENDA_database', - donwload_url='https://github.com/robaina/BRENDA_database', - author='Semidán Robaina Estévez, 2020-2022', - author_email='srobaina@ull.edu.es', - maintainer='Semidán Robaina Estévez', - maintainer_email='srobaina@ull.edu.es', - license='BSD-3-Clause license', - install_requires=['numpy', 'pandas', 'importlib-metadata >= 1.0 ; python_version < "3.8"'], - packages=['brendapyrser'] -) \ No newline at end of file diff --git a/brendapyrser/__init__.py b/src/brendapyrser/__init__.py similarity index 100% rename from brendapyrser/__init__.py rename to src/brendapyrser/__init__.py diff --git a/brendapyrser/constants.py b/src/brendapyrser/constants.py similarity index 100% rename from brendapyrser/constants.py rename to src/brendapyrser/constants.py diff --git a/brendapyrser/parser.py b/src/brendapyrser/parser.py similarity index 100% rename from brendapyrser/parser.py rename to src/brendapyrser/parser.py diff --git a/brendapyrser/tests.py b/tests/tests.py similarity index 100% rename from brendapyrser/tests.py rename to tests/tests.py From c519ad86000dd0a69b12270f6308ba774ace1c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Semid=C3=A1n=20Robaina=20Est=C3=A9vez?= Date: Mon, 11 Sep 2023 12:18:23 +0100 Subject: [PATCH 02/10] update structure --- .../CODE_OF_CONDUCT.md | 0 .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++ .github/workflows/ci.yml | 20 +++++ .github/workflows/docker.yml | 58 ++++++++++++++ .github/workflows/docs.yml | 21 +++++ .github/workflows/{draft-pdf.yml => joss.yml} | 7 +- .github/workflows/tests.yml | 57 +++++++++++++ CONTRIBUTING.md | 80 +++++++++++++++++++ 9 files changed, 298 insertions(+), 3 deletions(-) rename CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md (100%) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/docs.yml rename .github/workflows/{draft-pdf.yml => joss.yml} (84%) create mode 100644 .github/workflows/tests.yml create mode 100644 CONTRIBUTING.md diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..11afc02 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: CI +on: push +jobs: + quality: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: "3.8" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ruff black + # Include `--format=github` to enable automatic inline annotations. + # - name: Check linters + # run: ruff --format=github . # ruff does not allow trailing white space in logo (cli.py) + - name: Check format + run: black --check . diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..94f174e --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,58 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# GitHub recommends pinning actions to a commit SHA. +# To get a newer version, you will need to update the SHA. +# You can also reference a tag or branch, but the action may change without warning. + +name: Publish Docker image + +on: + release: + types: [published] + + push: + branches: + - main + +jobs: + push_to_registries: + name: Push Docker image to multiple registries + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Check out the repo + uses: actions/checkout@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.API_GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: | + robaina/pynteny + ghcr.io/${{ github.repository }} + + - name: Build and push Docker images + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..9dc964f --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,21 @@ +name: docs +on: + push: + branches: [ main ] + paths: + - 'docs/**' + - 'mkdocs.yml' + +jobs: + + build-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-python@v2 + - run: pip install --upgrade pip && pip install mkdocs mkdocs-gen-files pymdown-extensions mkdocs-jupyter mkdocstrings[python] + - run: git config user.name 'github-actions[bot]' && git config user.email 'github-actions[bot]@users.noreply.github.com' + - name: Publish docs + run: mkdocs gh-deploy diff --git a/.github/workflows/draft-pdf.yml b/.github/workflows/joss.yml similarity index 84% rename from .github/workflows/draft-pdf.yml rename to .github/workflows/joss.yml index 1abe25b..a472744 100644 --- a/.github/workflows/draft-pdf.yml +++ b/.github/workflows/joss.yml @@ -1,3 +1,4 @@ +name: joss on: [push] jobs: @@ -6,13 +7,13 @@ jobs: name: Paper Draft steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Open Journals PDF Generator uses: openjournals/openjournals-draft-action@master with: journal: joss # This should be the path to the paper within your repo. - paper-path: paper/paper.md + paper-path: ms/paper.md - name: Upload uses: actions/upload-artifact@v1 with: @@ -20,4 +21,4 @@ jobs: # This is the output path where Pandoc will write the compiled # PDF. Note, this should be the same directory as the input # paper.md - path: paper/paper.pdf + path: ms/paper.pdf diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..d6da3e6 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,57 @@ +name: tests +on: + push: + branches: [ main ] + paths-ignore: + - '**.md' + - '**.ipynb' + - '**.bib' + - 'ms/*' + pull_request: + types: [opened, reopened, edited] + paths-ignore: + - '**.md' + - '**.ipynb' + - '**.bib' + - 'ms/*' + +jobs: + + create-env: + name: ${{ matrix.os }} + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash -l {0} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + + steps: + - name: checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: create environment + uses: conda-incubator/setup-miniconda@v2 + with: + python-version: 3.8 + # mamba-version: "*" + channels: conda-forge,bioconda,defaults + auto-activate-base: false + activate-environment: tests_pynteny + environment-file: envs/pynteny-dev.yml + + - name: Build & Install Pynteny + run: poetry build && pip install dist/pynteny*.whl + + - name: Run tests and collect coverage + run: pynteny --help && coverage run --omit pynteny/wrappers.py,pynteny/cli.py,pynteny/subcommands.py,pynteny/utils.py -m unittest discover tests && coverage xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + version: "v0.1.15" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e943307 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,80 @@ +# Contributing to BRENDApyrser + +First of all, thanks for taking the time to contribute! :tada::+1: + +Here you will find a set of guidelines for contributing to BRENDApyrser. Feel free to propose changes to this document in a pull request. + +## Code of conduct + +This project and everyone participating in it is governed by the [Contributor Covenant, v2.0](.github/CODE_OF_CONDUCT.md) code of conduct. By participating, you are expected to uphold this code. + +## I have a question! + +If you only have a question about all things related to BRENDApyrser, the best course of actions for you is to open a new [discussion](https://github.com/Robaina/BRENDApyrser/discussions). + +## How can I contribute? + +### 1. Reporting bugs + +We all make mistakes, and the developers behind BRENDApyrser are no exception... So, if you find a bug in the source code, please open an [issue](https://github.com/Robaina/BRENDApyrser/issues) and report it. Please, first search for similar issues that are currrently open. + +### 2. Suggesting enhancements + +Are you missing some feature that would like BRENDApyrser to have? No problem! You can contribute by suggesting an enhancement, just open a new issue and tag it with the [```enhancement```](https://github.com/Robaina/BRENDApyrser/labels/enhancement) label. Please, first search for similar issues that are currrently open. + +### 3. Improving the documentation + +Help is always needed at improving the [documentation](https://robaina.github.io/BRENDApyrser/). Either adding more detailed docstrings, usage explanations or new examples. + +## First contribution + +Unsure where to begin contributing to BRENDApyrser? You can start by looking for issues with the label [```good first issue```](https://github.com/Robaina/BRENDApyrser/labels/good%20first%20issue). If you are unsure about how to set a developer environment for BRENDApyrser, do take a look at the section below. Thanks! + +## Setting up a local developer environment + +To setup up a developer environment for BRENDApyrser: + +1. Fork and download repo, cd to downloaded directory. You should create a new branch to work on your issue. + +2. Create conda environment with required dependencies: + +The file `envs/BRENDApyrser-dev.yml` contains all dependencies required to use BRENDApyrser. Conda is very slow solving the environment. It is recommended to use [mamba](https://github.com/mamba-org/mamba) instead: + +```bash +mamba env create -n BRENDApyrser-dev -f envs/BRENDApyrser-dev.yml +conda activate BRENDApyrser-dev +``` + +3. Build package + +```bash +(BRENDApyrser-dev) poetry build +``` + +4. Install BRENDApyrser + +```bash +(BRENDApyrser-dev) pip install dist/BRENDApyrser*.whl +``` + +5. Run tests + +```bash +(BRENDApyrser-dev) python -m unittest discover tests +``` + +## Building the documentation + +The documentation is formed by a series of markdown files located in directory [docs](https://github.com/Robaina/BRENDApyrser/tree/main/docs). This repo uses [mkdocs](https://www.mkdocs.org/) to automatically generate documentation pages from markdown files. Also, [MathJax](https://github.com/mathjax/MathJax) syntax is allowed! + +This means that, to modify the [API reference](https://robaina.github.io/BRENDApyrser/references/api/), all you need to do is to modify the docstring directly in the source file where the definion/class is located. And, to update the documentation pages, you just have to update the corresponding markdown file in the [docs](https://github.com/Robaina/BRENDApyrser/tree/main/docs) directory. Note that, if you need to change the documentation structure (e.g., add or new pages),you would need to tell mkdocs about this change through its [configuration file](https://github.com/Robaina/BRENDApyrser/blob/main/mkdocs.yml). Or just open an issue and ask for help! + +When all the changes are ready to deploy, just open a pull request. After reviewing and merging the changes, the documentation will be automatically deployed. + +Run the documentation locally with: + +> mkdocs serve + +## Tests on push and pull request to main + +BRENDApyrser's repo contains a [GitHub Action](https://github.com/features/actions) to perform build and integration tests which is triggered automatically on push and pull request events to the main brach. Currently the tests include building and installing BRENDApyrser in Ubuntu and MacOS and running the [test](tests) suit. From 84e91633c54bc43e3b531ea3566947b008065cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Semid=C3=A1n=20Robaina=20Est=C3=A9vez?= Date: Mon, 11 Sep 2023 12:27:17 +0100 Subject: [PATCH 03/10] refactor, actions --- .github/CODE_OF_CONDUCT.md | 0 .github/ISSUE_TEMPLATE/bug_report.md | 0 .github/ISSUE_TEMPLATE/feature_request.md | 0 .github/workflows/ci.yml | 0 .github/workflows/docker.yml | 2 +- .github/workflows/docs.yml | 0 .github/workflows/joss.yml | 0 .github/workflows/tests.yml | 10 +++++----- .gitignore | 0 CITATION.cff | 0 CONTRIBUTING.md | 0 LICENSE | 0 MANIFEST.in | 0 README.md | 0 README_files/output_13_0.png | Bin README_files/output_14_0.png | Bin README_files/output_16_0.png | Bin README_files/output_18_0.png | Bin README_files/output_5_0.png | Bin README_files/output_6_0.png | Bin README_files/output_7_0.png | Bin README_files/output_9_0.png | Bin assets/logo.png | Bin assets/social_logo_cut.png | Bin envs/brendapyrser-dev.yml | 0 examples/examples.ipynb | 0 paper/paper.bib | 0 paper/paper.md | 0 pyproject.toml | 0 src/brendapyrser/__init__.py | 0 src/brendapyrser/constants.py | 0 src/brendapyrser/parser.py | 0 tests/tests.py | 0 33 files changed, 6 insertions(+), 6 deletions(-) mode change 100644 => 100755 .github/CODE_OF_CONDUCT.md mode change 100644 => 100755 .github/ISSUE_TEMPLATE/bug_report.md mode change 100644 => 100755 .github/ISSUE_TEMPLATE/feature_request.md mode change 100644 => 100755 .github/workflows/ci.yml mode change 100644 => 100755 .github/workflows/docker.yml mode change 100644 => 100755 .github/workflows/docs.yml mode change 100644 => 100755 .github/workflows/joss.yml mode change 100644 => 100755 .github/workflows/tests.yml mode change 100644 => 100755 .gitignore mode change 100644 => 100755 CITATION.cff mode change 100644 => 100755 CONTRIBUTING.md mode change 100644 => 100755 LICENSE mode change 100644 => 100755 MANIFEST.in mode change 100644 => 100755 README.md mode change 100644 => 100755 README_files/output_13_0.png mode change 100644 => 100755 README_files/output_14_0.png mode change 100644 => 100755 README_files/output_16_0.png mode change 100644 => 100755 README_files/output_18_0.png mode change 100644 => 100755 README_files/output_5_0.png mode change 100644 => 100755 README_files/output_6_0.png mode change 100644 => 100755 README_files/output_7_0.png mode change 100644 => 100755 README_files/output_9_0.png mode change 100644 => 100755 assets/logo.png mode change 100644 => 100755 assets/social_logo_cut.png mode change 100644 => 100755 envs/brendapyrser-dev.yml mode change 100644 => 100755 examples/examples.ipynb mode change 100644 => 100755 paper/paper.bib mode change 100644 => 100755 paper/paper.md mode change 100644 => 100755 pyproject.toml mode change 100644 => 100755 src/brendapyrser/__init__.py mode change 100644 => 100755 src/brendapyrser/constants.py mode change 100644 => 100755 src/brendapyrser/parser.py mode change 100644 => 100755 tests/tests.py diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md old mode 100644 new mode 100755 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md old mode 100644 new mode 100755 diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md old mode 100644 new mode 100755 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml old mode 100644 new mode 100755 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml old mode 100644 new mode 100755 index 94f174e..57817bd --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -46,7 +46,7 @@ jobs: uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 with: images: | - robaina/pynteny + robaina/brendapyrser ghcr.io/${{ github.repository }} - name: Build and push Docker images diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml old mode 100644 new mode 100755 diff --git a/.github/workflows/joss.yml b/.github/workflows/joss.yml old mode 100644 new mode 100755 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml old mode 100644 new mode 100755 index d6da3e6..ba970d2 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -42,14 +42,14 @@ jobs: # mamba-version: "*" channels: conda-forge,bioconda,defaults auto-activate-base: false - activate-environment: tests_pynteny - environment-file: envs/pynteny-dev.yml + activate-environment: tests_brendapyrser + environment-file: envs/brendapyrser-dev.yml - - name: Build & Install Pynteny - run: poetry build && pip install dist/pynteny*.whl + - name: Build & Install BRENDApyrser + run: poetry build && pip install dist/brendapyrser*.whl - name: Run tests and collect coverage - run: pynteny --help && coverage run --omit pynteny/wrappers.py,pynteny/cli.py,pynteny/subcommands.py,pynteny/utils.py -m unittest discover tests && coverage xml + run: coverage run -m unittest discover tests && coverage xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/CITATION.cff b/CITATION.cff old mode 100644 new mode 100755 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/MANIFEST.in b/MANIFEST.in old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/README_files/output_13_0.png b/README_files/output_13_0.png old mode 100644 new mode 100755 diff --git a/README_files/output_14_0.png b/README_files/output_14_0.png old mode 100644 new mode 100755 diff --git a/README_files/output_16_0.png b/README_files/output_16_0.png old mode 100644 new mode 100755 diff --git a/README_files/output_18_0.png b/README_files/output_18_0.png old mode 100644 new mode 100755 diff --git a/README_files/output_5_0.png b/README_files/output_5_0.png old mode 100644 new mode 100755 diff --git a/README_files/output_6_0.png b/README_files/output_6_0.png old mode 100644 new mode 100755 diff --git a/README_files/output_7_0.png b/README_files/output_7_0.png old mode 100644 new mode 100755 diff --git a/README_files/output_9_0.png b/README_files/output_9_0.png old mode 100644 new mode 100755 diff --git a/assets/logo.png b/assets/logo.png old mode 100644 new mode 100755 diff --git a/assets/social_logo_cut.png b/assets/social_logo_cut.png old mode 100644 new mode 100755 diff --git a/envs/brendapyrser-dev.yml b/envs/brendapyrser-dev.yml old mode 100644 new mode 100755 diff --git a/examples/examples.ipynb b/examples/examples.ipynb old mode 100644 new mode 100755 diff --git a/paper/paper.bib b/paper/paper.bib old mode 100644 new mode 100755 diff --git a/paper/paper.md b/paper/paper.md old mode 100644 new mode 100755 diff --git a/pyproject.toml b/pyproject.toml old mode 100644 new mode 100755 diff --git a/src/brendapyrser/__init__.py b/src/brendapyrser/__init__.py old mode 100644 new mode 100755 diff --git a/src/brendapyrser/constants.py b/src/brendapyrser/constants.py old mode 100644 new mode 100755 diff --git a/src/brendapyrser/parser.py b/src/brendapyrser/parser.py old mode 100644 new mode 100755 diff --git a/tests/tests.py b/tests/tests.py old mode 100644 new mode 100755 From bac3fcc4efb6a78bd79722e6f3e0a9d35ec50c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Semid=C3=A1n=20Robaina=20Est=C3=A9vez?= Date: Mon, 11 Sep 2023 13:33:17 +0100 Subject: [PATCH 04/10] tests --- .github/workflows/tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ba970d2..df8e6fa 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,7 +1,6 @@ name: tests on: push: - branches: [ main ] paths-ignore: - '**.md' - '**.ipynb' From 8de18eee2d93421f6de64ce6bce2109c695f10fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Semid=C3=A1n=20Robaina=20Est=C3=A9vez?= Date: Mon, 11 Sep 2023 13:34:50 +0100 Subject: [PATCH 05/10] format --- src/brendapyrser/__init__.py | 3 - src/brendapyrser/constants.py | 108 +++++------ src/brendapyrser/parser.py | 333 ++++++++++++++++++++-------------- tests/tests.py | 62 ++++--- 4 files changed, 286 insertions(+), 220 deletions(-) diff --git a/src/brendapyrser/__init__.py b/src/brendapyrser/__init__.py index aba2342..e69de29 100755 --- a/src/brendapyrser/__init__.py +++ b/src/brendapyrser/__init__.py @@ -1,3 +0,0 @@ -from .parser import (BRENDA, ReactionList, - EnzymeDict, EnzymePropertyDict, - EnzymeConditionDict, Reaction) diff --git a/src/brendapyrser/constants.py b/src/brendapyrser/constants.py index 8f93815..7a08a40 100755 --- a/src/brendapyrser/constants.py +++ b/src/brendapyrser/constants.py @@ -6,60 +6,60 @@ """ fields = { - 'AC': 'activating compound', - 'AP': 'application', - 'CF': 'cofactor', - 'CL': 'cloned', - 'CR': 'crystallization', - 'EN': 'engineering', - 'EXP': 'expression', - 'GI': 'general information on enzyme', - 'GS': 'general stability', - 'IC50': 'IC-50 Value', - 'ID': 'EC-class', - 'IN': 'inhibitors', - 'KKM': 'Kcat/KM-Value substrate in {...}', - 'KI': 'Ki-value, inhibitor in {...}', - 'KM': 'KM-value, substrate in {...}', - 'LO': 'localization', - 'ME': 'metals/ions', - 'MW': 'molecular weight', - 'NSP': 'natural substrates/products reversibilty information in {...}', - 'OS': 'oxygen stability', - 'OSS': 'organic solvent stability', - 'PHO': 'pH-optimum', - 'PHR': 'pH-range', - 'PHS': 'pH stability', - 'PI': 'isoelectric point', - 'PM': 'posttranslation modification', - 'PR': 'protein', - 'PU': 'purification', - 'RE': 'reaction catalyzed', - 'RF': 'references', - 'REN': 'renatured', - 'RN': 'accepted name (IUPAC)', - 'RT': 'reaction type', - 'SA': 'specific activity', - 'SN': 'synonyms', - 'SP': 'substrates/products, reversibilty information in {...}', - 'SS': 'storage stability', - 'ST': 'source/tissue', - 'SU': 'subunits', - 'SY': 'systematic name', - 'TN': 'turnover number, substrate in {...}', - 'TO': 'temperature optimum', - 'TR': 'temperature range', - 'TS': 'temperature stability' + "AC": "activating compound", + "AP": "application", + "CF": "cofactor", + "CL": "cloned", + "CR": "crystallization", + "EN": "engineering", + "EXP": "expression", + "GI": "general information on enzyme", + "GS": "general stability", + "IC50": "IC-50 Value", + "ID": "EC-class", + "IN": "inhibitors", + "KKM": "Kcat/KM-Value substrate in {...}", + "KI": "Ki-value, inhibitor in {...}", + "KM": "KM-value, substrate in {...}", + "LO": "localization", + "ME": "metals/ions", + "MW": "molecular weight", + "NSP": "natural substrates/products reversibilty information in {...}", + "OS": "oxygen stability", + "OSS": "organic solvent stability", + "PHO": "pH-optimum", + "PHR": "pH-range", + "PHS": "pH stability", + "PI": "isoelectric point", + "PM": "posttranslation modification", + "PR": "protein", + "PU": "purification", + "RE": "reaction catalyzed", + "RF": "references", + "REN": "renatured", + "RN": "accepted name (IUPAC)", + "RT": "reaction type", + "SA": "specific activity", + "SN": "synonyms", + "SP": "substrates/products, reversibilty information in {...}", + "SS": "storage stability", + "ST": "source/tissue", + "SU": "subunits", + "SY": "systematic name", + "TN": "turnover number, substrate in {...}", + "TO": "temperature optimum", + "TR": "temperature range", + "TS": "temperature stability", } units = { - 'KM': 'mM', - 'KI': 'mM', - 'TN': '$s^{-1}$', - 'SA': '$µmol.min^{-1}.mg^{-1}$', - 'KKM': '$mM^{-1}.s^{-1}$', - 'TO': '${}^oC$', - 'TR': '${}^oC$', - 'TS': '${}^oC$', - 'MW': 'Da' -} \ No newline at end of file + "KM": "mM", + "KI": "mM", + "TN": "$s^{-1}$", + "SA": "$µmol.min^{-1}.mg^{-1}$", + "KKM": "$mM^{-1}.s^{-1}$", + "TO": "${}^oC$", + "TR": "${}^oC$", + "TS": "${}^oC$", + "MW": "Da", +} diff --git a/src/brendapyrser/parser.py b/src/brendapyrser/parser.py index 98c9e63..6190251 100755 --- a/src/brendapyrser/parser.py +++ b/src/brendapyrser/parser.py @@ -6,8 +6,9 @@ """ from __future__ import annotations -from importlib import metadata + import re +from importlib import metadata import numpy as np import pandas as pd @@ -23,16 +24,18 @@ class BRENDA: """ Provides methods to parse the BRENDA database (https://www.brenda-enzymes.org/) """ + def __init__(self, path_to_database): with open(path_to_database, encoding="iso-8859-1") as file: self.__data = file.read() - self.__ec_numbers = [ec.group(1) - for ec in re.finditer('(?<=ID\\t)(.*)(?=\\n)', self.__data)] + self.__ec_numbers = [ + ec.group(1) for ec in re.finditer("(?<=ID\\t)(.*)(?=\\n)", self.__data) + ] self.__reactions = self.__initializeReactionObjects() - self.__copyright = ("""Copyrighted by Dietmar Schomburg, Techn. University + self.__copyright = """Copyrighted by Dietmar Schomburg, Techn. University Braunschweig, GERMANY. Distributed under the License as stated - at http:/www.brenda-enzymes.org""") + at http:/www.brenda-enzymes.org""" self.__fields = fields self.__units = units @@ -50,14 +53,18 @@ def _repr_html_(self): Author{author} - """.format(n_ec=len(self.__reactions), - cr=self.__copyright, - parser=__version__, - author=__author__) + """.format( + n_ec=len(self.__reactions), + cr=self.__copyright, + parser=__version__, + author=__author__, + ) def __getRxnData(self): - rxn_data = [r.group(0) - for r in re.finditer('ID\\t(.+?)///', self.__data, flags=re.DOTALL)] + rxn_data = [ + r.group(0) + for r in re.finditer("ID\\t(.+?)///", self.__data, flags=re.DOTALL) + ] del self.__data return rxn_data @@ -86,9 +93,9 @@ def getOrganisms(self) -> list: """ species = set() for rxn in self.__reactions: - species.update([s['name'] for s in rxn.proteins.values()]) - species.remove('') - species = list(set([s for s in species if 'no activity' not in s])) + species.update([s["name"] for s in rxn.proteins.values()]) + species.remove("") + species = list(set([s for s in species if "no activity" not in s])) return species def getKMcompounds(self) -> list: @@ -99,7 +106,7 @@ def getKMcompounds(self) -> list: for rxn in self.__reactions: cpds.update([s for s in rxn.KMvalues.keys()]) try: - cpds.remove('') + cpds.remove("") except Exception: pass return list(cpds) @@ -123,13 +130,13 @@ def get_by_id(self, id: str): try: return [rxn for rxn in self if rxn.ec_number == id][0] except Exception: - raise ValueError(f'Enzyme with EC {id} not found in database') + raise ValueError(f"Enzyme with EC {id} not found in database") def get_by_name(self, name: str): try: return [rxn for rxn in self if rxn.name.lower() == name.lower()][0] except Exception: - raise ValueError(f'Enzyme {name} not found in database') + raise ValueError(f"Enzyme {name} not found in database") def filter_by_substrate(self, substrate: str) -> list[Reaction]: """ @@ -139,10 +146,9 @@ def filter_by_substrate(self, substrate: str) -> list[Reaction]: rxn for rxn in self if any( - [substrate in mets["substrates"] - for mets in rxn.substratesAndProducts] + [substrate in mets["substrates"] for mets in rxn.substratesAndProducts] ) - ] + ] def filter_by_product(self, product: str) -> list[Reaction]: """ @@ -151,11 +157,8 @@ def filter_by_product(self, product: str) -> list[Reaction]: return [ rxn for rxn in self - if any( - [product in mets["products"] - for mets in rxn.substratesAndProducts] - ) - ] + if any([product in mets["products"] for mets in rxn.substratesAndProducts]) + ] def filter_by_compound(self, compound: str) -> list[Reaction]: """ @@ -165,32 +168,39 @@ def filter_by_compound(self, compound: str) -> list[Reaction]: rxn for rxn in self if any( - [(compound in mets["substrates"] - or compound in mets["products"]) - for mets in rxn.substratesAndProducts] + [ + (compound in mets["substrates"] or compound in mets["products"]) + for mets in rxn.substratesAndProducts + ] ) - ] + ] def filter_by_organism(self, species: str): - def is_contained(p, S): return any([p in s.lower() for s in S]) + def is_contained(p, S): + return any([p in s.lower() for s in S]) + return self.__class__( - [rxn for rxn in self if is_contained(species.lower(), rxn.organisms)] - ) - + [rxn for rxn in self if is_contained(species.lower(), rxn.organisms)] + ) class EnzymeDict(dict): def filter_by_organism(self, species: str): filtered_dict = {} - def is_contained(p, S): return any([p in s for s in S]) + + def is_contained(p, S): + return any([p in s for s in S]) + for k in self.keys(): - filtered_values = [v for v in self[k] if is_contained(species, v['species'])] + filtered_values = [ + v for v in self[k] if is_contained(species, v["species"]) + ] if len(filtered_values) > 0: filtered_dict[k] = filtered_values return self.__class__(filtered_dict) def get_values(self): - return [v['value'] for k in self.keys() for v in self[k]] + return [v["value"] for k in self.keys() for v in self[k]] class EnzymePropertyDict(EnzymeDict): @@ -206,21 +216,26 @@ def filter_by_condition(self, condition: str): try: return self.__class__({condition: self[condition]}) except Exception: - raise KeyError(f'Invalid condition, valid conditions are: {", ".join(list(self.keys()))}') + raise KeyError( + f'Invalid condition, valid conditions are: {", ".join(list(self.keys()))}' + ) class Reaction: def __init__(self, reaction_data): self.__reaction_data = reaction_data - self.__ec_number = self.__extractRegexPattern('(?<=ID\t)(.*)(?=\n)') - self.__systematic_name = self.__extractRegexPattern('(?<=SN\t)(.*)(?=\n)') - self.__name = self.__extractRegexPattern('(?<=RN\t)(.*)(?=\n)').capitalize() - self.__mechanism_str = (self.__extractRegexPattern('(?<=RE\t)(.*)(?=\n[A-Z])', - dotall=True).replace('=', '<=>') - .replace('\n\t', '').split('\nRE\t')) + self.__ec_number = self.__extractRegexPattern("(?<=ID\t)(.*)(?=\n)") + self.__systematic_name = self.__extractRegexPattern("(?<=SN\t)(.*)(?=\n)") + self.__name = self.__extractRegexPattern("(?<=RN\t)(.*)(?=\n)").capitalize() + self.__mechanism_str = ( + self.__extractRegexPattern("(?<=RE\t)(.*)(?=\n[A-Z])", dotall=True) + .replace("=", "<=>") + .replace("\n\t", "") + .split("\nRE\t") + ) self.__reaction_type = self.__extractRegexPattern( - '(?<=RT\t)(.*)(?=\n)', dotall=True - ).split('\nRT\t') + "(?<=RT\t)(.*)(?=\n)", dotall=True + ).split("\nRT\t") self.__proteins = self.getSpeciesDict() self.__references = self.getReferencesDict() @@ -229,13 +244,15 @@ def getSpeciesDict(self) -> dict: Returns a dict listing all proteins for given EC number """ species = {} - lines = self.__getDataLines('PR') + lines = self.__getDataLines("PR") for line in lines: res = self.extractDataLineInfo(line) - species_name, protein_ID = self.__splitSpeciesFromProteinID(res['value']) - species[res['species'][0]] = {'name': species_name, - 'proteinID': protein_ID, - 'refs': res['refs']} + species_name, protein_ID = self.__splitSpeciesFromProteinID(res["value"]) + species[res["species"][0]] = { + "name": species_name, + "proteinID": protein_ID, + "refs": res["refs"], + } return species def getReferencesDict(self): @@ -243,20 +260,22 @@ def getReferencesDict(self): Returns a dict listing the bibliography cited for the given EC number """ references = {} - lines = self.__getDataLines('RF') + lines = self.__getDataLines("RF") for line in lines: line = self.__removeTabs(line) - line, refs = self.__extractDataField(line, ('<', '>')) + line, refs = self.__extractDataField(line, ("<", ">")) references[refs[0]] = line return references def printReactionSummary(self): - data = {'EC number': self.__ec_number, - 'Name': self.__name, - 'Systematic name': self.__systematic_name, - 'Reaction type': self.__reaction_type, - 'Mechanism': self.__mechanism} - return pd.DataFrame.from_dict(data, orient='index', columns=['']) + data = { + "EC number": self.__ec_number, + "Name": self.__name, + "Systematic name": self.__systematic_name, + "Reaction type": self.__reaction_type, + "Mechanism": self.__mechanism, + } + return pd.DataFrame.from_dict(data, orient="index", columns=[""]) def _repr_html_(self): """This method is executed automatically by Jupyter to print html!""" @@ -274,11 +293,13 @@ def _repr_html_(self): Reaction{rxn_str} - """.format(ec=self.__ec_number, - name=self.__name, - sys_name=self.__systematic_name, - rxn_type=self.__reaction_type, - rxn_str=self.reaction_str) + """.format( + ec=self.__ec_number, + name=self.__name, + sys_name=self.__systematic_name, + rxn_type=self.__reaction_type, + rxn_str=self.reaction_str, + ) def __extractRegexPattern(self, pattern, dotall=False): if dotall: @@ -288,50 +309,53 @@ def __extractRegexPattern(self, pattern, dotall=False): try: return re.search(pattern, self.__reaction_data, flags=flag).group(1) except Exception: - return '' + return "" def __getDataLines(self, pattern: str): try: - search_pattern = f'{pattern}\t(.+?)\n(?!\t)' - return [p.group(1) - for p in re.finditer( - search_pattern, self.__reaction_data, flags=re.DOTALL)] + search_pattern = f"{pattern}\t(.+?)\n(?!\t)" + return [ + p.group(1) + for p in re.finditer( + search_pattern, self.__reaction_data, flags=re.DOTALL + ) + ] except Exception: return [] @staticmethod def __removeTabs(line): - return line.replace('\n', '').replace('\t', '').strip() + return line.replace("\n", "").replace("\t", "").strip() @staticmethod def __extractDataField(line, regex_tags: tuple): try: l, r = regex_tags - searched_s = re.search(f'{l}(.+?){r}', line) + searched_s = re.search(f"{l}(.+?){r}", line) span = searched_s.span() - matched_s = line[span[0] + 1:span[1] - 1].strip() - line = line.replace(f'{searched_s.group()}', '') + matched_s = line[span[0] + 1 : span[1] - 1].strip() + line = line.replace(f"{searched_s.group()}", "") return (line, matched_s) except Exception: - return (line, '') + return (line, "") @staticmethod def __eval_range_value(v): try: - if not re.search('\d-\d', v): + if not re.search("\d-\d", v): return float(v) else: - return np.mean([float(s) for s in v.split('-')]) + return np.mean([float(s) for s in v.split("-")]) except Exception: return -999 @staticmethod def __splitSpeciesFromProteinID(line): try: - idx = re.search('[A-Z]{1}[0-9]{1}', line).start() + idx = re.search("[A-Z]{1}[0-9]{1}", line).start() return (line[:idx].strip(), line[idx:].strip()) except Exception: - return (line.strip(), '') + return (line.strip(), "") def extractDataLineInfo(self, line: str, numeric_value=False): """ @@ -340,32 +364,40 @@ def extractDataLineInfo(self, line: str, numeric_value=False): is the value of that particular data field, e.g., KM value. """ line = self.__removeTabs(line) - line, specific_info = self.__extractDataField(line, ('{', '.*}')) - line, meta = self.__extractDataField(line, ('\(', '.*\)')) - line, refs = self.__extractDataField(line, ('<', '>')) - line, species = self.__extractDataField(line, ('#', '#')) + line, specific_info = self.__extractDataField(line, ("{", ".*}")) + line, meta = self.__extractDataField(line, ("\(", ".*\)")) + line, refs = self.__extractDataField(line, ("<", ">")) + line, species = self.__extractDataField(line, ("#", "#")) if numeric_value: value = self.__eval_range_value(line.strip()) else: value = line.strip() - return {'value': value, 'species': species.split(','), - 'meta': meta, 'refs': refs.split(','), - 'specific_info': specific_info} + return { + "value": value, + "species": species.split(","), + "meta": meta, + "refs": refs.split(","), + "specific_info": specific_info, + } def __extractReactionMechanismInfo(self, line: str): """ Extracts reaction string and mechanism info """ line = self.__removeTabs(line) - line, meta = self.__extractDataField(line, ('\(', '.*\)')) + line, meta = self.__extractDataField(line, ("\(", ".*\)")) rxn_str = line.strip() meta_list = [] - for meta_line in meta.split(';'): - meta_line, refs = self.__extractDataField(meta_line, ('<', '>')) - meta_line, species = self.__extractDataField(meta_line, ('#', '#')) - meta_list.append({'species': species.split(','), - 'refs': refs.split(','), - 'meta': meta_line.strip()}) + for meta_line in meta.split(";"): + meta_line, refs = self.__extractDataField(meta_line, ("<", ">")) + meta_line, species = self.__extractDataField(meta_line, ("#", "#")) + meta_list.append( + { + "species": species.split(","), + "refs": refs.split(","), + "meta": meta_line.strip(), + } + ) return (rxn_str, meta_list) def __getBinomialNames(self, species_list: list) -> list: @@ -374,8 +406,15 @@ def __getBinomialNames(self, species_list: list) -> list: employed by BRENDA to attach species to protein entries """ species_dict = self.__proteins - return list(set([species_dict[s]['name'] for s in species_list - if s in species_dict.keys()])) + return list( + set( + [ + species_dict[s]["name"] + for s in species_list + if s in species_dict.keys() + ] + ) + ) def __getFullReferences(self, refs_list: list) -> list: """ @@ -390,11 +429,13 @@ def __getDictOfEnzymeActuators(self, pattern: str) -> dict: lines = self.__getDataLines(pattern) for line in lines: data = self.extractDataLineInfo(line) - if data['value'] != 'more': - res[data['value']] = {'species': self.__getBinomialNames(data['species']), - 'meta': data['meta'], - #'refs': data['refs']} - 'refs': self.__getFullReferences(data['refs'])} + if data["value"] != "more": + res[data["value"]] = { + "species": self.__getBinomialNames(data["species"]), + "meta": data["meta"], + #'refs': data['refs']} + "refs": self.__getFullReferences(data["refs"]), + } return EnzymePropertyDict(res) def __getDictOfEnzymeProperties(self, pattern: str) -> dict: @@ -402,35 +443,44 @@ def __getDictOfEnzymeProperties(self, pattern: str) -> dict: lines = self.__getDataLines(pattern) for line in lines: data = self.extractDataLineInfo(line, numeric_value=True) - substrate = data['specific_info'] - if substrate != 'more': + substrate = data["specific_info"] + if substrate != "more": if substrate not in res.keys(): res[substrate] = [] - res[substrate].append({'value': data['value'], - 'species': self.__getBinomialNames(data['species']), - 'meta': data['meta'], - #'refs': data['refs']}) - 'refs': self.__getFullReferences(data['refs'])}) + res[substrate].append( + { + "value": data["value"], + "species": self.__getBinomialNames(data["species"]), + "meta": data["meta"], + #'refs': data['refs']}) + "refs": self.__getFullReferences(data["refs"]), + } + ) return EnzymePropertyDict(res) def __extractTempOrPHData(self, data_type: str) -> list: values = [] lines = self.__getDataLines(data_type) - if 'R' not in data_type: + if "R" not in data_type: eval_value = self.__eval_range_value else: + def eval_value(v): try: - return [float(s) for s in v.split('-')] + return [float(s) for s in v.split("-")] except Exception: return [-999, -999] for line in lines: data = self.extractDataLineInfo(line) - values.append({'value': eval_value(data['value']), - 'species': self.__getBinomialNames(data['species']), - 'meta': data['meta'], - 'refs': data['refs']}) + values.append( + { + "value": eval_value(data["value"]), + "species": self.__getBinomialNames(data["species"]), + "meta": data["meta"], + "refs": data["refs"], + } + ) return values @property @@ -464,39 +514,39 @@ def reaction_type(self) -> list[str]: @property def cofactors(self): - return self.__getDictOfEnzymeActuators('CF') + return self.__getDictOfEnzymeActuators("CF") @property def metals(self): - return self.__getDictOfEnzymeActuators('ME') + return self.__getDictOfEnzymeActuators("ME") @property def inhibitors(self): - return self.__getDictOfEnzymeActuators('IN') + return self.__getDictOfEnzymeActuators("IN") @property def activators(self): - return self.__getDictOfEnzymeActuators('AC') + return self.__getDictOfEnzymeActuators("AC") @property def KMvalues(self): - return self.__getDictOfEnzymeProperties('KM') + return self.__getDictOfEnzymeProperties("KM") @property def KIvalues(self): - return self.__getDictOfEnzymeProperties('KI') + return self.__getDictOfEnzymeProperties("KI") @property def KKMvalues(self): - return self.__getDictOfEnzymeProperties('KKM') + return self.__getDictOfEnzymeProperties("KKM") @property def Kcatvalues(self): - return self.__getDictOfEnzymeProperties('TN') + return self.__getDictOfEnzymeProperties("TN") @property def specificActivities(self): - lines = self.__getDataLines('SA') + lines = self.__getDataLines("SA") return [self.extractDataLineInfo(line, numeric_value=True) for line in lines] @property @@ -506,36 +556,49 @@ def substratesAndProducts(self) -> list: of the enzyme across organisms. """ substrates, products, res = [], [], [] - lines = self.__getDataLines('NSP') + lines = self.__getDataLines("NSP") for line in lines: data = self.extractDataLineInfo(line) - rxn = data['value'].replace( - '{}', '').replace('?', '').replace('more', '').strip() + rxn = ( + data["value"] + .replace("{}", "") + .replace("?", "") + .replace("more", "") + .strip() + ) try: - subs, prods = rxn.split('=') - subs = [s.strip() for s in subs.split('+') if s.strip() != ''] - prods = [s.strip() for s in prods.split('+') if s.strip() != ''] + subs, prods = rxn.split("=") + subs = [s.strip() for s in subs.split("+") if s.strip() != ""] + prods = [s.strip() for s in prods.split("+") if s.strip() != ""] subs.sort() prods.sort() - if (subs not in substrates and len(subs) > 0 and len(prods) > 0): + if subs not in substrates and len(subs) > 0 and len(prods) > 0: substrates.append(subs) products.append(prods) - res.append({'substrates': subs, 'products': prods}) + res.append({"substrates": subs, "products": prods}) except Exception: pass return res @property def temperature(self): - return EnzymeConditionDict({'optimum': self.__extractTempOrPHData('TO'), - 'range': self.__extractTempOrPHData('TR'), - 'stability': self.__extractTempOrPHData('TS')}) + return EnzymeConditionDict( + { + "optimum": self.__extractTempOrPHData("TO"), + "range": self.__extractTempOrPHData("TR"), + "stability": self.__extractTempOrPHData("TS"), + } + ) @property def PH(self): - return EnzymeConditionDict({'optimum': self.__extractTempOrPHData('PHO'), - 'range': self.__extractTempOrPHData('PHR'), - 'stability': self.__extractTempOrPHData('PHS')}) + return EnzymeConditionDict( + { + "optimum": self.__extractTempOrPHData("PHO"), + "range": self.__extractTempOrPHData("PHR"), + "stability": self.__extractTempOrPHData("PHS"), + } + ) @property def proteins(self) -> dict: @@ -546,7 +609,7 @@ def organisms(self) -> list: """ Returns a list containing all represented species in the database for this reaction """ - organisms = list(set([s['name'] for s in self.proteins.values()])) + organisms = list(set([s["name"] for s in self.proteins.values()])) organisms.sort() return organisms diff --git a/tests/tests.py b/tests/tests.py index 985338f..70022dc 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -6,8 +6,8 @@ """ import unittest -from brendapyrser import Reaction, ReactionList +from brendapyrser import Reaction rxn_data = """ID 1.1.1.304 ******************************************************************************** @@ -405,55 +405,61 @@ dehydrogenase/reductases <7>) <7>""" - class TestReaction(unittest.TestCase): def test_ec_number(self): rxn = Reaction(rxn_data) self.assertEqual( - rxn.ec_number, "1.1.1.304", - "Failed to correctly retrieve EC number" - ) + rxn.ec_number, "1.1.1.304", "Failed to correctly retrieve EC number" + ) + def test_name(self): rxn = Reaction(rxn_data) self.assertEqual( - rxn.name, "Diacetyl reductase [(s)-acetoin forming]", - "Failed to correctly retrieve reaction name" - ) + rxn.name, + "Diacetyl reductase [(s)-acetoin forming]", + "Failed to correctly retrieve reaction name", + ) + def test_sysname(self): rxn = Reaction(rxn_data) self.assertEqual( - rxn.systematic_name, "(S)-acetoin:NAD+ oxidoreductase", - "Failed to correctly retrieve systematic reaction name" - ) + rxn.systematic_name, + "(S)-acetoin:NAD+ oxidoreductase", + "Failed to correctly retrieve systematic reaction name", + ) + def test_KMvalues(self): rxn = Reaction(rxn_data) self.assertEqual( - rxn.KMvalues.get_values()[:4], [0.045, 0.095, 0.025, 0.11], - "Failed to correctly retrieve KM values" - ) + rxn.KMvalues.get_values()[:4], + [0.045, 0.095, 0.025, 0.11], + "Failed to correctly retrieve KM values", + ) + def test_KKMvalues(self): rxn = Reaction(rxn_data) self.assertEqual( - rxn.KKMvalues.get_values()[:4], [16.9, 36.4, 81.5, 432.0], - "Failed to correctly retrieve KKM values" - ) + rxn.KKMvalues.get_values()[:4], + [16.9, 36.4, 81.5, 432.0], + "Failed to correctly retrieve KKM values", + ) + def test_Kcatvalues(self): rxn = Reaction(rxn_data) self.assertEqual( - rxn.Kcatvalues.get_values()[:4], [748.0, 202.0, 591.0, 1222.0], - "Failed to correctly retrieve Kcat values" - ) + rxn.Kcatvalues.get_values()[:4], + [748.0, 202.0, 591.0, 1222.0], + "Failed to correctly retrieve Kcat values", + ) + def test_temperature(self): rxn = Reaction(rxn_data) self.assertEqual( - rxn.temperature["optimum"][0]["value"], 50.0, - "Failed to correctly retrieve temperature values" - ) - - + rxn.temperature["optimum"][0]["value"], + 50.0, + "Failed to correctly retrieve temperature values", + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() - - From 7b89911935368ac7bf35bdafc9b956affc2865ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Semid=C3=A1n=20Robaina=20Est=C3=A9vez?= Date: Mon, 11 Sep 2023 13:37:50 +0100 Subject: [PATCH 06/10] black --- envs/brendapyrser-dev.yml | 1 + examples/examples.ipynb | 100 ++++++++++++++++++++++--------------- src/brendapyrser/parser.py | 1 - 3 files changed, 61 insertions(+), 41 deletions(-) diff --git a/envs/brendapyrser-dev.yml b/envs/brendapyrser-dev.yml index cb33277..0af8502 100755 --- a/envs/brendapyrser-dev.yml +++ b/envs/brendapyrser-dev.yml @@ -15,5 +15,6 @@ dependencies: - mkdocstrings[python] - ruff - black + - black[jupyter] - argmark - coverage diff --git a/examples/examples.ipynb b/examples/examples.ipynb index 929351a..28bc36b 100755 --- a/examples/examples.ipynb +++ b/examples/examples.ipynb @@ -22,7 +22,7 @@ "from brendapyrser import BRENDA\n", "\n", "\n", - "dataFile = 'data/brenda_download.txt'" + "dataFile = \"data/brenda_download.txt\"" ] }, { @@ -98,14 +98,13 @@ ], "source": [ "# Plot all Km values in the database\n", - "BRENDA_KMs = np.array([v for r in brenda.reactions \n", - " for v in r.KMvalues.get_values()])\n", + "BRENDA_KMs = np.array([v for r in brenda.reactions for v in r.KMvalues.get_values()])\n", "values = BRENDA_KMs[(BRENDA_KMs < 1000) & (BRENDA_KMs >= 0)]\n", "plt.hist(values)\n", - "plt.title(f'Median KM value: {np.median(values)}')\n", - "plt.xlabel('KM (mM)')\n", + "plt.title(f\"Median KM value: {np.median(values)}\")\n", + "plt.xlabel(\"KM (mM)\")\n", "plt.show()\n", - "print(f'Minimum and maximum values in database: {values.min()} mM, {values.max()} mM')" + "print(f\"Minimum and maximum values in database: {values.min()} mM, {values.max()} mM\")" ] }, { @@ -135,14 +134,15 @@ ], "source": [ "# Plot all Km values in the database\n", - "BRENDA_Kcats = np.array([v for r in brenda.reactions \n", - " for v in r.Kcatvalues.get_values()])\n", + "BRENDA_Kcats = np.array(\n", + " [v for r in brenda.reactions for v in r.Kcatvalues.get_values()]\n", + ")\n", "values = BRENDA_Kcats[(BRENDA_Kcats < 1000) & (BRENDA_Kcats >= 0)]\n", "plt.hist(values)\n", - "plt.title(f'Median Kcat value: {np.median(values)}')\n", - "plt.xlabel('Kcat (1/s)')\n", + "plt.title(f\"Median Kcat value: {np.median(values)}\")\n", + "plt.xlabel(\"Kcat (1/s)\")\n", "plt.show()\n", - "print(f'Minimum and maximum values in database: {values.min()} 1/s, {values.max()} 1/s')" + "print(f\"Minimum and maximum values in database: {values.min()} 1/s, {values.max()} 1/s\")" ] }, { @@ -172,15 +172,19 @@ ], "source": [ "# Plot all enzyme optimal temperature values in the database\n", - "BRENDA_TO = np.array([v for r in brenda.reactions \n", - " for v in r.temperature.filter_by_condition(\n", - " 'optimum').get_values()])\n", + "BRENDA_TO = np.array(\n", + " [\n", + " v\n", + " for r in brenda.reactions\n", + " for v in r.temperature.filter_by_condition(\"optimum\").get_values()\n", + " ]\n", + ")\n", "values = BRENDA_TO[(BRENDA_TO >= 0)]\n", "plt.hist(values)\n", - "plt.title(f'Median Optimum Temperature: {np.median(values)}')\n", - "plt.xlabel('TO (${}^oC$)')\n", + "plt.title(f\"Median Optimum Temperature: {np.median(values)}\")\n", + "plt.xlabel(\"TO (${}^oC$)\")\n", "plt.show()\n", - "print(f'Minimum and maximum values in database: {values.min()} °C, {values.max()} °C')" + "print(f\"Minimum and maximum values in database: {values.min()} °C, {values.max()} °C\")" ] }, { @@ -219,15 +223,22 @@ ], "source": [ "# Plot all enzyme optimal temperature values in the database\n", - "species = 'Thermotoga'\n", - "BRENDA_TO = np.array([v for r in brenda.reactions.filter_by_organism(species)\n", - " for v in r.temperature.filter_by_condition('optimum').filter_by_organism(species).get_values()])\n", + "species = \"Thermotoga\"\n", + "BRENDA_TO = np.array(\n", + " [\n", + " v\n", + " for r in brenda.reactions.filter_by_organism(species)\n", + " for v in r.temperature.filter_by_condition(\"optimum\")\n", + " .filter_by_organism(species)\n", + " .get_values()\n", + " ]\n", + ")\n", "values = BRENDA_TO[(BRENDA_TO >= 0)]\n", "plt.hist(values)\n", - "plt.title(f'Median Optimum Temperature: {np.median(values)}')\n", - "plt.xlabel('TO (${}^oC$)')\n", + "plt.title(f\"Median Optimum Temperature: {np.median(values)}\")\n", + "plt.xlabel(\"TO (${}^oC$)\")\n", "plt.show()\n", - "print(f'Minimum and maximum values in database: {values.min()} °C, {values.max()} °C')" + "print(f\"Minimum and maximum values in database: {values.min()} °C, {values.max()} °C\")" ] }, { @@ -279,7 +290,7 @@ ], "source": [ "# We can retrieve an enzyme entry by its EC number like this\n", - "r = brenda.reactions.get_by_id('2.7.1.40')\n", + "r = brenda.reactions.get_by_id(\"2.7.1.40\")\n", "r" ] }, @@ -303,11 +314,11 @@ ], "source": [ "# Here are all the KM values for phosphoenolpyruvate associated with this enzyme class\n", - "compound = 'phosphoenolpyruvate'\n", + "compound = \"phosphoenolpyruvate\"\n", "kms = r.KMvalues.filter_by_compound(compound).get_values()\n", "plt.hist(kms)\n", - "plt.xlabel('KM (mM)')\n", - "plt.title(f'{r.name} ({compound})')\n", + "plt.xlabel(\"KM (mM)\")\n", + "plt.title(f\"{r.name} ({compound})\")\n", "plt.show()" ] }, @@ -331,11 +342,11 @@ ], "source": [ "# Here are all the KM values for phosphoenolpyruvate associated with this enzyme class\n", - "compound = 'phosphoenolpyruvate'\n", + "compound = \"phosphoenolpyruvate\"\n", "KMs = r.KMvalues.filter_by_compound(compound).get_values()\n", "plt.hist(KMs)\n", - "plt.xlabel('KM (mM)')\n", - "plt.title(f'{r.name} ({compound})')\n", + "plt.xlabel(\"KM (mM)\")\n", + "plt.title(f\"{r.name} ({compound})\")\n", "plt.show()" ] }, @@ -357,7 +368,9 @@ ], "source": [ "# And further filtered by organism\n", - "r.KMvalues.filter_by_organism('Bos taurus').filter_by_compound('phosphoenolpyruvate').get_values()" + "r.KMvalues.filter_by_organism(\"Bos taurus\").filter_by_compound(\n", + " \"phosphoenolpyruvate\"\n", + ").get_values()" ] }, { @@ -380,11 +393,11 @@ ], "source": [ "# Here are all the Kcat values for phosphoenolpyruvate associated with this enzyme class\n", - "compound = 'phosphoenolpyruvate'\n", + "compound = \"phosphoenolpyruvate\"\n", "kcats = r.Kcatvalues.filter_by_compound(compound).get_values()\n", "plt.hist(kcats)\n", - "plt.xlabel('Kcat ($s^{-1}$)')\n", - "plt.title(f'{r.name} ({compound})')\n", + "plt.xlabel(\"Kcat ($s^{-1}$)\")\n", + "plt.title(f\"{r.name} ({compound})\")\n", "plt.show()" ] }, @@ -415,17 +428,24 @@ } ], "source": [ - "species, compound = 'Escherichia coli', 'NADH'\n", - "KMs = np.array([v for r in brenda.reactions.filter_by_organism(species)\n", - " for v in r.KMvalues.filter_by_compound(compound).filter_by_organism(species).get_values()])\n", + "species, compound = \"Escherichia coli\", \"NADH\"\n", + "KMs = np.array(\n", + " [\n", + " v\n", + " for r in brenda.reactions.filter_by_organism(species)\n", + " for v in r.KMvalues.filter_by_compound(compound)\n", + " .filter_by_organism(species)\n", + " .get_values()\n", + " ]\n", + ")\n", "\n", "if len(KMs) > 0:\n", " plt.hist(KMs)\n", - " plt.xlabel('KM (mM)')\n", - " plt.title(f'{species} KMs ({compound}), median = {np.median((KMs))}')\n", + " plt.xlabel(\"KM (mM)\")\n", + " plt.title(f\"{species} KMs ({compound}), median = {np.median((KMs))}\")\n", " plt.show()\n", "else:\n", - " print('No KM values for compound')" + " print(\"No KM values for compound\")" ] }, { diff --git a/src/brendapyrser/parser.py b/src/brendapyrser/parser.py index 6190251..f2ec5e9 100755 --- a/src/brendapyrser/parser.py +++ b/src/brendapyrser/parser.py @@ -26,7 +26,6 @@ class BRENDA: """ def __init__(self, path_to_database): - with open(path_to_database, encoding="iso-8859-1") as file: self.__data = file.read() self.__ec_numbers = [ From 149c6c113d7378d593758c43f7f651028ba82b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Semid=C3=A1n=20Robaina=20Est=C3=A9vez?= Date: Mon, 11 Sep 2023 13:42:44 +0100 Subject: [PATCH 07/10] tests --- src/brendapyrser/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/brendapyrser/__init__.py b/src/brendapyrser/__init__.py index e69de29..19870d8 100755 --- a/src/brendapyrser/__init__.py +++ b/src/brendapyrser/__init__.py @@ -0,0 +1 @@ +from .parser import Reaction, BRENDA, ReactionList From dc75ccb3a341f18c7068c2fd14e6b68bf6b81c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Semid=C3=A1n=20Robaina=20Est=C3=A9vez?= Date: Mon, 11 Sep 2023 14:00:58 +0100 Subject: [PATCH 08/10] docs --- README.md | 2 +- {examples => docs}/examples.ipynb | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {examples => docs}/examples.ipynb (100%) diff --git a/README.md b/README.md index aa28226..49a138a 100755 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ or Due to BRENDA's license, BRENDA's database cannot be downloaded directly by the parser, instead, the user is asked to download the database as a text file after accepting usage conditions [here](https://www.brenda-enzymes.org/download.php). -You can find a jupyter notebook with usage examples [here](examples/examples.ipynb). +You can find a jupyter notebook with usage examples [here](docs/examples.ipynb). ## Contribute diff --git a/examples/examples.ipynb b/docs/examples.ipynb similarity index 100% rename from examples/examples.ipynb rename to docs/examples.ipynb From 4aff804e8a8ae095c81500e2d1b93fb229147516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Semid=C3=A1n=20Robaina=20Est=C3=A9vez?= Date: Mon, 11 Sep 2023 14:02:55 +0100 Subject: [PATCH 09/10] refactor --- README_files/output_13_0.png | Bin 7388 -> 0 bytes README_files/output_14_0.png | Bin 7388 -> 0 bytes README_files/output_16_0.png | Bin 7123 -> 0 bytes README_files/output_18_0.png | Bin 7142 -> 0 bytes README_files/output_5_0.png | Bin 7361 -> 0 bytes README_files/output_6_0.png | Bin 7112 -> 0 bytes README_files/output_7_0.png | Bin 7800 -> 0 bytes README_files/output_9_0.png | Bin 6380 -> 0 bytes 8 files changed, 0 insertions(+), 0 deletions(-) delete mode 100755 README_files/output_13_0.png delete mode 100755 README_files/output_14_0.png delete mode 100755 README_files/output_16_0.png delete mode 100755 README_files/output_18_0.png delete mode 100755 README_files/output_5_0.png delete mode 100755 README_files/output_6_0.png delete mode 100755 README_files/output_7_0.png delete mode 100755 README_files/output_9_0.png diff --git a/README_files/output_13_0.png b/README_files/output_13_0.png deleted file mode 100755 index a35ddaa5dbf9a6c004f6dd013b581e9c44e42864..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7388 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 H`}O|-29^hG diff --git a/README_files/output_14_0.png b/README_files/output_14_0.png deleted file mode 100755 index a35ddaa5dbf9a6c004f6dd013b581e9c44e42864..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7388 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 H`}O|-29^hG diff --git a/README_files/output_16_0.png b/README_files/output_16_0.png deleted file mode 100755 index 82ed58857d2e2c3f08f898b57972db045bbccc98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7123 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 L3)pfak01UEVQaAM diff --git a/README_files/output_18_0.png b/README_files/output_18_0.png deleted file mode 100755 index a8c6dfc8ddeba220a9f82f849c978af4adb17312..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7142 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 i^S7h?-=_|%d(aHz*1VI)P0Blkfex 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 diff --git a/README_files/output_7_0.png b/README_files/output_7_0.png deleted file mode 100755 index 13b95e73c0fd058e70a98773276ea6dddacdffd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7800 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

v~UURxE^z?Icpmkj+x`WJ+` zMUPZen$LmWgU;n1`xg&5zyR$)z`wHt+SfO3@4ooKIsK~^soC81f5gClQPjUD#$U%9 zbK`=wHFt{s3DbW`kik!n-Req@MdF+^RJNE`lg+WFDwkRya;yh#G`;=IV2e%Mm&lkhy|5&n4U!gKd1_eYB#d^dyD{&MOi6IN~z zj|0d#q`9e5Zy7?YrNWL5=9y7hybC_?S?sT+MQixT9cc+WDa4ay# z2vqQQRj_1^h08x8c6!fCouAv&3oZAtl#!!`{0Owpw($;h2$B!Z&wq1kPx*mNzm!}? z)#zCqBPW{Eo(%RXuK0Mc($ItG)&nH)_Y~Ok_D6L9FNWgDAU1+Fc^_hVT3X8X(Kw-(;>DQF6u)v z=pxelELzD4=y}*+iBV0bq(f#JVg_xJoD~`(oM}$m*Qe!CPXOxyqy0;tqQp=4cv0TP zh>3ql{BUWG^{YrRmV1QYjOff05YXOjtLmH=oDA8?>pi#WL+Z{*lL7QC(gi*Coy}(y7iDL}JG$&=jy=+m#$}p{pLri3(bot=@wo`beao?X_X@d$z#Z?1YEHwwrwAKzOCk~dMJ8Y{`FAT zNh;?qqWh1j(=mKk&5{*lt7L3v{TL9T@c9?x4z2F0S6y5MUA|Jo6vx<=VcL`euGq=W zref=5R^2&VqbCLP8Of&1!Ha%k`7hQy2Xd)TO1+jR8|B!Qyc3wrG!1|lW4ICBw3_ry zYQ{7uK!FwvP0=Oxyih+{Ur@SMx)o>o=*P@3mI5giW=MEnc71=VB=qQH!LOkX#nS2j z){@#gu(z9t4B@ZzT3_MpANJPzI7c~(VAT}Hs&s#)0uV1=8oat_g|DM(^>F>BtuJ04 zyyny*>Dpl17WU-i)}}kxG~xydEwdXsW*z8*HU~o1R3YODXu_fDLTNllJ%(^g49&k` z7b@L=?{;giePYe57QFxD%bWUo(!`&+c!cM~69b8Qqpvs9$EyK&^dj6Z^JO?-lO=Rz z5VKn)kSO$NtuELBwFDY;2Ei)>9Dwfu5m+mbJ2&kVzSuW-g|$=&Q}_4Y>txnLa2T(O&rSQs6(XHAK=Ymnn?Pdb#=PKO(-AP$Zz_$aKsjn>|te%`N zLgc~HWa9nmX=k!o+$oJCXxgQ@nk0L^7E;neHFvkGz4lyjytF&DjHC6!_|wvfM>faw zMhvd$v>g#Ea=@4jvS`E><`A9|aG^6=f$g(Iy{1k0#G)s}HH)QSIKK;srmt!bsqXPL zoI&$CqnB`U>0zUilTLXu zn4PCQyC}o%T_@b0i=adgRuL8i7@L-F=%(3ZQp$18V@KnG0Knk-YV>rWywT^u1 z)cnh;T)NwW=i~hg(9AbM-i&zBb;jv{8mXkM5pdEObM$cxI96a<)rOo{ z^)sy5Qs$nNv-B^o?}0--Mz212CR5eciS_tyqflRxGVOxyfE9EwCWtN^E_?ZYaJ6cR_( Uthea}Pk|xmeV#6N_C}rkFTncbP5=M^ diff --git a/README_files/output_9_0.png b/README_files/output_9_0.png deleted file mode 100755 index e9dac95c917d3ed409371c10e199eae916fd13ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6380 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 From a6be0bc186b0a984592828a08dd6010fe9bcddf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Semid=C3=A1n=20Robaina=20Est=C3=A9vez?= Date: Mon, 11 Sep 2023 14:13:06 +0100 Subject: [PATCH 10/10] readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 49a138a..e012940 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ ![logo](assets/logo.png) ## a Python package to parse and manipulate the BRENDA database +[![tests](https://github.com/Robaina/BRENDApyrser/actions/workflows/tests.yml/badge.svg)](https://github.com/Robaina/BRENDApyrser/actions/workflows/tests.yml) ![PyPI](https://img.shields.io/pypi/v/brendapyrser) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/Robaina/Brendapyrser) [![GitHub license](https://img.shields.io/github/license/Robaina/BRENDApyrser)](https://github.com/Robaina/BRENDApyrser/blob/master/LICENSE)