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

Fix status code for missing IVA ID #18

Merged
merged 4 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/devcontainers/python:1-3.12-bullseye
FROM mcr.microsoft.com/devcontainers/python:1-3.12-bookworm

ENV PYTHONUNBUFFERED 1

Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ repos:
- id: no-commit-to-branch
args: [--branch, dev, --branch, int, --branch, main]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.2
rev: v0.4.9
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
Expand Down
8 changes: 4 additions & 4 deletions .pyproject_generation/pyproject_custom.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[project]
name = "ars"
version = "2.0.1"
version = "2.0.2"
description = "Access Request Service"
dependencies = [
"ghga-event-schemas~=3.1.0",
"ghga-service-commons[api,auth]>=3.1.3",
"hexkit[mongodb,akafka]>=3.0.0",
"ghga-event-schemas~=3.3.0",
"ghga-service-commons[api,auth]>=3.1.5",
"hexkit[mongodb,akafka]>=3.2.2",
"httpx>=0.27",
"typer>=0.12",
]
Expand Down
2 changes: 2 additions & 0 deletions .template/static_files.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

scripts/script_utils/__init__.py
scripts/script_utils/cli.py
scripts/script_utils/deps.py
scripts/script_utils/lock_deps.py

scripts/__init__.py
scripts/update_all.py
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@ We recommend using the provided Docker container.

A pre-build version is available at [docker hub](https://hub.docker.com/repository/docker/ghga/access-request-service):
```bash
docker pull ghga/access-request-service:2.0.1
docker pull ghga/access-request-service:2.0.2
```

Or you can build the container yourself from the [`./Dockerfile`](./Dockerfile):
```bash
# Execute in the repo's root dir:
docker build -t ghga/access-request-service:2.0.1 .
docker build -t ghga/access-request-service:2.0.2 .
```

For production-ready deployment, we recommend using Kubernetes, however,
for simple use cases, you could execute the service using docker
on a single server:
```bash
# The entrypoint is preconfigured:
docker run -p 8080:8080 ghga/access-request-service:2.0.1 --help
docker run -p 8080:8080 ghga/access-request-service:2.0.2 --help
```

If you prefer not to use containers, you may install the service from source:
Expand Down
2 changes: 1 addition & 1 deletion lock/requirements-dev-template.in
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ setuptools>=69.5
# required since switch to pyproject.toml and pip-tools
tomli_w>=1.0

uv>=0.1.39
uv>=0.1.44
1,276 changes: 742 additions & 534 deletions lock/requirements-dev.txt

Large diffs are not rendered by default.

1,002 changes: 605 additions & 397 deletions lock/requirements.txt

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ components:
info:
description: A service managing access requests for the GHGA Data Portal
title: Access Request Service
version: 2.0.1
version: 2.0.2
openapi: 3.1.0
paths:
/access-requests:
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ classifiers = [
"Intended Audience :: Developers",
]
name = "ars"
version = "2.0.1"
version = "2.0.2"
description = "Access Request Service"
dependencies = [
"ghga-event-schemas~=3.1.0",
"ghga-service-commons[api,auth]>=3.1.3",
"hexkit[mongodb,akafka]>=3.0.0",
"ghga-event-schemas~=3.3.0",
"ghga-service-commons[api,auth]>=3.1.5",
"hexkit[mongodb,akafka]>=3.2.2",
"httpx>=0.27",
"typer>=0.12",
]
Expand Down
60 changes: 28 additions & 32 deletions scripts/check_license.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
".git",
".github",
".flake8",
".gitignore" ".pre-commit-config.yaml",
".gitignore",
".mypy_cache",
".mypy.ini",
".pylintrc",
Expand Down Expand Up @@ -115,8 +115,8 @@
See the License for the specific language governing permissions and
limitations under the License."""

# A list of all chars that may be used to introduce a comment:
COMMENT_CHARS = ["#"]
# A list of strings that may be used to introduce a line comment:
LINE_COMMENTS = ["#"]

AUTHOR = """Universität Tübingen, DKFZ, EMBL, and Universität zu Köln
for the German Human Genome-Phenome Archive (GHGA)"""
Expand Down Expand Up @@ -202,24 +202,25 @@ def get_target_files(
file_
for file_ in all_files
if not (
any([file_.is_relative_to(excl) for excl in exclude_normalized])
or any([str(file_).endswith(ending) for ending in exclude_endings])
or any([re.match(pattern, str(file_)) for pattern in exclude_pattern])
any(file_.is_relative_to(excl) for excl in exclude_normalized)
or any(str(file_).endswith(ending) for ending in exclude_endings)
or any(re.match(pattern, str(file_)) for pattern in exclude_pattern)
)
]
return target_files


def normalized_line(line: str, chars_to_trim: list[str] = COMMENT_CHARS) -> str:
norm_line = line.strip()
def normalized_line(line: str, line_comments: list[str] = LINE_COMMENTS) -> str:
line = line.strip()
for line_comment in line_comments:
line_without_comment = line.removeprefix(line_comment)
if line_without_comment != line:
line = line_without_comment.lstrip()
break
return line

for char in chars_to_trim:
norm_line = norm_line.strip(char)

return norm_line.strip("\n").strip("\t").strip()


def normalized_text(text: str, chars_to_trim: list[str] = COMMENT_CHARS) -> str:
def normalized_text(text: str, line_comments: list[str] = LINE_COMMENTS) -> str:
"Normalize a license header text."
lines = text.split("\n")

Expand All @@ -231,7 +232,7 @@ def normalized_text(text: str, chars_to_trim: list[str] = COMMENT_CHARS) -> str:
if stripped_line.startswith("#!"):
continue

norm_line = normalized_line(stripped_line)
norm_line = normalized_line(stripped_line, line_comments=line_comments)

# exclude empty lines:
if norm_line == "":
Expand All @@ -249,30 +250,25 @@ def format_copyright_template(copyright_template: str, author: str) -> str:
return normalized_text(copyright_template.replace("{author}", author))


def is_commented_line(line: str, comment_chars: list[str] = COMMENT_CHARS) -> bool:
def is_commented_line(line: str, line_comments: list[str] = LINE_COMMENTS) -> bool:
"""Checks whether a line is a comment."""
line_stripped = line.strip()
for comment_char in comment_chars:
if line_stripped.startswith(comment_char):
return True

return False
return line.lstrip().startswith(tuple(line_comments))


def is_empty_line(line: str) -> bool:
"""Checks whether a line is empty."""
return line.strip("\n").strip("\t").strip() == ""
return not line.strip()


def get_header(file_path: Path, comment_chars: list[str] = COMMENT_CHARS):
def get_header(file_path: Path, line_comments: list[str] = LINE_COMMENTS):
"""Extracts the header from a file and normalizes it."""
header_lines: list[str] = []

try:
with open(file_path) as file:
for line in file:
if is_commented_line(
line, comment_chars=comment_chars
line, line_comments=line_comments
) or is_empty_line(line):
header_lines.append(line)
else:
Expand All @@ -282,7 +278,7 @@ def get_header(file_path: Path, comment_chars: list[str] = COMMENT_CHARS):

# normalize the lines:
header = "".join(header_lines)
return normalized_text(header, chars_to_trim=comment_chars)
return normalized_text(header, line_comments=line_comments)


def validate_year_string(year_string: str, min_year: int = MIN_YEAR) -> bool:
Expand Down Expand Up @@ -317,7 +313,7 @@ def check_copyright_notice(
global_copyright: GlobalCopyrightNotice,
copyright_template: str = COPYRIGHT_TEMPLATE,
author: str = AUTHOR,
comment_chars: list[str] = COMMENT_CHARS,
line_comments: list[str] = LINE_COMMENTS,
min_year: int = MIN_YEAR,
) -> bool:
"""Checks the specified copyright text against a template.
Expand Down Expand Up @@ -385,7 +381,7 @@ def check_file_headers(
exclude: list[str] = EXCLUDE,
exclude_endings: list[str] = EXCLUDE_ENDINGS,
exclude_pattern: list[str] = EXCLUDE_PATTERN,
comment_chars: list[str] = COMMENT_CHARS,
line_comments: list[str] = LINE_COMMENTS,
min_year: int = MIN_YEAR,
) -> tuple[list[Path], list[Path]]:
"""Check files for presence of a license header and verify that
Expand Down Expand Up @@ -429,13 +425,13 @@ def check_file_headers(

for target_file in target_files:
try:
header = get_header(target_file, comment_chars=comment_chars)
header = get_header(target_file, line_comments=line_comments)
if check_copyright_notice(
copyright=header,
global_copyright=global_copyright,
copyright_template=copyright_template,
author=author,
comment_chars=comment_chars,
line_comments=line_comments,
min_year=min_year,
):
passed_files.append(target_file)
Expand All @@ -453,7 +449,7 @@ def check_license_file(
global_copyright: GlobalCopyrightNotice,
copyright_template: str = COPYRIGHT_TEMPLATE,
author: str = AUTHOR,
comment_chars: list[str] = COMMENT_CHARS,
line_comments: list[str] = LINE_COMMENTS,
min_year: int = MIN_YEAR,
) -> bool:
"""Currently only checks if the copyright notice in the
Expand Down Expand Up @@ -495,7 +491,7 @@ def check_license_file(
global_copyright=global_copyright,
copyright_template=copyright_template,
author=author,
comment_chars=comment_chars,
line_comments=line_comments,
min_year=min_year,
)

Expand Down
4 changes: 2 additions & 2 deletions scripts/script_utils/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
#
"""Contains utils for working with dependencies, lock files, etc."""

import tomllib
from copy import deepcopy
from pathlib import Path
from typing import Any

import stringcase
import tomli


def exclude_from_dependency_list(*, package_name: str, dependencies: list) -> list:
Expand Down Expand Up @@ -69,7 +69,7 @@ def remove_self_dependencies(pyproject: dict) -> dict:
def get_modified_pyproject(pyproject_toml_path: Path) -> dict[str, Any]:
"""Get a copy of pyproject.toml with any self-referencing dependencies removed."""
with open(pyproject_toml_path, "rb") as pyproject_toml:
pyproject = tomli.load(pyproject_toml)
pyproject = tomllib.load(pyproject_toml)

modified_pyproject = remove_self_dependencies(pyproject)
return modified_pyproject
5 changes: 4 additions & 1 deletion src/ars/adapters/inbound/fastapi_/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,10 @@ async def patch_access_request(
raise HTTPException(status_code=403, detail=str(exc)) from exc
except repository.AccessRequestNotFoundError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
except repository.AccessRequestInvalidState as exc:
except (
repository.AccessRequestInvalidState,
repository.AccessRequestMissingIva,
) as exc:
raise HTTPException(status_code=422, detail=str(exc)) from exc
except Exception as exc:
log.error("Could not modify access request: %s", exc)
Expand Down
29 changes: 10 additions & 19 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,13 @@

"""Setup for testing the access request service."""

import pytest
from hexkit.providers.akafka.testutils import get_kafka_fixture
from hexkit.providers.mongodb.testutils import get_mongodb_fixture

from .fixtures import JointFixture, get_joint_fixture


@pytest.fixture(autouse=True)
def reset_state(joint_fixture: JointFixture):
"""Clear joint_fixture state before tests.

This is a function-level fixture because it needs to run in each test.
"""
joint_fixture.mongodb.empty_collections()


kafka_fixture = get_kafka_fixture("session")
mongodb_fixture = get_mongodb_fixture("session")
joint_fixture = get_joint_fixture("session")
from hexkit.providers.akafka.testutils import ( # noqa: F401
kafka_container_fixture,
kafka_fixture,
)
from hexkit.providers.mongodb.testutils import ( # noqa: F401
mongodb_container_fixture,
mongodb_fixture,
)

from .fixtures import joint_fixture # noqa: F401
21 changes: 8 additions & 13 deletions tests/fixtures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
generate_jwk,
sign_and_serialize_token,
)
from hexkit.custom_types import PytestScope
from hexkit.providers.akafka.testutils import KafkaFixture
from hexkit.providers.mongodb.testutils import MongoDbFixture
from pytest import fixture
Expand All @@ -38,7 +37,7 @@
"AUTH_CLAIMS_STEWARD",
"fixture_auth_headers_doe",
"fixture_auth_headers_steward",
"get_joint_fixture",
"joint_fixture",
"JointFixture",
"headers_for_token",
]
Expand Down Expand Up @@ -91,8 +90,9 @@ class JointFixture(NamedTuple):
rest_client: AsyncTestClient


async def joint_fixture_function(
mongodb_fixture: MongoDbFixture, kafka_fixture: KafkaFixture
@pytest_asyncio.fixture()
async def joint_fixture(
mongodb: MongoDbFixture, kafka: KafkaFixture
) -> AsyncGenerator[JointFixture, None]:
"""A fixture that embeds all other fixtures for API-level integration testing

Expand All @@ -101,8 +101,8 @@ async def joint_fixture_function(
config = Config(
auth_key=AUTH_KEY_PAIR.export_public(), # pyright: ignore
download_access_url="http://access",
**kafka_fixture.config.model_dump(),
**mongodb_fixture.config.model_dump(),
**kafka.config.model_dump(),
**mongodb.config.model_dump(),
)
async with prepare_core(config=config) as core:
async with (
Expand All @@ -111,12 +111,7 @@ async def joint_fixture_function(
async with AsyncTestClient(app=app) as rest_client:
yield JointFixture(
config=config,
kafka=kafka_fixture,
mongodb=mongodb_fixture,
kafka=kafka,
mongodb=mongodb,
rest_client=rest_client,
)


def get_joint_fixture(scope: PytestScope = "function"):
"""Produce a joint fixture with desired scope"""
return pytest_asyncio.fixture(joint_fixture_function, scope=scope)
2 changes: 1 addition & 1 deletion tests/test_access_grants.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

from ars.adapters.outbound.http import AccessGrantsAdapter, AccessGrantsConfig

pytestmark = pytest.mark.asyncio(scope="session")
pytestmark = pytest.mark.asyncio()


DOWNLOAD_ACCESS_URL = "http://test-access:1234"
Expand Down
Loading