Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added safe mode option and url builder #23

Merged
merged 7 commits into from
Aug 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/jokes/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,13 @@ def jokes(ctx: Context, debug: bool):
""",
default=[]
)
@click.option(
"--safe/--unsafe", "safe",
default=False
)
@click.pass_context
def get(ctx: Context, category: str, type: str, flags: list[str]) -> None:
def get(ctx: Context, category: str, type: str, flags: list[str], safe: bool) -> None:
ctx.obj["SAFE_MODE"] = safe
joke = get_joke(OptionData(category, type, flags))

click.echo(unsafe_perform_io(unwrap(ctx, joke)))
Expand Down
12 changes: 1 addition & 11 deletions src/jokes/models/error.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from pydantic import BaseModel
from click import get_current_context
from click.core import Context


class Error(BaseModel):
Expand All @@ -18,16 +17,7 @@ def debug(self) -> bool:
the thread nor the value exist, returns
False.
"""
return context.obj.get("DEBUG") if (context := self.get_context()) else False


def get_context(self) -> Context | None:
"""
Gets the current thread's context. If no
context exists, silently fails and returns
None instead.
"""
return get_current_context(silent=True)
return context.obj.get("DEBUG") if (context := get_current_context(silent=True)) else False
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern of adding an object to the context and fetching it this way is repeated (and will be repeated again if the submit dry-run is ever fully implemented).

Maybe add a new util? JokesContext?

Copy link
Owner Author

@te25son te25son Aug 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context can already be fetched from anywhere in the code via get_current_context.

We could add a method like this though:

def get_context_object(key: str, default: bool = False) -> bool:
    return c.obj.get(key) if (c := get_current_context(silent=True)) else default



def __str__(self) -> str:
Expand Down
11 changes: 7 additions & 4 deletions src/jokes/pipelines/get_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from returns.result import safe
from returns.pipeline import flow
from returns.pointfree import bind_result
from jokes.utils.urls import GetEndpointParams, Endpoints, build_endpoint_url
from jokes.models import (
APIResponse,
JokeBase,
Expand Down Expand Up @@ -32,12 +33,14 @@ def make_request(data: OptionData) -> Response:
Raises an error if the response's status code is not 200
"""

params = dict(
type = data.type,
params = GetEndpointParams(
category=data.category,
type=data.type,
blacklistFlags = "+".join(data.flags)
)
with Client(params=params) as client:
response = client.get(f"https://v2.jokeapi.dev/joke/{data.category}")

with Client() as client:
response = client.get(build_endpoint_url(Endpoints.GET, params))
response.raise_for_status()

return response
Expand Down
4 changes: 3 additions & 1 deletion src/jokes/pipelines/submit_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from returns.result import safe
from returns.pipeline import flow
from returns.pointfree import bind_ioresult, bind_result
from jokes.utils.urls import build_endpoint_url, Endpoints
from jokes.utils.params import SubmitEndpointParams
from jokes.models import (
APIResponse,
JokeBase,
Expand Down Expand Up @@ -51,7 +53,7 @@ def submit_request(data: str) -> Response:
"""

with Client() as client:
response = client.post("https://v2.jokeapi.dev/submit?dry-run", json=data)
response = client.post(build_endpoint_url(Endpoints.SUBMIT, SubmitEndpointParams()), json=data)
response.raise_for_status()

return response
Expand Down
Empty file added src/jokes/utils/__init__.py
Empty file.
55 changes: 55 additions & 0 deletions src/jokes/utils/params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from pydantic import BaseModel, Field
from click import get_current_context


def filter_items(items: list[str | None]) -> set[str]:
"""Filters none values from given list and returns a set."""

return set(filter(None, items))


class GetEndpointParams(BaseModel):
"""
Class representing the query parameters to be included in the get endpoint url.

Fields containing an "" string will be converted to a query parameter with an empty value, i.e. `?param=`.
Fields of type `None` will be converted to a valueless query parameter, i.e.`?param`.
"""

type: str
category: str
blacklist_flags: str | None = Field(default="", alias="blacklistFlags")
safe_mode: str | None = Field(default=None, alias="safe-mode")


def dict(self, **kwargs) -> dict[str, str]:
"""Converts fields to a dict that can be converted to a valid url."""

safe_mode = context.obj.get("SAFE_MODE") if (context := get_current_context(silent=True)) else False
included_fields = filter_items([
"type",
"blacklist_flags" if self.blacklist_flags else None,
"safe_mode" if safe_mode else None
])

return super().dict(by_alias=True, include=included_fields, **kwargs)


class SubmitEndpointParams(BaseModel):
"""
Class representing the query parameters to be included in the submit endpoint url.

Fields containing an "" string will be converted to a query parameter with an empty value, i.e. `?param=`.
Fields of type `None` will be converted to a valueless query parameter, i.e. `?param`.
"""

dry_run: str | None = Field(default=None, alias="dry-run")


def dict(self, **kwargs) -> dict[str, str]:
"""Converts fields to a dict that can be converted to a valid url."""

dry_run = context.obj.get("TEST") if (context := get_current_context(silent=True)) else False
included_fields = filter_items(["dry_run" if dry_run else None])

return super().dict(by_alias=True, include=included_fields, **kwargs)
29 changes: 29 additions & 0 deletions src/jokes/utils/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from typing import Mapping
from enum import Enum
from jokes.utils.params import GetEndpointParams, SubmitEndpointParams


BASE_URL = "https://v2.jokeapi.dev"

Primitive = str | int | float | bool | None
TParam = GetEndpointParams | SubmitEndpointParams


class Endpoints(Enum):
GET = "joke"
SUBMIT = "submit"


def build_query_string(dict: Mapping[str, Primitive]) -> str:
"""Formats the params into a string usable by the joke api."""

return "&".join([k if v is None else f"{k}={v}" for k, v in dict.items()])


def build_endpoint_url(endpoint: Endpoints, params: TParam) -> str:
"""Builds a valid joke api endpoint url."""

category = params.category if isinstance(params, GetEndpointParams) else None
url = "/".join(filter(None, [BASE_URL, endpoint.value, category]))

return f"{url}?{query_string}" if (query_string := build_query_string(params.dict())) else url