diff --git a/geomagio/api/secure/login.py b/geomagio/api/secure/login.py index c3407d9317c5f6d4fad6f6a0f62c0b2b23d1df40..b687dec3bd562b92b6b30ac4657db62936e5d279 100644 --- a/geomagio/api/secure/login.py +++ b/geomagio/api/secure/login.py @@ -35,6 +35,7 @@ Usage: """ import logging import os +import requests from typing import Callable, List, Optional from authlib.integrations.starlette_client import OAuth @@ -56,7 +57,8 @@ class User(BaseModel): async def current_user(request: Request) -> Optional[User]: - """Get logged in user, or None if not logged in. + """Get user information from gitlab access token or session(if currently logged in). + Returns none if access token is not vald or user is not logged in. Usage example: user: Optional[User] = Depends(current_user) @@ -64,9 +66,45 @@ async def current_user(request: Request) -> Optional[User]: """ if "user" in request.session: return User(**request.session["user"]) + if "apiuser" in request.session: + return User(**request.session["apiuser"]) + if "Authorization" in request.headers: + user = get_api_user(token=request.headers["Authorization"]) + if user is not None: + request.session["apiuser"] = user.dict() + return user return None +def get_api_user(token: str) -> Optional[User]: + url = os.getenv("GITLAB_API_URL") + header = {"PRIVATE-TOKEN": token} + # request user information from gitlab api with access token + userinfo_response = requests.get( + f"{url}/user", + headers=header, + ) + userinfo = userinfo_response.json() + try: + user = User( + email=userinfo["email"], + sub=userinfo["id"], + name=userinfo["name"], + nickname=userinfo["username"], + picture=userinfo["avatar_url"], + ) + except KeyError: + logging.info(f"Invalid token: {userinfo_response.status_code}") + return None + # use valid token to retrieve user's groups + groups_response = requests.get( + f"{url}/groups", + headers=header, + ) + user.groups = [g["full_path"] for g in groups_response.json()] + return user + + def require_user( allowed_groups: List[str] = None, ) -> Callable[[Request, User], User]: diff --git a/geomagio/api/secure/metadata.py b/geomagio/api/secure/metadata.py index 710128d1af33bd856d08c0b2d48b9b1cabff5029..e619c66362b8b4e960dd92cba5b5d88ff493b6b2 100644 --- a/geomagio/api/secure/metadata.py +++ b/geomagio/api/secure/metadata.py @@ -19,11 +19,10 @@ from typing import List from fastapi import APIRouter, Body, Depends, Request, Response from obspy import UTCDateTime -from ...metadata import Metadata, MetadataCategory +from ...metadata import Metadata, MetadataCategory, MetadataQuery +from ... import pydantic_utcdatetime from ..db import metadata_table from .login import require_user, User -from .MetadataQuery import MetadataQuery -from ... import pydantic_utcdatetime # routes for login/logout router = APIRouter() @@ -41,7 +40,7 @@ async def create_metadata( @router.delete("/metadata/{id}") async def delete_metadata( - id: int, user: User = Depends(require_user(os.getenv("ADMIN_GROUP", "admin"))) + id: int, user: User = Depends(require_user([os.getenv("ADMIN_GROUP", "admin")])) ): await metadata_table.delete_metadata(id) diff --git a/geomagio/api/ws/metadata.py b/geomagio/api/ws/metadata.py index 9f4febeb93bc0a3b89541697df2bc405a5f9881d..655c33e59f0ec84b4329e5e3c6642c6dbe24882c 100644 --- a/geomagio/api/ws/metadata.py +++ b/geomagio/api/ws/metadata.py @@ -3,8 +3,7 @@ from typing import List from fastapi import APIRouter from obspy import UTCDateTime -from ...metadata import Metadata, MetadataCategory -from ..secure.MetadataQuery import MetadataQuery +from ...metadata import Metadata, MetadataCategory, MetadataQuery from ..db import metadata_table router = APIRouter() diff --git a/geomagio/apiclient/MetadataFactory.py b/geomagio/apiclient/MetadataFactory.py new file mode 100644 index 0000000000000000000000000000000000000000..288901c6856ce2a2a90ec8054555283b301d4d2e --- /dev/null +++ b/geomagio/apiclient/MetadataFactory.py @@ -0,0 +1,71 @@ +import os +import requests +from typing import List, Union + +from obspy import UTCDateTime +from pydantic import parse_obj_as + +from ..metadata import Metadata, MetadataQuery + + +class MetadataFactory(object): + def __init__( + self, + url: str = "http://{}/ws/secure/metadata".format( + os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000") + ), + token: str = os.getenv("GITLAB_API_TOKEN"), + ): + self.url = url + self.token = token + self.header = self._get_headers() + + def _get_headers(self): + return {"Authorization": self.token} if self.token else None + + def delete_metadata(self, metadata: Metadata) -> bool: + response = requests.delete(url=f"{self.url}/{metadata.id}", headers=self.header) + if response.status_code == 200: + return True + return False + + def get_metadata(self, query: MetadataQuery) -> List[Metadata]: + if query.id: + response = self.get_metadata_by_id(id=query.id) + else: + args = parse_params(query=query) + response = requests.get(url=self.url, params=args, headers=self.header) + metadata = parse_obj_as(Union[List[Metadata], Metadata], response.json()) + if isinstance(metadata, Metadata): + metadata = [metadata] + return metadata + + def get_metadata_by_id(self, id: int) -> requests.Response: + return requests.get(f"{self.url}/{id}", headers=self.header) + + def create_metadata(self, metadata: Metadata) -> Metadata: + response = requests.post( + url=self.url, data=metadata.json(), headers=self.header + ) + return Metadata(**response.json()) + + def update_metadata(self, metadata: Metadata) -> Metadata: + response = requests.put( + url=f"{self.url}/{metadata.id}", data=metadata.json(), headers=self.header + ) + return Metadata(**response.json()) + + +def parse_params(query: MetadataQuery) -> str: + query = query.dict(exclude_none=True) + args = {} + for key in query.keys(): + element = query[key] + # convert times to strings + if isinstance(element, UTCDateTime): + element = element.isoformat() + # get string value of metadata category + if key == "category": + element = element.value + args[key] = element + return args diff --git a/geomagio/apiclient/__init__.py b/geomagio/apiclient/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..53bb45e4c2ebd258886d2bcf0d41522b7a519bdb --- /dev/null +++ b/geomagio/apiclient/__init__.py @@ -0,0 +1,4 @@ +from .metadata import app +from .MetadataFactory import MetadataFactory + +__all__ = ["app", "MetadataFactory"] diff --git a/geomagio/apiclient/metadata.py b/geomagio/apiclient/metadata.py new file mode 100644 index 0000000000000000000000000000000000000000..afb3ea63e3c64354553c11c0b8b8cc4773bdb7f3 --- /dev/null +++ b/geomagio/apiclient/metadata.py @@ -0,0 +1,144 @@ +import sys +import json +import os +from typing import Dict, Optional + +from obspy import UTCDateTime +import typer + +from ..metadata import Metadata, MetadataCategory, MetadataQuery +from .MetadataFactory import MetadataFactory + + +def load_metadata(input_file: str) -> Optional[Dict]: + if input_file is None: + return None + if input_file == "-": + data = json.loads(sys.stdin.read()) + return data + with open(input_file, "r") as file: + data = json.load(file) + return data + + +app = typer.Typer() + + +@app.command() +def create( + url: str = "http://{}/ws/secure/metadata".format( + os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000") + ), + category: MetadataCategory = None, + channel: str = None, + created_after: str = None, + created_before: str = None, + data_valid: bool = True, + endtime: str = None, + id: int = None, + input_file: str = None, + location: str = None, + metadata_valid: bool = True, + network: str = None, + starttime: str = None, + station: str = None, + wrap: bool = True, +): + input_metadata = load_metadata(input_file=input_file) + if not wrap: + metadata = Metadata(**input_metadata) + else: + metadata = Metadata( + category=category, + channel=channel, + created_after=UTCDateTime(created_after) if created_after else None, + created_before=UTCDateTime(created_before) if created_before else None, + data_valid=data_valid, + endtime=UTCDateTime(endtime) if endtime else None, + id=id, + location=location, + metadata=input_metadata, + metadata_valid=metadata_valid, + network=network, + starttime=UTCDateTime(starttime) if starttime else None, + station=station, + ) + metadata = MetadataFactory(url=url).create_metadata(metadata=metadata) + print(metadata.json()) + + +@app.command() +def delete( + input_file: str, + url: str = "http://{}/ws/secure/metadata".format( + os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000") + ), +): + metadata_dict = load_metadata(input_file=input_file) + metadata = Metadata(**metadata_dict) + deleted = MetadataFactory(url=url).delete_metadata(metadata=metadata) + if not deleted: + sys.exit(1) + + +@app.command() +def get( + url: str = "http://{}/ws/secure/metadata".format( + os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000") + ), + category: Optional[MetadataCategory] = None, + channel: Optional[str] = None, + created_after: Optional[str] = None, + created_before: Optional[str] = None, + data_valid: Optional[bool] = True, + endtime: Optional[str] = None, + id: Optional[int] = None, + location: Optional[str] = None, + metadata_valid: Optional[bool] = True, + network: Optional[str] = None, + starttime: Optional[str] = None, + station: Optional[str] = None, + getone: bool = False, +): + query = MetadataQuery( + category=category, + channel=channel, + created_after=UTCDateTime(created_after) if created_after else None, + created_before=UTCDateTime(created_before) if created_before else None, + data_valid=data_valid, + endtime=UTCDateTime(endtime) if endtime else None, + id=id, + location=location, + metadata_valid=metadata_valid, + network=network, + starttime=UTCDateTime(starttime) if starttime else None, + station=station, + ) + metadata = MetadataFactory(url=url).get_metadata(query=query) + if not metadata: + print([]) + return + + if getone: + if len(metadata) > 1: + raise ValueError("More than one matching record") + print(metadata[0].json()) + return + print("[" + ",".join([m.json() for m in metadata]) + "]") + + +@app.command() +def update( + input_file: str, + url: str = "http://{}/ws/secure/metadata".format( + os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000") + ), +): + metadata_dict = load_metadata(input_file=input_file) + metadata = Metadata(**metadata_dict) + response = MetadataFactory(url=url).update_metadata(metadata=metadata) + print(response.json()) + + +def main(): + app() diff --git a/geomagio/api/secure/MetadataQuery.py b/geomagio/metadata/MetadataQuery.py similarity index 79% rename from geomagio/api/secure/MetadataQuery.py rename to geomagio/metadata/MetadataQuery.py index 6b30699d9ef1371019a6b89aaa0244b5b0c30111..36e6c3ebe3cd20d880c25ff0d683757c84a2d833 100644 --- a/geomagio/api/secure/MetadataQuery.py +++ b/geomagio/metadata/MetadataQuery.py @@ -2,9 +2,10 @@ from datetime import timezone from obspy import UTCDateTime from pydantic import BaseModel +from typing import Optional -from ...metadata import MetadataCategory -from ... import pydantic_utcdatetime +from .. import pydantic_utcdatetime +from .MetadataCategory import MetadataCategory class MetadataQuery(BaseModel): @@ -18,8 +19,8 @@ class MetadataQuery(BaseModel): station: str = None channel: str = None location: str = None - data_valid: bool = None - metadata_valid: bool = True + data_valid: Optional[bool] = None + metadata_valid: Optional[bool] = None def datetime_dict(self, **kwargs): values = self.dict(**kwargs) diff --git a/geomagio/metadata/__init__.py b/geomagio/metadata/__init__.py index 7502db0243cac6af13d38c8386c3dd292ae386c2..37b2adc332b5f6a90f02c75ac9166cf19de48089 100644 --- a/geomagio/metadata/__init__.py +++ b/geomagio/metadata/__init__.py @@ -1,5 +1,6 @@ from .Metadata import Metadata from .MetadataCategory import MetadataCategory +from .MetadataQuery import MetadataQuery -__all__ = ["Metadata", "MetadataCategory"] +__all__ = ["Metadata", "MetadataCategory", "MetadataQuery"] diff --git a/geomagio/processing/__init__.py b/geomagio/processing/__init__.py index b17686a9ee5a9b5cda1a2960f69c27c9d8305d95..be97cfbd5e32485df54fc814fd646c595685fed7 100644 --- a/geomagio/processing/__init__.py +++ b/geomagio/processing/__init__.py @@ -17,7 +17,7 @@ __all__ = [ "obsrio_minute", "obsrio_second", "obsrio_temperatures", - "obsrid_tenhertz", + "obsrio_tenhertz", "rotate", "sqdist_minute", ] diff --git a/setup.py b/setup.py index 6216d647e18bb973c54c54a4011d9db32115d710..a73fd11e7b6ce8880a573af13857d342c6348f12 100644 --- a/setup.py +++ b/setup.py @@ -25,8 +25,9 @@ setuptools.setup( use_pipfile=True, entry_points={ "console_scripts": [ - "magproc-prepfiles=geomagio.processing.magproc:main", "generate-matrix=geomagio.processing.adjusted:main", + "geomag-apiclient=geomagio.apiclient.metadata:main", + "magproc-prepfiles=geomagio.processing.magproc:main", "obsrio-filter=geomagio.processing.obsrio:main", ], },