Skip to content

Commit

Permalink
Merge branch 'master' into maintenance/httpbin-fixture
Browse files Browse the repository at this point in the history
  • Loading branch information
pcrespov authored May 5, 2023
2 parents 0fa1fbd + dafc3a9 commit 08c97e1
Show file tree
Hide file tree
Showing 15 changed files with 85 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from .exporter.plugin import setup_exporter
from .garbage_collector import setup_garbage_collector
from .groups import setup_groups
from .invitations import setup_invitations
from .invitations.plugin import setup_invitations
from .login.plugin import setup_login
from .long_running_tasks import setup_long_running_tasks
from .meta_modeling.plugin import setup_meta_modeling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from .director_v2_settings import DirectorV2Settings
from .exporter.settings import ExporterSettings
from .garbage_collector_settings import GarbageCollectorSettings
from .invitations_settings import InvitationsSettings
from .invitations.settings import InvitationsSettings
from .login.settings import LoginSettings
from .projects.projects_settings import ProjectsSettings
from .resource_manager.settings import ResourceManagerSettings
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Plugin to interact with the 'invitations' service
"""
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@
import logging
from dataclasses import dataclass
from datetime import datetime
from typing import Optional

from aiohttp import BasicAuth, ClientSession, web
from aiohttp.client_exceptions import ClientError
from models_library.emails import LowerCaseEmailStr
from pydantic import AnyHttpUrl, BaseModel, parse_obj_as
from yarl import URL

from ._constants import APP_SETTINGS_KEY
from .invitations_settings import InvitationsSettings
from .._constants import APP_SETTINGS_KEY
from .settings import InvitationsSettings

logger = logging.getLogger(__name__)
_logger = logging.getLogger(__name__)


#
Expand All @@ -24,7 +23,7 @@
class InvitationContent(BaseModel):
issuer: str
guest: LowerCaseEmailStr
trial_account_days: Optional[int] = None
trial_account_days: int | None = None
created: datetime


Expand Down Expand Up @@ -74,12 +73,13 @@ async def close(self) -> None:
await self.exit_stack.aclose()

async def ping(self) -> bool:
ok = False
try:
response = await self.client.get(self._url(self.healthcheck_path))
return response.ok
ok = response.ok
except ClientError as err:
logger.debug("Invitations service is not responsive: %s", err)
return False
_logger.debug("Invitations service is not responsive: %s", err)
return ok

is_responsive = ping

Expand Down Expand Up @@ -116,9 +116,10 @@ async def invitations_service_api_cleanup_ctx(app: web.Application):
try:
await service_api.close()
except Exception: # pylint: disable=broad-except
logger.warning("Ignoring error while closing service-api")
_logger.warning("Ignoring error while closing service-api")


def get_invitations_service_api(app: web.Application) -> InvitationsServiceApi:
assert app[_APP_INVITATIONS_SERVICE_API_KEY] # nosec
return app[_APP_INVITATIONS_SERVICE_API_KEY]
service_api: InvitationsServiceApi = app[_APP_INVITATIONS_SERVICE_API_KEY]
return service_api
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
"""
Plugin to interact with the invitations service
"""

import logging
from contextlib import contextmanager

import sqlalchemy as sa
from aiohttp import ClientError, ClientResponseError, web
from pydantic import AnyHttpUrl, ValidationError, parse_obj_as
from pydantic.errors import PydanticErrorMixin
from servicelib.error_codes import create_error_code
from simcore_postgres_database.models.users import users

from .db import get_database_engine
from .invitations_client import (
from ..db import get_database_engine
from ._client import (
InvitationContent,
InvitationsServiceApi,
get_invitations_service_api,
)
from .errors import (
MSG_INVALID_INVITATION_URL,
MSG_INVITATION_ALREADY_USED,
InvalidInvitation,
InvitationsErrors,
InvitationsServiceUnavailable,
)

logger = logging.getLogger(__name__)


#
# DATABASE
#
_logger = logging.getLogger(__name__)


async def _is_user_registered(app: web.Application, email: str) -> bool:
Expand All @@ -37,26 +34,6 @@ async def _is_user_registered(app: web.Application, email: str) -> bool:
return user_id is not None


#
# API plugin ERRORS
#


class InvitationsErrors(PydanticErrorMixin, ValueError):
...


class InvalidInvitation(InvitationsErrors):
msg_template = "Invalid invitation. {reason}"


class InvitationsServiceUnavailable(InvitationsErrors):
msg_template = (
"Unable to process your invitation since the invitations service is currently unavailable. "
"Please try again later."
)


@contextmanager
def _handle_exceptions_as_invitations_errors():
try:
Expand All @@ -67,7 +44,7 @@ def _handle_exceptions_as_invitations_errors():
# check possible errors
if err.status == web.HTTPUnprocessableEntity.status_code:
error_code = create_error_code(err)
logger.exception(
_logger.exception(
"Invitation request %s unexpectedly failed [%s]",
f"{err=} ",
f"{error_code}",
Expand All @@ -87,7 +64,7 @@ def _handle_exceptions_as_invitations_errors():
raise

except Exception as err:
logger.exception("Unexpected error in invitations plugin")
_logger.exception("Unexpected error in invitations plugin")
raise InvitationsServiceUnavailable() from err


Expand All @@ -101,9 +78,6 @@ def is_service_invitation_code(code: str):
return len(code) > 100 # typically long strings


_MGS_INVALID_INVITATION_URL = "Link seems corrupted or incomplete"


async def validate_invitation_url(
app: web.Application, guest_email: str, invitation_url: str
) -> InvitationContent:
Expand All @@ -116,13 +90,13 @@ async def validate_invitation_url(
with _handle_exceptions_as_invitations_errors():

try:
invitation_url = parse_obj_as(AnyHttpUrl, invitation_url)
valid_url = parse_obj_as(AnyHttpUrl, invitation_url)
except ValidationError as err:
raise InvalidInvitation(reason=_MGS_INVALID_INVITATION_URL) from err
raise InvalidInvitation(reason=MSG_INVALID_INVITATION_URL) from err

# check with service
invitation = await invitations_service.extract_invitation(
invitation_url=invitation_url
invitation_url=valid_url
)

if invitation.guest != guest_email:
Expand All @@ -132,7 +106,7 @@ async def validate_invitation_url(

# existing users cannot be re-invited
if await _is_user_registered(app=app, email=invitation.guest):
raise InvalidInvitation(reason="This invitation was already used")
raise InvalidInvitation(reason=MSG_INVITATION_ALREADY_USED)

return invitation

Expand All @@ -149,12 +123,12 @@ async def extract_invitation(
with _handle_exceptions_as_invitations_errors():

try:
invitation_url = parse_obj_as(AnyHttpUrl, invitation_url)
valid_url = parse_obj_as(AnyHttpUrl, invitation_url)
except ValidationError as err:
raise InvalidInvitation(reason=_MGS_INVALID_INVITATION_URL) from err
raise InvalidInvitation(reason=MSG_INVALID_INVITATION_URL) from err

# check with service
invitation = await invitations_service.extract_invitation(
invitation_url=invitation_url
invitation_url=valid_url
)
return invitation
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""
API plugin errors
"""


from pydantic.errors import PydanticErrorMixin

MSG_INVALID_INVITATION_URL = "Link seems corrupted or incomplete"
MSG_INVITATION_ALREADY_USED = "This invitation was already used"


class InvitationsErrors(PydanticErrorMixin, ValueError):
...


class InvalidInvitation(InvitationsErrors):
msg_template = "Invalid invitation. {reason}"


class InvitationsServiceUnavailable(InvitationsErrors):
msg_template = (
"Unable to process your invitation since the invitations service is currently unavailable. "
"Please try again later."
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,23 @@
from aiohttp import web
from servicelib.aiohttp.application_setup import ModuleCategory, app_module_setup

from ._constants import APP_SETTINGS_KEY
from .db import setup_db
from .invitations_client import invitations_service_api_cleanup_ctx
from .invitations_core import (
InvalidInvitation,
InvitationsServiceUnavailable,
from .._constants import APP_SETTINGS_KEY
from ..db import setup_db
from ._client import invitations_service_api_cleanup_ctx
from ._core import (
extract_invitation,
is_service_invitation_code,
validate_invitation_url,
)

logger = logging.getLogger(__name__)
_logger = logging.getLogger(__name__)


@app_module_setup(
__name__,
ModuleCategory.ADDON,
settings_name="WEBSERVER_INVITATIONS",
logger=logger,
logger=_logger,
)
def setup_invitations(app: web.Application):
assert app[APP_SETTINGS_KEY].WEBSERVER_INVITATIONS # nosec
Expand All @@ -41,8 +39,6 @@ def setup_invitations(app: web.Application):

__all__: tuple[str, ...] = (
"extract_invitation",
"InvalidInvitation",
"InvitationsServiceUnavailable",
"is_service_invitation_code",
"setup_invitations",
"validate_invitation_url",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
URLPart,
)

from ._constants import APP_SETTINGS_KEY
from .._constants import APP_SETTINGS_KEY


class InvitationsSettings(BaseCustomSettings, MixinServiceSettings):
Expand All @@ -38,20 +38,22 @@ class InvitationsSettings(BaseCustomSettings, MixinServiceSettings):
@cached_property
def api_base_url(self) -> str:
# http://invitations:8000/v1
return self._compose_url(
base_url_with_vtag: str = self._compose_url(
prefix="INVITATIONS",
port=URLPart.REQUIRED,
vtag=URLPart.REQUIRED,
)
return base_url_with_vtag

@cached_property
def base_url(self) -> str:
# http://invitations:8000
return self._compose_url(
base_url_without_vtag: str = self._compose_url(
prefix="INVITATIONS",
port=URLPart.REQUIRED,
vtag=URLPart.EXCLUDE,
)
return base_url_without_vtag


def get_plugin_settings(app: web.Application) -> InvitationsSettings:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@
from simcore_postgres_database.models.confirmations import ConfirmationAction
from yarl import URL

from ..invitations import (
InvalidInvitation,
InvitationsServiceUnavailable,
from ..invitations.errors import InvalidInvitation, InvitationsServiceUnavailable
from ..invitations.plugin import (
extract_invitation,
is_service_invitation_code,
validate_invitation_url,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from .._meta import API_VTAG
from ..groups_api import auto_add_user_to_groups, auto_add_user_to_product_group
from ..invitations import is_service_invitation_code
from ..invitations.plugin import is_service_invitation_code
from ..products.plugin import Product, get_current_product
from ..security.api import encrypt_password
from ..session_access import on_success_grant_session_access_to, session_access_required
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from ..db_settings import get_plugin_settings as get_db_plugin_settings
from ..email import setup_email
from ..email_settings import get_plugin_settings as get_email_plugin_settings
from ..invitations import setup_invitations
from ..invitations.plugin import setup_invitations
from ..products.plugin import ProductName, list_products, setup_products
from ..redis import setup_redis
from ..rest import setup_rest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ async def test_add_new_project_from_model_instance(
file_picker_id: NodeID,
viewer_id: NodeID,
):
assert client.app

mock_directorv2_api = mocker.patch(
"simcore_service_webserver.director_v2_api.create_or_update_pipeline",
return_value=None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
from aiohttp.test_utils import TestClient
from models_library.utils.fastapi_encoders import jsonable_encoder
from pytest_simcore.aioresponses_mocker import AioResponsesMock
from simcore_service_webserver.invitations_client import InvitationContent
from simcore_service_webserver.invitations_settings import (
from simcore_service_webserver.invitations._client import InvitationContent
from simcore_service_webserver.invitations.settings import (
InvitationsSettings,
get_plugin_settings,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@
from pytest_simcore.helpers.utils_envs import EnvVarsDict, setenvs_from_dict
from pytest_simcore.helpers.utils_login import NewUser
from simcore_service_webserver.application_settings import ApplicationSettings
from simcore_service_webserver.invitations import (
InvalidInvitation,
InvitationsServiceUnavailable,
validate_invitation_url,
)
from simcore_service_webserver.invitations_client import (
from simcore_service_webserver.invitations._client import (
InvitationContent,
InvitationsServiceApi,
get_invitations_service_api,
)
from simcore_service_webserver.invitations.errors import (
InvalidInvitation,
InvitationsServiceUnavailable,
)
from simcore_service_webserver.invitations.plugin import validate_invitation_url
from yarl import URL


Expand Down
Loading

0 comments on commit 08c97e1

Please sign in to comment.