diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2531c18 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,170 @@ +name: Python CI + +on: + push: + branches: [] + paths-ignore: + - 'README.md' + tags: + - 'v*' + pull_request: + branches: [] + paths-ignore: + - 'README.md' + workflow_dispatch: + +# Specify concurrency such that only one workflow can run at a time +# * Different workflow files are not affected +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# Registry for storing Container images +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# Ensure the GitHub token can remove packages +permissions: + packages: write + + +jobs: + + # Define a dependencies job that runs on all branches and PRs + # * Installs dependencies and caches them + build-venv: + runs-on: ubuntu-latest + container: debian:12-slim + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Restore cached virtualenv, if available + # * The pyproject.toml hash is part of the cache key, invalidating + # the cache if the file changes + - name: Restore cached virtualenv + id: restore-cache + uses: actions/cache/restore@v3 + with: + path: .venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/pyproject.toml') }} + + # Should mirror the build-venv stage in the Containerfile + - name: Build venv + run: | + apt -qq update && apt -qq install -y python3-venv gcc libpython3-dev + python3 -m venv .venv + .venv/bin/python -m pip install --upgrade -q pip wheel setuptools + if: steps.restore-cache.outputs.cache-hit != 'true' + + # Should mirror the build-reqs stage in the Containerfile + # * Except this installs the dev dependencies and binaries as well + - name: Install all dependencies + run: .venv/bin/python -m pip install .[test] + if: steps.restore-cache.outputs.cache-hit != 'true' + + # Cache the virtualenv for future runs + - name: Cache virtualenv + uses: actions/cache/save@v3 + with: + path: .venv + key: ${{ steps.restore-cache.outputs.cache-primary-key }} + if: steps.restore-cache.outputs.cache-hit != 'true' + + # Define a unittest job that runs on all branches and PRs + test-unit: + runs-on: ubuntu-latest + container: debian:12-slim + needs: build-venv + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install libpython3-dev + run: apt -qq update && apt -qq install -y python3-venv libpython3-dev + + # Restore cached virtualenv, if available + - name: Restore cached virtualenv + id: restore-cache + uses: actions/cache/restore@v3 + with: + path: .venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/pyproject.toml') }} + fail-on-cache-miss: true + + # Run unittests + # * Produce JUnit XML report + - name: Run unit tests + run: | + .venv/bin/python3 -m xmlrunner discover -s src/fake_api -p "test_*.py" --output-file ut-report.xml + + # Create test summary to be visualised on the job summary screen on GitHub + # * Runs even if previous steps fail + - name: Create test summary + uses: test-summary/action@v2 + with: + paths: "*t-report.xml" + show: "fail, skip" + if: always() + + # Define a "build-container" job that runs on branch commits only + # * Builds and pushes an OCI Container image to the registry defined in the environment variables + # * Only runs if test job passes + build-container: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + needs: test-unit + if: github.event_name != 'pull_request' + + steps: + # Do a non-shallow clone of the repo to ensure tags are present + # * This allows setuptools-git-versioning to automatically set the version + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Tag the built image according to the event type + # * If the event is a valid version tag, use the tag name + # * If the event is a branch commit, use the commit sha + - name: Extract metadata (tags, labels) for Container + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + + # Build and push the Container image to the registry + # * Creates a multiplatform-aware image + # * Semantic versioning is handled via the meta action + # * The image layers are cached between action runs + - name: Build and push Container image + uses: docker/build-push-action@v4 + with: + context: . + file: Containerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max + key: ${{ runner.os }}-venv-${{ hashFiles('**/pyproject.toml') }} + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..925f070 --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# 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/ +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. +*.manifestutoPairsReturnutoPairsReturnutoPairsReturnutoPairsReturn +*.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/ +cover/ + +# 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 +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .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 + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__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/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# 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/ diff --git a/Containerfile b/Containerfile index 040f36a..839b4a7 100644 --- a/Containerfile +++ b/Containerfile @@ -1,13 +1,13 @@ # Build a virtualenv using venv # * Install required compilation tools for wheels via apt -FROM python:3.11-slim AS build-venv -RUN apt -qq update && apt -qq install -y build-essential -RUN python3 -m venv /venv -RUN /venv/bin/pip install --upgrade -q pip wheel setuptools +FROM debian:12-slim AS build +RUN apt -qq update && apt -qq install -y python3-venv gcc libpython3-dev && \ + python3 -m venv /venv && \ + /venv/bin/pip install --upgrade -q pip setuptools wheel # Install packages into the virtualenv as a separate step # * Only re-execute this step when the requirements files change -FROM build-venv AS install-deps +FROM build AS install-deps WORKDIR /app COPY pyproject.toml pyproject.toml RUN /venv/bin/pip install -q . --no-cache-dir --no-binary=fake_api @@ -21,12 +21,13 @@ COPY src src COPY .git .git COPY README.md README.md RUN /venv/bin/pip install . +RUN ls /venv/bin # Copy the virtualenv into a distroless image # * These are small images that only contain the runtime dependencies -FROM gcr.io/distroless/python3-debian11 -WORKDIR /app +FROM gcr.io/distroless/python3-debian12 COPY --from=build-app /venv /venv +WORKDIR /app HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1 ENTRYPOINT ["/venv/bin/fake-api"] diff --git a/pyproject.toml b/pyproject.toml index 8be70af..b4a4d50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ # --- PROJECT CONFIGURATION --- # [build-system] -requires = ["setuptools>=67", "wheel", "setuptools-git-versioning>=1.13.3"] +requires = ["setuptools>=69", "wheel", "setuptools-git-versioning>=1.13.3"] build-backend = "setuptools.build_meta" # Metadata (see https://peps.python.org/pep-0621/) @@ -18,7 +18,6 @@ authors = [ classifiers = ["Programming Language :: Python :: 3"] dependencies = [ "fastapi >= 0.105.0", - "numpy >= 1.23.2", "structlog >= 23.2.0", "uvicorn >= 0.24.0", ] @@ -53,12 +52,9 @@ enabled = true # MyPy configuration # * See https://mypy.readthedocs.io/en/stable/index.html [tool.mypy] -python_version = "3.12" +python_version = "3.11" warn_return_any = true disallow_untyped_defs = true -plugins = [ - 'numpy.typing.mypy_plugin' -] # Ruff configuration # * See https://beta.ruff.rs/docs/ diff --git a/src/fake_api.egg-info/PKG-INFO b/src/fake_api.egg-info/PKG-INFO deleted file mode 100644 index 3c2fe88..0000000 --- a/src/fake_api.egg-info/PKG-INFO +++ /dev/null @@ -1,35 +0,0 @@ -Metadata-Version: 2.1 -Name: fake-api -Version: 0.0.1 -Summary: Fake API for wind and solar data -Author-email: Sol Cotton -License: MIT License -Project-URL: repository, https://github.com/openclimatefix/fake-api -Classifier: Programming Language :: Python :: 3 -Requires-Python: >=3.11.0 -Description-Content-Type: text/markdown -Requires-Dist: fastapi>=0.105.0 -Requires-Dist: numpy>=1.23.2 -Requires-Dist: structlog>=23.2.0 -Requires-Dist: uvicorn>=0.24.0 -Provides-Extra: test -Requires-Dist: unittest-xml-reporting==3.2.0; extra == "test" -Provides-Extra: lint -Requires-Dist: mypy>=1.7.1; extra == "lint" -Requires-Dist: python-lsp-server>=1.9.0; extra == "lint" -Requires-Dist: ruff>=0.1.7; extra == "lint" -Provides-Extra: dev -Requires-Dist: fake-api[lint,test]; extra == "dev" - -# Fake API - -Defines a fake API to help with building frontends. - -## Running the service - -Clone the repository, -and create a new virtual environment with your favorite environment manager. -Install the dependencies with -``` - -``` diff --git a/src/fake_api.egg-info/SOURCES.txt b/src/fake_api.egg-info/SOURCES.txt deleted file mode 100644 index efedbb0..0000000 --- a/src/fake_api.egg-info/SOURCES.txt +++ /dev/null @@ -1,18 +0,0 @@ -README.md -pyproject.toml -src/fake_api/__init__.py -src/fake_api.egg-info/PKG-INFO -src/fake_api.egg-info/SOURCES.txt -src/fake_api.egg-info/dependency_links.txt -src/fake_api.egg-info/entry_points.txt -src/fake_api.egg-info/requires.txt -src/fake_api.egg-info/top_level.txt -src/fake_api/cmd/__init__.py -src/fake_api/cmd/main.py -src/fake_api/internal/__init__.py -src/fake_api/internal/models.py -src/fake_api/internal/inputs/__init__.py -src/fake_api/internal/inputs/dummydb.py -src/fake_api/internal/inputs/test_dummydb.py -src/fake_api/internal/service/__init__.py -src/fake_api/internal/service/server.py \ No newline at end of file diff --git a/src/fake_api.egg-info/dependency_links.txt b/src/fake_api.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/src/fake_api.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/fake_api.egg-info/entry_points.txt b/src/fake_api.egg-info/entry_points.txt deleted file mode 100644 index 169f356..0000000 --- a/src/fake_api.egg-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -run-api = fake_api.cmd.main:run diff --git a/src/fake_api.egg-info/requires.txt b/src/fake_api.egg-info/requires.txt deleted file mode 100644 index b964a72..0000000 --- a/src/fake_api.egg-info/requires.txt +++ /dev/null @@ -1,15 +0,0 @@ -fastapi>=0.105.0 -numpy>=1.23.2 -structlog>=23.2.0 -uvicorn>=0.24.0 - -[dev] -fake-api[lint,test] - -[lint] -mypy>=1.7.1 -python-lsp-server>=1.9.0 -ruff>=0.1.7 - -[test] -unittest-xml-reporting==3.2.0 diff --git a/src/fake_api.egg-info/top_level.txt b/src/fake_api.egg-info/top_level.txt deleted file mode 100644 index ce2c741..0000000 --- a/src/fake_api.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -fake_api diff --git a/src/fake_api/__pycache__/__init__.cpython-312.pyc b/src/fake_api/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index e117449..0000000 Binary files a/src/fake_api/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/src/fake_api/cmd/__pycache__/__init__.cpython-312.pyc b/src/fake_api/cmd/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index c7258f7..0000000 Binary files a/src/fake_api/cmd/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/src/fake_api/cmd/__pycache__/main.cpython-312.pyc b/src/fake_api/cmd/__pycache__/main.cpython-312.pyc deleted file mode 100644 index 54c05e1..0000000 Binary files a/src/fake_api/cmd/__pycache__/main.cpython-312.pyc and /dev/null differ diff --git a/src/fake_api/cmd/main.py b/src/fake_api/cmd/main.py index 65e5980..05cf4ed 100644 --- a/src/fake_api/cmd/main.py +++ b/src/fake_api/cmd/main.py @@ -20,3 +20,4 @@ def run() -> None: if __name__ == "__main__": run() + diff --git a/src/fake_api/internal/__pycache__/__init__.cpython-312.pyc b/src/fake_api/internal/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index cdd7b2c..0000000 Binary files a/src/fake_api/internal/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/src/fake_api/internal/__pycache__/models.cpython-312.pyc b/src/fake_api/internal/__pycache__/models.cpython-312.pyc deleted file mode 100644 index 4fdcde7..0000000 Binary files a/src/fake_api/internal/__pycache__/models.cpython-312.pyc and /dev/null differ diff --git a/src/fake_api/internal/inputs/__pycache__/__init__.cpython-312.pyc b/src/fake_api/internal/inputs/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index fdf4272..0000000 Binary files a/src/fake_api/internal/inputs/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/src/fake_api/internal/inputs/__pycache__/dummydb.cpython-312.pyc b/src/fake_api/internal/inputs/__pycache__/dummydb.cpython-312.pyc deleted file mode 100644 index 9d69cc8..0000000 Binary files a/src/fake_api/internal/inputs/__pycache__/dummydb.cpython-312.pyc and /dev/null differ diff --git a/src/fake_api/internal/service/__pycache__/__init__.cpython-312.pyc b/src/fake_api/internal/service/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index a54ebae..0000000 Binary files a/src/fake_api/internal/service/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/src/fake_api/internal/service/__pycache__/server.cpython-312.pyc b/src/fake_api/internal/service/__pycache__/server.cpython-312.pyc deleted file mode 100644 index 1439df4..0000000 Binary files a/src/fake_api/internal/service/__pycache__/server.cpython-312.pyc and /dev/null differ