From a5f290d0e7739eade6e2c6f9923a2e71297cdd1b Mon Sep 17 00:00:00 2001 From: Aerex Date: Sun, 3 Mar 2019 19:29:38 -0600 Subject: [PATCH] feat(added command to retrieve stock overview): --- .gitignore | 2 + README.rst | 1 + grocy/__init__.py | 78 ++++++++++++++++++ grocy/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1993 bytes grocy/__pycache__/cli.cpython-37.pyc | Bin 0 -> 2404 bytes grocy/__pycache__/rest.cpython-37.pyc | Bin 0 -> 1770 bytes grocy/cli.py | 75 +++++++++++++++++ grocy/commands/__init__.py | 1 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 210 bytes .../commands/__pycache__/stock.cpython-37.pyc | Bin 0 -> 2145 bytes grocy/commands/stock.py | 64 ++++++++++++++ requirements.txt | 7 ++ sample.config.yml | 9 ++ setup.py | 42 ++++++++++ 14 files changed, 279 insertions(+) create mode 100644 .gitignore create mode 100644 README.rst create mode 100644 grocy/__init__.py create mode 100644 grocy/__pycache__/__init__.cpython-37.pyc create mode 100644 grocy/__pycache__/cli.cpython-37.pyc create mode 100644 grocy/__pycache__/rest.cpython-37.pyc create mode 100644 grocy/cli.py create mode 100644 grocy/commands/__init__.py create mode 100644 grocy/commands/__pycache__/__init__.cpython-37.pyc create mode 100644 grocy/commands/__pycache__/stock.cpython-37.pyc create mode 100644 grocy/commands/stock.py create mode 100644 requirements.txt create mode 100644 sample.config.yml create mode 100644 setup.py 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 0000000000000000000000000000000000000000..e09624579c4a3554086576bfd213e8ac675641db GIT binary patch literal 1993 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 aOXB183My}L*yQG?l;)(`F@oF)au5Jy$~W2o literal 0 HcmV?d00001 diff --git a/grocy/commands/__pycache__/stock.cpython-37.pyc b/grocy/commands/__pycache__/stock.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..794d80c30d75247df52c7a8e4c1caac0fb44b496 GIT binary patch literal 2145 zcmZ?b<>g{vU|_geT@&+xgMr~Ohy%kc3=9ko3=9m#77PpwDGVu$ISjdsQH+crHd78$ zE>jc}n9ZESoXZl$!pPvxkiwF}+QN{+n#$VD9L1Kx7R;c@{t{%KCgUycpw!}$;MAhB z%;Z!}##+BO<_%8Yhj3DOJPspXkmzAPvK1AYGH`tNZ}4<(B!$r8eEc}oLwcMUksx4(^E@y zlS_+=Qu9i5!6H=>V6pt9tkmQZ{eq(Wl+xsqV*P4^+GIwkJ3!80W?*3O1_grz0|P@1 zLl(mV#u~h?{|d;g_3!Mt*Lpeqw4-YK6W_esXDU zYF&ImS2>cSWn-+-#Nxz~lAm<(5QoBf(-KQ_O5(xl;~^S~g+aamnZv-y#mL6U!N>!~RSHnwFl4cV(h@k-IQ)Jw>c0fV$IJi!|NjrsWW2?aRh*w! z#p9fxR{~16A(aKGRRW0x1v#0?i6xo&dHNuEO^#d4i3ORrxQa^>i%N>iGfOgVair#@ zfLTSLG<=H(IR)P0OiW4fNKH&hExN^#n358bpPia_i?swqVGr>lP*RdZ1T7Dv03!$s zF!C{0NkD@Zoam74fd?rld4tLsaFBjtU|^^`1`4bNj3rE2%nKQ78B-Wjm|7TW7+n~e z8Ecu+8ETm;(<%~5SV~xHn3@@z8C@7+18P}nm}*!g8ERQen6lUwu-C9IWUK`RASgq! zEo7`^uVGJNmSm`51gT14v0*6TDB)bd1>rAbY-X(Gs9|2fUBdwi@Is?-hH!>FH5P^v zo@}n7b&L$PoQw=r95tLxjNzaXj-i&bjx&NGk|B>Nf&o<0fJ&KkmRhb7-Wui_t`yE* zreFrB?l%NCevV@JHN)-}0>8U00 zAZ?kc#d38UB?bMs5{N~#2$Qj1FzoKn;B zi&7O_5=&C6cp)+h{spOdsVSO_x7ahlsi|0#uSg1%L7CH2ONv-QEJ1hIka*|NpdeSj zkoe#be`oJotVM}=>8ZDvb5iqefu#e2{9QtwL*ktx<2_w&u|ab3Ef$b}Zn1z0)LU$2 zi8-aI#kbfJ3kp*6Qf_g&RwSnufHFW4sOZw<1{V`if*@}~%lLS386U-s2*ctiUPw^^ z4TxK;nRzLx6<}W%f#Ty9J5({W3<8H(R@e0*VPVh%*6NEK8pf-EcI1+j!c1Spdh8G%?}w;>2n0s%({C`yV!JPt+{ zMixd6ZVqk^F0hQBCi^W`aNPnfFu=ijiz&Yt5-@C#N)oIR$@gFzK*rtTuz|SK4pe#- NvoSC*@Gye>2mqIOB(VSh literal 0 HcmV?d00001 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 + ''', + + )