commit a5f290d0e7739eade6e2c6f9923a2e71297cdd1b Author: Aerex Date: Sun Mar 3 19:29:38 2019 -0600 feat(added command to retrieve stock overview): diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2839fc9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +venv +grocy_cli.egg-info/ diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..152ecb0 --- /dev/null +++ b/README.rst @@ -0,0 +1 @@ +.. readme diff --git a/grocy/__init__.py b/grocy/__init__.py new file mode 100644 index 0000000..c90afa9 --- /dev/null +++ b/grocy/__init__.py @@ -0,0 +1,78 @@ +import requests +import json +class RestService(object): + API_KEY_HEADER = 'GROCY-API-KEY' + def __init__(self, api_url, json=False): + self.api_url = api_url + self.headers = {} + self.json = json + + def get(self, path, id=None): + + # TODO: change this to a single pattern assume a pattern then stick with it + if self.api_url.endswith('/'): + url = '{0}{1}'.format(self.api_url, path[1:]) + else: + url = '{0}{1}'.format(self.api_url, path) + + if id: + url = '{0}/{1}'.format(url, id) + + r = requests.get(url, headers=self.headers) + + if r.raise_for_status(): + logger.error(r.raise_for_status()) + raise r.raise_for_status() + + if self.json: + return r.json() + + return r.content + + def delete(self, path, id): + api_url = self.api_url + + if api_url.endswith('/'): + url = '{0}{1}'.format(api_url, path[1:]) + else: + url = '{0}/{1}'.format(api_url, path) + + r = requests.get(url, headers=self.headers) + + if r.raise_for_status(): + logger.error(r.raise_for_status()) + raise r.raise_for_status() + + if self.json: + return r.json() + + return r.content + + def post(self, path, payload): + api_url = self.api_url + + if self.api_url.endswith('/'): + api_url = api_url[1:] + + if path.startswith('/'): + url ='{0}{1}'.format(api_url, path) + else: + url = '{0}/{1}'.format(api_url, path) + + print('{}'.format(url)) + r = requests.post(url, data=json.dumps(payload), headers=self.headers) + + #if r.raise_for_status(): + # logger.error(r.raise_for_status()) + # raise r.raise_for_status() + + if self.json: + return r.json() + + return r.content + + def addHeader(self, type, value): + self.headers[type] = value + + def addToken(self, value): + self.headers[RestService.API_KEY_HEADER] = value; diff --git a/grocy/__pycache__/__init__.cpython-37.pyc b/grocy/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..e096245 Binary files /dev/null and b/grocy/__pycache__/__init__.cpython-37.pyc differ diff --git a/grocy/__pycache__/cli.cpython-37.pyc b/grocy/__pycache__/cli.cpython-37.pyc new file mode 100644 index 0000000..34e2ce5 Binary files /dev/null and b/grocy/__pycache__/cli.cpython-37.pyc differ diff --git a/grocy/__pycache__/rest.cpython-37.pyc b/grocy/__pycache__/rest.cpython-37.pyc new file mode 100644 index 0000000..26c1ded Binary files /dev/null and b/grocy/__pycache__/rest.cpython-37.pyc differ diff --git a/grocy/cli.py b/grocy/cli.py new file mode 100644 index 0000000..919128e --- /dev/null +++ b/grocy/cli.py @@ -0,0 +1,75 @@ +import click +from grocy import RestService +from grocy.commands import Stock +from pkg_resources import iter_entry_points +import yaml +from sys import exit +from shutil import copy +from os import path, chmod, makedirs +from taskw import TaskWarriorShellout +import logging + +logger = logging.getLogger(__name__) + +APP_NAME = 'grocy-cli' +SAMPLE_CONFIG_FILE = 'sample.config.yml' +CONFIG_FILE = 'config.yml' +CONFIG_DIR = click.get_app_dir(APP_NAME) +PROJ_DIR = path.join(path.dirname(path.realpath(__file__))) + + +def __create_config_file(): + user_cfg_file = path.join(CONFIG_DIR, CONFIG_FILE) + sample_cfg_file = path.join(PROJ_DIR, '..', SAMPLE_CONFIG_FILE) + if not path.exists(CONFIG_DIR): + click.echo('Config {} director does not exist, create...'.format(CONFIG_DIR)) + makedirs(CONFIG_DIR) + + copy(sample_cfg_file, user_cfg_file) + click.echo('Copying sample config to {}'.format(sample_cfg_file, user_cfg_file)) + chmod(user_cfg_file, 0o664) + return user_cfg_file + + +@click.group() +def main(): + pass + + +def get_config_file(): + no_config_msg = 'A config file was not found' + no_config_msg+= ' and will be created under {}. Is that Ok?' + no_config_msg = no_config_msg.format(click.get_app_dir(APP_NAME)) + cfg_file = path.join(click.get_app_dir(APP_NAME), 'config.yml') + if not path.exists(cfg_file): + create_config_app_dir = click.confirm(no_config_msg) + if create_config_app_dir: + cfg_file = __create_config_file() + exit(0) + fd = open(cfg_file) + parse_cfg_file = yaml.safe_load(fd) + return parse_cfg_file + + +@main.command() +def stock(): + cfg = get_config_file() + + # Get logger + if 'logger' in cfg: + log_cfg = cfg['logger'] + else: + log_cfg = path.join(click.get_app_dir(APP_NAME), 'grocy.log') + log_level = 'DEBUG' if 'level' not in log_cfg else log_cfg['level'] + log_filename = 'log' if 'file_location' not in log_cfg else log_cfg['file_location'] + logging.basicConfig(level=log_level, filename=log_filename) + + # Validate token + if hasattr(cfg, 'token') or cfg['token'] is None: + click.echo('No token was found. Please add your token to the config file', err=True) + logger.error('No token was found. Please add your token') + exit(1) + + stock = Stock(**cfg) + stock_entries = stock.get_entries() + click.echo(stock_entries) diff --git a/grocy/commands/__init__.py b/grocy/commands/__init__.py new file mode 100644 index 0000000..20d7d07 --- /dev/null +++ b/grocy/commands/__init__.py @@ -0,0 +1 @@ +from grocy.commands.stock import Stock diff --git a/grocy/commands/__pycache__/__init__.cpython-37.pyc b/grocy/commands/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..4263d1c Binary files /dev/null and b/grocy/commands/__pycache__/__init__.cpython-37.pyc differ diff --git a/grocy/commands/__pycache__/stock.cpython-37.pyc b/grocy/commands/__pycache__/stock.cpython-37.pyc new file mode 100644 index 0000000..794d80c Binary files /dev/null and b/grocy/commands/__pycache__/stock.cpython-37.pyc differ diff --git a/grocy/commands/stock.py b/grocy/commands/stock.py new file mode 100644 index 0000000..161bd12 --- /dev/null +++ b/grocy/commands/stock.py @@ -0,0 +1,64 @@ +from grocy import RestService +from tabulate import tabulate +from os import path + +class Stock(object): + GET_CURRENT_STOCK = '/stock/get-current-stock' + GET_PRODUCT_BY_ID = '/get-object/products/{0}' + def __init__(self, **entries): + self.__dict__.update(entries) + self._init_rest_service() + #self._set_default_table_formats() + #if not hasattr('tablefmt', self): + # self.tablefmt = None + #if not hasattr('colalign', self): + # self.colalign = None + + + + def _set_default_table_formats(self): + if not hasattr('formats', self): + self.tablefmt = None + self.colalign = None + elif not hasattr('table', self.formats): + self.tableformat = None + elif not hasattr('col', self.formats): + self.colalign = None + + + + def _init_rest_service(self): + if self.api.startswith == '/': + self.api = self.api[1:] + if self.api.endswith == '/': + self.api = self.api[1:-1] + self.rest_service = RestService(self.api, json=True) + self.rest_service.addHeader('Content-Type', 'application/json') + self.rest_service.addToken(self.token) + + + def get_entries(self): + try: + get_current_stock = self.rest_service.get(Stock.GET_CURRENT_STOCK) + + # Get product names from ids and replace + product_ids = [entry['product_id'] for entry in get_current_stock] + table_entries = [] + try: + for index in range(len(product_ids)): + product_id = product_ids[index] + path = Stock.GET_PRODUCT_BY_ID.format(product_id) + product = self.rest_service.get(path) + get_current_stock[index]['product_id'] = product['name'] + table_entry = list(dict.values(get_current_stock[index])) + table_entries.append(table_entry) + + except Exception as e: + raise e + + # Generate stock overview table + table_headers = ['Product', 'Amount', 'Best Before Date', 'Amount Opened'] + return tabulate(table_entries, headers=table_headers) + + except Exception as e: + raise e diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..10d4352 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +click +colorama +pyyaml +requests +lockfile +tox +tabulate diff --git a/sample.config.yml b/sample.config.yml new file mode 100644 index 0000000..a1f22f5 --- /dev/null +++ b/sample.config.yml @@ -0,0 +1,9 @@ +logger: + level: DEBUG + file_location: '' +api: '' +token: '' +formats: + col: 'center' + table: 'simple' + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..75d1549 --- /dev/null +++ b/setup.py @@ -0,0 +1,42 @@ +import os +from setuptools import setup, find_packages +f = open('README.rst') + +long_description = f.read().strip() +long_description = long_description.split('readme', 1)[1] +f.close() + + +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() + + +setup( + name='grocy-cli', + version='0.0.1', + author='Aerex', + author_email='aerex@aerex.me', + description=('A plugin to create, delete, and modify tasks across various services'), + keywords='taskwarrior, grocy', + url='http://packages.python.org/an_example_pypi_project', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + install_requires=['Click', 'pyyaml', 'requests', 'taskw', 'lockfile', 'tox'], + long_description=read('README.rst'), + tests_require=[ + "pytest_mock", + "pytest", + "mock" + ], + classifiers=[ + "Development Status :: 3 - Alpha", + "Topic :: Utilities", + "License :: OSI Approved :: BSD License", + ], + entry_points=''' + [console_scripts] + grocy=grocy.cli:main + ''', + + )