Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Auto-generated] Check & update dependencies (pyproject.toml) #204

Merged
merged 14 commits into from
Nov 4, 2024
Merged
33 changes: 0 additions & 33 deletions .github/utils/coverage_entrypoint.sh

This file was deleted.

2 changes: 1 addition & 1 deletion .github/utils/run_with_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from coverage import Coverage

cov = Coverage(data_file=".coverage.docker", config_file="pyproject.toml")
cov = Coverage(data_file="/app/.coverage.docker", config_file="/app/pyproject.toml")
cov.start()

from entities_service.main import APP # noqa: F401, E402
Expand Down
115 changes: 88 additions & 27 deletions .github/workflows/ci_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
ENTITIES_SERVICE_CA_FILE: docker_security/test-ca.pem
# These are used in the Dockerfile as well as in pytest
ENTITIES_SERVICE_HOST: localhost
ENTITIES_SERVICE_PORT: 8000
ENTITIES_SERVICE_PORT: 7000

steps:
- name: Checkout ${{ github.repository }}
Expand Down Expand Up @@ -83,15 +83,41 @@ jobs:
--volume "${{ github.workspace }}/docker_security:/mongo_tls" \
mongo:8 \
--tlsMode allowTLS --tlsCertificateKeyFile /mongo_tls/test-server1.pem --tlsCAFile /mongo_tls/test-ca.pem
sleep 10 # Wait for the database to start

# Health check
tmp_file=.dockerPs
while ! docker exec mongo mongosh --eval "db.runCommand('ping').ok" mongodb://root:root@localhost:27017/?authSource=admin --quiet ; do
docker ps -a > $tmp_file
grep -E "^.*mongo .* Exited .*$" $tmp_file && exited=yes && break
sleep 3
done
rm -f $tmp_file

# Write logs
docker logs mongo

if [ -n "$exited" ]; then
exit 1
fi

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build Docker image
run: |
docker build \
--pull \
--tag entities-service \
--target ${{ matrix.docker_target }} \
.
uses: docker/build-push-action@v6
with:
context: .
target: ${{ matrix.docker_target }}
load: true
tags: entities-service
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: false
build-args: |
CI=1

- name: Install test dependencies
run: |
Expand All @@ -106,20 +132,37 @@ jobs:
touch -a logs/entities_service.log

# Run the service in the background
docker run --rm -d \
docker run -d \
--env ENTITIES_SERVICE_MONGO_URI \
--env ENTITIES_SERVICE_X509_CERTIFICATE_FILE \
--env ENTITIES_SERVICE_CA_FILE \
--env PORT=${ENTITIES_SERVICE_PORT} \
--env RUN_TIME=60 \
--env STOP_TIME=3 \
--name "entities-service" \
--network "host" \
--volume "${PWD}:/app" \
--entrypoint "./.github/utils/coverage_entrypoint.sh" \
--user "$(id -u):$(id -g)" \
--init \
--entrypoint "gunicorn" \
entities-service \
${{ matrix.docker_target }}
sleep 5 # Wait for the service to start
--bind="0.0.0.0:${ENTITIES_SERVICE_PORT}" --workers=1 --worker-class="uvicorn.workers.UvicornWorker" --log-level="debug" --pythonpath=".github/utils" run_with_coverage:APP

# Install requirements for health check
sudo apt-get update && sudo apt-get install -y --no-install-recommends -fqqy curl

# Health check
tmp_file=.dockerPs
while ! curl -sf http://localhost:${ENTITIES_SERVICE_PORT}/openapi.json >> /dev/null ; do
docker ps -a > $tmp_file
grep -E "^.*entities-service .* Exited .*$" $tmp_file && exited=yes && break
sleep 1
done
rm -f $tmp_file

# Write logs
docker logs entities-service

if [ -n "$exited" ]; then
exit 1
fi

- name: Run tests
run: |
Expand All @@ -137,20 +180,30 @@ jobs:

- name: Collect coverage
run: |
# We have mapped the current working directory with the service's working
# directory in Docker. This means that we can, first of all, stop the service
# via a file touch, and then collect the coverage data from the service.
#
# See the .github/utils/coverage_entrypoint.sh file for more details on the
# stopping mechanism.
touch stop_gunicorn
# We can stop the service via a SIGINT signal.
# Since we map the current directory to /app in the container, the coverage file will be
# available in the current working directory.
docker kill --signal=SIGTERM entities-service

sleep 3 # Wait for the service to stop
# Wait for the coverage file to be written and the service to stop
sleep 10

mv .coverage .coverage.pytest
coverage combine --data-file=.coverage.final --rcfile=pyproject.toml .coverage.pytest .coverage.docker
coverage xml --data-file=.coverage.final --rcfile=pyproject.toml -o coverage.xml
coverage report --data-file=.coverage.final --rcfile=pyproject.toml --show-missing --skip-covered --skip-empty
# Combine the coverage data from pytest and the service
{
mv .coverage .coverage.pytest &&
coverage combine --data-file=.coverage.final --rcfile=pyproject.toml .coverage.pytest .coverage.docker &&
coverage xml --data-file=.coverage.final --rcfile=pyproject.toml -o coverage.xml &&
coverage report --data-file=.coverage.final --rcfile=pyproject.toml --show-missing --skip-covered --skip-empty
} || {
echo "Failed to collect coverage data." &&
echo "Here's the Docker logs for the service:" &&
docker logs entities-service &&

echo -e "\nAnd the service log:" &&
cat logs/entities_service.log &&

exit 1
}

- name: Upload coverage
if: github.repository_owner == 'SINTEF'
Expand All @@ -165,6 +218,14 @@ jobs:
OS: ubuntu-latest
PYTHON: '3.10'

- name: Clean up Docker
if: always()
run: |
docker stop entities-service ||:
docker rm entities-service ||:
docker stop mongo ||:
docker rm mongo ||:

pytest:
runs-on: ubuntu-latest

Expand Down
32 changes: 20 additions & 12 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
FROM python:3.10 as base
FROM python:3.10 AS base

# Prevent writing .pyc files on the import of source modules.
# Set unbuffered mode to ensure logging outputs.
# Ignore root user actions for pip.
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PIP_ROOT_USER_ACTION=ignore

# Set the working directory
WORKDIR /app

# Copy the source code
COPY entities_service entities_service/
COPY pyproject.toml LICENSE README.md ./

Expand All @@ -10,27 +19,26 @@ RUN python -m pip install -U pip && \
pip install -U setuptools wheel && \
pip install -U -e .[server]

EXPOSE 7000
ENTRYPOINT [ "gunicorn", "entities_service.main:APP", "--bind=0.0.0.0:7000", "--workers=1", "--worker-class=entities_service.uvicorn.UvicornWorker" ]

ARG CI=0
RUN --mount=type=bind,source=.github/utils/requirements.txt,target=/tmp/requirements_ci.txt \
if [ "$CI" = "1" ]; then pip install -U -r /tmp/requirements_ci.txt; fi

## DEVELOPMENT target
FROM base as development
FROM base AS development

# Copy over the self-signed certificates for development
COPY docker_security docker_security/

ENV PORT=80
EXPOSE ${PORT}

# Set debug mode, since we're running in development mode
ENV ENTITIES_SERVICE_DEBUG=1

ENTRYPOINT gunicorn --bind "0.0.0.0:${PORT}" --log-level debug --workers 1 --worker-class entities_service.uvicorn.UvicornWorker --reload entities_service.main:APP
CMD [ "--log-level=debug", "--reload" ]

## PRODUCTION target
FROM base as production

ENV PORT=80
EXPOSE ${PORT}
FROM base AS production

# Force debug mode to be off, since we're running in production mode
ENV ENTITIES_SERVICE_DEBUG=0

ENTRYPOINT gunicorn --bind "0.0.0.0:${PORT}" --workers 1 --worker-class entities_service.uvicorn.UvicornWorker entities_service.main:APP
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ ENTITIES_SERVICE_MONGO_PASSWORD=<your MongoDB Atlas user's password with read-on
Run the service:

```shell
uvicorn entities_service.main:APP --host localhost --port 8000 --no-server-header --header "Server:EntitiesService"
uvicorn entities_service.main:APP --host localhost --port 7000 --no-server-header --header "Server:EntitiesService"
```

Finally, go to [localhost:8000/docs](http://localhost:8000/docs) and try out retrieving an entity.
Finally, go to [localhost:7000/docs](http://localhost:7000/docs) and try out retrieving an entity.

`--log-level debug` can be added to the `uvicorn` command to get more verbose logging.
`--reload` can be added to the `uvicorn` command to enable auto-reloading of the service when any files are changed.
Expand Down Expand Up @@ -102,13 +102,13 @@ docker run --rm -d \
--env "ENTITIES_SERVICE_CA_FILE=docker_security/test-ca.pem" \
--name "entities-service" \
-u "${id -ur}:${id -gr}" \
-p "8000:80" \
-p "7000:7000" \
entities-service
```

Now, fill up the MongoDB with valid entities at the `entities_service` database in the `entities` collection.

Then go to [localhost:8000/docs](http://localhost:8000/docs) and try out retrieving an entity.
Then go to [localhost:7000/docs](http://localhost:7000/docs) and try out retrieving an entity.

---

Expand All @@ -129,8 +129,6 @@ By default the `development` target will be built, to change this, set the `ENTI
ENTITIES_SERVICE_DOCKER_TARGET=production docker compose --env-file=.env up --build
```

Furthermore, the used `localhost` port can be changed via the `PORT` environment variable.

The `--env-file` argument is optional, but if used, it should point to a file containing the environment variables needed by the service.
See the section on [using a file for environment variables](#using-a-file-for-environment-variables) for more information.

Expand Down Expand Up @@ -239,13 +237,13 @@ To test uploading entities using the CLI, one must note that validation of the e
The validation that is most tricky when testing locally is the namespace validation, as the service will validate the namespace against the `ENTITIES_SERVICE_BASE_URL` environment variable set when starting the service, which defaults to `http://onto-ns.com/meta`.
However, if using this namespace in the CLI, the CLI will connect to the publicly running service at `http://onto-ns.com/meta`, which will not work when testing locally.

So to make all this work together, one should start the service with the `ENTITIES_SERVICE_BASE_URL` environment variable set to `http://localhost:8000` (which is done through the locally available environment variable `ENTITIES_SERVICE_HOST`), and then use the CLI to upload entities to the service running at `http://localhost:8000`.
So to make all this work together, one should start the service with the `ENTITIES_SERVICE_BASE_URL` environment variable set to `http://localhost:7000` (which is done through the locally available environment variable `ENTITIES_SERVICE_HOST`), and then use the CLI to upload entities to the service running at `http://localhost:7000`.

In practice, this will look like this:

```shell
# Set the relevant environment variables
export ENTITIES_SERVICE_BASE_URL=http://localhost:8000
export ENTITIES_SERVICE_BASE_URL=http://localhost:7000
export ENTITIES_SERVICE_HOST=${ENTITIES_SERVICE_BASE_URL}

# Start the service
Expand All @@ -255,7 +253,7 @@ docker compose up -d
entities-service upload my_entities.yaml --format=yaml
```

The `my_entities.yaml` file should contain one or more entities with `uri` values of the form `http://localhost:8000/...`.
The `my_entities.yaml` file should contain one or more entities with `uri` values of the form `http://localhost:7000/...`.

### Extra pytest markers

Expand Down
9 changes: 2 additions & 7 deletions docker-compose.yml → compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ services:
context: "."
target: "${ENTITIES_SERVICE_DOCKER_TARGET:-development}"
ports:
- "${ENTITIES_SERVICE_PORT:-8000}:80"
- "${ENTITIES_SERVICE_PORT:-7000}:7000"
environment:
ENTITIES_SERVICE_BASE_URL: "${ENTITIES_SERVICE_HOST:-http://onto-ns.com/meta}"
ENTITIES_SERVICE_BACKEND: mongodb
Expand All @@ -28,14 +28,9 @@ services:
image: mongo:8
restart: always
ports:
- "${MONGO_PORT:-27017}:27017"
environment:
IN_DOCKER: "true"
HOST_USER: "${USER}"
- "27017:27017"
networks:
- entities_service_net
expose:
- 27017
volumes:
- "./.github/docker_init/create_x509_user.js:/docker-entrypoint-initdb.d/0_create_x509_user.js"
- "./docker_security:/mongo_tls"
Expand Down
2 changes: 1 addition & 1 deletion docs/example/dlite_example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
" - Local `entities_service` package.\n",
" - `requests`\n",
" - `DLite-Python`\n",
"- Running Entities Service at `http://localhost:8000` setup to connect to SOFT Cluster on MongoDB Atlas."
"- Running Entities Service at `http://localhost:7000` setup to connect to SOFT Cluster on MongoDB Atlas."
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion docs/example/dlite_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


def get_instance(uri: str) -> Instance:
uri = uri.replace("http://onto-ns.com/meta", "http://localhost:8000")
uri = uri.replace("http://onto-ns.com/meta", "http://localhost:7000")
response = requests.get(uri)
if not response.ok:
error_message = (
Expand Down
11 changes: 5 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,27 @@ dependencies = [
"pydantic-settings ~=2.6",
"pymongo ~=4.10",
"python-dotenv ~=1.0",
"uvicorn >=0.24.0,<1",
"uvicorn ~=0.32.0",
]

[project.optional-dependencies]
cli = [
"httpx-auth ~=0.22.0",
"pyyaml ~=6.0",
"typer[all] >=0.9.0,<1",
"typer[all] ~=0.12.5",
]
testing = [
"cryptography ~=43.0",
"dlite-python ~=0.5.23",
"mongomock ~=4.2",
"numpy <2", # requirement for DLite v0.5.16, which does not support NumPy v2
"pytest ~=8.3",
"pytest-asyncio ~=0.24.0",
"pytest-cov ~=5.0",
"pytest-httpx ~=0.32.0",
"pytest-cov ~=6.0",
"pytest-httpx ~=0.33.0",
"entities-service[cli]",
]
server = [
"gunicorn >=21.2.0,<24",
"gunicorn ~=23.0.0",
]
dev = [
"pre-commit ~=4.0",
Expand Down
Loading