diff --git a/api/.dockerignore b/api/.dockerignore new file mode 100644 index 0000000..2b29f27 --- /dev/null +++ b/api/.dockerignore @@ -0,0 +1 @@ +tests diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000..b50590a --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,44 @@ +ARG PYTHON_VERSION_CODE=3.10 +ARG ENVIRONMENT="dev" +# ENVIRONMENT: dev or prod, refer to project.optional-dependencies in pyproject.toml + +FROM python:${PYTHON_VERSION_CODE}-bookworm as builder +ARG PYTHON_VERSION_CODE +ARG ENVIRONMENT + +WORKDIR /app +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +COPY pyproject.toml README.md ./ +COPY birdxplorer_api/__init__.py ./birdxplorer_api/ + +RUN if [ "${ENVIRONMENT}" = "prod" ]; then \ + apt-get update && apt-get install -y --no-install-recommends \ + postgresql-client-15 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/*; \ + fi +RUN pip install --no-cache-dir -e ".[${ENVIRONMENT}]" + +FROM python:${PYTHON_VERSION_CODE}-slim-bookworm as runner +ARG PYTHON_VERSION_CODE +ARG ENVIRONMENT + +WORKDIR /app + +RUN if [ "${ENVIRONMENT}" = "prod" ]; then \ + apt-get update && apt-get install -y --no-install-recommends \ + libpq5 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/*; \ + fi + +RUN groupadd -r app && useradd -r -g app app +RUN chown -R app:app /app +USER app + +COPY --from=builder /usr/local/lib/python${PYTHON_VERSION_CODE}/site-packages /usr/local/lib/python${PYTHON_VERSION_CODE}/site-packages +COPY --chown=app:app . ./ + +ENTRYPOINT ["python", "-m", "uvicorn", "birdxplorer_api.main:app", "--host", "0.0.0.0"] diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..281ffc7 --- /dev/null +++ b/api/README.md @@ -0,0 +1,2 @@ + +# BirdXplorer API diff --git a/api/pyproject.toml b/api/pyproject.toml index 7503043..e1b12be 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -10,9 +10,9 @@ authors = [ {name = "osoken"}, ] dynamic = [ - "version", + "version", ] -readme = "../README.md" +readme = "README.md" license = {file = "../LICENSE"} requires-python = ">=3.10" @@ -62,6 +62,7 @@ dev=[ "httpx", ] prod=[ + "psycopg2", ] [tool.pytest.ini_options] @@ -100,11 +101,11 @@ legacy_tox_ini = """ envlist = py310 [testenv] - setenv = + setenv = VIRTUALENV_PIP = 24.0 deps = -e .[dev] - commands = + commands = black birdxplorer_api tests isort birdxplorer_api tests pytest diff --git a/compose.yml b/compose.yml index 5d3bd88..7d36098 100644 --- a/compose.yml +++ b/compose.yml @@ -8,10 +8,47 @@ services: POSTGRES_USER: postgres POSTGRES_PASSWORD: ${BX_STORAGE_SETTINGS__PASSWORD} POSTGRES_DB: postgres + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 ports: - '5432:5432' volumes: - postgres_data:/var/lib/postgresql/data + app: + depends_on: + db: + condition: service_healthy + build: + args: + - ENVIRONMENT=dev + context: ./api + dockerfile: Dockerfile + env_file: + - .env + ports: + - '8000:8000' + develop: + watch: + - action: rebuild + path: ./api + target: /app/api + migrate: + depends_on: + db: + condition: service_healthy + build: + args: + - ENVIRONMENT=dev + context: ./migrate + dockerfile: Dockerfile + environment: + - WAIT_HOSTS=db:5432 + env_file: + - .env + volumes: postgres_data: diff --git a/migrate/Dockerfile b/migrate/Dockerfile new file mode 100644 index 0000000..be0444e --- /dev/null +++ b/migrate/Dockerfile @@ -0,0 +1,45 @@ +ARG PYTHON_VERSION_CODE=3.10 +ARG ENVIRONMENT="dev" +# ENVIRONMENT: dev or prod, refer to project.optional-dependencies in pyproject.toml + +FROM python:${PYTHON_VERSION_CODE}-bookworm as builder +ARG PYTHON_VERSION_CODE +ARG ENVIRONMENT + +WORKDIR /app +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +COPY pyproject.toml README.md ./ +COPY birdxplorer_migration/__init__.py ./birdxplorer_migration/ + +RUN if [ "${ENVIRONMENT}" = "prod" ]; then \ + apt-get update && apt-get install -y --no-install-recommends \ + postgresql-client-15 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/*; \ + fi + +RUN pip install --no-cache-dir -e ".[${ENVIRONMENT}]" + +FROM python:${PYTHON_VERSION_CODE}-slim-bookworm as runner +ARG PYTHON_VERSION_CODE +ARG ENVIRONMENT + +WORKDIR /app + +RUN if [ "${ENVIRONMENT}" = "prod" ]; then \ + apt-get update && apt-get install -y --no-install-recommends \ + libpq5 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/*; \ + fi + +RUN groupadd -r app && useradd -r -g app app +RUN chown -R app:app /app +USER app + +COPY --from=builder /usr/local/lib/python${PYTHON_VERSION_CODE}/site-packages /usr/local/lib/python${PYTHON_VERSION_CODE}/site-packages +COPY --chown=app:app . ./ + +ENTRYPOINT ["python", "birdxplorer_migration/scripts/migrate_all.py", "birdxplorer_migration/data/appv1/"] diff --git a/migrate/README.md b/migrate/README.md new file mode 100644 index 0000000..a2e3a42 --- /dev/null +++ b/migrate/README.md @@ -0,0 +1,2 @@ + +# BirdXplorer Migrations diff --git a/migrate/birdxplorer_migration/__init__.py b/migrate/birdxplorer_migration/__init__.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/migrate/birdxplorer_migration/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/data/.gitignore b/migrate/birdxplorer_migration/data/.gitignore similarity index 100% rename from data/.gitignore rename to migrate/birdxplorer_migration/data/.gitignore diff --git a/scripts/migrations/convert_data_from_v1.py b/migrate/birdxplorer_migration/scripts/convert_data_from_v1.py similarity index 100% rename from scripts/migrations/convert_data_from_v1.py rename to migrate/birdxplorer_migration/scripts/convert_data_from_v1.py diff --git a/scripts/migrations/migrate_all.py b/migrate/birdxplorer_migration/scripts/migrate_all.py similarity index 100% rename from scripts/migrations/migrate_all.py rename to migrate/birdxplorer_migration/scripts/migrate_all.py diff --git a/migrate/pyproject.toml b/migrate/pyproject.toml new file mode 100644 index 0000000..f11c627 --- /dev/null +++ b/migrate/pyproject.toml @@ -0,0 +1,104 @@ +[build-system] +build-backend = "flit_core.buildapi" +requires = ["flit_core >=3.8.0,<4"] + + +[project] +name = "birdxplorer_migration" +description = "The Migration Scripts for BirdXplorer project." +authors = [ + {name = "osoken"}, +] +dynamic = [ + "version", +] +readme = "README.md" +license = {file = "../LICENSE"} +requires-python = ">=3.10" + +classifiers = [ + "Development Status :: 3 - Alpha", + "Natural Language :: Japanese", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.10", +] + +dependencies = [ + "birdxplorer_common @ git+https://github.com/codeforjapan/BirdXplorer.git@feature/issue-53-divide-python-packages#subdirectory=common", + "sqlalchemy", + "python-dotenv", +] + +[project.urls] +Source = "https://github.com/codeforjapan/BirdXplorer" + +[tool.setuptools] +packages=["birdxplorer"] + +[tool.setuptools.package-data] +birdxplorer = ["py.typed"] + +[project.optional-dependencies] +dev=[ + "black", + "flake8", + "pyproject-flake8", + "pytest", + "mypy", + "tox", + "isort", + "pytest-mock", + "pytest-cov", + "freezegun", + "types-python-dateutil", + "psycopg2-binary", + "factory_boy", + "uvicorn", + "polyfactory", + "httpx", +] +prod=[ + "psycopg2" +] + +[tool.black] +line-length = 120 +target-version = ['py310'] + +[tool.flake8] +max-line-length = 120 +extend-ignore = "E203,E701" + +[tool.mypy] +python_version = "3.10" +warn_return_any = true +warn_unused_configs = true +plugins = ["pydantic.mypy"] + +[tool.pydantic.mypy] +init_typed = true + +[tool.isort] +profile = "black" +known_first_party = "birdxplorer_api,birdxplorer_common,birdxplorer_etl" + +[tool.tox] +legacy_tox_ini = """ + [tox] + skipsdist = true + envlist = py310 + + [testenv] + setenv = + VIRTUALENV_PIP = 24.0 + deps = + -e .[dev] + commands = + black birdxplorer_api tests + isort birdxplorer_api tests + pytest + pflake8 birdxplorer_api/ tests/ + mypy birdxplorer_api --strict + mypy tests --strict +"""