From 96ac7c343b3af54bac4b741d78df3eb09c413f6a Mon Sep 17 00:00:00 2001
From: pcain-usgs <pcain@usgs.gov>
Date: Fri, 12 Feb 2021 17:15:21 -0700
Subject: [PATCH 01/12] Implement metadata factory for requests

---
 geomagio/metadata/MetadataFactory.py | 81 ++++++++++++++++++++++++++++
 geomagio/metadata/__init__.py        |  3 +-
 2 files changed, 83 insertions(+), 1 deletion(-)
 create mode 100644 geomagio/metadata/MetadataFactory.py

diff --git a/geomagio/metadata/MetadataFactory.py b/geomagio/metadata/MetadataFactory.py
new file mode 100644
index 000000000..31578aa0c
--- /dev/null
+++ b/geomagio/metadata/MetadataFactory.py
@@ -0,0 +1,81 @@
+import os
+import json
+import requests
+import urllib
+from typing import Dict, List, Optional
+
+from obspy import UTCDateTime
+from pydantic import parse_obj_as
+
+from ..residual import Reading
+from .Metadata import Metadata
+from .MetadataCategory import MetadataCategory
+
+HOST = os.getenv("EDGE_HOST", "127.0.0.1:8000")
+
+class MetadataFactory(object):
+    def __init__(self, url:str = None):
+        self.url = url or f"http://{HOST}/ws/secure/metadata"
+
+    def get_metadata(
+        self,
+        category: Optional[MetadataCategory] = None,
+        starttime: Optional[UTCDateTime] = None,
+        endtime: Optional[UTCDateTime] = None,
+        created_after: Optional[UTCDateTime] = None,
+        created_before: Optional[UTCDateTime] = None,
+        network: Optional[str] = None,
+        station: Optional[str] = None,
+        channel: Optional[str] = None,
+        location: Optional[str] = None,
+        data_valid: Optional[bool] = None,
+        metadata_valid: Optional[bool] = True,
+        # returns interior base models from metadata.metadata
+        return_objects: str = False,
+    ) -> List[Metadata]:
+        args = parse_params(params=locals())
+        response = web_request(url=f"{self.url}?{args}")
+        metadata = self.format_metadata(data=response, return_objects=return_objects)
+        return metadata
+
+    def put_metadata(self):
+        raise NotImplementedError
+
+    def format_metadata(self, data: Dict, return_objects: bool = False):
+        # formats responses as Metadata objects
+        data = parse_obj_as(List[Metadata], data)
+        if not return_objects:
+            return data
+        formatted_metadata = []
+        for metadata in data:
+            category = metadata.category
+            if category == MetadataCategory.ADJUSTED_MATRIX:
+                raise NotImplementedError
+            elif category == MetadataCategory.FLAG:
+                raise NotImplementedError
+            elif category == MetadataCategory.READING:
+                formatted_metadata.append(parse_obj_as(Reading, metadata.metadata))
+            # observatory and instrument metadata do not have a confining object other than metadata
+        return formatted_metadata
+
+
+def web_request(url:str) -> Dict:
+    client_id=os.getenv("OPENID_CLIENT_ID")
+    client_secret=os.getenv("OPENID_CLIENT_SECRET")
+    response = requests.get(url, data={'grant_type' : 'client_credentials'}, auth = (client_id, client_secret))
+    metadata = json.loads(response.text)
+    return metadata
+
+def parse_params(params: Dict):
+    data = {}
+    for param in params.keys():
+        if params[param] is not None and param != "self":
+            p = params[param]
+            # convert times to strings
+            if type(p) == UTCDateTime:
+                p=p.isoformat()
+            data[param] = p
+
+    return urllib.parse.urlencode(
+            data
+        )
\ No newline at end of file
diff --git a/geomagio/metadata/__init__.py b/geomagio/metadata/__init__.py
index 7502db024..f1c6be68b 100644
--- a/geomagio/metadata/__init__.py
+++ b/geomagio/metadata/__init__.py
@@ -1,5 +1,6 @@
 from .Metadata import Metadata
 from .MetadataCategory import MetadataCategory
+from .MetadataFactory import MetadataFactory
 
 
-__all__ = ["Metadata", "MetadataCategory"]
+__all__ = ["Metadata", "MetadataCategory", "MetadataFactory"]
-- 
GitLab


From d10fa1b84ddaff0f87628bd4088b27920607a273 Mon Sep 17 00:00:00 2001
From: pcain-usgs <pcain@usgs.gov>
Date: Tue, 16 Feb 2021 11:16:40 -0700
Subject: [PATCH 02/12] factory entrypoint for requests

---
 geomagio/geomagioapi/__init__.py     |  3 +
 geomagio/geomagioapi/metadata.py     | 54 ++++++++++++++++
 geomagio/metadata/MetadataFactory.py | 93 ++++++++++++----------------
 geomagio/processing/__init__.py      |  2 +-
 setup.py                             |  1 +
 5 files changed, 99 insertions(+), 54 deletions(-)
 create mode 100644 geomagio/geomagioapi/__init__.py
 create mode 100644 geomagio/geomagioapi/metadata.py

diff --git a/geomagio/geomagioapi/__init__.py b/geomagio/geomagioapi/__init__.py
new file mode 100644
index 000000000..20b3da7f7
--- /dev/null
+++ b/geomagio/geomagioapi/__init__.py
@@ -0,0 +1,3 @@
+from .metadata import client
+
+__all__ = ["client"]
diff --git a/geomagio/geomagioapi/metadata.py b/geomagio/geomagioapi/metadata.py
new file mode 100644
index 000000000..9d56037c7
--- /dev/null
+++ b/geomagio/geomagioapi/metadata.py
@@ -0,0 +1,54 @@
+import os
+from typing import Literal, Optional
+
+from obspy import UTCDateTime
+import typer
+
+from ..api.secure.MetadataQuery import MetadataQuery
+from ..metadata import MetadataCategory, MetadataFactory
+
+
+def main():
+    typer.run(client)
+
+
+def client(
+    action: str,
+    url: str = "http://{}/ws/secure/metadata".format(
+        os.getenv("EDGE_HOST", "127.0.0.1:8000")
+    ),
+    category: Optional[MetadataCategory] = None,
+    starttime: Optional[str] = None,
+    endtime: Optional[str] = None,
+    created_after: Optional[str] = None,
+    created_before: Optional[str] = None,
+    network: Optional[str] = None,
+    station: Optional[str] = None,
+    channel: Optional[str] = None,
+    location: Optional[str] = None,
+    data_valid: Optional[bool] = None,
+    metadata_valid: Optional[bool] = True,
+):
+    query = MetadataQuery(
+        category=category,
+        starttime=UTCDateTime(starttime) if starttime else None,
+        endtime=UTCDateTime(endtime) if endtime else None,
+        created_after=UTCDateTime(created_after) if created_after else None,
+        created_before=UTCDateTime(created_before) if created_before else None,
+        network=network,
+        station=station,
+        channel=channel,
+        location=location,
+        data_valid=data_valid,
+        metadata_valid=metadata_valid,
+    )
+    factory = MetadataFactory(url=url)
+    if action == "delete":
+        factory.delete_metadata(query=query)
+    elif action == "get":
+        metadata = factory.get_metadata(query=query)
+    if action == "post":
+        factory.post_metadata(query=query)
+    if action == "update":
+        factory.update_metadata(query=query)
+    return metadata
diff --git a/geomagio/metadata/MetadataFactory.py b/geomagio/metadata/MetadataFactory.py
index 31578aa0c..dc3fefe09 100644
--- a/geomagio/metadata/MetadataFactory.py
+++ b/geomagio/metadata/MetadataFactory.py
@@ -7,75 +7,62 @@ from typing import Dict, List, Optional
 from obspy import UTCDateTime
 from pydantic import parse_obj_as
 
+from ..api.secure import MetadataQuery
 from ..residual import Reading
 from .Metadata import Metadata
 from .MetadataCategory import MetadataCategory
 
-HOST = os.getenv("EDGE_HOST", "127.0.0.1:8000")
 
 class MetadataFactory(object):
-    def __init__(self, url:str = None):
-        self.url = url or f"http://{HOST}/ws/secure/metadata"
-
-    def get_metadata(
+    def __init__(
         self,
-        category: Optional[MetadataCategory] = None,
-        starttime: Optional[UTCDateTime] = None,
-        endtime: Optional[UTCDateTime] = None,
-        created_after: Optional[UTCDateTime] = None,
-        created_before: Optional[UTCDateTime] = None,
-        network: Optional[str] = None,
-        station: Optional[str] = None,
-        channel: Optional[str] = None,
-        location: Optional[str] = None,
-        data_valid: Optional[bool] = None,
-        metadata_valid: Optional[bool] = True,
-        # returns interior base models from metadata.metadata
-        return_objects: str = False,
-    ) -> List[Metadata]:
-        args = parse_params(params=locals())
+        url: str = "http://{}/ws/secure/metadata".format(
+            os.getenv("EDGE_HOST", "127.0.0.1:8000")
+        ),
+    ):
+        self.url = url
+
+    def delete_metadata(self, query: MetadataQuery):
+        raise NotImplementedError
+
+    def format_metadata(self, data: Dict):
+        # formats responses as Metadata objects
+        return parse_obj_as(List[Metadata], data)
+
+    def get_metadata(self, query: MetadataQuery) -> List[Metadata]:
+        args = parse_params(query=query)
         response = web_request(url=f"{self.url}?{args}")
-        metadata = self.format_metadata(data=response, return_objects=return_objects)
+        metadata = self.format_metadata(data=response)
         return metadata
 
-    def put_metadata(self):
+    def post_metadata(self, query: MetadataQuery):
         raise NotImplementedError
 
-    def format_metadata(self, data: Dict, return_objects: bool = False):
-        # formats responses as Metadata objects
-        data = parse_obj_as(List[Metadata], data)
-        if not return_objects:
-            return data
-        formatted_metadata = []
-        for metadata in data:
-            category = metadata.category
-            if category == MetadataCategory.ADJUSTED_MATRIX:
-                raise NotImplementedError
-            elif category == MetadataCategory.FLAG:
-                raise NotImplementedError
-            elif category == MetadataCategory.READING:
-                formatted_metadata.append(parse_obj_as(Reading, metadata.metadata))
-            # observatory and instrument metadata do not have a confining object other than metadata
-        return formatted_metadata
+    def update_metadata(self, query: MetadataQuery):
+        raise NotImplementedError
 
 
-def web_request(url:str) -> Dict:
-    client_id=os.getenv("OPENID_CLIENT_ID")
-    client_secret=os.getenv("OPENID_CLIENT_SECRET")
-    response = requests.get(url, data={'grant_type' : 'client_credentials'}, auth = (client_id, client_secret))
+def web_request(url: str) -> Dict:
+    client_id = os.getenv("OPENID_CLIENT_ID")
+    client_secret = os.getenv("OPENID_CLIENT_SECRET")
+    response = requests.get(
+        url, data={"grant_type": "client_credentials"}, auth=(client_id, client_secret)
+    )
     metadata = json.loads(response.text)
     return metadata
 
-def parse_params(params: Dict):
+
+def parse_params(query: MetadataQuery):
+    d = query.dict()
     data = {}
-    for param in params.keys():
-        if params[param] is not None and param != "self":
-            p = params[param]
-            # convert times to strings
-            if type(p) == UTCDateTime:
-                p=p.isoformat()
-            data[param] = p
+    for key in d.keys():
+        if d[key] is None:
+            continue
+        # convert times to strings
+        if type(d[key]) == UTCDateTime:
+            d[key] = d[key].isoformat()
+        if key == "category":
+            d[key] = d[key].value
+        data[key] = d[key]
 
-    return urllib.parse.urlencode(
-            data
-        )
\ No newline at end of file
+    return urllib.parse.urlencode(data)
diff --git a/geomagio/processing/__init__.py b/geomagio/processing/__init__.py
index b17686a9e..be97cfbd5 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 6216d647e..8009d4f78 100644
--- a/setup.py
+++ b/setup.py
@@ -28,6 +28,7 @@ setuptools.setup(
             "magproc-prepfiles=geomagio.processing.magproc:main",
             "generate-matrix=geomagio.processing.adjusted:main",
             "obsrio-filter=geomagio.processing.obsrio:main",
+            "metadata-client=geomagio.geomagioapi.metadata:main",
         ],
     },
 )
-- 
GitLab


From 8da741190ebace9d6d66a3fb1c8898d2c28da21f Mon Sep 17 00:00:00 2001
From: pcain-usgs <pcain@usgs.gov>
Date: Wed, 17 Feb 2021 15:11:36 -0700
Subject: [PATCH 03/12] Gitlab token for delete, post, and update

---
 geomagio/api/secure/login.py         | 46 +++++++++++++-
 geomagio/api/secure/metadata.py      |  2 +-
 geomagio/geomagioapi/metadata.py     | 41 ++++++++++---
 geomagio/metadata/MetadataFactory.py | 92 +++++++++++++++-------------
 4 files changed, 128 insertions(+), 53 deletions(-)

diff --git a/geomagio/api/secure/login.py b/geomagio/api/secure/login.py
index c3407d931..92246b504 100644
--- a/geomagio/api/secure/login.py
+++ b/geomagio/api/secure/login.py
@@ -33,8 +33,10 @@ Usage:
             /logout     - logout current user
             /user       - access current user information as json
 """
+import json
 import logging
 import os
+import requests
 from typing import Callable, List, Optional
 
 from authlib.integrations.starlette_client import OAuth
@@ -56,17 +58,59 @@ 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)
 
     """
+    if "Authorization" in request.headers:
+        return get_api_user(token=request.headers["Authorization"])
     if "user" in request.session:
         return User(**request.session["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
+    user_response = requests.get(
+        f"{url}/user",
+        headers=header,
+    )
+    userinfo = json.loads(user_response.content)
+    try:
+        user = User(
+            email=userinfo["email"],
+            sub=userinfo["id"],
+            name=userinfo["name"],
+            nickname=userinfo["username"],
+            picture=userinfo["avatar_url"],
+        )
+    except KeyError:
+        logging.info("Invalid token")
+        return None
+    # use valid token to retrieve user's groups
+    user.groups = get_groups_api(
+        groups_response=requests.get(
+            f"{url}/groups",
+            headers=header,
+        )
+    )
+
+    return user
+
+
+def get_groups_api(groups_response: requests.Response) -> List[str]:
+    """returns a user's groups via gitlab api"""
+    groups = json.loads(groups_response.content)
+    group_urls = [g["web_url"] for g in groups]
+    group_names = [url[url.find("ghsc") :] for url in group_urls]
+    return group_names
+
+
 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 710128d1a..227ce65d0 100644
--- a/geomagio/api/secure/metadata.py
+++ b/geomagio/api/secure/metadata.py
@@ -41,7 +41,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/geomagioapi/metadata.py b/geomagio/geomagioapi/metadata.py
index 9d56037c7..a33317e6f 100644
--- a/geomagio/geomagioapi/metadata.py
+++ b/geomagio/geomagioapi/metadata.py
@@ -1,5 +1,6 @@
+import json
 import os
-from typing import Literal, Optional
+from typing import Dict, Optional
 
 from obspy import UTCDateTime
 import typer
@@ -17,6 +18,10 @@ def client(
     url: str = "http://{}/ws/secure/metadata".format(
         os.getenv("EDGE_HOST", "127.0.0.1:8000")
     ),
+    id: Optional[int] = typer.Option(
+        None,
+        help="Database id required for deleting and updating metadata. NOTE: Metadata requests by id ignore additional parameters",
+    ),
     category: Optional[MetadataCategory] = None,
     starttime: Optional[str] = None,
     endtime: Optional[str] = None,
@@ -26,10 +31,18 @@ def client(
     station: Optional[str] = None,
     channel: Optional[str] = None,
     location: Optional[str] = None,
-    data_valid: Optional[bool] = None,
+    data_valid: Optional[bool] = True,
     metadata_valid: Optional[bool] = True,
+    input_file: Optional[str] = typer.Option(
+        None,
+        help="JSON formatted file containing non-shared metadata information",
+    ),
+    token: Optional[str] = typer.Option(
+        os.getenv("GITLAB_API_TOKEN"), help="Gitlab account access token"
+    ),
 ):
     query = MetadataQuery(
+        id=id,
         category=category,
         starttime=UTCDateTime(starttime) if starttime else None,
         endtime=UTCDateTime(endtime) if endtime else None,
@@ -42,13 +55,21 @@ def client(
         data_valid=data_valid,
         metadata_valid=metadata_valid,
     )
-    factory = MetadataFactory(url=url)
+    factory = MetadataFactory(url=url, token=token)
     if action == "delete":
-        factory.delete_metadata(query=query)
+        response = factory.delete_metadata(id=query.id)
     elif action == "get":
-        metadata = factory.get_metadata(query=query)
-    if action == "post":
-        factory.post_metadata(query=query)
-    if action == "update":
-        factory.update_metadata(query=query)
-    return metadata
+        response = factory.get_metadata(query=query)
+    elif action in ["post", "update"]:
+        try:
+            with open(input_file, "r") as file:
+                data = json.load(file)
+        except (FileNotFoundError, TypeError):
+            raise ValueError("Input file invalid or not provided")
+        if action == "post":
+            response = factory.post_metadata(query=query, data=data)
+        elif action == "update":
+            response = factory.update_metadata(id=query.id, query=query, data=data)
+    else:
+        raise ValueError("Invalid action")
+    return response
diff --git a/geomagio/metadata/MetadataFactory.py b/geomagio/metadata/MetadataFactory.py
index dc3fefe09..21c9e003f 100644
--- a/geomagio/metadata/MetadataFactory.py
+++ b/geomagio/metadata/MetadataFactory.py
@@ -1,16 +1,14 @@
-import os
 import json
+from json.decoder import JSONDecodeError
+import os
 import requests
-import urllib
 from typing import Dict, List, Optional
+import urllib
 
 from obspy import UTCDateTime
-from pydantic import parse_obj_as
 
-from ..api.secure import MetadataQuery
-from ..residual import Reading
+from ..api.secure.MetadataQuery import MetadataQuery
 from .Metadata import Metadata
-from .MetadataCategory import MetadataCategory
 
 
 class MetadataFactory(object):
@@ -19,50 +17,62 @@ class MetadataFactory(object):
         url: str = "http://{}/ws/secure/metadata".format(
             os.getenv("EDGE_HOST", "127.0.0.1:8000")
         ),
+        token: str = os.getenv("GITLAB_API_TOKEN"),
     ):
         self.url = url
+        self.token = token
+        self.header = {"Authorization": self.token} if token else None
 
-    def delete_metadata(self, query: MetadataQuery):
-        raise NotImplementedError
-
-    def format_metadata(self, data: Dict):
-        # formats responses as Metadata objects
-        return parse_obj_as(List[Metadata], data)
+    def delete_metadata(self, id: int) -> Dict:
+        response = requests.delete(url=f"{self.url}/{id}", headers=self.header)
+        return response
 
-    def get_metadata(self, query: MetadataQuery) -> List[Metadata]:
+    def get_metadata(self, query: MetadataQuery) -> List[Dict]:
         args = parse_params(query=query)
-        response = web_request(url=f"{self.url}?{args}")
-        metadata = self.format_metadata(data=response)
-        return metadata
+        raw_response = requests.get(url=f"{self.url}{args}", headers=self.header)
+        try:
+            response = json.loads(raw_response.content)
+        except JSONDecodeError:
+            raise ValueError("Data not found")
+        return response
 
-    def post_metadata(self, query: MetadataQuery):
-        raise NotImplementedError
+    def post_metadata(
+        self, query: MetadataQuery, data: Optional[Dict] = {}
+    ) -> requests.Response:
+        metadata = parse_metadata(query=query, data=data)
+        response = requests.post(url=self.url, data=metadata, headers=self.header)
+        return response
 
-    def update_metadata(self, query: MetadataQuery):
-        raise NotImplementedError
+    def update_metadata(
+        self, id: int, query: MetadataQuery, data: Optional[Dict] = {}
+    ) -> requests.Response:
+        metadata = parse_metadata(query=query, data=data)
+        response = requests.put(
+            url=f"{self.url}/{query.id}", data=metadata, headers=self.header
+        )
+        return response
 
 
-def web_request(url: str) -> Dict:
-    client_id = os.getenv("OPENID_CLIENT_ID")
-    client_secret = os.getenv("OPENID_CLIENT_SECRET")
-    response = requests.get(
-        url, data={"grant_type": "client_credentials"}, auth=(client_id, client_secret)
-    )
-    metadata = json.loads(response.text)
-    return metadata
+def parse_metadata(query: MetadataQuery, data: Optional[Dict] = {}) -> str:
+    metadata = Metadata(**query.dict())
+    metadata.metadata = data
+    return metadata.json()
 
 
-def parse_params(query: MetadataQuery):
-    d = query.dict()
-    data = {}
-    for key in d.keys():
-        if d[key] is None:
-            continue
-        # convert times to strings
-        if type(d[key]) == UTCDateTime:
-            d[key] = d[key].isoformat()
-        if key == "category":
-            d[key] = d[key].value
-        data[key] = d[key]
+def parse_params(query: MetadataQuery) -> str:
+    query = query.dict()
+    args = {}
+    for key in query.keys():
+        element = query[key]
+        if element is not None:
+            # convert times to strings
+            if type(element) == UTCDateTime:
+                element = element.isoformat()
+            # get string value of metadata category
+            if key == "category":
+                element = element.value
+            elif key == "id":
+                return f"/{element}"
+            args[key] = element
 
-    return urllib.parse.urlencode(data)
+    return f"?{urllib.parse.urlencode(args)}"
-- 
GitLab


From 4b3c6abb56470f83d4d050ed046017d506e2648b Mon Sep 17 00:00:00 2001
From: pcain-usgs <pcain@usgs.gov>
Date: Tue, 2 Mar 2021 14:26:47 -0700
Subject: [PATCH 04/12] Change login logic, rename package, move factory,
 rename post method/action

---
 geomagio/api/secure/login.py                  | 34 ++++++-------
 .../MetadataFactory.py                        | 47 ++++++++---------
 geomagio/apiclient/__init__.py                |  4 ++
 .../{geomagioapi => apiclient}/metadata.py    | 50 ++++++++++++-------
 geomagio/geomagioapi/__init__.py              |  3 --
 geomagio/metadata/__init__.py                 |  3 +-
 setup.py                                      |  2 +-
 7 files changed, 71 insertions(+), 72 deletions(-)
 rename geomagio/{metadata => apiclient}/MetadataFactory.py (51%)
 create mode 100644 geomagio/apiclient/__init__.py
 rename geomagio/{geomagioapi => apiclient}/metadata.py (55%)
 delete mode 100644 geomagio/geomagioapi/__init__.py

diff --git a/geomagio/api/secure/login.py b/geomagio/api/secure/login.py
index 92246b504..b687dec3b 100644
--- a/geomagio/api/secure/login.py
+++ b/geomagio/api/secure/login.py
@@ -33,7 +33,6 @@ Usage:
             /logout     - logout current user
             /user       - access current user information as json
 """
-import json
 import logging
 import os
 import requests
@@ -65,10 +64,15 @@ async def current_user(request: Request) -> Optional[User]:
         user: Optional[User] = Depends(current_user)
 
     """
-    if "Authorization" in request.headers:
-        return get_api_user(token=request.headers["Authorization"])
     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
 
 
@@ -76,11 +80,11 @@ 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
-    user_response = requests.get(
+    userinfo_response = requests.get(
         f"{url}/user",
         headers=header,
     )
-    userinfo = json.loads(user_response.content)
+    userinfo = userinfo_response.json()
     try:
         user = User(
             email=userinfo["email"],
@@ -90,27 +94,17 @@ def get_api_user(token: str) -> Optional[User]:
             picture=userinfo["avatar_url"],
         )
     except KeyError:
-        logging.info("Invalid token")
+        logging.info(f"Invalid token: {userinfo_response.status_code}")
         return None
     # use valid token to retrieve user's groups
-    user.groups = get_groups_api(
-        groups_response=requests.get(
-            f"{url}/groups",
-            headers=header,
-        )
+    groups_response = requests.get(
+        f"{url}/groups",
+        headers=header,
     )
-
+    user.groups = [g["full_path"] for g in groups_response.json()]
     return user
 
 
-def get_groups_api(groups_response: requests.Response) -> List[str]:
-    """returns a user's groups via gitlab api"""
-    groups = json.loads(groups_response.content)
-    group_urls = [g["web_url"] for g in groups]
-    group_names = [url[url.find("ghsc") :] for url in group_urls]
-    return group_names
-
-
 def require_user(
     allowed_groups: List[str] = None,
 ) -> Callable[[Request, User], User]:
diff --git a/geomagio/metadata/MetadataFactory.py b/geomagio/apiclient/MetadataFactory.py
similarity index 51%
rename from geomagio/metadata/MetadataFactory.py
rename to geomagio/apiclient/MetadataFactory.py
index 21c9e003f..64c43faae 100644
--- a/geomagio/metadata/MetadataFactory.py
+++ b/geomagio/apiclient/MetadataFactory.py
@@ -1,14 +1,13 @@
-import json
-from json.decoder import JSONDecodeError
 import os
 import requests
-from typing import Dict, List, Optional
+from typing import Dict, List, Union
 import urllib
 
 from obspy import UTCDateTime
+from pydantic import parse_obj_as
 
 from ..api.secure.MetadataQuery import MetadataQuery
-from .Metadata import Metadata
+from ..metadata import Metadata
 
 
 class MetadataFactory(object):
@@ -23,42 +22,36 @@ class MetadataFactory(object):
         self.token = token
         self.header = {"Authorization": self.token} if token else None
 
-    def delete_metadata(self, id: int) -> Dict:
-        response = requests.delete(url=f"{self.url}/{id}", headers=self.header)
+    def delete_metadata(self, metadata: Metadata) -> Dict:
+        response = requests.delete(url=f"{self.url}/{metadata.id}", headers=self.header)
         return response
 
-    def get_metadata(self, query: MetadataQuery) -> List[Dict]:
+    def get_metadata(self, query: MetadataQuery) -> Union[List[Metadata], Metadata]:
         args = parse_params(query=query)
-        raw_response = requests.get(url=f"{self.url}{args}", headers=self.header)
+        responses = requests.get(url=f"{self.url}{args}", headers=self.header)
         try:
-            response = json.loads(raw_response.content)
-        except JSONDecodeError:
+            metadata = parse_obj_as(Union[List[Metadata], Metadata], responses.json())
+        except:
             raise ValueError("Data not found")
-        return response
+        return metadata
 
-    def post_metadata(
-        self, query: MetadataQuery, data: Optional[Dict] = {}
-    ) -> requests.Response:
-        metadata = parse_metadata(query=query, data=data)
-        response = requests.post(url=self.url, data=metadata, headers=self.header)
+    def create_metadata(self, metadata: Metadata) -> requests.Response:
+        response = requests.post(
+            url=self.url, data=metadata.json(), headers=self.header
+        )
         return response
 
-    def update_metadata(
-        self, id: int, query: MetadataQuery, data: Optional[Dict] = {}
-    ) -> requests.Response:
-        metadata = parse_metadata(query=query, data=data)
+    def update_metadata(self, metadata: Metadata) -> requests.Response:
+        if metadata.metadata is None:
+            metadata.metadata = self.get_metadata(
+                query=MetadataQuery(**metadata.dict())
+            ).metadata
         response = requests.put(
-            url=f"{self.url}/{query.id}", data=metadata, headers=self.header
+            url=f"{self.url}/{metadata.id}", data=metadata.json(), headers=self.header
         )
         return response
 
 
-def parse_metadata(query: MetadataQuery, data: Optional[Dict] = {}) -> str:
-    metadata = Metadata(**query.dict())
-    metadata.metadata = data
-    return metadata.json()
-
-
 def parse_params(query: MetadataQuery) -> str:
     query = query.dict()
     args = {}
diff --git a/geomagio/apiclient/__init__.py b/geomagio/apiclient/__init__.py
new file mode 100644
index 000000000..a2e83d717
--- /dev/null
+++ b/geomagio/apiclient/__init__.py
@@ -0,0 +1,4 @@
+from .metadata import client
+from .MetadataFactory import MetadataFactory
+
+__all__ = ["client", "MetadataFactory"]
diff --git a/geomagio/geomagioapi/metadata.py b/geomagio/apiclient/metadata.py
similarity index 55%
rename from geomagio/geomagioapi/metadata.py
rename to geomagio/apiclient/metadata.py
index a33317e6f..65ea91eed 100644
--- a/geomagio/geomagioapi/metadata.py
+++ b/geomagio/apiclient/metadata.py
@@ -1,12 +1,13 @@
 import json
 import os
-from typing import Dict, Optional
+from typing import Optional
 
 from obspy import UTCDateTime
 import typer
 
 from ..api.secure.MetadataQuery import MetadataQuery
-from ..metadata import MetadataCategory, MetadataFactory
+from ..metadata import Metadata, MetadataCategory
+from .MetadataFactory import MetadataFactory
 
 
 def main():
@@ -16,12 +17,9 @@ def main():
 def client(
     action: str,
     url: str = "http://{}/ws/secure/metadata".format(
-        os.getenv("EDGE_HOST", "127.0.0.1:8000")
-    ),
-    id: Optional[int] = typer.Option(
-        None,
-        help="Database id required for deleting and updating metadata. NOTE: Metadata requests by id ignore additional parameters",
+        os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000")
     ),
+    id: Optional[int] = None,
     category: Optional[MetadataCategory] = None,
     starttime: Optional[str] = None,
     endtime: Optional[str] = None,
@@ -37,11 +35,8 @@ def client(
         None,
         help="JSON formatted file containing non-shared metadata information",
     ),
-    token: Optional[str] = typer.Option(
-        os.getenv("GITLAB_API_TOKEN"), help="Gitlab account access token"
-    ),
 ):
-    query = MetadataQuery(
+    metadata = Metadata(
         id=id,
         category=category,
         starttime=UTCDateTime(starttime) if starttime else None,
@@ -55,21 +50,38 @@ def client(
         data_valid=data_valid,
         metadata_valid=metadata_valid,
     )
-    factory = MetadataFactory(url=url, token=token)
+
+    factory = MetadataFactory(url=url, token=os.getenv("GITLAB_API_TOKEN"))
     if action == "delete":
-        response = factory.delete_metadata(id=query.id)
+        response = factory.delete_metadata(metadata=metadata)
     elif action == "get":
-        response = factory.get_metadata(query=query)
-    elif action in ["post", "update"]:
+        response = factory.get_metadata(
+            query=MetadataQuery(
+                id=id,
+                category=category,
+                starttime=UTCDateTime(starttime) if starttime else None,
+                endtime=UTCDateTime(endtime) if endtime else None,
+                created_after=UTCDateTime(created_after) if created_after else None,
+                created_before=UTCDateTime(created_before) if created_before else None,
+                network=network,
+                station=station,
+                channel=channel,
+                location=location,
+                data_valid=data_valid,
+                metadata_valid=metadata_valid,
+            )
+        )
+    elif action in ["create", "update"]:
         try:
             with open(input_file, "r") as file:
                 data = json.load(file)
         except (FileNotFoundError, TypeError):
-            raise ValueError("Input file invalid or not provided")
-        if action == "post":
-            response = factory.post_metadata(query=query, data=data)
+            data = None
+        metadata.metadata = data
+        if action == "create":
+            response = factory.create_metadata(metadata=metadata)
         elif action == "update":
-            response = factory.update_metadata(id=query.id, query=query, data=data)
+            response = factory.update_metadata(metadata=metadata)
     else:
         raise ValueError("Invalid action")
     return response
diff --git a/geomagio/geomagioapi/__init__.py b/geomagio/geomagioapi/__init__.py
deleted file mode 100644
index 20b3da7f7..000000000
--- a/geomagio/geomagioapi/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .metadata import client
-
-__all__ = ["client"]
diff --git a/geomagio/metadata/__init__.py b/geomagio/metadata/__init__.py
index f1c6be68b..7502db024 100644
--- a/geomagio/metadata/__init__.py
+++ b/geomagio/metadata/__init__.py
@@ -1,6 +1,5 @@
 from .Metadata import Metadata
 from .MetadataCategory import MetadataCategory
-from .MetadataFactory import MetadataFactory
 
 
-__all__ = ["Metadata", "MetadataCategory", "MetadataFactory"]
+__all__ = ["Metadata", "MetadataCategory"]
diff --git a/setup.py b/setup.py
index 8009d4f78..f7b05c319 100644
--- a/setup.py
+++ b/setup.py
@@ -28,7 +28,7 @@ setuptools.setup(
             "magproc-prepfiles=geomagio.processing.magproc:main",
             "generate-matrix=geomagio.processing.adjusted:main",
             "obsrio-filter=geomagio.processing.obsrio:main",
-            "metadata-client=geomagio.geomagioapi.metadata:main",
+            "geomag-apiclient=geomagio.apiclient.metadata:main",
         ],
     },
 )
-- 
GitLab


From 704ba79d96212e1cbdbbf1c1a3ecdc044822c275 Mon Sep 17 00:00:00 2001
From: pcain-usgs <pcain@usgs.gov>
Date: Wed, 10 Mar 2021 11:48:00 -0700
Subject: [PATCH 05/12] Change hostname, request.get with params, null
 metadata, typer option

---
 geomagio/apiclient/MetadataFactory.py | 15 +++++++--------
 geomagio/apiclient/metadata.py        |  4 +++-
 2 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/geomagio/apiclient/MetadataFactory.py b/geomagio/apiclient/MetadataFactory.py
index 64c43faae..adfedcfef 100644
--- a/geomagio/apiclient/MetadataFactory.py
+++ b/geomagio/apiclient/MetadataFactory.py
@@ -14,7 +14,7 @@ class MetadataFactory(object):
     def __init__(
         self,
         url: str = "http://{}/ws/secure/metadata".format(
-            os.getenv("EDGE_HOST", "127.0.0.1:8000")
+            os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000")
         ),
         token: str = os.getenv("GITLAB_API_TOKEN"),
     ):
@@ -28,7 +28,10 @@ class MetadataFactory(object):
 
     def get_metadata(self, query: MetadataQuery) -> Union[List[Metadata], Metadata]:
         args = parse_params(query=query)
-        responses = requests.get(url=f"{self.url}{args}", headers=self.header)
+        if "id" in args:
+            self.url = f"{self.url}/{args['id']}"
+            args = {}
+        responses = requests.get(url=self.url, params=args, headers=self.header)
         try:
             metadata = parse_obj_as(Union[List[Metadata], Metadata], responses.json())
         except:
@@ -42,10 +45,6 @@ class MetadataFactory(object):
         return response
 
     def update_metadata(self, metadata: Metadata) -> requests.Response:
-        if metadata.metadata is None:
-            metadata.metadata = self.get_metadata(
-                query=MetadataQuery(**metadata.dict())
-            ).metadata
         response = requests.put(
             url=f"{self.url}/{metadata.id}", data=metadata.json(), headers=self.header
         )
@@ -65,7 +64,7 @@ def parse_params(query: MetadataQuery) -> str:
             if key == "category":
                 element = element.value
             elif key == "id":
-                return f"/{element}"
+                return {"id": element}
             args[key] = element
 
-    return f"?{urllib.parse.urlencode(args)}"
+    return args
diff --git a/geomagio/apiclient/metadata.py b/geomagio/apiclient/metadata.py
index 65ea91eed..535c881c0 100644
--- a/geomagio/apiclient/metadata.py
+++ b/geomagio/apiclient/metadata.py
@@ -15,7 +15,9 @@ def main():
 
 
 def client(
-    action: str,
+    action: str = typer.Option(
+        default="get", help="get(default), delete, create, or update"
+    ),
     url: str = "http://{}/ws/secure/metadata".format(
         os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000")
     ),
-- 
GitLab


From 684271e4bbf4e1c564b1eb8c99b434a3d829a3ee Mon Sep 17 00:00:00 2001
From: pcain-usgs <pcain@usgs.gov>
Date: Thu, 11 Mar 2021 09:51:54 -0700
Subject: [PATCH 06/12] Create typer commands

---
 geomagio/api/secure/MetadataQuery.py |   5 +-
 geomagio/apiclient/__init__.py       |   4 +-
 geomagio/apiclient/metadata.py       | 146 +++++++++++++++++++--------
 3 files changed, 111 insertions(+), 44 deletions(-)

diff --git a/geomagio/api/secure/MetadataQuery.py b/geomagio/api/secure/MetadataQuery.py
index 6b30699d9..f414dd478 100644
--- a/geomagio/api/secure/MetadataQuery.py
+++ b/geomagio/api/secure/MetadataQuery.py
@@ -2,6 +2,7 @@ from datetime import timezone
 
 from obspy import UTCDateTime
 from pydantic import BaseModel
+from typing import Optional
 
 from ...metadata import MetadataCategory
 from ... import pydantic_utcdatetime
@@ -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/apiclient/__init__.py b/geomagio/apiclient/__init__.py
index a2e83d717..53bb45e4c 100644
--- a/geomagio/apiclient/__init__.py
+++ b/geomagio/apiclient/__init__.py
@@ -1,4 +1,4 @@
-from .metadata import client
+from .metadata import app
 from .MetadataFactory import MetadataFactory
 
-__all__ = ["client", "MetadataFactory"]
+__all__ = ["app", "MetadataFactory"]
diff --git a/geomagio/apiclient/metadata.py b/geomagio/apiclient/metadata.py
index 535c881c0..fae26289b 100644
--- a/geomagio/apiclient/metadata.py
+++ b/geomagio/apiclient/metadata.py
@@ -1,6 +1,6 @@
 import json
 import os
-from typing import Optional
+from typing import Dict, Optional
 
 from obspy import UTCDateTime
 import typer
@@ -10,14 +10,20 @@ from ..metadata import Metadata, MetadataCategory
 from .MetadataFactory import MetadataFactory
 
 
-def main():
-    typer.run(client)
+def load_metadata(input_file) -> Optional[Dict]:
+    try:
+        with open(input_file, "r") as file:
+            data = json.load(file)
+        return data
+    except (FileNotFoundError, TypeError):
+        return None
 
 
-def client(
-    action: str = typer.Option(
-        default="get", help="get(default), delete, create, or update"
-    ),
+app = typer.Typer()
+
+
+@app.command()
+def create(
     url: str = "http://{}/ws/secure/metadata".format(
         os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000")
     ),
@@ -52,38 +58,98 @@ def client(
         data_valid=data_valid,
         metadata_valid=metadata_valid,
     )
+    metadata.metadata = load_metadata(input_file=input_file)
+    response = MetadataFactory(url=url).create_metadata(metadata=metadata)
+    return response
+
+
+@app.command()
+def delete(
+    id: int,
+    url: str = "http://{}/ws/secure/metadata".format(
+        os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000")
+    ),
+):
+    response = MetadataFactory(url=url).delete_metadata(metadata=Metadata(id=id))
+    return response
+
+
+@app.command()
+def get(
+    url: str = "http://{}/ws/secure/metadata".format(
+        os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000")
+    ),
+    id: Optional[int] = None,
+    category: Optional[MetadataCategory] = None,
+    starttime: Optional[str] = None,
+    endtime: Optional[str] = None,
+    created_after: Optional[str] = None,
+    created_before: Optional[str] = None,
+    network: Optional[str] = None,
+    station: Optional[str] = None,
+    channel: Optional[str] = None,
+    location: Optional[str] = None,
+    data_valid: Optional[bool] = None,
+    metadata_valid: Optional[bool] = None,
+):
+    query = MetadataQuery(
+        id=id,
+        category=category,
+        starttime=UTCDateTime(starttime) if starttime else None,
+        endtime=UTCDateTime(endtime) if endtime else None,
+        created_after=UTCDateTime(created_after) if created_after else None,
+        created_before=UTCDateTime(created_before) if created_before else None,
+        network=network,
+        station=station,
+        channel=channel,
+        location=location,
+        data_valid=data_valid,
+        metadata_valid=metadata_valid,
+    )
+    metadata = MetadataFactory(url=url).get_metadata(query=query)
+    return metadata
+
 
-    factory = MetadataFactory(url=url, token=os.getenv("GITLAB_API_TOKEN"))
-    if action == "delete":
-        response = factory.delete_metadata(metadata=metadata)
-    elif action == "get":
-        response = factory.get_metadata(
-            query=MetadataQuery(
-                id=id,
-                category=category,
-                starttime=UTCDateTime(starttime) if starttime else None,
-                endtime=UTCDateTime(endtime) if endtime else None,
-                created_after=UTCDateTime(created_after) if created_after else None,
-                created_before=UTCDateTime(created_before) if created_before else None,
-                network=network,
-                station=station,
-                channel=channel,
-                location=location,
-                data_valid=data_valid,
-                metadata_valid=metadata_valid,
-            )
-        )
-    elif action in ["create", "update"]:
-        try:
-            with open(input_file, "r") as file:
-                data = json.load(file)
-        except (FileNotFoundError, TypeError):
-            data = None
-        metadata.metadata = data
-        if action == "create":
-            response = factory.create_metadata(metadata=metadata)
-        elif action == "update":
-            response = factory.update_metadata(metadata=metadata)
-    else:
-        raise ValueError("Invalid action")
+@app.command()
+def update(
+    id: int,
+    url: str = "http://{}/ws/secure/metadata".format(
+        os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000")
+    ),
+    category: Optional[MetadataCategory] = None,
+    starttime: Optional[str] = None,
+    endtime: Optional[str] = None,
+    created_after: Optional[str] = None,
+    created_before: Optional[str] = None,
+    network: Optional[str] = None,
+    station: Optional[str] = None,
+    channel: Optional[str] = None,
+    location: Optional[str] = None,
+    data_valid: Optional[bool] = True,
+    metadata_valid: Optional[bool] = True,
+    input_file: Optional[str] = typer.Option(
+        None,
+        help="JSON formatted file containing non-shared metadata information",
+    ),
+):
+    metadata = Metadata(
+        id=id,
+        category=category,
+        starttime=UTCDateTime(starttime) if starttime else None,
+        endtime=UTCDateTime(endtime) if endtime else None,
+        created_after=UTCDateTime(created_after) if created_after else None,
+        created_before=UTCDateTime(created_before) if created_before else None,
+        network=network,
+        station=station,
+        channel=channel,
+        location=location,
+        data_valid=data_valid,
+        metadata_valid=metadata_valid,
+    )
+    metadata.metadata = load_metadata(input_file=input_file)
+    response = MetadataFactory(url=url).update_metadata(metadata=metadata)
     return response
+
+
+def main():
+    app()
-- 
GitLab


From 3f2a2be67fbe47a1773de758021432b64991d0b3 Mon Sep 17 00:00:00 2001
From: pcain-usgs <pcain@usgs.gov>
Date: Mon, 15 Mar 2021 07:47:01 -0600
Subject: [PATCH 07/12] Address comments

---
 geomagio/apiclient/MetadataFactory.py |   2 +
 geomagio/apiclient/metadata.py        | 193 +++++++++++++++++---------
 setup.py                              |   4 +-
 3 files changed, 133 insertions(+), 66 deletions(-)

diff --git a/geomagio/apiclient/MetadataFactory.py b/geomagio/apiclient/MetadataFactory.py
index adfedcfef..8ffaead94 100644
--- a/geomagio/apiclient/MetadataFactory.py
+++ b/geomagio/apiclient/MetadataFactory.py
@@ -36,6 +36,8 @@ class MetadataFactory(object):
             metadata = parse_obj_as(Union[List[Metadata], Metadata], responses.json())
         except:
             raise ValueError("Data not found")
+        if isinstance(metadata, Metadata):
+            metadata = [metadata]
         return metadata
 
     def create_metadata(self, metadata: Metadata) -> requests.Response:
diff --git a/geomagio/apiclient/metadata.py b/geomagio/apiclient/metadata.py
index fae26289b..cfed6e7c5 100644
--- a/geomagio/apiclient/metadata.py
+++ b/geomagio/apiclient/metadata.py
@@ -1,3 +1,4 @@
+import sys
 import json
 import os
 from typing import Dict, Optional
@@ -11,6 +12,11 @@ from .MetadataFactory import MetadataFactory
 
 
 def load_metadata(input_file) -> Optional[Dict]:
+    if input_file is None:
+        return None
+    if input_file == "-":
+        data = json.loads(sys.stdin.read())
+        return data
     try:
         with open(input_file, "r") as file:
             data = json.load(file)
@@ -27,50 +33,91 @@ def create(
     url: str = "http://{}/ws/secure/metadata".format(
         os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000")
     ),
-    id: Optional[int] = None,
-    category: Optional[MetadataCategory] = None,
-    starttime: Optional[str] = None,
-    endtime: Optional[str] = None,
-    created_after: Optional[str] = None,
-    created_before: Optional[str] = None,
-    network: Optional[str] = None,
-    station: Optional[str] = None,
-    channel: Optional[str] = None,
-    location: Optional[str] = None,
-    data_valid: Optional[bool] = True,
-    metadata_valid: Optional[bool] = True,
-    input_file: Optional[str] = typer.Option(
+    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 = typer.Option(
         None,
         help="JSON formatted file containing non-shared metadata information",
     ),
+    location: str = None,
+    metadata_valid: bool = True,
+    network: str = None,
+    starttime: str = None,
+    station: str = None,
+    wrap: bool = True,
 ):
-    metadata = Metadata(
-        id=id,
-        category=category,
-        starttime=UTCDateTime(starttime) if starttime else None,
-        endtime=UTCDateTime(endtime) if endtime else None,
-        created_after=UTCDateTime(created_after) if created_after else None,
-        created_before=UTCDateTime(created_before) if created_before else None,
-        network=network,
-        station=station,
-        channel=channel,
-        location=location,
-        data_valid=data_valid,
-        metadata_valid=metadata_valid,
-    )
-    metadata.metadata = load_metadata(input_file=input_file)
+
+    if wrap == True:
+        metadata = Metadata(
+            id=id,
+            category=category,
+            starttime=UTCDateTime(starttime) if starttime else None,
+            endtime=UTCDateTime(endtime) if endtime else None,
+            created_after=UTCDateTime(created_after) if created_after else None,
+            created_before=UTCDateTime(created_before) if created_before else None,
+            network=network,
+            station=station,
+            channel=channel,
+            location=location,
+            data_valid=data_valid,
+            metadata_valid=metadata_valid,
+        )
+        metadata.metadata = load_metadata(input_file=input_file)
+    elif wrap == False:
+        metadata_dict = load_metadata(input_file=input_file)
+        metadata = Metadata(**metadata_dict)
     response = MetadataFactory(url=url).create_metadata(metadata=metadata)
+    print(response.json())
     return response
 
 
 @app.command()
 def delete(
-    id: int,
     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 = typer.Option(
+        None,
+        help="JSON formatted file containing non-shared metadata information",
+    ),
+    location: str = None,
+    metadata_valid: bool = True,
+    network: str = None,
+    starttime: str = None,
+    station: str = None,
 ):
-    response = MetadataFactory(url=url).delete_metadata(metadata=Metadata(id=id))
+    if input_file is not None:
+        metadata_dict = load_metadata(input_file=input_file)
+        metadata = Metadata(**metadata_dict)
+    else:
+        metadata = Metadata(
+            id=id,
+            category=category,
+            starttime=starttime,
+            endtime=endtime,
+            created_after=created_after,
+            created_before=created_before,
+            network=network,
+            station=station,
+            channel=channel,
+            location=location,
+            data_valid=data_valid,
+            metadata_valid=metadata_valid,
+        )
+    response = MetadataFactory(url=url).delete_metadata(metadata=metadata)
+    print(response.json())
     return response
 
 
@@ -79,18 +126,19 @@ def get(
     url: str = "http://{}/ws/secure/metadata".format(
         os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000")
     ),
-    id: Optional[int] = None,
     category: Optional[MetadataCategory] = None,
-    starttime: Optional[str] = None,
-    endtime: Optional[str] = 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,
-    channel: Optional[str] = None,
-    location: Optional[str] = None,
-    data_valid: Optional[bool] = None,
-    metadata_valid: Optional[bool] = None,
+    unwrap: bool = False,
 ):
     query = MetadataQuery(
         id=id,
@@ -107,47 +155,64 @@ def get(
         metadata_valid=metadata_valid,
     )
     metadata = MetadataFactory(url=url).get_metadata(query=query)
+    if unwrap:
+        data = [m.metadata for m in metadata]
+        if len(data) == 1:
+            print(json.dumps(data[0]))
+            return data[0]
+        print([json.dumps(d) for d in data])
+        return data
+    if len(metadata) == 1:
+        print(metadata[0].json())
+        return metadata[0]
+    print([m.json() for m in metadata])
     return metadata
 
 
 @app.command()
 def update(
-    id: int,
     url: str = "http://{}/ws/secure/metadata".format(
         os.getenv("GEOMAG_API_HOST", "127.0.0.1:8000")
     ),
-    category: Optional[MetadataCategory] = None,
-    starttime: Optional[str] = None,
-    endtime: Optional[str] = None,
-    created_after: Optional[str] = None,
-    created_before: Optional[str] = None,
-    network: Optional[str] = None,
-    station: Optional[str] = None,
-    channel: Optional[str] = None,
-    location: Optional[str] = None,
-    data_valid: Optional[bool] = True,
-    metadata_valid: Optional[bool] = True,
-    input_file: Optional[str] = typer.Option(
+    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 = typer.Option(
         None,
         help="JSON formatted file containing non-shared metadata information",
     ),
+    location: str = None,
+    metadata_valid: bool = True,
+    network: str = None,
+    starttime: str = None,
+    station: str = None,
 ):
-    metadata = Metadata(
-        id=id,
-        category=category,
-        starttime=UTCDateTime(starttime) if starttime else None,
-        endtime=UTCDateTime(endtime) if endtime else None,
-        created_after=UTCDateTime(created_after) if created_after else None,
-        created_before=UTCDateTime(created_before) if created_before else None,
-        network=network,
-        station=station,
-        channel=channel,
-        location=location,
-        data_valid=data_valid,
-        metadata_valid=metadata_valid,
-    )
+    if input_file is not None:
+        metadata_dict = load_metadata(input_file=input_file)
+        metadata = Metadata(**metadata_dict)
+
+    else:
+        metadata = Metadata(
+            id=id,
+            category=category,
+            starttime=UTCDateTime(starttime) if starttime else None,
+            endtime=UTCDateTime(endtime) if endtime else None,
+            created_after=UTCDateTime(created_after) if created_after else None,
+            created_before=UTCDateTime(created_before) if created_before else None,
+            network=network,
+            station=station,
+            channel=channel,
+            location=location,
+            data_valid=data_valid,
+            metadata_valid=metadata_valid,
+        )
     metadata.metadata = load_metadata(input_file=input_file)
     response = MetadataFactory(url=url).update_metadata(metadata=metadata)
+    print(response.json())
     return response
 
 
diff --git a/setup.py b/setup.py
index f7b05c319..a73fd11e7 100644
--- a/setup.py
+++ b/setup.py
@@ -25,10 +25,10 @@ setuptools.setup(
     use_pipfile=True,
     entry_points={
         "console_scripts": [
-            "magproc-prepfiles=geomagio.processing.magproc:main",
             "generate-matrix=geomagio.processing.adjusted:main",
-            "obsrio-filter=geomagio.processing.obsrio:main",
             "geomag-apiclient=geomagio.apiclient.metadata:main",
+            "magproc-prepfiles=geomagio.processing.magproc:main",
+            "obsrio-filter=geomagio.processing.obsrio:main",
         ],
     },
 )
-- 
GitLab


From 48037d19043e346d729684d1b565f3600969a60e Mon Sep 17 00:00:00 2001
From: pcain-usgs <pcain@usgs.gov>
Date: Mon, 15 Mar 2021 15:31:35 -0600
Subject: [PATCH 08/12] Remove returns

---
 geomagio/apiclient/metadata.py | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/geomagio/apiclient/metadata.py b/geomagio/apiclient/metadata.py
index cfed6e7c5..8d3af668a 100644
--- a/geomagio/apiclient/metadata.py
+++ b/geomagio/apiclient/metadata.py
@@ -73,7 +73,6 @@ def create(
         metadata = Metadata(**metadata_dict)
     response = MetadataFactory(url=url).create_metadata(metadata=metadata)
     print(response.json())
-    return response
 
 
 @app.command()
@@ -118,7 +117,6 @@ def delete(
         )
     response = MetadataFactory(url=url).delete_metadata(metadata=metadata)
     print(response.json())
-    return response
 
 
 @app.command()
@@ -159,14 +157,10 @@ def get(
         data = [m.metadata for m in metadata]
         if len(data) == 1:
             print(json.dumps(data[0]))
-            return data[0]
         print([json.dumps(d) for d in data])
-        return data
     if len(metadata) == 1:
         print(metadata[0].json())
-        return metadata[0]
     print([m.json() for m in metadata])
-    return metadata
 
 
 @app.command()
@@ -213,7 +207,6 @@ def update(
     metadata.metadata = load_metadata(input_file=input_file)
     response = MetadataFactory(url=url).update_metadata(metadata=metadata)
     print(response.json())
-    return response
 
 
 def main():
-- 
GitLab


From 0126b04a3af7caa289e9211672952f075213d53b Mon Sep 17 00:00:00 2001
From: pcain-usgs <pcain@usgs.gov>
Date: Tue, 16 Mar 2021 07:31:05 -0600
Subject: [PATCH 09/12] Clean up prints for get method

---
 geomagio/apiclient/metadata.py | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/geomagio/apiclient/metadata.py b/geomagio/apiclient/metadata.py
index 8d3af668a..42551ad45 100644
--- a/geomagio/apiclient/metadata.py
+++ b/geomagio/apiclient/metadata.py
@@ -154,13 +154,13 @@ def get(
     )
     metadata = MetadataFactory(url=url).get_metadata(query=query)
     if unwrap:
-        data = [m.metadata for m in metadata]
-        if len(data) == 1:
-            print(json.dumps(data[0]))
-        print([json.dumps(d) for d in data])
+        metadata = [json.dumps(m.metadata) for m in metadata]
+    else:
+        metadata = [m.json() for m in metadata]
     if len(metadata) == 1:
-        print(metadata[0].json())
-    print([m.json() for m in metadata])
+        print(metadata[0])
+        return
+    print(metadata)
 
 
 @app.command()
-- 
GitLab


From 1c4af072922f9644e1060b6812d780e609443575 Mon Sep 17 00:00:00 2001
From: pcain <pcain@usgs.gov>
Date: Thu, 18 Mar 2021 16:05:19 -0600
Subject: [PATCH 10/12] Raise file errors, add getone parameter, change bool
 logic, update factory return types, remove parameters from delete/update

---
 debugger.py                           |   3 +
 geomagio/apiclient/MetadataFactory.py |  25 ++--
 geomagio/apiclient/metadata.py        | 158 ++++++++------------------
 3 files changed, 61 insertions(+), 125 deletions(-)
 create mode 100644 debugger.py

diff --git a/debugger.py b/debugger.py
new file mode 100644
index 000000000..f8e711b4b
--- /dev/null
+++ b/debugger.py
@@ -0,0 +1,3 @@
+from geomagio.apiclient.metadata import create
+
+create(station="BOU", category="reading")
\ No newline at end of file
diff --git a/geomagio/apiclient/MetadataFactory.py b/geomagio/apiclient/MetadataFactory.py
index 8ffaead94..1d8ded018 100644
--- a/geomagio/apiclient/MetadataFactory.py
+++ b/geomagio/apiclient/MetadataFactory.py
@@ -1,7 +1,6 @@
 import os
 import requests
-from typing import Dict, List, Union
-import urllib
+from typing import List, Union
 
 from obspy import UTCDateTime
 from pydantic import parse_obj_as
@@ -22,35 +21,37 @@ class MetadataFactory(object):
         self.token = token
         self.header = {"Authorization": self.token} if token else None
 
-    def delete_metadata(self, metadata: Metadata) -> Dict:
+    def delete_metadata(self, metadata: Metadata) -> bool:
         response = requests.delete(url=f"{self.url}/{metadata.id}", headers=self.header)
-        return response
+        if response.status_code == 200:
+            return True
+        return False
 
-    def get_metadata(self, query: MetadataQuery) -> Union[List[Metadata], Metadata]:
+    def get_metadata(self, query: MetadataQuery) -> List[Metadata]:
         args = parse_params(query=query)
         if "id" in args:
             self.url = f"{self.url}/{args['id']}"
             args = {}
-        responses = requests.get(url=self.url, params=args, headers=self.header)
+        response = requests.get(url=self.url, params=args, headers=self.header)
         try:
-            metadata = parse_obj_as(Union[List[Metadata], Metadata], responses.json())
+            metadata = parse_obj_as(Union[List[Metadata], Metadata], response.json())
         except:
-            raise ValueError("Data not found")
+            return []
         if isinstance(metadata, Metadata):
             metadata = [metadata]
         return metadata
 
-    def create_metadata(self, metadata: Metadata) -> requests.Response:
+    def create_metadata(self, metadata: Metadata) -> Metadata:
         response = requests.post(
             url=self.url, data=metadata.json(), headers=self.header
         )
-        return response
+        return Metadata(**response.json())
 
-    def update_metadata(self, metadata: Metadata) -> requests.Response:
+    def update_metadata(self, metadata: Metadata) -> Metadata:
         response = requests.put(
             url=f"{self.url}/{metadata.id}", data=metadata.json(), headers=self.header
         )
-        return response
+        return Metadata(**response.json())
 
 
 def parse_params(query: MetadataQuery) -> str:
diff --git a/geomagio/apiclient/metadata.py b/geomagio/apiclient/metadata.py
index 42551ad45..272e2cd87 100644
--- a/geomagio/apiclient/metadata.py
+++ b/geomagio/apiclient/metadata.py
@@ -11,18 +11,15 @@ from ..metadata import Metadata, MetadataCategory
 from .MetadataFactory import MetadataFactory
 
 
-def load_metadata(input_file) -> Optional[Dict]:
+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
-    try:
-        with open(input_file, "r") as file:
-            data = json.load(file)
-        return data
-    except (FileNotFoundError, TypeError):
-        return None
+    with open(input_file, "r") as file:
+        data = json.load(file)
+    return data
 
 
 app = typer.Typer()
@@ -40,10 +37,7 @@ def create(
     data_valid: bool = True,
     endtime: str = None,
     id: int = None,
-    input_file: str = typer.Option(
-        None,
-        help="JSON formatted file containing non-shared metadata information",
-    ),
+    input_file: str = None,
     location: str = None,
     metadata_valid: bool = True,
     network: str = None,
@@ -51,72 +45,40 @@ def create(
     station: str = None,
     wrap: bool = True,
 ):
-
-    if wrap == True:
+    input_metadata = load_metadata(input_file=input_file)
+    if not wrap:
+        metadata = Metadata(**input_metadata)
+    else:
         metadata = Metadata(
-            id=id,
             category=category,
-            starttime=UTCDateTime(starttime) if starttime else None,
-            endtime=UTCDateTime(endtime) if endtime else None,
+            channel=channel,
             created_after=UTCDateTime(created_after) if created_after else None,
             created_before=UTCDateTime(created_before) if created_before else None,
-            network=network,
-            station=station,
-            channel=channel,
-            location=location,
             data_valid=data_valid,
+            endtime=UTCDateTime(endtime) if endtime else None,
+            id=id,
+            location=location,
+            metadata = input_metadata["metadata"],
             metadata_valid=metadata_valid,
+            network=network,
+            starttime=UTCDateTime(starttime) if starttime else None,
+            station=station,
         )
-        metadata.metadata = load_metadata(input_file=input_file)
-    elif wrap == False:
-        metadata_dict = load_metadata(input_file=input_file)
-        metadata = Metadata(**metadata_dict)
-    response = MetadataFactory(url=url).create_metadata(metadata=metadata)
-    print(response.json())
+    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")
     ),
-    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 = typer.Option(
-        None,
-        help="JSON formatted file containing non-shared metadata information",
-    ),
-    location: str = None,
-    metadata_valid: bool = True,
-    network: str = None,
-    starttime: str = None,
-    station: str = None,
 ):
-    if input_file is not None:
-        metadata_dict = load_metadata(input_file=input_file)
-        metadata = Metadata(**metadata_dict)
-    else:
-        metadata = Metadata(
-            id=id,
-            category=category,
-            starttime=starttime,
-            endtime=endtime,
-            created_after=created_after,
-            created_before=created_before,
-            network=network,
-            station=station,
-            channel=channel,
-            location=location,
-            data_valid=data_valid,
-            metadata_valid=metadata_valid,
-        )
-    response = MetadataFactory(url=url).delete_metadata(metadata=metadata)
-    print(response.json())
+    metadata_dict = load_metadata(input_file=input_file)
+    metadata = Metadata(**metadata_dict)
+    deleted = MetadataFactory(url=url).delete_metadata(metadata=metadata)
+    print(deleted)
 
 
 @app.command()
@@ -136,75 +98,45 @@ def get(
     network: Optional[str] = None,
     starttime: Optional[str] = None,
     station: Optional[str] = None,
-    unwrap: bool = False,
+    getone: bool = False,
 ):
     query = MetadataQuery(
-        id=id,
         category=category,
-        starttime=UTCDateTime(starttime) if starttime else None,
-        endtime=UTCDateTime(endtime) if endtime else None,
+        channel=channel,
         created_after=UTCDateTime(created_after) if created_after else None,
         created_before=UTCDateTime(created_before) if created_before else None,
-        network=network,
-        station=station,
-        channel=channel,
-        location=location,
         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 unwrap:
-        metadata = [json.dumps(m.metadata) for m in metadata]
-    else:
-        metadata = [m.json() for m in metadata]
-    if len(metadata) == 1:
-        print(metadata[0])
+    if not metadata:
+        print([])
+        return
+
+    if getone:
+        if len(metadata) > 1:
+            raise ValueError("More than one matching record")
+        print(metadata[0].json())
         return
-    print(metadata)
+    print([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")
     ),
-    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 = typer.Option(
-        None,
-        help="JSON formatted file containing non-shared metadata information",
-    ),
-    location: str = None,
-    metadata_valid: bool = True,
-    network: str = None,
-    starttime: str = None,
-    station: str = None,
 ):
-    if input_file is not None:
-        metadata_dict = load_metadata(input_file=input_file)
-        metadata = Metadata(**metadata_dict)
-
-    else:
-        metadata = Metadata(
-            id=id,
-            category=category,
-            starttime=UTCDateTime(starttime) if starttime else None,
-            endtime=UTCDateTime(endtime) if endtime else None,
-            created_after=UTCDateTime(created_after) if created_after else None,
-            created_before=UTCDateTime(created_before) if created_before else None,
-            network=network,
-            station=station,
-            channel=channel,
-            location=location,
-            data_valid=data_valid,
-            metadata_valid=metadata_valid,
-        )
-    metadata.metadata = load_metadata(input_file=input_file)
+    metadata_dict = load_metadata(input_file=input_file)
+    metadata = Metadata(**metadata_dict)
     response = MetadataFactory(url=url).update_metadata(metadata=metadata)
     print(response.json())
 
-- 
GitLab


From f0da49c60eedaf58ede951cff4fb069d939333b8 Mon Sep 17 00:00:00 2001
From: pcain-usgs <pcain@usgs.gov>
Date: Fri, 19 Mar 2021 09:41:16 -0600
Subject: [PATCH 11/12] Black reformat

---
 debugger.py                    | 2 +-
 geomagio/apiclient/metadata.py | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/debugger.py b/debugger.py
index f8e711b4b..e62088d4b 100644
--- a/debugger.py
+++ b/debugger.py
@@ -1,3 +1,3 @@
 from geomagio.apiclient.metadata import create
 
-create(station="BOU", category="reading")
\ No newline at end of file
+create(station="BOU", category="reading")
diff --git a/geomagio/apiclient/metadata.py b/geomagio/apiclient/metadata.py
index 272e2cd87..0835d5ae0 100644
--- a/geomagio/apiclient/metadata.py
+++ b/geomagio/apiclient/metadata.py
@@ -58,7 +58,7 @@ def create(
             endtime=UTCDateTime(endtime) if endtime else None,
             id=id,
             location=location,
-            metadata = input_metadata["metadata"],
+            metadata=input_metadata["metadata"],
             metadata_valid=metadata_valid,
             network=network,
             starttime=UTCDateTime(starttime) if starttime else None,
@@ -125,7 +125,6 @@ def get(
         print(metadata[0].json())
         return
     print([m.json() for m in metadata])
-    
 
 
 @app.command()
-- 
GitLab


From 970705c13c49cc62fbe43ded315bf810228d5e2b Mon Sep 17 00:00:00 2001
From: pcain-usgs <pcain@usgs.gov>
Date: Tue, 23 Mar 2021 09:33:11 -0600
Subject: [PATCH 12/12] _get_header, get_metadata_by_id, allow exception for no
 data, move query

---
 debugger.py                                   |  3 --
 geomagio/api/secure/metadata.py               |  5 +-
 geomagio/api/ws/metadata.py                   |  3 +-
 geomagio/apiclient/MetadataFactory.py         | 46 +++++++++----------
 geomagio/apiclient/metadata.py                | 10 ++--
 .../{api/secure => metadata}/MetadataQuery.py |  4 +-
 geomagio/metadata/__init__.py                 |  3 +-
 7 files changed, 34 insertions(+), 40 deletions(-)
 delete mode 100644 debugger.py
 rename geomagio/{api/secure => metadata}/MetadataQuery.py (91%)

diff --git a/debugger.py b/debugger.py
deleted file mode 100644
index e62088d4b..000000000
--- a/debugger.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from geomagio.apiclient.metadata import create
-
-create(station="BOU", category="reading")
diff --git a/geomagio/api/secure/metadata.py b/geomagio/api/secure/metadata.py
index 227ce65d0..e619c6636 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()
diff --git a/geomagio/api/ws/metadata.py b/geomagio/api/ws/metadata.py
index 9f4febeb9..655c33e59 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
index 1d8ded018..288901c68 100644
--- a/geomagio/apiclient/MetadataFactory.py
+++ b/geomagio/apiclient/MetadataFactory.py
@@ -5,8 +5,7 @@ from typing import List, Union
 from obspy import UTCDateTime
 from pydantic import parse_obj_as
 
-from ..api.secure.MetadataQuery import MetadataQuery
-from ..metadata import Metadata
+from ..metadata import Metadata, MetadataQuery
 
 
 class MetadataFactory(object):
@@ -19,7 +18,10 @@ class MetadataFactory(object):
     ):
         self.url = url
         self.token = token
-        self.header = {"Authorization": self.token} if token else None
+        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)
@@ -28,19 +30,19 @@ class MetadataFactory(object):
         return False
 
     def get_metadata(self, query: MetadataQuery) -> List[Metadata]:
-        args = parse_params(query=query)
-        if "id" in args:
-            self.url = f"{self.url}/{args['id']}"
-            args = {}
-        response = requests.get(url=self.url, params=args, headers=self.header)
-        try:
-            metadata = parse_obj_as(Union[List[Metadata], Metadata], response.json())
-        except:
-            return []
+        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
@@ -55,19 +57,15 @@ class MetadataFactory(object):
 
 
 def parse_params(query: MetadataQuery) -> str:
-    query = query.dict()
+    query = query.dict(exclude_none=True)
     args = {}
     for key in query.keys():
         element = query[key]
-        if element is not None:
-            # convert times to strings
-            if type(element) == UTCDateTime:
-                element = element.isoformat()
-            # get string value of metadata category
-            if key == "category":
-                element = element.value
-            elif key == "id":
-                return {"id": element}
-            args[key] = element
-
+        # 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/metadata.py b/geomagio/apiclient/metadata.py
index 0835d5ae0..afb3ea63e 100644
--- a/geomagio/apiclient/metadata.py
+++ b/geomagio/apiclient/metadata.py
@@ -6,8 +6,7 @@ from typing import Dict, Optional
 from obspy import UTCDateTime
 import typer
 
-from ..api.secure.MetadataQuery import MetadataQuery
-from ..metadata import Metadata, MetadataCategory
+from ..metadata import Metadata, MetadataCategory, MetadataQuery
 from .MetadataFactory import MetadataFactory
 
 
@@ -58,7 +57,7 @@ def create(
             endtime=UTCDateTime(endtime) if endtime else None,
             id=id,
             location=location,
-            metadata=input_metadata["metadata"],
+            metadata=input_metadata,
             metadata_valid=metadata_valid,
             network=network,
             starttime=UTCDateTime(starttime) if starttime else None,
@@ -78,7 +77,8 @@ def delete(
     metadata_dict = load_metadata(input_file=input_file)
     metadata = Metadata(**metadata_dict)
     deleted = MetadataFactory(url=url).delete_metadata(metadata=metadata)
-    print(deleted)
+    if not deleted:
+        sys.exit(1)
 
 
 @app.command()
@@ -124,7 +124,7 @@ def get(
             raise ValueError("More than one matching record")
         print(metadata[0].json())
         return
-    print([m.json() for m in metadata])
+    print("[" + ",".join([m.json() for m in metadata]) + "]")
 
 
 @app.command()
diff --git a/geomagio/api/secure/MetadataQuery.py b/geomagio/metadata/MetadataQuery.py
similarity index 91%
rename from geomagio/api/secure/MetadataQuery.py
rename to geomagio/metadata/MetadataQuery.py
index f414dd478..36e6c3ebe 100644
--- a/geomagio/api/secure/MetadataQuery.py
+++ b/geomagio/metadata/MetadataQuery.py
@@ -4,8 +4,8 @@ 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):
diff --git a/geomagio/metadata/__init__.py b/geomagio/metadata/__init__.py
index 7502db024..37b2adc33 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"]
-- 
GitLab