Changeset - 2ea1af3149c2
[Not reviewed]
default
0 3 0
Dennis Fink - 3 years ago 2022-03-03 17:40:15
dennis.fink@c3l.lu
Add more debug logging
3 files changed with 12 insertions and 0 deletions:
0 comments (0 inline, 0 general)
stockcli/cli.py
Show inline comments
 
import json
 
import logging
 
import logging.config
 
from operator import itemgetter
 

	
 
import click
 
import requests
 
from rich.panel import Panel
 
from rich.table import Table
 

	
 
from .console import DEFAULT_PADDING, console, int_prompt, prompt
 
from .stock import (
 
    add_by_barcode,
 
    get_info_by_barcode,
 
    transfer_by_barcode,
 
    update_by_barcode,
 
)
 
from .utils import prepare_barcode
 

	
 
TASK_MAP = {
 
    "1": ("Transfer stock", transfer_by_barcode),
 
    "2": ("Add stock", add_by_barcode),
 
    "3": ("Update stock", update_by_barcode),
 
    "4": ("Get product info", get_info_by_barcode),
 
}
 

	
 

	
 
@click.command(context_settings={"help_option_names": ("-h", "--help", "-?")})
 
@click.option(
 
    "-c",
 
    "--config",
 
    "configfile",
 
    default="/etc/stockcli.json",
 
    type=click.File("r"),
 
    help="Config file to load",
 
)
 
@click.pass_context
 
def stockcli(ctx: click.Context, configfile: click.File) -> None:
 

	
 
    config = json.load(configfile)  # type: ignore
 
    logging.config.dictConfig(config["logging"])
 

	
 
    ctx.ensure_object(dict)
 
    ctx.obj["request_session"] = requests.Session()
 
    ctx.obj["request_session"].headers.update(
 
        {
 
            "accept": "application/json",
 
            "GROCY-API-KEY": config["grocy"]["apikey"],
 
        }
 
    )
 
    ctx.obj["base_url"] = f"{config['grocy']['url']}/api/"
 

	
 
    menu = Table.grid(padding=DEFAULT_PADDING)
 
    menu.add_column(justify="left", style="green", no_wrap=True)
 
    menu.add_column(justify="left", style="cyan", no_wrap=True)
 

	
 
    for task_id, task in sorted(TASK_MAP.items(), key=itemgetter(0)):
 
        menu.add_row(task_id, task[0])
 

	
 
    while True:
 
        click.clear()
 
        console.print(Panel(menu, title="[green bold]Menu[/green bold]"))
 
        choice = prompt.ask(
 
            "Enter a number to select a task", choices=list(TASK_MAP.keys())
 
        )
 
        logging.debug(f"User selected task: {choice}")
 
        selected_task = TASK_MAP[choice][1]
 

	
 
        rerun = True
 
        while rerun:
 
            barcode = prompt.ask("Please scan the barcode")
 
            barcode = prepare_barcode(barcode)
 

	
 
            try:
 
                selected_task(barcode)
 
            except (
 
                requests.Timeout,
 
                requests.ConnectionError,
 
                requests.HTTPError,
 
                requests.TooManyRedirects,
 
            ):
 
                rerun = False
 
                console.input("Continue")
 
            else:
 
                rerun = bool(
 
                    int_prompt.ask(
 
                        "Do the same with another product",
 
                        choices=["0", "1"],
 
                        default=0,
 
                    )
 
                )
 

	
 

	
 
if __name__ == "__main__":
 
    stockcli()
stockcli/stock.py
Show inline comments
 
import logging
 
from operator import itemgetter
 
from typing import Any
 

	
 
from rich.panel import Panel
 
from rich.table import Table
 

	
 
from . import utils
 
from .console import DEFAULT_PADDING, console, int_prompt, prompt
 
from .style import GreenBoldText
 

	
 

	
 
def get_info_by_barcode(barcode: str) -> None:
 

	
 
    product = utils.get_request(
 
        f"stock/products/by-barcode/{barcode}"
 
    )  # type: dict[str, Any]
 

	
 
    inner_product = product["product"]
 
    product_id = inner_product["id"]
 

	
 
    product_group = utils.get_request(
 
        f"objects/product_groups/{inner_product['product_group_id']}"
 
    )  # type: dict[str, Any]
 

	
 
    grid = Table.grid(padding=DEFAULT_PADDING)
 
    grid.add_column(justify="left", no_wrap=True)
 
    grid.add_column(justify="right", style="cyan", no_wrap=True)
 
    grid.add_column(justify="left", no_wrap=True)
 

	
 
    grid.add_row(GreenBoldText("Product ID:"), product_id)
 
    grid.add_row(GreenBoldText("Product Name:"), inner_product["name"])
 
    grid.add_row(GreenBoldText("Product Group:"), product_group["name"])
 

	
 
    for i, barcode_data in enumerate(product["product_barcodes"], start=1):
 
        grid.add_row(
 
            GreenBoldText(f"Barcode N°{i}:"),
 
            barcode_data["barcode"],
 
            barcode_data["note"],
 
        )
 

	
 
    purchase_quantity_unit = product["default_quantity_unit_purchase"]
 
    stock_quantity_unit = product["quantity_unit_stock"]
 

	
 
    purchase_to_stock_conversion = utils.get_request(
 
        f"objects/quantity_unit_conversions?query[]=from_qu_id={purchase_quantity_unit['id']}&query[]=to_qu_id={stock_quantity_unit['id']}"
 
    )  # type: list[dict[str, Any]]
 

	
 
    if len(purchase_to_stock_conversion) != 0:
 
@@ -85,167 +86,172 @@ def transfer_by_barcode(barcode: str) ->
 
    product = utils.get_request(
 
        f"stock/products/by-barcode/{barcode}"
 
    )  # type: dict[str, Any]
 

	
 
    inner_product = product["product"]
 
    product_id = inner_product["id"]
 

	
 
    locations = utils.get_request(
 
        f"stock/products/{product_id}/locations"
 
    )  # type: list[dict[str, Any]]
 
    all_locations = utils.get_request("objects/locations")  # type: list[dict[str, Any]]
 

	
 
    grid = Table.grid(padding=DEFAULT_PADDING)
 
    grid.add_column(justify="right", no_wrap=True)
 
    grid.add_column(justify="left", style="cyan", no_wrap=True)
 
    grid.add_column(justify="right", style="magenta", no_wrap=True)
 

	
 
    grid.add_row(GreenBoldText("Product ID:"), product_id)
 
    grid.add_row(GreenBoldText("Product Name:"), inner_product["name"])
 
    grid.add_row(GreenBoldText("Locations:"))
 

	
 
    choices = []
 

	
 
    for location in sorted(locations, key=itemgetter("location_id")):
 
        location_id = location["location_id"]
 
        choices.append(location_id)
 
        grid.add_row(
 
            f"[blue]{location_id}[/blue]",
 
            location["location_name"],
 
            location["amount"],
 
        )
 

	
 
    choices.append("0")
 
    console.print(
 
        Panel(
 
            grid,
 
            title="[green bold]Product Info[/green bold]",
 
            subtitle="[blue]Transfer stock[/blue]",
 
        )
 
    )
 

	
 
    from_id = int_prompt.ask(
 
        "From (Enter 0 to abort)",
 
        choices=choices,
 
        default=int(choices[0]),
 
    )
 

	
 
    if not from_id:
 
        logging.debug("User aborted task!")
 
        return
 

	
 
    grid = Table.grid(padding=DEFAULT_PADDING)
 
    grid.add_column(justify="right", style="blue", no_wrap=True)
 
    grid.add_column(justify="left", style="cyan", no_wrap=True)
 

	
 
    choices = []
 
    for location in sorted(all_locations, key=itemgetter("id")):
 
        location_id = location["id"]
 
        if int(location_id) != from_id:
 
            choices.append(location_id)
 
            grid.add_row(location_id, location["name"])
 

	
 
    choices.append("0")
 
    console.print(
 
        Panel(
 
            grid,
 
            title="[green bold]Locations[/green bold]",
 
            subtitle="[blue]Transfer Stock[/blue]",
 
        )
 
    )
 

	
 
    to_id = int_prompt.ask(
 
        "To (Enter 0 to abort)",
 
        choices=choices,
 
        default=int(choices[0]),
 
    )
 

	
 
    if not to_id:
 
        logging.debug("User aborted task!")
 
        return
 

	
 
    amount = int_prompt.ask("Amount (Enter 0 to abort)")
 

	
 
    if not amount:
 
        logging.debug("User aborted task!")
 
        return
 

	
 
    data = {"amount": amount, "location_id_from": from_id, "location_id_to": to_id}
 

	
 
    response = utils.post_request(
 
        f"stock/products/by-barcode/{barcode}/transfer", data
 
    )  # type: dict[str, Any]
 
    console.print("Successfully transfered!")
 
    return
 

	
 

	
 
def add_by_barcode(barcode: str) -> None:
 

	
 
    product = utils.get_request(
 
        f"stock/products/by-barcode/{barcode}"
 
    )  # type: dict[str, Any]
 

	
 
    inner_product = product["product"]
 
    product_id = inner_product["id"]
 

	
 
    grid = Table.grid(padding=DEFAULT_PADDING)
 
    grid.add_column(justify="right", no_wrap=True)
 
    grid.add_column(justify="left", style="cyan", no_wrap=True)
 
    grid.add_row(GreenBoldText("Product ID:"), product_id)
 
    grid.add_row(GreenBoldText("Product Name:"), inner_product["name"])
 
    console.print(
 
        Panel(
 
            grid,
 
            title="[green bold]Product Info[/green bold]",
 
            subtitle="[blue]Add Stock[/blue]",
 
        )
 
    )
 

	
 
    amount = int_prompt.ask("Amount (Enter 0 to abort)")
 

	
 
    if not amount:
 
        logging.debug("User aborted task!")
 
        return
 

	
 
    data = {"amount": amount, "transaction_type": "purchase"}
 

	
 
    response = utils.post_request(
 
        f"stock/products/by-barcode/{barcode}/add", data
 
    )  # type: dict[str, Any]
 
    console.print("Successfully added!")
 
    return
 

	
 

	
 
def update_by_barcode(barcode: str) -> None:
 

	
 
    product = utils.get_request(
 
        f"stock/products/by-barcode/{barcode}"
 
    )  # type: dict[str, Any]
 

	
 
    inner_product = product["product"]
 
    product_id = inner_product["id"]
 

	
 
    grid = Table.grid(padding=DEFAULT_PADDING)
 
    grid.add_column(justify="right", no_wrap=True)
 
    grid.add_column(justify="left", style="cyan", no_wrap=True)
 

	
 
    grid.add_row(GreenBoldText("Product ID:"), product_id)
 
    grid.add_row(GreenBoldText("Product Name:"), inner_product["name"])
 
    grid.add_row(GreenBoldText("Overall Stock Amount:"), product["stock_amount"])
 

	
 
    console.print(
 
        Panel(
 
            grid,
 
            title="[green bold]Product Info[/green bold]",
 
            subtitle="[blue]Update stock[/blue]",
 
        )
 
    )
 
    amount = int_prompt.ask("Amount (Enter 0 to abort)")
 

	
 
    if not amount:
 
        logging.debug("User aborted task!")
 
        return
 

	
 
    data = {
 
        "new_amount": amount,
 
    }
 

	
 
    response = utils.post_request(
 
        f"stock/products/{product_id}/inventory", data
 
    )  # type: dict[str, Any]
 
    console.print("Successfully updated!")
 
    return
stockcli/utils.py
Show inline comments
 
import logging
 
import string
 
from operator import attrgetter
 
from typing import Any, Dict, Optional
 

	
 
import click
 
import requests
 

	
 
from .console import error_console
 

	
 
UNALLOWED_CHARACTERS = str.maketrans(dict((c, None) for c in string.whitespace))
 

	
 

	
 
def make_request(method: str, url_path: str, data: Optional[Any] = None) -> Any:
 
    obj = click.get_current_context().obj
 
    session = obj["request_session"]
 
    base_url = obj["base_url"]
 
    requested_url = base_url + url_path
 

	
 
    method_function = attrgetter(method)
 

	
 
    try:
 
        if data is not None:
 
            logging.debug(
 
                f"Making {method.upper()} request: {requested_url} with {data}"
 
            )
 
            response = method_function(session)(requested_url, json=data)
 
        else:
 
            logging.debug(f"Making {method.upper()} request: {requested_url}")
 
            response = method_function(session)(requested_url)
 
        response.raise_for_status()
 
    except requests.Timeout:
 
        logging.error(f"The connection to {requested_url} timed out!")
 
        error_console.print("Connection timed out!")
 
        raise
 
    except requests.ConnectionError:
 
        logging.error(f"Couldn't establish a connection to {requested_url}!")
 
        error_console.print("Couldn't establish a connection!")
 
        raise
 
    except requests.HTTPError:
 
        logging.error(f"{requested_url} sent back an HTTPError")
 
        error_console.print("Got the following error:")
 
        error_message = response.json()["error_message"]
 
        logging.error(error_message)
 
        error_console.print(error_message)
 
        raise
 
    except requests.TooManyRedirects:
 
        logging.error(f"{requested_url} had too many redirects!")
 
        error_console.print("Too many redirects!")
 
        raise
 
    else:
 
        return response.json()
 

	
 

	
 
def get_request(url_path: str) -> Any:
 
    return make_request("get", url_path)
 

	
 

	
 
def post_request(url_path: str, data: Dict[str, Any]) -> Any:
 
    return make_request("post", url_path, data)
 

	
 

	
 
def put_request(url_path: str, data: Dict[str, Any]) -> Any:
 
    return make_request("put", url_path, data)
 

	
 

	
 
def prepare_barcode(barcode: str) -> str:
 
    return barcode.translate(UNALLOWED_CHARACTERS)
0 comments (0 inline, 0 general)