diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..6d5ce4f --- /dev/null +++ b/.flake8 @@ -0,0 +1,6 @@ +[flake8] +exclude = + .git, + __pycache__, + build +max-complexity = 10 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/ISSUE_TEMPLATE/user-story.md b/.github/ISSUE_TEMPLATE/user-story.md new file mode 100644 index 0000000..0a635a0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/user-story.md @@ -0,0 +1,27 @@ +--- +name: User story +about: This template provides a basic structure for user story issues. +title: '' +labels: '' +assignees: '' + +--- + +# User story +As a ..., I want to ..., so I can ... + +*Ideally, this is in the issue title, but if not, you can put it here. If so, delete this section.* + +# Acceptance criteria +- [ ] This is something that can be verified to show that this user story is satisfied. + +# Sprint Ready Checklist +- [ ] 1. Acceptance criteria defined +- [ ] 2. Team understands acceptance criteria +- [ ] 3. Team has defined solution / steps to satisfy acceptance criteria +- [ ] 4. Acceptance criteria is verifiable / testable +- [ ] 5. External / 3rd Party dependencies identified +- [ ] 6. Ticket is prioritized and sized + +# Notes +*Add any helpful notes here.* diff --git a/.github/workflows/init.yml b/.github/workflows/init.yml new file mode 100644 index 0000000..2aa5cad --- /dev/null +++ b/.github/workflows/init.yml @@ -0,0 +1,52 @@ +# Workflow runs only once when the template is first used. +# File can be safely deleted after repo is initialized. +name: Initialize repository +on: + push: + branches: + - main + +jobs: + initialize-package: + name: Initialize the package + if: ${{github.event.repository.name != 'aind-library-template'}} + runs-on: ubuntu-latest + env: + REPO_NAME: ${{ github.event.repository.name }} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Rename package + run: | + pkg_name=$(echo "${REPO_NAME}" | tr - _) + current_description='description = "Prints messages to stdout. Simple boilerplate for libraries."' + new_description='description = "Generated from aind-library-template"' + readme_description='Template for a minimal, basic repository for an AIND library.' + new_readme_description='Generated from aind-library-template' + echo "Package Name ${pkg_name}" + mkdir src/${pkg_name} + touch src/${pkg_name}/__init__.py + echo '"""Init package"""' >> src/${pkg_name}/__init__.py + echo '__version__ = "0.0.0"' >> src/${pkg_name}/__init__.py + sed -i "s/aind_library_template/${pkg_name}/" pyproject.toml + sed -i "s/aind-library-template/${REPO_NAME}/" pyproject.toml + sed -i "s/aind_library_template/${pkg_name}/" docs/source/conf.py + sed -i "s/${current_description}/${new_description}/" pyproject.toml + sed -i "/pandas/d" pyproject.toml + sed -i "s/aind-library-template/${REPO_NAME}/" README.md + sed -i "s/${readme_description}/${new_readme_description}/" README.md + - name: Commit changes + uses: EndBug/add-and-commit@v9 + with: + default_author: github_actions + message: "ci: version bump [skip actions]" + add: '["pyproject.toml", "README.md", "src/*", "docs/source/conf.py"]' + remove: '["-r src/aind_library_template", "tests/test_message_handler.py"]' + - name: Add first tag + run: | + git tag v0.0.0 + git push origin v0.0.0 + - name: Disable workflow + run: | + gh workflow disable -R $GITHUB_REPOSITORY "${{ github.workflow }}" diff --git a/.github/workflows/tag_and_publish.yml b/.github/workflows/tag_and_publish.yml new file mode 100644 index 0000000..6418422 --- /dev/null +++ b/.github/workflows/tag_and_publish.yml @@ -0,0 +1,89 @@ +name: Tag and publish +on: + push: + branches: + - main +# Remove line 65 to enable automated semantic version bumps. +# Change line 71 from "if: false" to "if: true" to enable PyPI publishing. +# Requires that svc-aindscicomp be added as an admin to repo. +jobs: + update_badges: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ env.DEFAULT_BRANCH }} + fetch-depth: 0 + token: ${{ secrets.SERVICE_TOKEN }} + - name: Set up Python 3.8 + uses: actions/setup-python@v3 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install -e .[dev] --no-cache-dir + - name: Get Python version and Update README.md + run: | + python_version=$(grep "requires-python" pyproject.toml | grep -o ">=[^\"]*") + python_badge=$(grep -o 'python-[^)]*' README.md) + new_python_badge="python-$python_version-blue?logo=python" + sed -i "s/$python_badge/$new_python_badge/g" README.md + - name: Get interrogate values and Update README.md + run: | + interrogate_val=$(interrogate . | grep -o 'actual: [0-9]*\.[0-9]*' | awk '{print $2}') + interrogate_badge=$(grep -o 'interrogate-[^)]*' README.md) + if (( $(echo "$interrogate_val >= 90.00" | bc -l) )); then + new_interrogate_badge="interrogate-$interrogate_val%25-brightgreen" + elif (( $(echo "$interrogate_val < 80.00" | bc -l) )); then + new_interrogate_badge="interrogate-$interrogate_val%25-red" + else + new_interrogate_badge="interrogate-$interrogate_val%25-yellow" + fi + sed -i "s/$interrogate_badge/$new_interrogate_badge/g" README.md + - name: Get Coverage values and Update README.md + run: | + coverage run -m unittest discover + coverage_val=$(coverage report | grep "^TOTAL" | grep -o '[0-9]\+%' | grep -o '[0-9]\+') + coverage_badge=$(grep -o "coverage-[^?]*" README.md) + if (( $(echo "$coverage_val >= 90.00" | bc -l) )); then + new_coverage_badge="coverage-$coverage_val%25-brightgreen" + elif (( $(echo "$coverage_val < 80.00" | bc -l) )); then + new_coverage_badge="coverage-$coverage_val%25-red" + else + new_coverage_badge="coverage-$coverage_val%25-yellow" + fi + sed -i "s/$coverage_badge/$new_coverage_badge/g" README.md + - name: Commit changes + uses: EndBug/add-and-commit@v9 + with: + default_author: github_actions + message: "ci: update badges [skip actions]" + add: '["README.md"]' + tag: + needs: update_badges + if: ${{github.event.repository.name == 'aind-library-template'}} + uses: AllenNeuralDynamics/aind-github-actions/.github/workflows/tag.yml@main + secrets: + SERVICE_TOKEN: ${{ secrets.SERVICE_TOKEN }} + publish: + needs: tag + if: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Pull latest changes + run: git pull origin main + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install dependencies + run: | + pip install --upgrade setuptools wheel twine build + python -m build + twine check dist/* + - name: Publish on PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.AIND_PYPI_TOKEN }} diff --git a/.github/workflows/test_and_lint.yml b/.github/workflows/test_and_lint.yml new file mode 100644 index 0000000..c8d832d --- /dev/null +++ b/.github/workflows/test_and_lint.yml @@ -0,0 +1,26 @@ +name: Lint and run tests + +on: + pull_request: + branches: + - main + +jobs: + ci: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ '3.8', '3.9', '3.10' ] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install -e .[dev] + - name: Run linter checks + run: flake8 . && interrogate --verbose . + - name: Run tests and coverage + run: coverage run -m unittest discover && coverage report diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06a56dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,139 @@ +# 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/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + +# MacOs +**/.DS_Store diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d8f2a22 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Allen Institute for Neural Dynamics + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2907b6c --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# aind-library-template + +[![License](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE) +![Code Style](https://img.shields.io/badge/code%20style-black-black) +[![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release) +![Interrogate](https://img.shields.io/badge/interrogate-100.0%25-brightgreen) +![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen?logo=codecov) +![Python](https://img.shields.io/badge/python->=3.7-blue?logo=python) + + + +## Usage + - To use this template, click the green `Use this template` button and `Create new repository`. + - After github initially creates the new repository, please wait an extra minute for the initialization scripts to finish organizing the repo. + - To enable the automatic semantic version increments: in the repository go to `Settings` and `Collaborators and teams`. Click the green `Add people` button. Add `svc-aindscicomp` as an admin. Modify the file in `.github/workflows/tag_and_publish.yml` and remove the if statement in line 65. The semantic version will now be incremented every time a code is committed into the main branch. + - To publish to PyPI, enable semantic versioning and uncomment the publish block in `.github/workflows/tag_and_publish.yml`. The code will now be published to PyPI every time the code is committed into the main branch. + - The `.github/workflows/test_and_lint.yml` file will run automated tests and style checks every time a Pull Request is opened. If the checks are undesired, the `test_and_lint.yml` can be deleted. The strictness of the code coverage level, etc., can be modified by altering the configurations in the `pyproject.toml` file and the `.flake8` file. + +## Installation +To use the software, in the root directory, run +```bash +pip install -e . +``` + +To develop the code, run +```bash +pip install -e .[dev] +``` + +## Contributing + +### Linters and testing + +There are several libraries used to run linters, check documentation, and run tests. + +- Please test your changes using the **coverage** library, which will run the tests and log a coverage report: + +```bash +coverage run -m unittest discover && coverage report +``` + +- Use **interrogate** to check that modules, methods, etc. have been documented thoroughly: + +```bash +interrogate . +``` + +- Use **flake8** to check that code is up to standards (no unused imports, etc.): +```bash +flake8 . +``` + +- Use **black** to automatically format the code into PEP standards: +```bash +black . +``` + +- Use **isort** to automatically sort import statements: +```bash +isort . +``` + +### Pull requests + +For internal members, please create a branch. For external members, please fork the repository and open a pull request from the fork. We'll primarily use [Angular](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit) style for commit messages. Roughly, they should follow the pattern: +```text +(): +``` + +where scope (optional) describes the packages affected by the code changes and type (mandatory) is one of: + +- **build**: Changes that affect build tools or external dependencies (example scopes: pyproject.toml, setup.py) +- **ci**: Changes to our CI configuration files and scripts (examples: .github/workflows/ci.yml) +- **docs**: Documentation only changes +- **feat**: A new feature +- **fix**: A bugfix +- **perf**: A code change that improves performance +- **refactor**: A code change that neither fixes a bug nor adds a feature +- **test**: Adding missing tests or correcting existing tests + +### Semantic Release + +The table below, from [semantic release](https://github.com/semantic-release/semantic-release), shows which commit message gets you which release type when `semantic-release` runs (using the default configuration): + +| Commit message | Release type | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | +| `fix(pencil): stop graphite breaking when too much pressure applied` | ~~Patch~~ Fix Release, Default release | +| `feat(pencil): add 'graphiteWidth' option` | ~~Minor~~ Feature Release | +| `perf(pencil): remove graphiteWidth option`

`BREAKING CHANGE: The graphiteWidth option has been removed.`
`The default graphite width of 10mm is always used for performance reasons.` | ~~Major~~ Breaking Release
(Note that the `BREAKING CHANGE: ` token must be in the footer of the commit) | + +### Documentation +To generate the rst files source files for documentation, run +```bash +sphinx-apidoc -o docs/source/ src +``` +Then to create the documentation HTML files, run +```bash +sphinx-build -b html docs/source/ docs/build/html +``` +More info on sphinx installation can be found [here](https://www.sphinx-doc.org/en/master/usage/installation.html). diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..dc1312a --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/_static/dark-logo.svg b/docs/source/_static/dark-logo.svg new file mode 100644 index 0000000..dcc68fb --- /dev/null +++ b/docs/source/_static/dark-logo.svg @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/_static/favicon.ico b/docs/source/_static/favicon.ico new file mode 100644 index 0000000..4cec150 Binary files /dev/null and b/docs/source/_static/favicon.ico differ diff --git a/docs/source/_static/light-logo.svg b/docs/source/_static/light-logo.svg new file mode 100644 index 0000000..b20cb67 --- /dev/null +++ b/docs/source/_static/light-logo.svg @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..0462fbc --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,53 @@ +"""Configuration file for the Sphinx documentation builder.""" +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +from datetime import date + +# -- Path Setup -------------------------------------------------------------- +from os.path import abspath, dirname +from pathlib import Path + +from aind_library_template import __version__ as package_version + +INSTITUTE_NAME = "Allen Institute for Neural Dynamics" + +current_year = date.today().year + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = Path(dirname(dirname(dirname(abspath(__file__))))).name +copyright = f"{current_year}, {INSTITUTE_NAME}" +author = INSTITUTE_NAME +release = package_version + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx.ext.duration", + "sphinx.ext.doctest", + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", +] +templates_path = ["_templates"] +exclude_patterns = [] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "furo" +html_static_path = ["_static"] +html_favicon = "_static/favicon.ico" +html_theme_options = { + "light_logo": "light-logo.svg", + "dark_logo": "dark-logo.svg", +} + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +html_show_sphinx = False + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +html_show_copyright = False diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..07adcad --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,22 @@ +.. Doc Template documentation master file, created by + sphinx-quickstart on Wed Aug 17 15:36:32 2022. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + + +Welcome to this repository's documentation! +=========================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + modules + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..eebfca1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,80 @@ +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[project] +name = "aind-library-template" +description = "Prints messages to stdout. Simple boilerplate for libraries." +license = {text = "MIT"} +requires-python = ">=3.7" +authors = [ + {name = "Allen Institute for Neural Dynamics"} +] +classifiers = [ + "Programming Language :: Python :: 3" +] +readme = "README.md" +dynamic = ["version"] + +dependencies = [ + 'pandas' +] + +[project.optional-dependencies] +dev = [ + 'black', + 'coverage', + 'flake8', + 'interrogate', + 'isort', + 'Sphinx', + 'furo' +] + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.dynamic] +version = {attr = "aind_library_template.__version__"} + +[tool.black] +line-length = 79 +target_version = ['py37'] +exclude = ''' + +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | build + | dist + )/ + | .gitignore +) +''' + +[tool.coverage.run] +omit = ["*__init__*"] +source = ["aind_library_template", "tests"] + +[tool.coverage.report] +exclude_lines = [ + "if __name__ == .__main__.:", + "from", + "import", + "pragma: no cover" +] +fail_under = 100 + +[tool.isort] +line_length = 79 +profile = "black" + +[tool.interrogate] +exclude = ["setup.py", "docs", "build"] +fail-under = 100 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..7f1a176 --- /dev/null +++ b/setup.py @@ -0,0 +1,4 @@ +from setuptools import setup + +if __name__ == "__main__": + setup() diff --git a/src/aind_library_template/__init__.py b/src/aind_library_template/__init__.py new file mode 100644 index 0000000..9a044dd --- /dev/null +++ b/src/aind_library_template/__init__.py @@ -0,0 +1,3 @@ +"""Simple package to demo project structure. +""" +__version__ = "0.3.5" diff --git a/src/aind_library_template/message_handlers.py b/src/aind_library_template/message_handlers.py new file mode 100644 index 0000000..f98cc7d --- /dev/null +++ b/src/aind_library_template/message_handlers.py @@ -0,0 +1,67 @@ +""" Example module for a basic class with methods and doctests. We use Numpy +docstring format.""" + +import logging +import re + +import pandas as pd + + +class MessageHandler: + """ + Small class to handle messages. + """ + + def __init__(self, msg: str): + """ + Initialize class with a msg + Parameters + ---------- + msg : str + """ + self.msg = msg + + @staticmethod + def _to_screaming_snake_case(input_str: str) -> str: + """ + Converts a string to SCREAMING_SNAKE_CASE + >>> MessageHandler._to_screaming_snake_case("hello world!") + 'HELLO_WORLD' + + Parameters + ---------- + input_str : str + + Returns + ------- + str + The input_str in SCREAMING_SNAKE_CASE format + + """ + new_str = re.sub(r"[^\w\s]", "", input_str).strip() + new_str = re.sub(r"(\s|_|-)+", "_", new_str.upper()) + return new_str + + def log_msg(self): + """Simply logs the message.""" + logging.info(self.msg) + + def msg_as_df(self, col_name: str = "message") -> pd.DataFrame: + """ + Returns message as a dataframe. + >>> msg_handler = MessageHandler('hello world') + >>> msg_handler.msg_as_df(col_name='msg') + msg + 0 hello world + + Parameters + ---------- + col_name : str + Custom column name for the pandas data frame. 'message' by default. + + Returns + ------- + pd.DataFrame + + """ + return pd.DataFrame.from_dict({col_name: [self.msg]}) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..816e430 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Testing library""" diff --git a/tests/test_example.py b/tests/test_example.py new file mode 100644 index 0000000..06e9e0d --- /dev/null +++ b/tests/test_example.py @@ -0,0 +1,16 @@ +"""Example test template.""" + +import unittest + + +class ExampleTest(unittest.TestCase): + """Example Test Class""" + + def test_assert_example(self): + """Example of how to test the truth of a statement.""" + + self.assertTrue(1 == 1) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_message_handler.py b/tests/test_message_handler.py new file mode 100644 index 0000000..221bc1c --- /dev/null +++ b/tests/test_message_handler.py @@ -0,0 +1,62 @@ +"""Tests aind_library_template printers methods.""" + +import doctest +import unittest + +import pandas as pd + +from aind_library_template import message_handlers +from aind_library_template.message_handlers import MessageHandler + + +class MessageHandlerTest(unittest.TestCase): + """Tests MessageHandler methods.""" + + @classmethod + def setUpClass(cls): + """Set up the Test class with basic attributes that can be shared.""" + my_msg = "Hello World!!" + cls.my_msg = my_msg + cls.msg_handler = MessageHandler(my_msg) + + def test_to_screaming_snake_case(self): + """Tests _to_screaming_snake_case method.""" + input_string = "! a random message 012 %" + expected_output = "A_RANDOM_MESSAGE_012" + actual_output = self.msg_handler._to_screaming_snake_case(input_string) + self.assertEqual(expected_output, actual_output) + + def test_log_msg(self): + """Tests that the log_msg method logs a message.""" + with self.assertLogs() as captured: + self.msg_handler.log_msg() + + self.assertEqual(len(captured.records), 1) + self.assertEqual(captured.records[0].getMessage(), self.my_msg) + + def test_msg_as_df(self): + """Tests that the message gets returned as a pandas DataFrame.""" + + # df from msg with default col_name + df1 = self.msg_handler.msg_as_df() + # df from msg with non-default col_name + df2 = self.msg_handler.msg_as_df(col_name="non_default") + + # Expected outputs + expected_df1 = pd.DataFrame.from_dict({"message": [self.my_msg]}) + expected_df2 = pd.DataFrame.from_dict({"non_default": [self.my_msg]}) + + self.assertTrue(df1.equals(expected_df1)) + self.assertTrue(df2.equals(expected_df2)) + self.assertTrue(not df1.equals(expected_df2)) + + +# It's possible to run doctests the same time unit tests are run by adding this +def load_tests(_loader, tests, _ignore): + """Add this to run doctests in module""" + tests.addTests(doctest.DocTestSuite(message_handlers)) + return tests + + +if __name__ == "__main__": + unittest.main()