Skip to content

Commit

Permalink
WIP: cli and client
Browse files Browse the repository at this point in the history
  • Loading branch information
Yifan Zhang committed Jun 24, 2021
1 parent c7d02bd commit db8c69f
Show file tree
Hide file tree
Showing 14 changed files with 611 additions and 285 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ env
.python-version
.env
dist
**.apihub
test.sh
31 changes: 31 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
variables:
GET_SOURCES_ATTEMPTS: 3

stages:
- test
- build
- deploy

test:
image: docker
stage: test
variables:
NAME: $CI_PROJECT_NAME
script:
- docker build -t $NAME -f Dockerfile.webapi .
- docker run --name $NAME -w /code $NAME pytest
after_script:
- docker stop $NAME
- docker rm $NAME
- docker rmi $NAME:latest

build:
stage: build
variables:
NAME: $CI_PROJECT_NAME
TAG: $CI_COMMIT_REF_NAME
script:
- docker login $ACR_REGISTERY -u $ACR_USERNAME -p $ACR_PASSWORD
- docker build -t $NAME:$TAG -f Dockerfile .
only:
- tags
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
FROM python:3.8-slim

RUN pip install apihub
RUN apt-get update && apt-get install -y git

#RUN pip install apihub
COPY . /code
RUN pip install /code/dist/apihub-0.1.0-py3-none-any.whl

# expose port for prometheus
EXPOSE 8000
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
.PHONY: init
init:
poetry env use $(shell pyenv which python) && \
poetry install

.PHONY: pytest
pytest:
poetry run pytest -vv $(ARGS)
Expand Down
121 changes: 111 additions & 10 deletions apihub/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import sys
import json
from datetime import datetime, timedelta

try:
import click
Expand All @@ -13,23 +15,122 @@
from apihub.client import Client


@click.command()
@click.option("--username", "-u", type=str, default="", help="username")
@click.option("--password", "-p", type=str, default="", help="password")
@click.option("--endpoint", "-e", type=str, default="http://localhost", help="endpoint")
def login(username, password, endpoint):
@click.group()
def cli():
pass


@cli.command()
@click.option(
"--endpoint", "-e", type=str, default="http://localhost:5000", help="endpoint"
)
@click.argument("username", nargs=1)
@click.argument("password", nargs=1)
def login(endpoint, username, password):
client = Client({"endpoint": endpoint})
client.authenticate(username=username, password=password)
client.save_state()
client.save_state(filename=f"{username}.apihub")


@click.command()
@cli.command()
@click.option("--username", "-u", type=str, default="", help="username")
@click.argument("application", nargs=1)
def refresh_token(application):
def refresh_token(username, application):
# check if login
client = Client.load_state()
client = Client.load_state(filename=f"{username}.apihub")
if client.token is None:
sys.stderr.write("You need to login first")
click.echo("You need to login first")
sys.exit(1)

client.refresh_application_token(application)
client.save_state(filename=f"{username}.apihub")


@cli.command()
@click.option("--admin", "-a", type=str, default="", help="admin username")
@click.option(
"--role", "-r", type=str, default="user", help="role [admin, manager, user]"
)
@click.argument("username", nargs=1)
@click.argument("password", nargs=1)
@click.argument("email", nargs=1)
def create_user(admin, role, username, password, email):
client = Client.load_state(filename=f"{admin}.apihub")
if client.token is None:
click.echo("You need to login first")
sys.exit(1)

client.create_user(
{
"username": username,
"password": password,
"role": role,
"email": email,
}
)


@cli.command()
@click.option("--admin", "-a", type=str, default="", help="admin username")
@click.option("--limit", "-l", type=int, default=1000, help="limit")
@click.option("--days", "-d", type=int, default=None, help="subscription valid days")
@click.option(
"--recurring", "-r", type=bool, default=False, help="recurring subscription"
)
@click.argument("username", nargs=1)
@click.argument("application", nargs=1)
def create_subscription(admin, limit, days, recurring, username, application):
client = Client.load_state(filename=f"{admin}.apihub")
if client.token is None:
click.echo("You need to login first")
sys.exit(1)

expires_at = datetime.now() + timedelta(days=days) if days else None
client.create_subscription(
{
"username": username,
"application": application,
"starts_at": datetime.now(),
"limit": limit,
"expires_at": expires_at,
"recurring": recurring,
}
)


@cli.command()
@click.option("--username", "-u", type=str, default="", help="username")
@click.argument("application", nargs=1)
def post_request(username, application):
client = Client.load_state(filename=f"{username}.apihub")
if client.token is None:
click.echo("You need to login first")
sys.exit(1)

MARKER = "# Please write body in json above"
message = click.edit("\n\n" + MARKER)
if message is not None:
data = json.loads(message.split(MARKER, 1)[0])
response = client.async_request(application, data)
print(response)
else:
click.echo("input cannot be empty")
sys.exit(1)


@cli.command()
@click.option("--username", "-u", type=str, default="", help="username")
@click.argument("application", nargs=1)
@click.argument("key", nargs=1)
def fetch_result(username, application, key):
client = Client.load_state(filename=f"{username}.apihub")
if client.token is None:
click.echo("You need to login first")
sys.exit(1)

response = client.async_result(application, key)
print(response)


if __name__ == "__main__":
cli()
48 changes: 44 additions & 4 deletions apihub/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import requests
from pydantic import BaseSettings

from apihub_users.security.router import UserCreate
from apihub_users.subscription.router import SubscriptionIn


class ClientSettings(BaseSettings):
endpoint: str = "http://localhost"
Expand All @@ -25,7 +28,7 @@ def _make_url(self, path: str) -> str:
def save_state(self, filename="~/.apihubrc") -> None:
json.dump(
{
"settings": self.settings,
"settings": self.settings.dict(),
"token": self.token,
"applications": self.applications,
},
Expand All @@ -47,7 +50,32 @@ def authenticate(self, username: str, password: str) -> None:
auth=(username, password),
)
if response.status_code == 200:
self.token = response.json()["token"]
print(response.json())
self.token = response.json()["access_token"]
else:
print(response.json())

def create_user(self, user):
username = user["username"]
response = requests.post(
self._make_url(f"user/{username}"),
headers={"Authorization": f"Bearer {self.token}"},
json=UserCreate.parse_obj(user).dict(),
)
print(response.json())
if response.status_code == 200:
print(response.json())

def create_subscription(self, subscription):
response = requests.post(
self._make_url("subscription"),
headers={"Authorization": f"Bearer {self.token}"},
json=SubscriptionIn.parse_obj(subscription).dict(),
)
if response.status_code == 200:
return True
else:
raise Exception(response.json())

def refresh_application_token(self, application: str) -> None:
# TODO exceptions
Expand All @@ -56,28 +84,40 @@ def refresh_application_token(self, application: str) -> None:
headers={"Authorization": f"Bearer {self.token}"},
)
if response.status_code == 200:
print(response.json())
self.applications[application] = response.json()["token"]
else:
print(response.text)
print(response.json())

def _check_token_for(self, application: str) -> bool:
return application in self.applications

def async_request(self, application: str, params: Dict[str, Any]):
def async_request(self, application: str, data: dict):
response = requests.post(
self._make_url(f"async/{application}"),
headers={
"Authorization": f"Bearer {self.applications[application]}",
},
json=data,
)
if response.status_code == 200:
return response.json()
else:
return response.json()

def async_result(self, application: str, key: str):
# TODO wait and timeout
response = requests.get(
self._make_url(f"async/{application}"),
params={
"key": key,
},
headers={
"Authorization": f"Bearer {self.applications[application]}",
"Authorization": f"Bearer {self.token}",
},
)
if response.status_code == 200:
return response.json()
else:
return response.json()
38 changes: 10 additions & 28 deletions apihub/result.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,15 @@
import logging
from enum import Enum
import json

import redis
from pydantic import Field, BaseModel
from prometheus_client import Counter, Histogram
from dotenv import load_dotenv

load_dotenv()

from pipeline import ProcessorSettings, Processor, Settings, DescribeMessage
from pipeline import ProcessorSettings, Processor, Command, CommandActions
from apihub.utils import Result, Status, RedisSettings, DEFINITION
from apihub import __worker__, __version__


logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class RedisSettings(Settings):
redis: str = Field("redis://localhost:6379/1", title="redis url")


class Status(str, Enum):
ACCEPTED = "ACCEPTED"
PROCESSED = "PROCESSED"


class Result(BaseModel):
user: str
api: str
status: Status
result: dict = dict()
load_dotenv()


class ResultWriter(Processor):
Expand Down Expand Up @@ -63,9 +43,11 @@ def setup(self) -> None:
settings = RedisSettings()
self.redis = redis.Redis.from_url(settings.redis)

def process_special_message(self, message: DescribeMessage) -> None:
print(message.input_schema)
print(message.output_schema)
def process_command(self, command: Command) -> None:
if command.action == CommandActions.Define:
for k, v in command.content.items():
self.redis.hset(DEFINITION, k, json.dumps(v))
self.logger.info(f"{k} definition:\n{json.dumps(v, indent=2)}")

def process(self, message_content, message_id):
result = Result.parse_obj(message_content)
Expand All @@ -77,7 +59,7 @@ def process(self, message_content, message_id):
self.api_counter.labels(api=result.api, user=result.user, status=result.status)

if self.redis.get(message_id) is not None:
logger.warning("Found result with key %s, overwriting...", message_id)
self.logger.warning("Found result with key %s, overwriting...", message_id)

self.redis.set(message_id, result.json(), ex=86400)
return None
Expand Down

0 comments on commit db8c69f

Please sign in to comment.