├── app ├── __init__.py ├── exceptions.py ├── models │ ├── __init__.py │ └── base.py ├── routes │ ├── __init__.py │ ├── models │ │ ├── __init__.py │ │ └── base.py │ ├── base.py │ └── script_launch.py ├── utils │ ├── __init__.py │ └── scripts.py ├── schema.py ├── __main__.py └── settings.py ├── requirements.dev.txt ├── requirements.txt ├── Makefile ├── tests └── test_routes │ ├── conftest.py │ └── script_test.py ├── pyproject.toml ├── logging_prod.conf ├── scripts ├── pr_stop.py ├── test_script.py └── pr_run.py ├── logging_test.conf ├── Dockerfile ├── flake8.conf ├── README.md ├── .github └── workflows │ ├── build.yml │ └── checks.yml ├── LICENSE └── .gitignore /app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/exceptions.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/routes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/routes/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.dev.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pytest-cov 3 | pytest-mock 4 | httpx 5 | isort -------------------------------------------------------------------------------- /app/schema.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | __all__ = ('BaseModel',) 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | pydantic 3 | pydantic_settings 4 | uvicorn 5 | gunicorn 6 | auth_lib_profcomff[fastapi] 7 | logging-profcomff -------------------------------------------------------------------------------- /app/__main__.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | 3 | from app.routes.base import app 4 | 5 | 6 | if __name__ == '__main__': 7 | uvicorn.run(app) 8 | -------------------------------------------------------------------------------- /app/utils/scripts.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | 4 | async def run(cmd: str): 5 | proc = await asyncio.create_subprocess_shell(cmd) 6 | await proc.communicate() 7 | return proc.returncode 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | source ./venv/bin/activate && uvicorn --reload --log-level debug app.routes.base:app 3 | 4 | db: 5 | docker run -d -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust --name db-app postgres:15 6 | 7 | migrate: db 8 | alembic upgrade head 9 | -------------------------------------------------------------------------------- /tests/test_routes/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from fastapi.testclient import TestClient 3 | 4 | from app.routes.base import app 5 | 6 | 7 | @pytest.fixture 8 | def client(mocker): 9 | mocker.patch("auth_lib.fastapi.UnionAuth.__call__", return_value={"email": "test"}) 10 | client = TestClient(app) 11 | return client 12 | -------------------------------------------------------------------------------- /tests/test_routes/script_test.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def test_script_execute(client): 5 | action = "test_script" 6 | url = f"/{action}" 7 | body = {"repo_url": 'https://...', "git_ref": '123123123'} 8 | res = client.post(url, data=json.dumps(body)) 9 | print(str(res.json())) 10 | assert res.json()['code'] == 0 11 | -------------------------------------------------------------------------------- /app/routes/models/base.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, ConfigDict 2 | 3 | 4 | class Base(BaseModel): 5 | def __repr__(self) -> str: 6 | attrs = [] 7 | for k, v in self.__class__.model_json_schema().items(): 8 | attrs.append(f"{k}={v}") 9 | return "{}({})".format(self.__class__.__name__, ', '.join(attrs)) 10 | 11 | model_config = ConfigDict(from_attributes=True, extra="ignore") 12 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 120 3 | target-version = ['py310'] 4 | skip-string-normalization = true 5 | 6 | [tool.isort] 7 | line_length = 120 8 | multi_line_output = 3 9 | profile = "black" 10 | lines_after_imports = 2 11 | include_trailing_comma = true 12 | 13 | [tool.pytest.ini_options] 14 | minversion = "7.0" 15 | python_files = "*.py" 16 | testpaths = [ 17 | "tests" 18 | ] 19 | pythonpath = [ 20 | "." 21 | ] 22 | log_cli=true 23 | log_level=0 24 | -------------------------------------------------------------------------------- /app/routes/base.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from fastapi.middleware.cors import CORSMiddleware 3 | 4 | from app.routes.script_launch import router as script_router 5 | 6 | from ..settings import Settings 7 | 8 | 9 | settings = Settings() 10 | app = FastAPI() 11 | 12 | app.add_middleware( 13 | CORSMiddleware, 14 | allow_origins=settings.CORS_ALLOW_ORIGINS, 15 | allow_credentials=str(settings.CORS_ALLOW_CREDENTIALS), 16 | allow_methods=str(settings.CORS_ALLOW_METHODS), 17 | allow_headers=str(settings.CORS_ALLOW_HEADERS), 18 | ) 19 | 20 | app.include_router(script_router, prefix='', tags=['User']) 21 | -------------------------------------------------------------------------------- /logging_prod.conf: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root,gunicorn.error,gunicorn.access 3 | 4 | [handlers] 5 | keys=all 6 | 7 | [formatters] 8 | keys=json 9 | 10 | [logger_root] 11 | level=INFO 12 | handlers=all 13 | 14 | [logger_gunicorn.error] 15 | level=INFO 16 | handlers=all 17 | propagate=0 18 | qualname=gunicorn.error 19 | formatter=json 20 | 21 | [logger_gunicorn.access] 22 | level=INFO 23 | handlers=all 24 | propagate=0 25 | qualname=gunicorn.access 26 | formatter=json 27 | 28 | [handler_all] 29 | class=StreamHandler 30 | formatter=json 31 | level=INFO 32 | args=(sys.stdout,) 33 | 34 | [formatter_json] 35 | class=logger.formatter.JSONLogFormatter 36 | -------------------------------------------------------------------------------- /scripts/pr_stop.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import re 3 | from subprocess import Popen 4 | 5 | 6 | def get_args(): 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument('--repo-url', type=str, required=True) 9 | parser.add_argument('--git-ref', type=str, required=True) 10 | return parser.parse_args() 11 | 12 | 13 | if __name__ == "__main__": 14 | args = get_args() 15 | org, repo = args.repo_url.removeprefix('https://github.com/').rsplit('/', 1) 16 | pr_num = re.match("^refs/pull/(?P\d+)", args.git_ref).group('pr_num') 17 | Popen(['docker', 'rm', '-f', f'pkff_dev__{repo}__pr-{pr_num}']).communicate() 18 | -------------------------------------------------------------------------------- /logging_test.conf: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root,gunicorn.error,gunicorn.access 3 | 4 | [handlers] 5 | keys=all 6 | 7 | [formatters] 8 | keys=json 9 | 10 | [logger_root] 11 | level=DEBUG 12 | handlers=all 13 | 14 | [logger_gunicorn.error] 15 | level=DEBUG 16 | handlers=all 17 | propagate=0 18 | qualname=gunicorn.error 19 | formatter=json 20 | 21 | [logger_gunicorn.access] 22 | level=DEBUG 23 | handlers=all 24 | propagate=0 25 | qualname=gunicorn.access 26 | formatter=json 27 | 28 | [handler_all] 29 | class=StreamHandler 30 | formatter=json 31 | level=DEBUG 32 | args=(sys.stdout,) 33 | 34 | [formatter_json] 35 | class=logger.formatter.JSONLogFormatter 36 | -------------------------------------------------------------------------------- /app/settings.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | 3 | from pydantic import AnyUrl, ConfigDict, PostgresDsn 4 | from pydantic_settings import BaseSettings 5 | 6 | 7 | class Settings(BaseSettings): 8 | """Application settings""" 9 | 10 | ALLOWED_SCOPE: str | None = None 11 | 12 | CORS_ALLOW_ORIGINS: list[str] = ['*'] 13 | CORS_ALLOW_CREDENTIALS: bool = True 14 | CORS_ALLOW_METHODS: list[str] = ['*'] 15 | CORS_ALLOW_HEADERS: list[str] = ['*'] 16 | 17 | model_config = ConfigDict(case_sensitive=True, env_file=".env", extra="ignore") 18 | 19 | 20 | @lru_cache 21 | def get_settings() -> Settings: 22 | settings = Settings() 23 | return settings 24 | -------------------------------------------------------------------------------- /app/models/base.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from sqlalchemy.ext.declarative import as_declarative, declared_attr 4 | 5 | 6 | @as_declarative() 7 | class Base: 8 | """Base class for all database entities""" 9 | 10 | @classmethod 11 | @declared_attr 12 | def __tablename__(cls) -> str: 13 | """Generate database table name automatically. 14 | Convert CamelCase class name to snake_case db table name. 15 | """ 16 | return re.sub(r"(? str: 19 | attrs = [] 20 | for c in self.__table__.columns: 21 | attrs.append(f"{c.name}={getattr(self, c.name)}") 22 | return "{}({})".format(self.__class__.__name__, ', '.join(attrs)) 23 | -------------------------------------------------------------------------------- /scripts/test_script.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beautiful is better than ugly. 3 | Explicit is better than implicit. 4 | Simple is better than complex. 5 | Complex is better than complicated. 6 | Flat is better than nested. 7 | Sparse is better than dense. 8 | Readability counts. 9 | Special cases aren't special enough to break the rules. 10 | Although practicality beats purity. 11 | Errors should never pass silently. 12 | Unless explicitly silenced. 13 | In the face of ambiguity, refuse the temptation to guess. 14 | There should be one-- and preferably only one --obvious way to do it. 15 | Although that way may not be obvious at first unless you're Dutch. 16 | Now is better than never. 17 | Although never is often better than *right* now. 18 | If the implementation is hard to explain, it's a bad idea. 19 | If the implementation is easy to explain, it may be a good idea. 20 | Namespaces are one honking great idea -- let's do more of those! 21 | """ 22 | exit(0) 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11 2 | ENV APP_NAME=app 3 | ENV APP_MODULE=${APP_NAME}.routes.base:app 4 | 5 | # Docker installation 6 | RUN apt-get update \ 7 | && apt-get install -y ca-certificates curl gnupg lsb-release \ 8 | && mkdir -m 0755 -p /etc/apt/keyrings \ 9 | && (curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg) \ 10 | && echo \ 11 | "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \ 12 | $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \ 13 | && apt-get update \ 14 | && apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 15 | 16 | COPY ./requirements.txt /app/ 17 | COPY ./logging_prod.conf /app/ 18 | COPY ./logging_test.conf /app/ 19 | 20 | RUN pip install -U -r /app/requirements.txt 21 | 22 | COPY ./${APP_NAME} /app/${APP_NAME} 23 | 24 | COPY ./scripts /app/scripts 25 | -------------------------------------------------------------------------------- /scripts/pr_run.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import re 3 | from subprocess import Popen 4 | 5 | 6 | def get_args(): 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument('--repo-url', type=str, required=True) 9 | parser.add_argument('--git-ref', type=str, required=True) 10 | return parser.parse_args() 11 | 12 | 13 | if __name__ == "__main__": 14 | args = get_args() 15 | org, repo = args.repo_url.removeprefix('https://github.com/').rsplit('/', 1) 16 | pr_num = re.match("^refs/pull/(?P\d+)", args.git_ref).group('pr_num') 17 | Popen(['docker', 'rm', '-f', f'pkff_dev__{repo}__pr-{pr_num}']).communicate() 18 | Popen( 19 | [ 20 | 'docker', 21 | 'run', 22 | '--rm', 23 | '--detach', 24 | '--network', 25 | 'web', 26 | '--pull', 27 | 'always', 28 | '--name', 29 | f'pkff_dev__{repo}__pr-{pr_num}', 30 | f'ghcr.io/{org}/{repo}:pr-{pr_num}', 31 | ] 32 | ).communicate() 33 | -------------------------------------------------------------------------------- /app/routes/script_launch.py: -------------------------------------------------------------------------------- 1 | from auth_lib.fastapi import UnionAuth 2 | from fastapi import APIRouter, Depends 3 | from pydantic import Field 4 | 5 | from app.schema import BaseModel 6 | from app.settings import get_settings 7 | from app.utils.scripts import run 8 | 9 | 10 | router = APIRouter() 11 | settings = get_settings() 12 | 13 | 14 | class Input(BaseModel): 15 | repo_url: str = Field(description='url of repository, str', example='https://github.com/profcomff/print-api.git') 16 | git_ref: str = Field(description='git reference, str', example='f7cf8ea038edfb31cc8f7dd880dbb53e61980d8c') 17 | 18 | 19 | class SendOutput(BaseModel): 20 | code: int = Field(description='exit-code of the script you launched', example='0') 21 | 22 | 23 | @router.post('/{action:str}', response_model=SendOutput) 24 | async def run_script( 25 | action: str, 26 | inp: Input, 27 | user: dict = Depends(UnionAuth(scopes=[] if settings.ALLOWED_SCOPE is None else [settings.ALLOWED_SCOPE])), 28 | ): 29 | """runs a bash script, located in scripts/{action}. The script takes 2 arguments: git_ref and repo_url""" 30 | 31 | code = await run(f"python3 ./scripts/{action}.py --repo-url {inp.repo_url} --git-ref {inp.git_ref}") 32 | return {'code': code} 33 | -------------------------------------------------------------------------------- /flake8.conf: -------------------------------------------------------------------------------- 1 | [flake8] 2 | select = 3 | E, W, # pep8 errors and warnings 4 | F, # pyflakes 5 | C9, # McCabe 6 | N8, # Naming Conventions 7 | #B, S, # bandit 8 | #C, # commas 9 | #D, # docstrings 10 | #P, # string-format 11 | #Q, # quotes 12 | 13 | ignore = 14 | E122, # continuation line missing indentation or outdented 15 | E123, # closing bracket does not match indentation of opening bracket's line 16 | E127, # continuation line over-indented for visual indent 17 | E131, # continuation line unaligned for hanging 18 | E203, # whitespace before ':' 19 | E225, # missing whitespace around operator 20 | E226, # missing whitespace around arithmetic operator 21 | E24, # multiple spaces after ',' or tab after ',' 22 | E275, # missing whitespace after keyword 23 | E305, # expected 2 blank lines after end of function or class 24 | E306, # expected 1 blank line before a nested definition 25 | E402, # module level import not at top of file 26 | E722, # do not use bare except, specify exception instead 27 | E731, # do not assign a lambda expression, use a def 28 | E741, # do not use variables named 'l', 'O', or 'I' 29 | 30 | F722, # syntax error in forward annotation 31 | 32 | W503, # line break before binary operator 33 | W504, # line break after binary operator 34 | 35 | max-line-length = 120 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ci-api 2 | 3 | API для запуска CI тасков на серверах. 4 | Для использования сделайте запрос следующего вида по адресу, на котором настроено CI API для вашего сервера 5 | 6 | [](https://easycode.profcomff.com/templates/docker-fastapi/workspace?mode=manual¶m.Repository+URL=https://github.com/profcomff/cicd-api.git¶m.Working+directory=cicd-api) 7 | 8 | ``` 9 | curl -X 'POST' \ 10 | 'https://ci.api.profcomff.com/{action}' \ 11 | -H 'accept: application/json' \ 12 | -H 'Content-Type: application/json' \ 13 | -H 'Authorization: token {token}' \ 14 | -d '{ 15 | "repo_url": "string", 16 | "commit_hash": "string" 17 | }' 18 | ``` 19 | 20 | ## Запуск 21 | 22 | На проде запуск производится через Docker Compose 23 | 24 | ```docker-compose 25 | version: '3' 26 | 27 | services: 28 | ci-api: 29 | image: ghcr.io/profcomff/ci-api:latest 30 | restart: always 31 | environment: 32 | - AUTH_URL=https://auth.api.profcomff.com/ 33 | volumes: 34 | - /var/run/docker.sock:/var/run/docker.sock 35 | networks: 36 | web: 37 | aliases: 38 | - ci_api 39 | 40 | networks: 41 | web: 42 | external: true 43 | name: web 44 | ``` 45 | 46 | ## ENV-file description 47 | 48 | `AUTH_URL` – url корня API авторизации профкома, по умолчанию `https://auth.api.test.profcomff.com/` 49 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build, publish and deploy docker 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | 9 | env: 10 | REGISTRY: ghcr.io 11 | IMAGE_NAME: ${{ github.repository }} 12 | 13 | jobs: 14 | build-and-push-image: 15 | name: Build and push 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | packages: write 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v4 24 | 25 | - name: Log in to the Container registry 26 | uses: docker/login-action@v2 27 | with: 28 | registry: ${{ env.REGISTRY }} 29 | username: ${{ github.actor }} 30 | password: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | - name: Extract metadata (tags, labels) for Docker 33 | id: meta 34 | uses: docker/metadata-action@v4 35 | with: 36 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 37 | tags: | 38 | type=ref,event=tag,enable=${{ startsWith(github.ref, 'refs/tags/v') }} 39 | type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} 40 | 41 | - name: Build and push Docker image 42 | uses: docker/build-push-action@v4 43 | with: 44 | context: . 45 | push: true 46 | tags: ${{ steps.meta.outputs.tags }} 47 | labels: ${{ steps.meta.outputs.labels }} 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Профком студентов физфака МГУ 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: 4 | pull_request: 5 | 6 | 7 | jobs: 8 | test: 9 | name: Unit tests 10 | runs-on: ubuntu-latest 11 | continue-on-error: true 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | - uses: actions/setup-python@v4 16 | with: 17 | python-version: '3.11' 18 | - name: Install dependencies 19 | run: | 20 | python -m ensurepip 21 | python -m pip install --upgrade pip 22 | pip install -r requirements.txt -r requirements.dev.txt 23 | - name: Build coverage file 24 | run: | 25 | DB_DSN=postgresql://postgres@localhost:5432/postgres pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=app tests/ | tee pytest-coverage.txt 26 | - name: Print report 27 | if: always() 28 | run: | 29 | cat pytest-coverage.txt 30 | - name: Pytest coverage comment 31 | uses: MishaKav/pytest-coverage-comment@main 32 | with: 33 | pytest-coverage-path: ./pytest-coverage.txt 34 | title: Coverage Report 35 | badge-title: Code Coverage 36 | hide-badge: false 37 | hide-report: false 38 | create-new-comment: false 39 | hide-comment: false 40 | report-only-changed-files: false 41 | remove-link-from-badge: false 42 | junitxml-path: ./pytest.xml 43 | junitxml-title: Summary 44 | linting: 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v4 48 | - uses: actions/setup-python@v2 49 | with: 50 | python-version: 3.11 51 | - uses: isort/isort-action@master 52 | with: 53 | requirementsFiles: "requirements.txt requirements.dev.txt" 54 | - uses: psf/black@stable 55 | - name: Comment if linting failed 56 | if: ${{ failure() }} 57 | uses: thollander/actions-comment-pull-request@v2 58 | with: 59 | message: | 60 | :poop: Code linting failed, use `black` and `isort` to fix it. 61 | 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | --------------------------------------------------------------------------------