From 32aa121ef4dcb6b9b9e63aa79d08ad174d5aad22 Mon Sep 17 00:00:00 2001 From: ibludau Date: Thu, 21 Oct 2021 14:49:08 +0200 Subject: [PATCH] Initializing structuremap from AlphaTemplate --- .github/ISSUE_TEMPLATE/bug-report.md | 44 ++++ .../workflows/github_action_test_dummy.yml | 12 ++ .github/workflows/pip_installation.yml | 60 ++++++ .github/workflows/publish_and_release.yml | 195 +++++++++++++++++ .gitignore | 134 ++++++++++++ HISTORY.md | 5 + LICENSE.txt | 201 ++++++++++++++++++ MANIFEST.in | 4 + README.md | 170 +++++++++++++++ misc/CLA.md | 66 ++++++ misc/bumpversion.cfg | 31 +++ misc/check_version.sh | 12 ++ misc/loose_pip_install.sh | 5 + misc/stable_pip_install.sh | 5 + nbs/tutorial.ipynb | 43 ++++ release/logos/alpha_logo.icns | Bin 0 -> 85999 bytes release/logos/alpha_logo.ico | Bin 0 -> 8843 bytes release/logos/alpha_logo.png | Bin 0 -> 5182 bytes release/one_click_linux_gui/control | 7 + .../create_installer_linux.sh | 36 ++++ release/one_click_macos_gui/Info.plist | 24 +++ .../Resources/conclusion.html | 13 ++ .../Resources/welcome.html | 13 ++ .../create_installer_macos.sh | 44 ++++ release/one_click_macos_gui/distribution.xml | 17 ++ .../one_click_macos_gui/scripts/postinstall | 6 + .../one_click_macos_gui/scripts/preinstall | 5 + .../one_click_macos_gui/structuremap_terminal | 3 + .../create_installer_windows.sh | 32 +++ .../structuremap_innoinstaller.iss | 50 +++++ release/pyinstaller/structuremap.spec | 152 +++++++++++++ .../pyinstaller/structuremap_pyinstaller.py | 13 ++ release/pypi/install_pypi_wheel.sh | 5 + release/pypi/install_test_pypi_wheel.sh | 5 + release/pypi/prepare_pypi_wheel.sh | 9 + requirements/requirements.txt | 1 + requirements/requirements_development.txt | 11 + setup.py | 72 +++++++ structuremap/__init__.py | 44 ++++ structuremap/cli.py | 31 +++ structuremap/gui.py | 5 + tests/run_tests.sh | 4 + tests/test_cli.py | 11 + tests/test_gui.py | 11 + 44 files changed, 1611 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/workflows/github_action_test_dummy.yml create mode 100644 .github/workflows/pip_installation.yml create mode 100644 .github/workflows/publish_and_release.yml create mode 100644 .gitignore create mode 100644 HISTORY.md create mode 100644 LICENSE.txt create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 misc/CLA.md create mode 100644 misc/bumpversion.cfg create mode 100644 misc/check_version.sh create mode 100644 misc/loose_pip_install.sh create mode 100644 misc/stable_pip_install.sh create mode 100644 nbs/tutorial.ipynb create mode 100644 release/logos/alpha_logo.icns create mode 100644 release/logos/alpha_logo.ico create mode 100644 release/logos/alpha_logo.png create mode 100644 release/one_click_linux_gui/control create mode 100644 release/one_click_linux_gui/create_installer_linux.sh create mode 100644 release/one_click_macos_gui/Info.plist create mode 100644 release/one_click_macos_gui/Resources/conclusion.html create mode 100644 release/one_click_macos_gui/Resources/welcome.html create mode 100644 release/one_click_macos_gui/create_installer_macos.sh create mode 100644 release/one_click_macos_gui/distribution.xml create mode 100644 release/one_click_macos_gui/scripts/postinstall create mode 100644 release/one_click_macos_gui/scripts/preinstall create mode 100644 release/one_click_macos_gui/structuremap_terminal create mode 100644 release/one_click_windows_gui/create_installer_windows.sh create mode 100644 release/one_click_windows_gui/structuremap_innoinstaller.iss create mode 100644 release/pyinstaller/structuremap.spec create mode 100644 release/pyinstaller/structuremap_pyinstaller.py create mode 100644 release/pypi/install_pypi_wheel.sh create mode 100644 release/pypi/install_test_pypi_wheel.sh create mode 100644 release/pypi/prepare_pypi_wheel.sh create mode 100644 requirements/requirements.txt create mode 100644 requirements/requirements_development.txt create mode 100644 setup.py create mode 100644 structuremap/__init__.py create mode 100644 structuremap/cli.py create mode 100644 structuremap/gui.py create mode 100644 tests/run_tests.sh create mode 100644 tests/test_cli.py create mode 100644 tests/test_gui.py diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..d05011b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,44 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +Make sure your bug is not addressed in the [troubleshooting section](https://github.com/MannLabs/structuremap#troubleshooting) or in [previous issues](https://github.com/MannLabs/structuremap/issues?q=is%3Aissue). If not, provide 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. + +**Logs** +Please provide the log (see the structuremap terminal on where to find it). + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Version (please complete the following information):** + - Installation Type [e.g. One-Click Installer / Pip / Developer] + - If no log is available, provide the following: + - Platform information + - system [e.g. Darwin] + - release [e.g. 19.6.0] + - version [e.g. 10.15.7] + - machine [e.g. x86_64] + - processor [e.g. i386] + - cpu count [e.g. 8] + - Python information: + - structuremap version [e.g. 0.1.2] + - [other packages] + +**Additional context** +Add any other context about the problem here. Attached log files or upload data files if possible. diff --git a/.github/workflows/github_action_test_dummy.yml b/.github/workflows/github_action_test_dummy.yml new file mode 100644 index 0000000..5f92e60 --- /dev/null +++ b/.github/workflows/github_action_test_dummy.yml @@ -0,0 +1,12 @@ +on: + workflow_dispatch: + +name: Test new GitHub action workflow + + +jobs: + Version_bumped: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 diff --git a/.github/workflows/pip_installation.yml b/.github/workflows/pip_installation.yml new file mode 100644 index 0000000..1d7a0cc --- /dev/null +++ b/.github/workflows/pip_installation.yml @@ -0,0 +1,60 @@ +on: + push: + branches: [ main ] + pull_request: + branches: [ main, development ] + workflow_dispatch: + +name: Default installation and tests + +jobs: + stable_installation: + name: Test stable pip installation on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macOS-latest, windows-latest] + steps: + - uses: actions/checkout@v2 + - uses: conda-incubator/setup-miniconda@v2 + with: + auto-update-conda: true + python-version: ${{ matrix.python-version }} + - name: Conda info + shell: bash -l {0} + run: conda info + - name: Test pip installation with all stable dependencies + shell: bash -l {0} + run: | + cd misc + . ./stable_pip_install.sh + - name: Unittests + shell: bash -l {0} + run: | + cd tests + . ./run_tests.sh + loose_installation: + name: Test loose pip installation on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macOS-latest, windows-latest] + steps: + - uses: actions/checkout@v2 + - uses: conda-incubator/setup-miniconda@v2 + with: + auto-update-conda: true + python-version: ${{ matrix.python-version }} + - name: Conda info + shell: bash -l {0} + run: conda info + - name: Test pip installation with all loose dependencies + shell: bash -l {0} + run: | + cd misc + . ./loose_pip_install.sh + - name: Unittests + shell: bash -l {0} + run: | + cd tests + . ./run_tests.sh diff --git a/.github/workflows/publish_and_release.yml b/.github/workflows/publish_and_release.yml new file mode 100644 index 0000000..3678fd2 --- /dev/null +++ b/.github/workflows/publish_and_release.yml @@ -0,0 +1,195 @@ +on: + # push: + # branches: [ main ] + workflow_dispatch: + + +name: Publish on PyPi and release on GitHub + +jobs: + Version_Bumped: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.master_version_bumped.outputs.version }} + steps: + - name: Checkout code + uses: actions/checkout@v2 + - uses: conda-incubator/setup-miniconda@v2 + with: + auto-update-conda: true + python-version: ${{ matrix.python-version }} + - name: Master version bumped + id: master_version_bumped + shell: bash -l {0} + run: | + cd misc + . ./check_version.sh + echo ::set-output name=version::$current_version + Create_Draft_On_GitHub: + runs-on: ubuntu-latest + needs: Version_Bumped + outputs: + upload_url: ${{ steps.draft_release.outputs.upload_url }} + steps: + - name: Draft Release + id: draft_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: ${{ needs.Version_Bumped.outputs.version }} + release_name: Release version ${{ needs.Version_Bumped.outputs.version }} + draft: false + prerelease: false + Create_Linux_Release: + runs-on: ubuntu-latest + needs: Create_Draft_On_GitHub + steps: + - name: Checkout code + uses: actions/checkout@v2 + - uses: conda-incubator/setup-miniconda@v2 + with: + auto-update-conda: true + python-version: ${{ matrix.python-version }} + - name: Conda info + shell: bash -l {0} + run: conda info + - name: Creating installer for Linux + shell: bash -l {0} + run: | + cd release/one_click_linux_gui + . ./create_installer_linux.sh + - name: Test installer for Linux + shell: bash -l {0} + run: | + sudo dpkg -i release/one_click_linux_gui/dist/structuremap_gui_installer_linux.deb + - name: Upload Linux Installer + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.Create_Draft_On_GitHub.outputs.upload_url }} + asset_path: release/one_click_linux_gui/dist/structuremap_gui_installer_linux.deb + asset_name: structuremap_gui_installer_linux.deb + asset_content_type: application/octet-stream + Create_MacOS_Release: + runs-on: macos-latest + needs: Create_Draft_On_GitHub + steps: + - name: Checkout code + uses: actions/checkout@v2 + - uses: conda-incubator/setup-miniconda@v2 + with: + auto-update-conda: true + python-version: ${{ matrix.python-version }} + - name: Conda info + shell: bash -l {0} + run: conda info + - name: Creating installer for MacOS + shell: bash -l {0} + run: | + cd release/one_click_macos_gui + . ./create_installer_macos.sh + - name: Test installer for MacOS + shell: bash -l {0} + run: | + sudo installer -pkg release/one_click_macos_gui/dist/structuremap_gui_installer_macos.pkg -target / + - name: Upload MacOS Installer + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.Create_Draft_On_GitHub.outputs.upload_url }} + asset_path: release/one_click_macos_gui/dist/structuremap_gui_installer_macos.pkg + asset_name: structuremap_gui_installer_macos.pkg + asset_content_type: application/octet-stream + Create_Windows_Release: + runs-on: windows-latest + needs: Create_Draft_On_GitHub + steps: + - name: Checkout code + uses: actions/checkout@v2 + - uses: conda-incubator/setup-miniconda@v2 + with: + auto-update-conda: true + python-version: ${{ matrix.python-version }} + - name: Conda info + shell: bash -l {0} + run: conda info + - name: Creating installer for Windows + shell: bash -l {0} + run: | + cd release/one_click_windows_gui + . ./create_installer_windows.sh + - name: Test installer for Windows + shell: bash -l {0} + run: | + cd release/one_click_windows_gui/dist/ + echo "TODO, this test seems to freeze the runner..." + # ./structuremap_gui_installer_windows.exe //verysilent //log=log.txt //noicons //tasks= //portable=1 + # cat log.txt + - name: Upload Windows Installer + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.Create_Draft_On_GitHub.outputs.upload_url }} + asset_path: release/one_click_windows_gui/dist/structuremap_gui_installer_windows.exe + asset_name: structuremap_gui_installer_windows.exe + asset_content_type: application/octet-stream + Create_PyPi_Release: + runs-on: ubuntu-latest + needs: [Create_Linux_Release, Create_MacOs_Release, Create_Windows_Release] + steps: + - name: Checkout code + uses: actions/checkout@v2 + - uses: conda-incubator/setup-miniconda@v2 + with: + auto-update-conda: true + python-version: ${{ matrix.python-version }} + - name: Conda info + shell: bash -l {0} + run: conda info + - name: Prepare distribution + shell: bash -l {0} + run: | + cd release/pypi + . ./prepare_pypi_wheel.sh + - name: Publish distribution to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + - name: Test PyPI test release + shell: bash -l {0} + run: | + cd release/pypi + . ./install_test_pypi_wheel.sh + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.PYPI_API_TOKEN }} + Test_PyPi_Release: + name: Test_PyPi_version_on_${{ matrix.os }} + runs-on: ${{ matrix.os }} + needs: Create_PyPi_Release + strategy: + matrix: + os: [ubuntu-latest, macOS-latest, windows-latest] + steps: + - uses: actions/checkout@v2 + - uses: conda-incubator/setup-miniconda@v2 + with: + auto-update-conda: true + python-version: ${{ matrix.python-version }} + - name: Conda info + shell: bash -l {0} + run: conda info + - name: Test pip installation from PyPi + shell: bash -l {0} + run: | + cd release/pypi + . ./install_pypi_wheel.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b30281 --- /dev/null +++ b/.gitignore @@ -0,0 +1,134 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +# lib/ +# lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +# *.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# User defined: +structuremap/logs +*.DS_Store +*sandbox* diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..370f42e --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,5 @@ +## Changelog + +### 0.0.1 + +* FEAT: Initial creation of structuremap. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..3958411 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 MannLabs + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..902d43d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +recursive-include structuremap * +include LICENSE.txt +include README.md +recursive-exclude structuremap/logs * diff --git a/README.md b/README.md new file mode 100644 index 0000000..beb3f67 --- /dev/null +++ b/README.md @@ -0,0 +1,170 @@ +![Pip installation](https://github.com/MannLabs/structuremap/workflows/Default%20installation%20and%20tests/badge.svg) +![GUI and PyPi releases](https://github.com/MannLabs/structuremap/workflows/Publish%20on%20PyPi%20and%20release%20on%20GitHub/badge.svg) +[![Downloads](https://pepy.tech/badge/structuremap)](https://pepy.tech/project/structuremap) +[![Downloads](https://pepy.tech/badge/structuremap/month)](https://pepy.tech/project/structuremap) +[![Downloads](https://pepy.tech/badge/structuremap/week)](https://pepy.tech/project/structuremap) + + +# structuremap +An open-source Python package of the AlphaPept ecosystem from the [Mann Labs at the Max Planck Institute of Biochemistry](https://www.biochem.mpg.de/mann) and the [University of Copenhagen](https://www.cpr.ku.dk/research/proteomics/mann/). To enable all hyperlinks in this document, please view it at [GitHub](https://github.com/MannLabs/structuremap). + +* [**About**](#about) +* [**License**](#license) +* [**Installation**](#installation) + * [**One-click GUI**](#one-click-gui) + * [**Pip installer**](#pip) + * [**Developer installer**](#developer) +* [**Usage**](#usage) + * [**GUI**](#gui) + * [**CLI**](#cli) + * [**Python and jupyter notebooks**](#python-and-jupyter-notebooks) +* [**Troubleshooting**](#troubleshooting) +* [**Citations**](#citations) +* [**How to contribute**](#how-to-contribute) +* [**Changelog**](#changelog) + +--- +## About + +An open-source Python package of the AlphaPept ecosystem from the [Mann Labs at the Max Planck Institute of Biochemistry](https://www.biochem.mpg.de/mann) and the [University of Copenhagen](https://www.cpr.ku.dk/research/proteomics/mann/). + +--- +## License + +structuremap was developed by the [Mann Labs at the Max Planck Institute of Biochemistry](https://www.biochem.mpg.de/mann) and the [University of Copenhagen](https://www.cpr.ku.dk/research/proteomics/mann/) and is freely available with an [Apache License](LICENSE.txt). External Python packages (available in the [requirements](requirements) folder) have their own licenses, which can be consulted on their respective websites. + +--- +## Installation + +structuremap can be installed and used on all major operating systems (Windows, macOS and Linux). +There are three different types of installation possible: + +* [**One-click GUI installer:**](#one-click-gui) Choose this installation if you only want the GUI and/or keep things as simple as possible. +* [**Pip installer:**](#pip) Choose this installation if you want to use structuremap as a Python package in an existing Python 3.8 environment (e.g. a Jupyter notebook). If needed, the GUI and CLI can be installed with pip as well. +* [**Developer installer:**](#developer) Choose this installation if you are familiar with CLI tools, [conda](https://docs.conda.io/en/latest/) and Python. This installation allows access to all available features of structuremap and even allows to modify its source code directly. Generally, the developer version of structuremap outperforms the precompiled versions which makes this the installation of choice for high-throughput experiments. + +### One-click GUI + +The GUI of structuremap is a completely stand-alone tool that requires no knowledge of Python or CLI tools. Click on one of the links below to download the latest release for: + +* [**Windows**](https://github.com/MannLabs/structuremap/releases/latest/download/structuremap_gui_installer_windows.exe) +* [**macOS**](https://github.com/MannLabs/structuremap/releases/latest/download/structuremap_gui_installer_macos.pkg) +* [**Linux**](https://github.com/MannLabs/structuremap/releases/latest/download/structuremap_gui_installer_linux.deb) + +Older releases remain available on the [release page](https://github.com/MannLabs/structuremap/releases), but no backwards compatibility is guaranteed. + +### Pip + +structuremap can be installed in an existing Python 3.8 environment with a single `bash` command. *This `bash` command can also be run directly from within a Jupyter notebook by prepending it with a `!`*: + +```bash +pip install structuremap +``` + +Installing structuremap like this avoids conflicts when integrating it in other tools, as this does not enforce strict versioning of dependancies. However, if new versions of dependancies are released, they are not guaranteed to be fully compatible with structuremap. While this should only occur in rare cases where dependencies are not backwards compatible, you can always force structuremap to use dependancy versions which are known to be compatible with: + +```bash +pip install "structuremap[stable]" +``` + +NOTE: You might need to run `pip install pip==21.0` before installing structuremap like this. Also note the double quotes `"`. + +For those who are really adventurous, it is also possible to directly install any branch (e.g. `@development`) with any extras (e.g. `#egg=structuremap[stable,development-stable]`) from GitHub with e.g. + +```bash +pip install "git+https://github.com/MannLabs/structuremap.git@development#egg=structuremap[stable,development-stable]" +``` + +### Developer + +structuremap can also be installed in editable (i.e. developer) mode with a few `bash` commands. This allows to fully customize the software and even modify the source code to your specific needs. When an editable Python package is installed, its source code is stored in a transparent location of your choice. While optional, it is advised to first (create and) navigate to e.g. a general software folder: + +```bash +mkdir ~/folder/where/to/install/software +cd ~/folder/where/to/install/software +``` + +***The following commands assume you do not perform any additional `cd` commands anymore***. + +Next, download the structuremap repository from GitHub either directly or with a `git` command. This creates a new structuremap subfolder in your current directory. + +```bash +git clone https://github.com/MannLabs/structuremap.git +``` + +For any Python package, it is highly recommended to use a separate [conda virtual environment](https://docs.conda.io/en/latest/), as otherwise *dependancy conflicts can occur with already existing packages*. + +```bash +conda create --name structuremap python=3.8 -y +conda activate structuremap +``` + +Finally, structuremap and all its [dependancies](requirements) need to be installed. To take advantage of all features and allow development (with the `-e` flag), this is best done by also installing the [development dependencies](requirements/requirements_development.txt) instead of only the [core dependencies](requirements/requirements.txt): + +```bash +pip install -e "./structuremap[development]" +``` + +By default this installs loose dependancies (no explicit versioning), although it is also possible to use stable dependencies (e.g. `pip install -e "./structuremap[stable,development-stable]"`). + +***By using the editable flag `-e`, all modifications to the [structuremap source code folder](structuremap) are directly reflected when running structuremap. Note that the structuremap folder cannot be moved and/or renamed if an editable version is installed. In case of confusion, you can always retrieve the location of any Python module with e.g. the command `import module` followed by `module.__file__`.*** + +--- +## Usage + +There are three ways to use structuremap: + +* [**GUI**](#gui) +* [**CLI**](#cli) +* [**Python**](#python-and-jupyter-notebooks) + +NOTE: The first time you use a fresh installation of structuremap, it is often quite slow because some functions might still need compilation on your local operating system and architecture. Subsequent use should be a lot faster. + +### GUI + +If the GUI was not installed through a one-click GUI installer, it can be activate with the following `bash` command: + +```bash +structuremap gui +``` + +Note that this needs to be prepended with a `!` when you want to run this from within a Jupyter notebook. When the command is run directly from the command-line, make sure you use the right environment (activate it with e.g. `conda activate structuremap` or set an alias to the binary executable (can be obtained with `where structuremap` or `which structuremap`)). + +### CLI + +The CLI can be run with the following command (after activating the `conda` environment with `conda activate structuremap` or if an alias was set to the structuremap executable): + +```bash +structuremap -h +``` + +It is possible to get help about each function and their (required) parameters by using the `-h` flag. + +### Python and Jupyter notebooks + +structuremap can be imported as a Python package into any Python script or notebook with the command `import structuremap`. + +A brief [Jupyter notebook tutorial](nbs/tutorial.ipynb) on how to use the API is also present in the [nbs folder](nbs). + +--- +## Troubleshooting + +In case of issues, check out the following: + +* [Issues](https://github.com/MannLabs/structuremap/issues): Try a few different search terms to find out if a similar problem has been encountered before +* [Discussions](https://github.com/MannLabs/structuremap/discussions): Check if your problem or feature requests has been discussed before. + +--- +## Citations + +There are currently no plans to draft a manuscript. + +--- +## How to contribute + +If you like this software, you can give us a [star](https://github.com/MannLabs/structuremap/stargazers) to boost our visibility! All direct contributions are also welcome. Feel free to post a new [issue](https://github.com/MannLabs/structuremap/issues) or clone the repository and create a [pull request](https://github.com/MannLabs/structuremap/pulls) with a new branch. For an even more interactive participation, check out the [discussions](https://github.com/MannLabs/structuremap/discussions) and the [the Contributors License Agreement](misc/CLA.md). + +--- +## Changelog + +See the [HISTORY.md](HISTORY.md) for a full overview of the changes made in each version. diff --git a/misc/CLA.md b/misc/CLA.md new file mode 100644 index 0000000..38da21c --- /dev/null +++ b/misc/CLA.md @@ -0,0 +1,66 @@ +### MannLabs Individual Contributor License Agreement + +Thank you for your interest in contributing to open source software projects (“Projects”) made available by MannLabs or its affiliates (“MannLabs”). This Individual Contributor License Agreement (“Agreement”) sets out the terms governing any source code, object code, bug fixes, configuration changes, tools, specifications, documentation, data, materials, feedback, information or other works of authorship that you submit or have submitted, in any form and in any manner, to MannLabs in respect of any of the Projects (collectively “Contributions”). If you have any questions respecting this Agreement, please contact opensource@alphapept.com. + + +You agree that the following terms apply to all of your past, present and future Contributions. Except for the licenses granted in this Agreement, you retain all of your right, title and interest in and to your Contributions. + + +**Copyright License.** You hereby grant, and agree to grant, to MannLabs a non-exclusive, perpetual, irrevocable, worldwide, fully-paid, royalty-free, transferable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute your Contributions and such derivative works, with the right to sublicense the foregoing rights through multiple tiers of sublicensees. + + +**Patent License.** You hereby grant, and agree to grant, to MannLabs a non-exclusive, perpetual, irrevocable, +worldwide, fully-paid, royalty-free, transferable patent license to make, have made, use, offer to sell, sell, +import, and otherwise transfer your Contributions, where such license applies only to those patent claims +licensable by you that are necessarily infringed by your Contributions alone or by combination of your +Contributions with the Project to which such Contributions were submitted, with the right to sublicense the +foregoing rights through multiple tiers of sublicensees. + + +**Moral Rights.** To the fullest extent permitted under applicable law, you hereby waive, and agree not to +assert, all of your “moral rights” in or relating to your Contributions for the benefit of MannLabs, its assigns, and +their respective direct and indirect sublicensees. + + +**Third Party Content/Rights.** If your Contribution includes or is based on any source code, object code, bug +fixes, configuration changes, tools, specifications, documentation, data, materials, feedback, information or +other works of authorship that were not authored by you (“Third Party Content”) or if you are aware of any +third party intellectual property or proprietary rights associated with your Contribution (“Third Party Rights”), +then you agree to include with the submission of your Contribution full details respecting such Third Party +Content and Third Party Rights, including, without limitation, identification of which aspects of your +Contribution contain Third Party Content or are associated with Third Party Rights, the owner/author of the +Third Party Content and Third Party Rights, where you obtained the Third Party Content, and any applicable +third party license terms or restrictions respecting the Third Party Content and Third Party Rights. For greater +certainty, the foregoing obligations respecting the identification of Third Party Content and Third Party Rights +do not apply to any portion of a Project that is incorporated into your Contribution to that same Project. + + +**Representations.** You represent that, other than the Third Party Content and Third Party Rights identified by +you in accordance with this Agreement, you are the sole author of your Contributions and are legally entitled +to grant the foregoing licenses and waivers in respect of your Contributions. If your Contributions were +created in the course of your employment with your past or present employer(s), you represent that such +employer(s) has authorized you to make your Contributions on behalf of such employer(s) or such employer +(s) has waived all of their right, title or interest in or to your Contributions. + + +**Disclaimer.** To the fullest extent permitted under applicable law, your Contributions are provided on an "as is" +basis, without any warranties or conditions, express or implied, including, without limitation, any implied +warranties or conditions of non-infringement, merchantability or fitness for a particular purpose. You are not +required to provide support for your Contributions, except to the extent you desire to provide support. + + +**No Obligation.** You acknowledge that MannLabs is under no obligation to use or incorporate your Contributions +into any of the Projects. The decision to use or incorporate your Contributions into any of the Projects will be +made at the sole discretion of MannLabs or its authorized delegates .. + + +**Disputes.** This Agreement shall be governed by and construed in accordance with the laws of the State of +New York, United States of America, without giving effect to its principles or rules regarding conflicts of laws, +other than such principles directing application of New York law. The parties hereby submit to venue in, and +jurisdiction of the courts located in New York, New York for purposes relating to this Agreement. In the event +that any of the provisions of this Agreement shall be held by a court or other tribunal of competent jurisdiction +to be unenforceable, the remaining portions hereof shall remain in full force and effect. + + +**Assignment.** You agree that MannLabs may assign this Agreement, and all of its rights, obligations and licenses +hereunder diff --git a/misc/bumpversion.cfg b/misc/bumpversion.cfg new file mode 100644 index 0000000..cc4634d --- /dev/null +++ b/misc/bumpversion.cfg @@ -0,0 +1,31 @@ +[bumpversion] +current_version = 0.0.1 +commit = True +tag = False +parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+)(?P\d+))? +serialize = + {major}.{minor}.{patch} + {major}.{minor}.{patch} + +[bumpversion:part:release] + +[bumpversion:part:build] + +[bumpversion:file:../structuremap/__init__.py] + +[bumpversion:file:../release/one_click_linux_gui/control] + +[bumpversion:file:../release/one_click_linux_gui/create_installer_linux.sh] + +[bumpversion:file:../release/one_click_macos_gui/distribution.xml] + +[bumpversion:file:../release/one_click_macos_gui/Info.plist] + +[bumpversion:file:../release/one_click_macos_gui/create_installer_macos.sh] + +[bumpversion:file:../release/one_click_windows_gui/create_installer_windows.sh] + +[bumpversion:file:../release/one_click_windows_gui/structuremap_innoinstaller.iss] + +search = {current_version} +replace = {new_version} diff --git a/misc/check_version.sh b/misc/check_version.sh new file mode 100644 index 0000000..232cff6 --- /dev/null +++ b/misc/check_version.sh @@ -0,0 +1,12 @@ +current_version=$(grep "__version__" ../structuremap/__init__.py | cut -f3 -d ' ' | sed 's/"//g') +current_version_as_regex=$(echo $current_version | sed 's/\./\\./g') +conda create -n version_check python=3.8 pip=20.1 -y +conda activate version_check +set +e +already_on_pypi=$(pip install structuremap== 2>&1 | grep -c "$current_version_as_regex") +set -e +conda deactivate +if [ $already_on_pypi -ne 0 ]; then + echo "Version is already on PyPi" + exit 1 +fi diff --git a/misc/loose_pip_install.sh b/misc/loose_pip_install.sh new file mode 100644 index 0000000..9fdfccc --- /dev/null +++ b/misc/loose_pip_install.sh @@ -0,0 +1,5 @@ +conda create -n structuremap python=3.8 -y +conda activate structuremap +pip install -e '../.[development]' +structuremap +conda deactivate diff --git a/misc/stable_pip_install.sh b/misc/stable_pip_install.sh new file mode 100644 index 0000000..cc6cd2b --- /dev/null +++ b/misc/stable_pip_install.sh @@ -0,0 +1,5 @@ +conda create -n structuremap python=3.8 -y +conda activate structuremap +pip install -e '../.[stable,development-stable]' +structuremap +conda deactivate diff --git a/nbs/tutorial.ipynb b/nbs/tutorial.ipynb new file mode 100644 index 0000000..7611a17 --- /dev/null +++ b/nbs/tutorial.ipynb @@ -0,0 +1,43 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:structuremap]", + "language": "python", + "name": "conda-env-structuremap-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/release/logos/alpha_logo.icns b/release/logos/alpha_logo.icns new file mode 100644 index 0000000000000000000000000000000000000000..1a9075342e7fb6f2d590deced84f35e8b772759c GIT binary patch literal 85999 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 N{{11q4nS=lU literal 0 HcmV?d00001 diff --git a/release/logos/alpha_logo.ico b/release/logos/alpha_logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..03a8c4e02ffa0dc6af7fe180544855bcce86c3c6 GIT binary patch literal 8843 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 literal 0 HcmV?d00001 diff --git a/release/logos/alpha_logo.png b/release/logos/alpha_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9a4bf8511c98f064b6b826e139d72cd190323807 GIT binary patch literal 5182 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 literal 0 HcmV?d00001 diff --git a/release/one_click_linux_gui/control b/release/one_click_linux_gui/control new file mode 100644 index 0000000..8f39773 --- /dev/null +++ b/release/one_click_linux_gui/control @@ -0,0 +1,7 @@ +Package: structuremap +Version: 0.0.1 +Architecture: all +Maintainer: Mann Labs +Description: structuremap + structuremap is an open-source Python package in the AlphaPept ecosystem. + structuremap was developed by the Mann Labs at the Max Planck Institute of Biochemistry and University of Copenhagen and is freely available with an Apache License. Additional third-party licenses are applicable for external Python packages (see https://github.com/MannLabs/structuremap for more details.). diff --git a/release/one_click_linux_gui/create_installer_linux.sh b/release/one_click_linux_gui/create_installer_linux.sh new file mode 100644 index 0000000..bd17fc1 --- /dev/null +++ b/release/one_click_linux_gui/create_installer_linux.sh @@ -0,0 +1,36 @@ +#!bash + +# Initial cleanup +rm -rf dist +rm -rf build +cd ../.. +rm -rf dist +rm -rf build + +# Creating a conda environment +conda create -n structuremap_installer python=3.8 -y +conda activate structuremap_installer + +# Creating the wheel +python setup.py sdist bdist_wheel + +# Setting up the local package +cd release/one_click_linux_gui +# Make sure you include the required extra packages and always use the stable or very-stable options! +pip install "../../dist/structuremap-0.0.1-py3-none-any.whl[stable]" + +# Creating the stand-alone pyinstaller folder +pip install pyinstaller==4.2 +pyinstaller ../pyinstaller/structuremap.spec -y +conda deactivate + +# If needed, include additional source such as e.g.: +# cp ../../structuremap/data/*.fasta dist/structuremap/data +# WARNING: this probably does not work!!!! + +# Wrapping the pyinstaller folder in a .deb package +mkdir -p dist/structuremap_gui_installer_linux/usr/local/bin +mv dist/structuremap dist/structuremap_gui_installer_linux/usr/local/bin/structuremap +mkdir dist/structuremap_gui_installer_linux/DEBIAN +cp control dist/structuremap_gui_installer_linux/DEBIAN +dpkg-deb --build --root-owner-group dist/structuremap_gui_installer_linux/ diff --git a/release/one_click_macos_gui/Info.plist b/release/one_click_macos_gui/Info.plist new file mode 100644 index 0000000..1b8a928 --- /dev/null +++ b/release/one_click_macos_gui/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDisplayName + structuremap + CFBundleExecutable + MacOS/structuremap_terminal + CFBundleIconFile + alpha_logo.icns + CFBundleIdentifier + structuremap.0.0.1 + CFBundleShortVersionString + 0.0.1 + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + structuremap + CFBundlePackageType + APPL + LSBackgroundOnly + + + diff --git a/release/one_click_macos_gui/Resources/conclusion.html b/release/one_click_macos_gui/Resources/conclusion.html new file mode 100644 index 0000000..c08b1a3 --- /dev/null +++ b/release/one_click_macos_gui/Resources/conclusion.html @@ -0,0 +1,13 @@ + + + + + + + +

+

structuremap

+

Thank you for installing structuremap.

+
+ + diff --git a/release/one_click_macos_gui/Resources/welcome.html b/release/one_click_macos_gui/Resources/welcome.html new file mode 100644 index 0000000..13b8115 --- /dev/null +++ b/release/one_click_macos_gui/Resources/welcome.html @@ -0,0 +1,13 @@ + + + + + + +
+

structuremap

+

structuremap is an open-source Python package of the AlphaPept ecosystem.

+

structuremap was developed by the Mann Labs at the Max Planck Institute of Biochemistry and the University of Copenhagen and is freely available with an Apache License. Since structuremap uses external Python packages, additional third-party licenses are applicable.

+
+ + diff --git a/release/one_click_macos_gui/create_installer_macos.sh b/release/one_click_macos_gui/create_installer_macos.sh new file mode 100644 index 0000000..1752ba1 --- /dev/null +++ b/release/one_click_macos_gui/create_installer_macos.sh @@ -0,0 +1,44 @@ +#!bash + +# Initial cleanup +rm -rf dist +rm -rf build +FILE=structuremap.pkg +if test -f "$FILE"; then + rm structuremap.pkg +fi +cd ../.. +rm -rf dist +rm -rf build + +# Creating a conda environment +conda create -n structuremapinstaller python=3.8 -y +conda activate structuremapinstaller + +# Creating the wheel +python setup.py sdist bdist_wheel + +# Setting up the local package +cd release/one_click_macos_gui +pip install "../../dist/structuremap-0.0.1-py3-none-any.whl[stable]" + +# Creating the stand-alone pyinstaller folder +pip install pyinstaller==4.2 +pyinstaller ../pyinstaller/structuremap.spec -y +conda deactivate + +# If needed, include additional source such as e.g.: +# cp ../../structuremap/data/*.fasta dist/structuremap/data + +# Wrapping the pyinstaller folder in a .pkg package +mkdir -p dist/structuremap/Contents/Resources +cp ../logos/alpha_logo.icns dist/structuremap/Contents/Resources +mv dist/structuremap_gui dist/structuremap/Contents/MacOS +cp Info.plist dist/structuremap/Contents +cp structuremap_terminal dist/structuremap/Contents/MacOS +cp ../../LICENSE.txt Resources/LICENSE.txt +cp ../logos/alpha_logo.png Resources/alpha_logo.png +chmod 777 scripts/* + +pkgbuild --root dist/structuremap --identifier de.mpg.biochem.structuremap.app --version 0.0.1 --install-location /Applications/structuremap.app --scripts scripts structuremap.pkg +productbuild --distribution distribution.xml --resources Resources --package-path structuremap.pkg dist/structuremap_gui_installer_macos.pkg diff --git a/release/one_click_macos_gui/distribution.xml b/release/one_click_macos_gui/distribution.xml new file mode 100644 index 0000000..df22e45 --- /dev/null +++ b/release/one_click_macos_gui/distribution.xml @@ -0,0 +1,17 @@ + + + structuremap 0.0.1 + + + + + + + + + + + + + structuremap.pkg + diff --git a/release/one_click_macos_gui/scripts/postinstall b/release/one_click_macos_gui/scripts/postinstall new file mode 100644 index 0000000..1d2344f --- /dev/null +++ b/release/one_click_macos_gui/scripts/postinstall @@ -0,0 +1,6 @@ +#!/bin/sh + +# make sure this file itself is executable +xattr -dr com.apple.quarantine /Applications/structuremap.app +chmod -R 577 /Applications/structuremap.app +echo "Postinstall finished" diff --git a/release/one_click_macos_gui/scripts/preinstall b/release/one_click_macos_gui/scripts/preinstall new file mode 100644 index 0000000..3d459f6 --- /dev/null +++ b/release/one_click_macos_gui/scripts/preinstall @@ -0,0 +1,5 @@ +#!/bin/sh + +# make sure this file itself is executable +rm -rf /Applications/structuremap.app +echo "Preinstall finished" diff --git a/release/one_click_macos_gui/structuremap_terminal b/release/one_click_macos_gui/structuremap_terminal new file mode 100644 index 0000000..a65ea04 --- /dev/null +++ b/release/one_click_macos_gui/structuremap_terminal @@ -0,0 +1,3 @@ +#!/bin/sh + +open -a Terminal "${BASH_SOURCE%/*}/structuremap_gui" diff --git a/release/one_click_windows_gui/create_installer_windows.sh b/release/one_click_windows_gui/create_installer_windows.sh new file mode 100644 index 0000000..973fd8b --- /dev/null +++ b/release/one_click_windows_gui/create_installer_windows.sh @@ -0,0 +1,32 @@ +#!bash + +# Initial cleanup +rm -rf dist +rm -rf build +cd ../.. +rm -rf dist +rm -rf build + +# Creating a conda environment +conda create -n structuremap_installer python=3.8 -y +conda activate structuremap_installer + +# Creating the wheel +python setup.py sdist bdist_wheel + +# Setting up the local package +cd release/one_click_windows_gui +# Make sure you include the required extra packages and always use the stable or very-stable options! +pip install "../../dist/structuremap-0.0.1-py3-none-any.whl[stable]" + +# Creating the stand-alone pyinstaller folder +pip install pyinstaller==4.2 +pyinstaller ../pyinstaller/structuremap.spec -y +conda deactivate + +# If needed, include additional source such as e.g.: +# cp ../../structuremap/data/*.fasta dist/structuremap/data + +# Wrapping the pyinstaller folder in a .exe package +"C:\Program Files (x86)\Inno Setup 6\ISCC.exe" structuremap_innoinstaller.iss +# WARNING: this assumes a static location for innosetup diff --git a/release/one_click_windows_gui/structuremap_innoinstaller.iss b/release/one_click_windows_gui/structuremap_innoinstaller.iss new file mode 100644 index 0000000..7bcb0f4 --- /dev/null +++ b/release/one_click_windows_gui/structuremap_innoinstaller.iss @@ -0,0 +1,50 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define MyAppName "structuremap" +#define MyAppVersion "0.0.1" +#define MyAppPublisher "Max Planck Institute of Biochemistry and the University of Copenhagen, Mann Labs" +#define MyAppURL "https://github.com/MannLabs/structuremap" +#define MyAppExeName "structuremap_gui.exe" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{structuremap_Mann_Labs_MPI_CPR} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +;AppVerName={#MyAppName} {#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={autopf}\{#MyAppName} +DisableProgramGroupPage=yes +LicenseFile=..\..\LICENSE.txt +; Uncomment the following line to run in non administrative install mode (install for current user only.) +PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog +OutputDir=dist +OutputBaseFilename=structuremap_gui_installer_windows +SetupIconFile=..\logos\alpha_logo.ico +Compression=lzma +SolidCompression=yes +WizardStyle=modern + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +Source: "dist\structuremap_gui\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion +Source: "dist\structuremap_gui\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent diff --git a/release/pyinstaller/structuremap.spec b/release/pyinstaller/structuremap.spec new file mode 100644 index 0000000..2c96c7c --- /dev/null +++ b/release/pyinstaller/structuremap.spec @@ -0,0 +1,152 @@ +# -*- mode: python ; coding: utf-8 -*- + +import pkgutil +import os +import sys +from PyInstaller.building.build_main import Analysis, PYZ, EXE, COLLECT, BUNDLE, TOC +import PyInstaller.utils.hooks +import pkg_resources +import importlib.metadata +import structuremap + + +##################### User definitions +exe_name = 'structuremap_gui' +script_name = 'structuremap_pyinstaller.py' +if sys.platform[:6] == "darwin": + icon = '../logos/alpha_logo.icns' +else: + icon = '../logos/alpha_logo.ico' +block_cipher = None +location = os.getcwd() +project = "structuremap" +remove_tests = True +bundle_name = "structuremap" +##################### + + +requirements = { + req.split()[0] for req in importlib.metadata.requires(project) +} +requirements.add(project) +requirements.add("distributed") +hidden_imports = set() +datas = [] +binaries = [] +checked = set() +while requirements: + requirement = requirements.pop() + checked.add(requirement) + if requirement in ["pywin32"]: + continue + try: + module_version = importlib.metadata.version(requirement) + except ( + importlib.metadata.PackageNotFoundError, + ModuleNotFoundError, + ImportError + ): + continue + try: + datas_, binaries_, hidden_imports_ = PyInstaller.utils.hooks.collect_all( + requirement, + include_py_files=True + ) + except ImportError: + continue + datas += datas_ + # binaries += binaries_ + hidden_imports_ = set(hidden_imports_) + if "" in hidden_imports_: + hidden_imports_.remove("") + if None in hidden_imports_: + hidden_imports_.remove(None) + requirements |= hidden_imports_ - checked + hidden_imports |= hidden_imports_ + +if remove_tests: + hidden_imports = sorted( + [h for h in hidden_imports if "tests" not in h.split(".")] + ) +else: + hidden_imports = sorted(hidden_imports) + + +hidden_imports = [h for h in hidden_imports if "__pycache__" not in h] +datas = [d for d in datas if ("__pycache__" not in d[0]) and (d[1] not in [".", "Resources", "scripts"])] + +if sys.platform[:5] == "win32": + base_path = os.path.dirname(sys.executable) + library_path = os.path.join(base_path, "Library", "bin") + dll_path = os.path.join(base_path, "DLLs") + libcrypto_dll_path = os.path.join(dll_path, "libcrypto-1_1-x64.dll") + libssl_dll_path = os.path.join(dll_path, "libssl-1_1-x64.dll") + libcrypto_lib_path = os.path.join(library_path, "libcrypto-1_1-x64.dll") + libssl_lib_path = os.path.join(library_path, "libssl-1_1-x64.dll") + if not os.path.exists(libcrypto_dll_path): + datas.append((libcrypto_lib_path, ".")) + if not os.path.exists(libssl_dll_path): + datas.append((libssl_lib_path, ".")) + +a = Analysis( + [script_name], + pathex=[location], + binaries=binaries, + datas=datas, + hiddenimports=hidden_imports, + hookspath=[], + runtime_hooks=[], + excludes=[h for h in hidden_imports if "datashader" in h], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False +) +pyz = PYZ( + a.pure, + a.zipped_data, + cipher=block_cipher +) + +if sys.platform[:5] == "linux": + exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + name=bundle_name, + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, + upx_exclude=[], + icon=icon + ) +else: + exe = EXE( + pyz, + a.scripts, + # a.binaries, + a.zipfiles, + # a.datas, + exclude_binaries=True, + name=exe_name, + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, + icon=icon + ) + coll = COLLECT( + exe, + a.binaries, + # a.zipfiles, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name=exe_name + ) diff --git a/release/pyinstaller/structuremap_pyinstaller.py b/release/pyinstaller/structuremap_pyinstaller.py new file mode 100644 index 0000000..b3232e7 --- /dev/null +++ b/release/pyinstaller/structuremap_pyinstaller.py @@ -0,0 +1,13 @@ +if __name__ == "__main__": + try: + import structuremap.gui + import multiprocessing + multiprocessing.freeze_support() + structuremap.gui.run() + except e: + import traceback + import sys + exc_info = sys.exc_info() + # Display the *original* exception + traceback.print_exception(*exc_info) + input("Something went wrong, press any key to continue...") diff --git a/release/pypi/install_pypi_wheel.sh b/release/pypi/install_pypi_wheel.sh new file mode 100644 index 0000000..79e24f0 --- /dev/null +++ b/release/pypi/install_pypi_wheel.sh @@ -0,0 +1,5 @@ +conda create -n structuremap_pip_test python=3.8 -y +conda activate structuremap_pip_test +pip install "structuremap[stable]" +structuremap +conda deactivate diff --git a/release/pypi/install_test_pypi_wheel.sh b/release/pypi/install_test_pypi_wheel.sh new file mode 100644 index 0000000..6f9ac73 --- /dev/null +++ b/release/pypi/install_test_pypi_wheel.sh @@ -0,0 +1,5 @@ +conda create -n structuremap_pip_test python=3.8 -y +conda activate structuremap_pip_test +pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple "structuremap[stable]" +structuremap +conda deactivate diff --git a/release/pypi/prepare_pypi_wheel.sh b/release/pypi/prepare_pypi_wheel.sh new file mode 100644 index 0000000..4313aa7 --- /dev/null +++ b/release/pypi/prepare_pypi_wheel.sh @@ -0,0 +1,9 @@ +cd ../.. +conda create -n structuremap_pypi_wheel python=3.8 +conda activate structuremap_pypi_wheel +pip install twine +rm -rf dist +rm -rf build +python setup.py sdist bdist_wheel +twine check dist/* +conda deactivate diff --git a/requirements/requirements.txt b/requirements/requirements.txt new file mode 100644 index 0000000..77c1d85 --- /dev/null +++ b/requirements/requirements.txt @@ -0,0 +1 @@ +click==8.0.1 diff --git a/requirements/requirements_development.txt b/requirements/requirements_development.txt new file mode 100644 index 0000000..b2942cb --- /dev/null +++ b/requirements/requirements_development.txt @@ -0,0 +1,11 @@ +jupyter==1.0.0 +jupyter_contrib_nbextensions==0.5.1 +pyinstaller==4.2 +autodocsumm==0.2.6 +sphinx-rtd-theme==0.5.2 +twine==3.4.1 +bumpversion==0.6.0 +pipdeptree==2.1.0 +ipykernel==6.4.0 +tqdm==4.61.1 +psutil==5.8.0 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c97528f --- /dev/null +++ b/setup.py @@ -0,0 +1,72 @@ +#!python + +# builtin +import setuptools +import re +import os +# local +import structuremap as package2install + + +def get_long_description(): + with open("README.md", "r") as readme_file: + long_description = readme_file.read() + return long_description + + +def get_requirements(): + extra_requirements = {} + requirement_file_names = package2install.__extra_requirements__ + requirement_file_names[""] = "requirements.txt" + for extra, requirement_file_name in requirement_file_names.items(): + full_requirement_file_name = os.path.join( + "requirements", + requirement_file_name, + ) + with open(full_requirement_file_name) as requirements_file: + if extra != "": + extra_stable = f"{extra}-stable" + else: + extra_stable = "stable" + extra_requirements[extra_stable] = [] + extra_requirements[extra] = [] + for line in requirements_file: + extra_requirements[extra_stable].append(line) + requirement, *comparison = re.split("[><=~!]", line) + requirement == requirement.strip() + extra_requirements[extra].append(requirement) + requirements = extra_requirements.pop("") + return requirements, extra_requirements + + +def create_pip_wheel(): + requirements, extra_requirements = get_requirements() + setuptools.setup( + name=package2install.__project__, + version=package2install.__version__, + license=package2install.__license__, + description=package2install.__description__, + long_description=get_long_description(), + long_description_content_type="text/markdown", + author=package2install.__author__, + author_email=package2install.__author_email__, + url=package2install.__github__, + project_urls=package2install.__urls__, + keywords=package2install.__keywords__, + classifiers=package2install.__classifiers__, + packages=[package2install.__project__], + include_package_data=True, + entry_points={ + "console_scripts": package2install.__console_scripts__, + }, + install_requires=requirements + [ + # TODO Remove hardcoded requirement? + "pywin32==225; sys_platform=='win32'" + ], + extras_require=extra_requirements, + python_requires=package2install.__python_version__, + ) + + +if __name__ == "__main__": + create_pip_wheel() diff --git a/structuremap/__init__.py b/structuremap/__init__.py new file mode 100644 index 0000000..0774d0e --- /dev/null +++ b/structuremap/__init__.py @@ -0,0 +1,44 @@ +#!python + + +__project__ = "structuremap" +__version__ = "0.0.1" +__license__ = "Apache" +__description__ = "An open-source Python package of the AlphaPept ecosystem" +__author__ = "Mann Labs" +__author_email__ = "opensource@alphapept.com" +__github__ = "https://github.com/MannLabs/structuremap" +__keywords__ = [ + "bioinformatics", + "software", + "AlphaPept ecosystem", +] +__python_version__ = ">=3.8,<3.10" +__classifiers__ = [ + "Development Status :: 1 - Planning", + # "Development Status :: 2 - Pre-Alpha", + # "Development Status :: 3 - Alpha", + # "Development Status :: 4 - Beta", + # "Development Status :: 5 - Production/Stable", + # "Development Status :: 6 - Mature", + # "Development Status :: 7 - Inactive" + "Intended Audience :: Science/Research", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Topic :: Scientific/Engineering :: Bio-Informatics", +] +__console_scripts__ = [ + "structuremap=structuremap.cli:run", +] +__urls__ = { + "Mann Labs at MPIB": "https://www.biochem.mpg.de/mann", + "Mann Labs at CPR": "https://www.cpr.ku.dk/research/proteomics/mann/", + "GitHub": __github__, + # "ReadTheDocs": None, + # "PyPi": None, + # "Scientific paper": None, +} +__extra_requirements__ = { + "development": "requirements_development.txt", +} diff --git a/structuremap/cli.py b/structuremap/cli.py new file mode 100644 index 0000000..6e55e94 --- /dev/null +++ b/structuremap/cli.py @@ -0,0 +1,31 @@ +#!python + + +# external +import click + +# local +import structuremap + + +@click.group( + context_settings=dict( + help_option_names=['-h', '--help'], + ), + invoke_without_command=True +) +@click.pass_context +@click.version_option(structuremap.__version__, "-v", "--version") +def run(ctx, **kwargs): + name = f"structuremap {structuremap.__version__}" + click.echo("*" * (len(name) + 4)) + click.echo(f"* {name} *") + click.echo("*" * (len(name) + 4)) + if ctx.invoked_subcommand is None: + click.echo(run.get_help(ctx)) + + +@run.command("gui", help="Start graphical user interface.") +def gui(): + import structuremap.gui + structuremap.gui.run() diff --git a/structuremap/gui.py b/structuremap/gui.py new file mode 100644 index 0000000..231335d --- /dev/null +++ b/structuremap/gui.py @@ -0,0 +1,5 @@ +#!python + + +def run(): + raise NotImplementedError diff --git a/tests/run_tests.sh b/tests/run_tests.sh new file mode 100644 index 0000000..8b4ce8f --- /dev/null +++ b/tests/run_tests.sh @@ -0,0 +1,4 @@ +conda activate structuremap +python -m unittest test_cli +python -m unittest test_gui +conda deactivate diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..049afa1 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,11 @@ +#!python -m unittest tests.test_utils +"""This module provides unit tests for structuremap.cli.""" + +# builtin +import unittest + +# local +import structuremap.cli + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_gui.py b/tests/test_gui.py new file mode 100644 index 0000000..4361fa9 --- /dev/null +++ b/tests/test_gui.py @@ -0,0 +1,11 @@ +#!python -m unittest tests.test_utils +"""This module provides unit tests for structuremap.gui.""" + +# builtin +import unittest + +# local +import structuremap.gui + +if __name__ == "__main__": + unittest.main()