From 00247e5f7e7ab53bc9da484117a3854e3fd4905d Mon Sep 17 00:00:00 2001 From: akatief Date: Fri, 9 Feb 2024 18:41:51 +0100 Subject: [PATCH] chore: update files & change repo references --- .github/FUNDING.yml | 12 - .github/release_message.sh | 3 - .github/rename_project.sh | 10 +- .github/template.yml | 1 - .github/workflows/docs.yml | 31 +++ .github/workflows/main.yml | 135 ++++++----- .github/workflows/release.yml | 50 ---- .github/workflows/rename_project.yml | 6 +- .github/workflows/tests.yml | 40 ++++ .gitignore | 3 + ABOUT_THIS_TEMPLATE.md | 217 ++++++----------- CONTRIBUTING.md | 113 --------- Containerfile | 5 - HISTORY.md | 13 - LICENSE | 225 ++++++++++++++++-- MANIFEST.in | 5 - Makefile | 122 ---------- NOTICE.txt | 9 + README.md | 133 ++++++----- docs/index.md | 4 + logo.png | Bin 0 -> 106645 bytes mkdocs.yml | 17 +- my_project_template/__init__.py | 5 + my_project_template/__main__.py | 6 + my_project_template/base.py | 56 +++++ {project_name => my_project_template}/cli.py | 4 +- project_name/VERSION | 1 - project_name/__init__.py | 0 project_name/__main__.py | 6 - project_name/base.py | 17 -- requirements-test.txt => requirements-dev.txt | 6 +- requirements.txt | 4 +- setup.py | 14 +- tests/conftest.py | 3 + tests/test_base.py | 14 +- 35 files changed, 622 insertions(+), 668 deletions(-) delete mode 100644 .github/FUNDING.yml delete mode 100755 .github/release_message.sh delete mode 100644 .github/template.yml create mode 100644 .github/workflows/docs.yml delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/tests.yml delete mode 100644 CONTRIBUTING.md delete mode 100644 Containerfile delete mode 100644 HISTORY.md delete mode 100644 MANIFEST.in delete mode 100644 Makefile create mode 100644 NOTICE.txt create mode 100644 logo.png create mode 100644 my_project_template/__init__.py create mode 100644 my_project_template/__main__.py create mode 100644 my_project_template/base.py rename {project_name => my_project_template}/cli.py (84%) delete mode 100644 project_name/VERSION delete mode 100644 project_name/__init__.py delete mode 100644 project_name/__main__.py delete mode 100644 project_name/base.py rename requirements-test.txt => requirements-dev.txt (51%) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 1e9d2a1..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: [rochacbruno] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/release_message.sh b/.github/release_message.sh deleted file mode 100755 index f5a9062..0000000 --- a/.github/release_message.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -previous_tag=$(git tag --sort=-creatordate | sed -n 2p) -git shortlog "${previous_tag}.." | sed 's/^./ &/' diff --git a/.github/rename_project.sh b/.github/rename_project.sh index 8f05495..dfd7ce0 100755 --- a/.github/rename_project.sh +++ b/.github/rename_project.sh @@ -16,10 +16,10 @@ echo "Description: $description"; echo "Renaming project..." -original_author="author_name" -original_name="project_name" -original_urlname="project_urlname" -original_description="project_description" +original_author="UKPLab" +original_name="ukp-project-template" +original_urlname="ukp-project-template" +original_description="The official template for new Python projects at UKP Lab" # for filename in $(find . -name "*.*") for filename in $(git ls-files) do @@ -30,7 +30,7 @@ do echo "Renamed $filename" done -mv project_name $name +mv ukp_project_template $name # This command runs only once on GHA! rm -rf .github/template.yml diff --git a/.github/template.yml b/.github/template.yml deleted file mode 100644 index 3386bee..0000000 --- a/.github/template.yml +++ /dev/null @@ -1 +0,0 @@ -author: rochacbruno diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..45c94f6 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,31 @@ +name: Build Docs + +on: + push: + branches: + - main # Change this to your main branch name + pull_request: + branches: + - main # Change this to your main branch name + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 # Change this to your Python version + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + + - name: Build Docs + run: | + python -m mkdocs build --verbose diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5e82e08..6be451c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,5 +1,4 @@ # This is a basic workflow to help you get started with Actions - name: CI # Controls when the workflow will run @@ -14,79 +13,83 @@ on: workflow_dispatch: jobs: + + check-files: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check files + run: | + # Define the list of filenames you want to check + FILES_TO_CHECK=("LICENSE" "README.md" "requirements.txt" "requirements-dev.txt" "NOTICE.txt" "setup.py") + + # Initialize a variable to track missing files + MISSING_FILES=() + + # Check if each file exists in the root directory + for FILE in "${FILES_TO_CHECK[@]}"; do + if [ ! -f "$FILE" ]; then + MISSING_FILES+=("$FILE") + fi + done + + # Output the missing files + if [ ${#MISSING_FILES[@]} -eq 0 ]; then + echo "All files exist." + else + echo "The following files are missing:" + for MISSING_FILE in "${MISSING_FILES[@]}"; do + echo "- $MISSING_FILE" + done + echo "::error::One or more files are missing." + exit 1 + fi + linter: - strategy: - fail-fast: false - matrix: - python-version: [3.9] - os: [ubuntu-latest] - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest + needs: check-files steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install project - run: make install - - name: Run linter - run: make lint + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + pip install -r requirements-dev.txt + + - name: Analysing the code with pylint + run: | + pylint --disable=trailing-whitespace,missing-class-docstring,missing-final-newline,trailing-newlines \ + --fail-under=9.0 \ + $(git ls-files '*.py') || echo "::warning::Pylint check failed, but the workflow will continue." - tests_linux: - needs: linter + tests: + needs: check-files strategy: fail-fast: false matrix: - python-version: [3.9] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install project - run: make install - - name: Run tests - run: make test - - name: "Upload coverage to Codecov" - uses: codecov/codecov-action@v3 - # with: - # fail_ci_if_error: true + - name: Checkout repository + uses: actions/checkout@v4 - tests_mac: - needs: linter - strategy: - fail-fast: false - matrix: - python-version: [3.9] - os: [macos-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install project - run: make install - - name: Run tests - run: make test + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + pip install . + pip install -r requirements-dev.txt - tests_win: - needs: linter - strategy: - fail-fast: false - matrix: - python-version: [3.9] - os: [windows-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install Pip - run: pip install --user --upgrade pip - - name: Install project - run: pip install -e .[test] - - name: run tests - run: pytest -s -vvvv -l --tb=long tests + - name: Run tests + run: | + pytest -v --cov-fail-under=60 --cov=ukp_project_template -l --tb=short --maxfail=1 tests/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index c42c994..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Upload Python Package - -on: - push: - # Sequence of patterns matched against refs/tags - tags: - - '*' # Push events to matching v*, i.e. v1.0, v20.15.10 - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -jobs: - release: - name: Create Release - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v3 - with: - # by default, it uses a depth of 1 - # this fetches all history so that we can read each commit - fetch-depth: 0 - - name: Generate Changelog - run: .github/release_message.sh > release_message.md - - name: Release - uses: softprops/action-gh-release@v1 - with: - body_path: release_message.md - - deploy: - needs: release - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* diff --git a/.github/workflows/rename_project.yml b/.github/workflows/rename_project.yml index ae6d2eb..4cef27e 100644 --- a/.github/workflows/rename_project.yml +++ b/.github/workflows/rename_project.yml @@ -6,10 +6,10 @@ permissions: write-all jobs: rename-project: - if: ${{ !contains (github.repository, '/python-project-template') }} + if: ${{ !contains (github.repository, '/ukp-project-template') }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # by default, it uses a depth of 1 # this fetches all history so that we can read each commit @@ -35,7 +35,7 @@ jobs: echo "Renaming the project with -a(author) ${{ env.REPOSITORY_OWNER }} -n(name) ${{ env.REPOSITORY_NAME }} -u(urlname) ${{ env.REPOSITORY_URLNAME }}" .github/rename_project.sh -a ${{ env.REPOSITORY_OWNER }} -n ${{ env.REPOSITORY_NAME }} -u ${{ env.REPOSITORY_URLNAME }} -d "Awesome ${{ env.REPOSITORY_NAME }} created by ${{ env.REPOSITORY_OWNER }}" - - uses: stefanzweifel/git-auto-commit-action@v4 + - uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "βœ… Ready to clone and code." # commit_options: '--amend --no-edit' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..f3108ea --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,40 @@ +# This is a basic workflow to help you get started with Actions +name: Run tests + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the main branch + push: + branches: [ main ] + pull_request: + branches: [ main ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + tests: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + pip install . + pip install -r requirements-dev.txt + + - name: Run tests + run: | + pytest -v --cov-fail-under=60 --cov=ukp_project_template -l --tb=short --maxfail=1 tests/ + coverage xml + coverage html \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2d0fadb..211b061 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,6 @@ dmypy.json # templates .github/templates/* + +# History +.history \ No newline at end of file diff --git a/ABOUT_THIS_TEMPLATE.md b/ABOUT_THIS_TEMPLATE.md index 11795f3..7eabfb4 100644 --- a/ABOUT_THIS_TEMPLATE.md +++ b/ABOUT_THIS_TEMPLATE.md @@ -1,130 +1,107 @@ # About this template -Hi, I created this template to help you get started with a new project. +Hi, I've adapted this template from the excellent [python-project-template](https://github.com/rochacbruno/python-project-template/) by [rochacbruno](https://github.com/rochacbruno). It was created having in mind UKP Lab people and what the most common use-cases would be. Following its structure you'll get into developing your next paper in no time! -I have created and maintained a number of python libraries, applications and -frameworks and during those years I have learned a lot about how to create a -project structure and how to structure a project to be as modular and simple -as possible. +It includes: -Some decisions I have made while creating this template are: +- πŸ“¦ A basic [setup.py](setup.py) file to provide installation, packaging and distribution for your project. + Template uses setuptools because it's the de-facto standard for Python packages +- πŸ“ƒ Documentation structure using [mkdocs](http://www.mkdocs.org) +- πŸ§ͺ Testing structure using [pytest](https://docs.pytest.org/en/latest/) +- βœ… Code linting using [pylint](https://pypi.org/project/pylint/) +- 🎯 Entry points to execute your program using `python -m ` with basic CLI argument parsing. +- πŸ”„ Continuous integration using [Github Actions](https://github.com/UKPLab/ukp-project-template/actions) with jobs to check, lint and test your project. - - Create a project structure that is as modular as possible. - - Keep it simple and easy to maintain. - - Allow for a lot of flexibility and customizability. - - Low dependency (this template doesn't add dependencies) +Are there any changes you'd like to request? Feel free to fork and open a pull request! ## Structure Lets take a look at the structure of this template: ```text -β”œβ”€β”€ Containerfile # The file to build a container using buildah or docker -β”œβ”€β”€ CONTRIBUTING.md # Onboarding instructions for new contributors -β”œβ”€β”€ docs # Documentation site (add more .md files here) -β”‚Β Β  └── index.md # The index page for the docs site -β”œβ”€β”€ .github # Github metadata for repository -β”‚Β Β  β”œβ”€β”€ release_message.sh # A script to generate a release message -β”‚Β Β  └── workflows # The CI pipeline for Github Actions -β”œβ”€β”€ .gitignore # A list of files to ignore when pushing to Github -β”œβ”€β”€ HISTORY.md # Auto generated list of changes to the project -β”œβ”€β”€ LICENSE # The license for the project -β”œβ”€β”€ Makefile # A collection of utilities to manage the project -β”œβ”€β”€ MANIFEST.in # A list of files to include in a package -β”œβ”€β”€ mkdocs.yml # Configuration for documentation site -β”œβ”€β”€ project_name # The main python package for the project -β”‚Β Β  β”œβ”€β”€ base.py # The base module for the project -β”‚Β Β  β”œβ”€β”€ __init__.py # This tells Python that this is a package -β”‚Β Β  β”œβ”€β”€ __main__.py # The entry point for the project -β”‚Β Β  └── VERSION # The version for the project is kept in a static file -β”œβ”€β”€ README.md # The main readme for the project -β”œβ”€β”€ setup.py # The setup.py file for installing and packaging the project -β”œβ”€β”€ requirements.txt # An empty file to hold the requirements for the project -β”œβ”€β”€ requirements-test.txt # List of requirements for testing and devlopment -β”œβ”€β”€ setup.py # The setup.py file for installing and packaging the project -└── tests # Unit tests for the project (add mote tests files here) - β”œβ”€β”€ conftest.py # Configuration, hooks and fixtures for pytest - β”œβ”€β”€ __init__.py # This tells Python that this is a test package - └── test_base.py # The base test case for the project +β”‚ .gitignore # A list of files to ignore when pushing to GH +β”‚ ABOUT_THIS_TEMPLATE.md # The file you're reading right now +β”‚ LICENSE # The license for the project +β”‚ mkdocs.yml # Configuration for documentation site +β”‚ NOTICE.txt # Legal notice for the repository +β”‚ README.md # The main readme for the project +β”‚ requirements-dev.txt # List of requirements for testing and devlopment +β”‚ requirements.txt # An empty file to hold the requirements for the project +β”‚ setup.py # The setup.py file for installing and packaging the project +β”‚ +β”œβ”€β”€β”€.github # Github metadata for repository +β”‚ β”‚ dependabot.yml # Dependabot workflow for updating requirements +β”‚ β”‚ init.sh # Initializes the repository +β”‚ β”‚ PULL_REQUEST_TEMPLATE.md # Used automatically by GH for pull requests +β”‚ β”‚ rename_project.sh # Called once at repository creation +β”‚ β”‚ +β”‚ β”œβ”€β”€β”€ISSUE_TEMPLATE # Templates for creating issues on GH +β”‚ β”‚ +β”‚ └───workflows # GH Actions folder +β”‚ docs.yml # Builds documentation automatically +β”‚ main.yml # Runs install and file checks +β”‚ rename_project.yml # Renames repository at creation +β”‚ tests.yml # Run all tests in 'tests' folder +β”‚ +β”œβ”€β”€β”€docs # Auto-generated documentation +β”‚ index.md # Landing page of docs +β”‚ +β”œβ”€β”€β”€ukp_project_template # The main python package for the project +β”‚ base.py # The base module for the project +β”‚ cli.py # Defines CLI instructions +β”‚ __init__.py # This tells Python that this is a package +β”‚ __main__.py # The entry point for the project +β”‚ +└───tests # Unit tests for the project (add more tests files here) + conftest.py # Configuration, hooks and fixtures for pytest + test_base.py # The base test case for the project + __init__.py # This tells Python that this is a test package ``` -## FAQ +## FAQs -Frequent asked questions. -### Why this template is not using [Poetry](https://python-poetry.org/) ? +### Where should I add new stuff ? -I really like Poetry and I think it is a great tool to manage your python projects, -if you want to switch to poetry, you can run `make switch-to-poetry`. +You should create new files and subpackages inside ukp_project_template and implement your functionalities there. Remember to add what you write to `__init__.py` so that the imports work smoothly. Take a look at `base.py` and `__init__.py` to understand how it works -But for this template I wanted to keep it simple. - -Setuptools is the most simple and well supported way of packaging a Python project, -it doesn't require extra dependencies and is the easiest way to install the project. - -Also, poetry doesn't have a good support for installing projects in development mode yet. - -### Why the `requirements.txt` is empty ? +### Why is `requirements.txt` empty ? This template is a low dependency project, so it doesn't have any extra dependencies. -You can add new dependencies as you will or you can use the `make init` command to -generate a `requirements.txt` file based on the template you choose `flask, fastapi, click etc`. - -### Why there is a `requirements-test.txt` file ? - -This file lists all the requirements for testing and development, -I think the development environment and testing environment should be as similar as possible. - -Except those tools that are up to the developer choice (like ipython, ipdb etc). - -### Why the template doesn't have a `pyproject.toml` file ? - -It is possible to run `pip install https://github.com/name/repo/tarball/main` and -have pip to download the package direcly from Git repo. - -For that to work you need to have a `setup.py` file, and `pyproject.toml` is not -supported for that kind of installation. - -I think it is easier for example you want to install specific branch or tag you can -do `pip install https://github.com/name/repo/tarball/{TAG|REVISON|COMMIT}` - -People automating CI for your project will be grateful for having a setup.py file +You can freely add new dependencies. -### Why isn't this template made as a cookiecutter template? +You should put here everything needed to replicate your work. +Testing, linting, and other requirements used only in development should go in `requirements-dev.txt` -I really like [cookiecutter](https://github.com/cookiecutter/cookiecutter) and it is a great way to create new projects, -but for this template I wanted to use the Github `Use this template` button, -to use this template doesn't require to install extra tooling such as cookiecutter. +### Why is there a `requirements-dev.txt` file ? -Just click on [Use this template](https://github.com/rochacbruno/python-project-template/generate) and you are good to go. +This file lists all the requirements for testing and development. Use it to separate things you used during development from the essential stuff needed to replicate your work. -The substituions are done using github actions and a simple sed script. +### What is the `.github` folder? -### Why `VERSION` is kept in a static plain text file? +It contains [GitHub Actions](https://docs.github.com/en/actions) that are executed automatically when pushing your code. You can see results for your repository [here](https://github.com/UKPLab/ukp-project-template/actions). -I used to have my version inside my main module in a `__version__` variable, then -I had to do some tricks to read that version variable inside the setuptools -`setup.py` file because that would be available only after the installation. +### What does the linter workflow do? -I decided to keep the version in a static file because it is easier to read from -wherever I want without the need to install the package. +It checks whether your code is clean enough from duplication, inconsistencies, violations to the naming convention etc. +It's not supposed to fail, but you should still look into it to get an idea of which parts of your code may need adjustments. -e.g: `cat project_name/VERSION` will get the project version without harming -with module imports or anything else, it is useful for CI, logs and debugging. +### Why do automated actions fail ? -### Why to include `tests`, `history` and `Containerfile` as part of the release? +This means there is something wrong in the files/tests/requirements. +Click on the failing run to read more details. -The `MANIFEST.in` file is used to include the files in the release, once the -project is released to PyPI all the files listed on MANIFEST.in will be included -even if the files are static or not related to Python. +### Why include `tests` and `docs` as part of the release? -Some build systems such as RPM, DEB, AUR for some Linux distributions, and also -internal repackaging systems tends to run the tests before the packaging is performed. +This template ships with everything you may need. You can remove what you don't like in this way: + - If you don't need automatic documentation generation, you can delete folder `docs`, file `.github\workflows\docs.yml` and `mkdocs.yml` + - If you don't want automatic testing, you can delete folder `tests` and file `.github\workflows\tests.yml` -The Containerfile can be useful to provide a safer execution environment for -the project when running on a testing environment. +### How can I use pytest & pylint to check my code? -I added those files to make it easier for packaging in different formats. +Command `pytest` called from the project folder will run all tests inside the `tests` folder. +Similarly, `pylint` will run linting checks on your code and give you a status report. +It checks things such as logic, formatting, correct imports, duplication etc. ### Why conftest includes a go_to_tmpdir fixture? @@ -145,54 +122,4 @@ pre-commit is an excellent tool to automate checks and formatting on your code. However I figured out that pre-commit adds extra dependency and it an entry barrier for new contributors. -Having the linting, checks and formatting as simple commands on the [Makefile](Makefile) -makes it easier to undestand and change. - Once the project is bigger and complex, having pre-commit as a dependency can be a good idea. - -### Why the CLI is not using click? - -I wanted to provide a simple template for a CLI application on the project main entry point -click and typer are great alternatives but are external dependencies and this template -doesn't add dependencies besides those used for development. - -### Why this doesn't provide a full example of application using Flask or Django? - -as I said before, I want it to be simple and multipurpose, so I decided to not include -external dependencies and programming design decisions. - -It is up to you to decide if you want to use Flask or Django and to create your application -the way you think is best. - -This template provides utilities in the Makefile to make it easier to you can run: - -```bash -$ make init -Which template do you want to apply? [flask, fastapi, click, typer]? > flask -Generating a new project with Flask ... -``` - -Then the above will download the Flask template and apply it to the project. - -## The Makefile - -All the utilities for the template and project are on the Makefile - -```bash -❯ make -Usage: make - -Targets: -help: ## Show the help. -install: ## Install the project in dev mode. -fmt: ## Format code using black & isort. -lint: ## Run pep8, black, mypy linters. -test: lint ## Run tests and generate coverage report. -watch: ## Run tests on every change. -clean: ## Clean unused files. -virtualenv: ## Create a virtual environment. -release: ## Create a new tag for release. -docs: ## Build the documentation. -switch-to-poetry: ## Switch to poetry package manager. -init: ## Initialize the project based on an application template. -``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 0d0dd72..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,113 +0,0 @@ -# How to develop on this project - -project_name welcomes contributions from the community. - -**You need PYTHON3!** - -This instructions are for linux base systems. (Linux, MacOS, BSD, etc.) -## Setting up your own fork of this repo. - -- On github interface click on `Fork` button. -- Clone your fork of this repo. `git clone git@github.com:YOUR_GIT_USERNAME/project_urlname.git` -- Enter the directory `cd project_urlname` -- Add upstream repo `git remote add upstream https://github.com/author_name/project_urlname` - -## Setting up your own virtual environment - -Run `make virtualenv` to create a virtual environment. -then activate it with `source .venv/bin/activate`. - -## Install the project in develop mode - -Run `make install` to install the project in develop mode. - -## Run the tests to ensure everything is working - -Run `make test` to run the tests. - -## Create a new branch to work on your contribution - -Run `git checkout -b my_contribution` - -## Make your changes - -Edit the files using your preferred editor. (we recommend VIM or VSCode) - -## Format the code - -Run `make fmt` to format the code. - -## Run the linter - -Run `make lint` to run the linter. - -## Test your changes - -Run `make test` to run the tests. - -Ensure code coverage report shows `100%` coverage, add tests to your PR. - -## Build the docs locally - -Run `make docs` to build the docs. - -Ensure your new changes are documented. - -## Commit your changes - -This project uses [conventional git commit messages](https://www.conventionalcommits.org/en/v1.0.0/). - -Example: `fix(package): update setup.py arguments πŸŽ‰` (emojis are fine too) - -## Push your changes to your fork - -Run `git push origin my_contribution` - -## Submit a pull request - -On github interface, click on `Pull Request` button. - -Wait CI to run and one of the developers will review your PR. -## Makefile utilities - -This project comes with a `Makefile` that contains a number of useful utility. - -```bash -❯ make -Usage: make - -Targets: -help: ## Show the help. -install: ## Install the project in dev mode. -fmt: ## Format code using black & isort. -lint: ## Run pep8, black, mypy linters. -test: lint ## Run tests and generate coverage report. -watch: ## Run tests on every change. -clean: ## Clean unused files. -virtualenv: ## Create a virtual environment. -release: ## Create a new tag for release. -docs: ## Build the documentation. -switch-to-poetry: ## Switch to poetry package manager. -init: ## Initialize the project based on an application template. -``` - -## Making a new release - -This project uses [semantic versioning](https://semver.org/) and tags releases with `X.Y.Z` -Every time a new tag is created and pushed to the remote repo, github actions will -automatically create a new release on github and trigger a release on PyPI. - -For this to work you need to setup a secret called `PIPY_API_TOKEN` on the project settings>secrets, -this token can be generated on [pypi.org](https://pypi.org/account/). - -To trigger a new release all you need to do is. - -1. If you have changes to add to the repo - * Make your changes following the steps described above. - * Commit your changes following the [conventional git commit messages](https://www.conventionalcommits.org/en/v1.0.0/). -2. Run the tests to ensure everything is working. -4. Run `make release` to create a new tag and push it to the remote repo. - -the `make release` will ask you the version number to create the tag, ex: type `0.1.1` when you are asked. - -> **CAUTION**: The make release will change local changelog files and commit all the unstaged changes you have. diff --git a/Containerfile b/Containerfile deleted file mode 100644 index 83bb605..0000000 --- a/Containerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM python:3.7-slim -COPY . /app -WORKDIR /app -RUN pip install . -CMD ["project_name"] diff --git a/HISTORY.md b/HISTORY.md deleted file mode 100644 index 9bf6ef0..0000000 --- a/HISTORY.md +++ /dev/null @@ -1,13 +0,0 @@ -Changelog -========= - - -0.1.2 (2021-08-14) ------------------- -- Fix release, README and windows CI. [Bruno Rocha] -- Release: version 0.1.0. [Bruno Rocha] - - -0.1.0 (2021-08-14) ------------------- -- Add release command. [Bruno Rocha] diff --git a/LICENSE b/LICENSE index fdddb29..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,24 +1,201 @@ -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -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 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. - -For more information, please refer to + 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 [yyyy] [name of copyright owner] + + 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 deleted file mode 100644 index ef198d6..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include LICENSE -include HISTORY.md -include Containerfile -graft tests -graft project_name diff --git a/Makefile b/Makefile deleted file mode 100644 index 52d91ac..0000000 --- a/Makefile +++ /dev/null @@ -1,122 +0,0 @@ -.ONESHELL: -ENV_PREFIX=$(shell python -c "if __import__('pathlib').Path('.venv/bin/pip').exists(): print('.venv/bin/')") -USING_POETRY=$(shell grep "tool.poetry" pyproject.toml && echo "yes") - -.PHONY: help -help: ## Show the help. - @echo "Usage: make " - @echo "" - @echo "Targets:" - @fgrep "##" Makefile | fgrep -v fgrep - - -.PHONY: show -show: ## Show the current environment. - @echo "Current environment:" - @if [ "$(USING_POETRY)" ]; then poetry env info && exit; fi - @echo "Running using $(ENV_PREFIX)" - @$(ENV_PREFIX)python -V - @$(ENV_PREFIX)python -m site - -.PHONY: install -install: ## Install the project in dev mode. - @if [ "$(USING_POETRY)" ]; then poetry install && exit; fi - @echo "Don't forget to run 'make virtualenv' if you got errors." - $(ENV_PREFIX)pip install -e .[test] - -.PHONY: fmt -fmt: ## Format code using black & isort. - $(ENV_PREFIX)isort project_name/ - $(ENV_PREFIX)black -l 79 project_name/ - $(ENV_PREFIX)black -l 79 tests/ - -.PHONY: lint -lint: ## Run pep8, black, mypy linters. - $(ENV_PREFIX)flake8 project_name/ - $(ENV_PREFIX)black -l 79 --check project_name/ - $(ENV_PREFIX)black -l 79 --check tests/ - $(ENV_PREFIX)mypy --ignore-missing-imports project_name/ - -.PHONY: test -test: lint ## Run tests and generate coverage report. - $(ENV_PREFIX)pytest -v --cov-config .coveragerc --cov=project_name -l --tb=short --maxfail=1 tests/ - $(ENV_PREFIX)coverage xml - $(ENV_PREFIX)coverage html - -.PHONY: watch -watch: ## Run tests on every change. - ls **/**.py | entr $(ENV_PREFIX)pytest -s -vvv -l --tb=long --maxfail=1 tests/ - -.PHONY: clean -clean: ## Clean unused files. - @find ./ -name '*.pyc' -exec rm -f {} \; - @find ./ -name '__pycache__' -exec rm -rf {} \; - @find ./ -name 'Thumbs.db' -exec rm -f {} \; - @find ./ -name '*~' -exec rm -f {} \; - @rm -rf .cache - @rm -rf .pytest_cache - @rm -rf .mypy_cache - @rm -rf build - @rm -rf dist - @rm -rf *.egg-info - @rm -rf htmlcov - @rm -rf .tox/ - @rm -rf docs/_build - -.PHONY: virtualenv -virtualenv: ## Create a virtual environment. - @if [ "$(USING_POETRY)" ]; then poetry install && exit; fi - @echo "creating virtualenv ..." - @rm -rf .venv - @python3 -m venv .venv - @./.venv/bin/pip install -U pip - @./.venv/bin/pip install -e .[test] - @echo - @echo "!!! Please run 'source .venv/bin/activate' to enable the environment !!!" - -.PHONY: release -release: ## Create a new tag for release. - @echo "WARNING: This operation will create s version tag and push to github" - @read -p "Version? (provide the next x.y.z semver) : " TAG - @echo "$${TAG}" > project_name/VERSION - @$(ENV_PREFIX)gitchangelog > HISTORY.md - @git add project_name/VERSION HISTORY.md - @git commit -m "release: version $${TAG} πŸš€" - @echo "creating git tag : $${TAG}" - @git tag $${TAG} - @git push -u origin HEAD --tags - @echo "Github Actions will detect the new tag and release the new version." - -.PHONY: docs -docs: ## Build the documentation. - @echo "building documentation ..." - @$(ENV_PREFIX)mkdocs build - URL="site/index.html"; xdg-open $$URL || sensible-browser $$URL || x-www-browser $$URL || gnome-open $$URL || open $$URL - -.PHONY: switch-to-poetry -switch-to-poetry: ## Switch to poetry package manager. - @echo "Switching to poetry ..." - @if ! poetry --version > /dev/null; then echo 'poetry is required, install from https://python-poetry.org/'; exit 1; fi - @rm -rf .venv - @poetry init --no-interaction --name=a_flask_test --author=rochacbruno - @echo "" >> pyproject.toml - @echo "[tool.poetry.scripts]" >> pyproject.toml - @echo "project_name = 'project_name.__main__:main'" >> pyproject.toml - @cat requirements.txt | while read in; do poetry add --no-interaction "$${in}"; done - @cat requirements-test.txt | while read in; do poetry add --no-interaction "$${in}" --dev; done - @poetry install --no-interaction - @mkdir -p .github/backup - @mv requirements* .github/backup - @mv setup.py .github/backup - @echo "You have switched to https://python-poetry.org/ package manager." - @echo "Please run 'poetry shell' or 'poetry run project_name'" - -.PHONY: init -init: ## Initialize the project based on an application template. - @./.github/init.sh - - -# This project has been generated from rochacbruno/python-project-template -# __author__ = 'rochacbruno' -# __repo__ = https://github.com/rochacbruno/python-project-template -# __sponsor__ = https://github.com/sponsors/rochacbruno/ diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000..fa2cdd8 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,9 @@ +------------------------------------------------------------------------------- +Copyright 2016 +Ubiquitous Knowledge Processing (UKP) Lab +Technische UniversitΓ€t Darmstadt + +------------------------------------------------------------------------------- +Third party legal information + + diff --git a/README.md b/README.md index 21f444c..29975ad 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,102 @@ +# ukp_project_template +[![Arxiv](https://img.shields.io/badge/Arxiv-YYMM.NNNNN-red?style=flat-square&logo=arxiv&logoColor=white)](https://put-here-your-paper.com) +[![License](https://img.shields.io/github/license/UKPLab/ukp-project-template)](https://opensource.org/licenses/Apache-2.0) +[![Python Versions](https://img.shields.io/badge/Python-3.9-blue.svg?style=flat&logo=python&logoColor=white)](https://www.python.org/) +[![CI](https://github.com/UKPLab/ukp-project-template/actions/workflows/main.yml/badge.svg)](https://github.com/UKPLab/ukp-project-template/actions/workflows/main.yml) -# Python Project Template +This is the official template for new Python projects at UKP Lab. It was adapted for the needs of UKP Lab from the excellent [python-project-template](https://github.com/rochacbruno/python-project-template/) by [rochacbruno](https://github.com/rochacbruno). -A low dependency and really simple to start project template for Python Projects. +It should help you start your project and give you continuous status updates on the development through [GitHub Actions](https://docs.github.com/en/actions). -See also -- [Flask-Project-Template](https://github.com/rochacbruno/flask-project-template/) for a full feature Flask project including database, API, admin interface, etc. -- [FastAPI-Project-Template](https://github.com/rochacbruno/fastapi-project-template/) The base to start an openapi project featuring: SQLModel, Typer, FastAPI, JWT Token Auth, Interactive Shell, Management Commands. +> **Abstract:** The study of natural language processing (NLP) has gained increasing importance in recent years, with applications ranging from machine translation to sentiment analysis. Properly managing Python projects in this domain is of paramount importance to ensure reproducibility and facilitate collaboration. The template provides a structured starting point for projects and offers continuous status updates on development through GitHub Actions. Key features include a basic setup.py file for installation, packaging, and distribution, documentation structure using mkdocs, testing structure using pytest, code linting with pylint, and entry points for executing the program with basic CLI argument parsing. Additionally, the template incorporates continuous integration using GitHub Actions with jobs to check, lint, and test the project, ensuring robustness and reliability throughout the development process. -### HOW TO USE THIS TEMPLATE +Contact person: [Federico Tiblias](mailto:federico.tiblias@tu-darmstadt.de) -> **DO NOT FORK** this is meant to be used from **[Use this template](https://github.com/rochacbruno/python-project-template/generate)** feature. +[UKP Lab](https://www.ukp.tu-darmstadt.de/) | [TU Darmstadt](https://www.tu-darmstadt.de/ +) -1. Click on **[Use this template](https://github.com/rochacbruno/python-project-template/generate)** -3. Give a name to your project - (e.g. `my_awesome_project` recommendation is to use all lowercase and underscores separation for repo names.) -3. Wait until the first run of CI finishes - (Github Actions will process the template and commit to your new repo) -4. If you want [codecov](https://about.codecov.io/sign-up/) Reports and Automatic Release to [PyPI](https://pypi.org) - On the new repository `settings->secrets` add your `PYPI_API_TOKEN` and `CODECOV_TOKEN` (get the tokens on respective websites) -4. Read the file [CONTRIBUTING.md](CONTRIBUTING.md) -5. Then clone your new project and happy coding! +Don't hesitate to send us an e-mail or report an issue, if something is broken (and it shouldn't be) or if you have further questions. -> **NOTE**: **WAIT** until first CI run on github actions before cloning your new project. -### What is included on this template? +## Getting Started -- πŸ–ΌοΈ Templates for starting multiple application types: - * **Basic low dependency** Python program (default) [use this template](https://github.com/rochacbruno/python-project-template/generate) - * **Flask** with database, admin interface, restapi and authentication [use this template](https://github.com/rochacbruno/flask-project-template/generate). - **or Run `make init` after cloning to generate a new project based on a template.** -- πŸ“¦ A basic [setup.py](setup.py) file to provide installation, packaging and distribution for your project. - Template uses setuptools because it's the de-facto standard for Python packages, you can run `make switch-to-poetry` later if you want. -- πŸ€– A [Makefile](Makefile) with the most useful commands to install, test, lint, format and release your project. -- πŸ“ƒ Documentation structure using [mkdocs](http://www.mkdocs.org) -- πŸ’¬ Auto generation of change log using **gitchangelog** to keep a HISTORY.md file automatically based on your commit history on every release. -- πŸ‹ A simple [Containerfile](Containerfile) to build a container image for your project. - `Containerfile` is a more open standard for building container images than Dockerfile, you can use buildah or docker with this file. -- πŸ§ͺ Testing structure using [pytest](https://docs.pytest.org/en/latest/) -- βœ… Code linting using [flake8](https://flake8.pycqa.org/en/latest/) -- πŸ“Š Code coverage reports using [codecov](https://about.codecov.io/sign-up/) -- πŸ›³οΈ Automatic release to [PyPI](https://pypi.org) using [twine](https://twine.readthedocs.io/en/latest/) and github actions. -- 🎯 Entry points to execute your program using `python -m ` or `$ project_name` with basic CLI argument parsing. -- πŸ”„ Continuous integration using [Github Actions](.github/workflows/) with jobs to lint, test and release your project on Linux, Mac and Windows environments. +> **DO NOT CLONE OR FORK** -> Curious about architectural decisions on this template? read [ABOUT_THIS_TEMPLATE.md](ABOUT_THIS_TEMPLATE.md) -> If you want to contribute to this template please open an [issue](https://github.com/rochacbruno/python-project-template/issues) or fork and send a PULL REQUEST. - -[❀️ Sponsor this project](https://github.com/sponsors/rochacbruno/) - - - ---- -# project_name - -[![codecov](https://codecov.io/gh/author_name/project_urlname/branch/main/graph/badge.svg?token=project_urlname_token_here)](https://codecov.io/gh/author_name/project_urlname) -[![CI](https://github.com/author_name/project_urlname/actions/workflows/main.yml/badge.svg)](https://github.com/author_name/project_urlname/actions/workflows/main.yml) - -project_description - -## Install it from PyPI +If you want to set up this template: +1. Request a repository on UKP Lab's GitHub by following the standard procedure on the wiki. It will install the template directly. Alternatively, set it up in your personal GitHub account by clicking **[Use this template](https://github.com/rochacbruno/python-project-template/generate)**. +2. Wait until the first run of CI finishes. Github Actions will commit to your new repo with a "βœ… Ready to clone and code" message. +3. Delete optional files: + - If you don't need automatic documentation generation, you can delete folder `docs`, file `.github\workflows\docs.yml` and `mkdocs.yml` + - If you don't want automatic testing, you can delete folder `tests` and file `.github\workflows\tests.yml` +4. Prepare a virtual environment: ```bash -pip install project_name +python -m venv .venv +source .venv/bin/activate +pip install . +pip install -r requirements-dev.txt # Only needed for development ``` +5. Adapt anything else (for example this file) to your project. + +6. Read the file [ABOUT_THIS_TEMPLATE.md](ABOUT_THIS_TEMPLATE.md) for more information about development. ## Usage +### Using the classes + +This is how you can use classes inside `ukp_project_template`: + ```py -from project_name import BaseClass -from project_name import base_function +from ukp_project_template import BaseClass +from ukp_project_template import base_function BaseClass().base_method() base_function() ``` +### Using scripts + +This is how you can use `ukp_project_template` from command line: ```bash -$ python -m project_name -#or -$ project_name +$ python -m ukp_project_template ``` +### Expected results + +After running the experiments, you should expect the following results: + +(Feel free to describe your expected results here...) + +### Parameter description + +* `x, --xxxx`: This parameter does something nice + +* ... + +* `z, --zzzz`: This parameter does something even nicer + ## Development -Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. +Read the FAQs in [ABOUT_THIS_TEMPLATE.md](ABOUT_THIS_TEMPLATE.md) to learn more about how this template works and where you should put your classes & methods. Make sure you've correctly installed `requirements-dev.txt` dependencies + +## Cite + +Please use the following citation: + +``` +@InProceedings{smith:20xx:CONFERENCE_TITLE, + author = {Smith, John}, + title = {My Paper Title}, + booktitle = {Proceedings of the 20XX Conference on XXXX}, + month = mmm, + year = {20xx}, + address = {Gotham City, USA}, + publisher = {Association for XXX}, + pages = {XXXX--XXXX}, + url = {http://xxxx.xxx} +} +``` + +## Disclaimer + +> This repository contains experimental software and is published for the sole purpose of giving additional background details on the respective publication. diff --git a/docs/index.md b/docs/index.md index 000ea34..fe27dfe 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,3 +15,7 @@ For full documentation visit [mkdocs.org](https://www.mkdocs.org). docs/ index.md # The documentation homepage. ... # Other markdown pages, images and other files. + +## Docs + +::: ukp_project_template \ No newline at end of file diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9ed22d2a02f93961f7f8ee312782ff8bf11fd338 GIT binary patch literal 106645 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