Skip to content
This repository has been archived by the owner on Apr 23, 2024. It is now read-only.

Commit

Permalink
Merge branch 'release/1.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
SebRut committed May 16, 2022
2 parents e457647 + ea29009 commit bf1fb03
Show file tree
Hide file tree
Showing 75 changed files with 1,982 additions and 2,325 deletions.
25 changes: 25 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,30 @@
# Changelog

## [v1.2.0](https://github.com/SebRut/pygrocy/tree/v1.2.0) (2022-05-16)

[Full Changelog](https://github.com/SebRut/pygrocy/compare/v1.1.0...v1.2.0)

**Fixed bugs:**

- Bug: Error if product group is not set [\#215](https://github.com/SebRut/pygrocy/issues/215)

**Closed issues:**

- Failed TC with Grocy 3.3.0 [\#229](https://github.com/SebRut/pygrocy/issues/229)
- Enable "allow\_subproduct\_substitution" on consume endpoint [\#220](https://github.com/SebRut/pygrocy/issues/220)

**Merged pull requests:**

- Fix parsing API responses with empty str or null values for optional fields [\#234](https://github.com/SebRut/pygrocy/pull/234) ([marcelvriend](https://github.com/marcelvriend))
- Add get task method [\#233](https://github.com/SebRut/pygrocy/pull/233) ([onegambler](https://github.com/onegambler))
- Add adaptive PeriodType [\#231](https://github.com/SebRut/pygrocy/pull/231) ([BenoitAnastay](https://github.com/BenoitAnastay))
- Updated tests to be compatible with Grocy 3.3.0 [\#230](https://github.com/SebRut/pygrocy/pull/230) ([andreheuer](https://github.com/andreheuer))
- Added support for hourly chores [\#228](https://github.com/SebRut/pygrocy/pull/228) ([andreheuer](https://github.com/andreheuer))
- Delete generic now uses DELETE verb, instead of GET [\#227](https://github.com/SebRut/pygrocy/pull/227) ([onegambler](https://github.com/onegambler))
- Update pytz requirement from ~=2021.1 to \>=2021.1,\<2023.0 [\#225](https://github.com/SebRut/pygrocy/pull/225) ([dependabot[bot]](https://github.com/apps/dependabot))
- Update responses requirement from ~=0.18.0 to ~=0.19.0 [\#223](https://github.com/SebRut/pygrocy/pull/223) ([dependabot[bot]](https://github.com/apps/dependabot))
- Add subproduct substution to consume\_product [\#221](https://github.com/SebRut/pygrocy/pull/221) ([kingo55](https://github.com/kingo55))

## [v1.1.0](https://github.com/SebRut/pygrocy/tree/v1.1.0) (2022-03-05)

[Full Changelog](https://github.com/SebRut/pygrocy/compare/v1.0.0...v1.1.0)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -13,7 +13,7 @@
`pip install pygrocy`

## Usage
Import the package:
Import the package:
```python
from pygrocy import Grocy
```
Expand All @@ -38,4 +38,4 @@ for entry in grocy.stock():
If you need help using pygrocy check the [discussions](https://github.com/SebRut/pygrocy/issues) section. Feel free to create an issue for feature requests, bugs and errors in the library.

## Development testing
You need tox and Python 3.6/8/9 to run the tests. Navigate to the root dir of `pygrocy` and execute `tox` to run the tests.
You need tox and Python 3.8/9/10 to run the tests. Navigate to the root dir of `pygrocy` and execute `tox` to run the tests.
2 changes: 2 additions & 0 deletions pygrocy/data_models/chore.py
Expand Up @@ -18,6 +18,8 @@ class PeriodType(str, Enum):
WEEKLY = "weekly"
MONTHLY = "monthly"
YEARLY = "yearly"
ADAPTIVE = "adaptive"
HOURLY = "hourly"


class AssignmentType(str, Enum):
Expand Down
7 changes: 6 additions & 1 deletion pygrocy/grocy.py
Expand Up @@ -148,9 +148,10 @@ def consume_product(
amount: float = 1,
spoiled: bool = False,
transaction_type: TransactionType = TransactionType.CONSUME,
allow_subproduct_substitution: bool = False,
):
return self._api_client.consume_product(
product_id, amount, spoiled, transaction_type
product_id, amount, spoiled, transaction_type, allow_subproduct_substitution
)

def inventory_product(
Expand Down Expand Up @@ -284,6 +285,10 @@ def tasks(self) -> List[Task]:
raw_tasks = self._api_client.get_tasks()
return [Task(task) for task in raw_tasks]

def task(self, task_id: int) -> Task:
resp = self._api_client.get_task(task_id)
return Task(resp)

def complete_task(self, task_id, done_time: datetime = datetime.now()):
return self._api_client.complete_task(task_id, done_time)

Expand Down
53 changes: 41 additions & 12 deletions pygrocy/grocy_api_client.py
Expand Up @@ -3,11 +3,11 @@
import logging
from datetime import datetime
from enum import Enum
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Any
from urllib.parse import urljoin

import requests
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, validator
from pydantic.schema import date

from pygrocy import EntityType
Expand All @@ -21,6 +21,17 @@
_LOGGER.setLevel(logging.INFO)


def _field_not_empty_validator(field_name: str):
"""Reusable Pydantic field pre-validator to convert empty str to None."""
return validator(field_name, allow_reuse=True, pre=True)(_none_if_empty_str)


def _none_if_empty_str(value: Any):
if isinstance(value, str) and value == "":
return None
return value


class ShoppingListItem(BaseModel):
id: int
product_id: Optional[int] = None
Expand Down Expand Up @@ -76,7 +87,7 @@ class ProductData(BaseModel):
id: int
name: str
description: Optional[str] = None
location_id: int
location_id: Optional[int] = None
product_group_id: Optional[int] = None
qu_id_stock: int
qu_id_purchase: int
Expand All @@ -87,6 +98,9 @@ class ProductData(BaseModel):
min_stock_amount: Optional[float]
default_best_before_days: int

location_id_validator = _field_not_empty_validator("location_id")
product_group_id_validator = _field_not_empty_validator("product_group_id")


class ChoreData(BaseModel):
id: int
Expand All @@ -102,6 +116,10 @@ class ChoreData(BaseModel):
next_execution_assigned_to_user_id: Optional[int] = None
userfields: Optional[Dict]

next_execution_assigned_to_user_id_validator = _field_not_empty_validator(
"next_execution_assigned_to_user_id"
)


class UserDto(BaseModel):
id: int
Expand All @@ -113,8 +131,8 @@ class UserDto(BaseModel):

class CurrentChoreResponse(BaseModel):
chore_id: int
last_tracked_time: Optional[datetime]
next_estimated_execution_time: datetime
last_tracked_time: Optional[datetime] = None
next_estimated_execution_time: Optional[datetime] = None


class CurrentStockResponse(BaseModel):
Expand Down Expand Up @@ -160,7 +178,7 @@ class ProductDetailsResponse(BaseModel):
class ChoreDetailsResponse(BaseModel):
chore: ChoreData
last_tracked: Optional[datetime] = None
next_estimated_execution_time: datetime
next_estimated_execution_time: Optional[datetime] = None
track_count: int = 0
next_execution_assigned_user: Optional[UserDto] = None
last_done_by: Optional[UserDto] = None
Expand All @@ -184,7 +202,7 @@ class TaskResponse(BaseModel):
id: int
name: str
description: Optional[str] = None
due_date: date
due_date: date = None
done: int
done_timestamp: Optional[datetime] = None
category_id: Optional[int] = None
Expand All @@ -193,11 +211,14 @@ class TaskResponse(BaseModel):
assigned_to_user: Optional[UserDto] = None
userfields: Optional[Dict] = None

category_id_validator = _field_not_empty_validator("category_id")
assigned_to_user_id_validator = _field_not_empty_validator("assigned_to_user_id")


class CurrentBatteryResponse(BaseModel):
id: int
last_tracked_time: Optional[datetime] = None
next_estimated_charge_time: datetime
next_estimated_charge_time: Optional[datetime] = None


class BatteryData(BaseModel):
Expand All @@ -214,7 +235,7 @@ class BatteryDetailsResponse(BaseModel):
battery: BatteryData
charge_cycles_count: int
last_charged: Optional[datetime] = None
next_estimated_charge_time: datetime
next_estimated_charge_time: Optional[datetime] = None


class MealPlanSectionResponse(BaseModel):
Expand All @@ -223,6 +244,8 @@ class MealPlanSectionResponse(BaseModel):
sort_number: Optional[int] = None
row_created_timestamp: datetime

sort_number_validator = _field_not_empty_validator("sort_number")


class StockLogResponse(BaseModel):
id: int
Expand Down Expand Up @@ -323,7 +346,7 @@ def _do_put_request(self, end_url: str, data):

def _do_delete_request(self, end_url: str):
req_url = urljoin(self._base_url, end_url)
resp = requests.get(req_url, verify=self._verify_ssl, headers=self._headers)
resp = requests.delete(req_url, verify=self._verify_ssl, headers=self._headers)

_LOGGER.debug("-->\tDELETE /%s", end_url)
_LOGGER.debug("<--\t%d for /%s", resp.status_code, end_url)
Expand Down Expand Up @@ -405,11 +428,13 @@ def consume_product(
amount: float = 1,
spoiled: bool = False,
transaction_type: TransactionType = TransactionType.CONSUME,
allow_subproduct_substitution: bool = False,
):
data = {
"amount": amount,
"spoiled": spoiled,
"transaction_type": transaction_type.value,
"allow_subproduct_substitution": allow_subproduct_substitution,
}

self._do_post_request(f"stock/products/{product_id}/consume", data)
Expand Down Expand Up @@ -547,7 +572,6 @@ def add_product_to_shopping_list(
}
if quantity_unit_id:
data["qu_id"] = quantity_unit_id
print(data)
self._do_post_request("stock/shoppinglist/add-product", data)

def clear_shopping_list(self, shopping_list_id: int = 1):
Expand Down Expand Up @@ -597,6 +621,11 @@ def get_tasks(self) -> List[TaskResponse]:
parsed_json = self._do_get_request("tasks")
return [TaskResponse(**data) for data in parsed_json]

def get_task(self, task_id: int) -> TaskResponse:
url = f"objects/tasks/{task_id}"
parsed_json = self._do_get_request(url)
return TaskResponse(**parsed_json)

def complete_task(self, task_id: int, done_time: datetime = datetime.now()):
url = f"tasks/{task_id}/complete"

Expand All @@ -615,7 +644,7 @@ def get_recipe(self, object_id: int) -> RecipeDetailsResponse:
return RecipeDetailsResponse(**parsed_json)

def get_batteries(self) -> List[CurrentBatteryResponse]:
parsed_json = self._do_get_request(f"batteries")
parsed_json = self._do_get_request("batteries")
if parsed_json:
return [CurrentBatteryResponse(**data) for data in parsed_json]

Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.txt
@@ -1,4 +1,4 @@
responses~=0.18.0
responses~=0.19.0
pre-commit
isort
pytest
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="pygrocy",
version="1.1.0",
version="1.2.0",
author="Sebastian Rutofski",
author_email="kontakt@sebastian-rutofski.de",
description="",
Expand All @@ -17,7 +17,7 @@
install_requires=[
"requests",
"iso8601>=0.1.16,<1.1.0",
"pytz~=2021.1",
"pytz>=2021.1,<2023.0",
"tzlocal>=2.1,<5.0",
"deprecation~=2.1.0",
"pydantic>=1.8.2,<1.10.0",
Expand Down
18 changes: 7 additions & 11 deletions test/cassettes/test_battery/TestBattery.test_charge_battery.yaml
@@ -1,6 +1,6 @@
interactions:
- request:
body: '{"tracked_time": "2021-08-21T19:42:48.452104+00:00"}'
body: '{"tracked_time": "2022-04-22T08:40:12.632543+00:00"}'
headers:
Accept-Encoding:
- gzip, deflate
Expand All @@ -11,17 +11,15 @@ interactions:
Content-Type:
- application/json
User-Agent:
- python-requests/2.26.0
- python-requests/2.27.1
accept:
- application/json
method: POST
uri: https://localhost/api/batteries/1/charge
response:
body:
string: !!binary |
H4sIAAAAAAAEA3WMQQqAIBQFrxJvraDWoryMmLqIUsN+RER3L6ptu/dgZg4MHlrWDL0lCmU3z2eg
Yt0YvKEhBmgooSQXLVeykp1ulG5aMJS8GVeCpQ9cyMb5l16Tz+mOCYZ3PvHPSes0nRfZ1FG0jgAA
AA==
string: '{"id":"11","battery_id":"1","tracked_time":"2022-04-22 09:40:13","undone":"0","undone_timestamp":null,"row_created_timestamp":"2022-04-22
08:40:13"}'
headers:
Access-Control-Allow-Headers:
- '*'
Expand All @@ -31,18 +29,16 @@ interactions:
- '*'
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Sat, 21 Aug 2021 19:42:48 GMT
- Fri, 22 Apr 2022 08:40:13 GMT
Server:
- nginx/1.20.1
- nginx/1.20.2
Transfer-Encoding:
- chunked
X-Powered-By:
- PHP/8.0.9
- PHP/8.0.13
status:
code: 200
message: OK
Expand Down
Expand Up @@ -7,18 +7,18 @@ interactions:
Connection:
- keep-alive
User-Agent:
- python-requests/2.26.0
- python-requests/2.27.1
accept:
- application/json
method: GET
uri: https://localhost/api/batteries
response:
body:
string: !!binary |
H4sIAAAAAAAEA6XRUQuCMBAH8K8i9+xgd3Pp9lUixtJRkvWgC4rou7dKk4FpEOzlD3f3427rG9QV
aExha7137dX0sbGdN7615cFVxtdHBxqIEzIuGfKE55pQyxxSOLmLN64LNdaH2nJv2537tCilGBIT
mJDQUoUH9/SNUoyG+AXlzwkkE1Q6Qy2Kf1ARoyHOoWHTn9HXcYjxYmwZNs1iNMRZVI0Tls7bo5iP
LQO6itEQJ9DTuWmWjKkv3DwAZYKjyzkCAAA=
string: '[{"id":"1","battery_id":"1","last_tracked_time":"2021-10-24 23:59:59","next_estimated_charge_time":"2022-04-22
23:59:59"},{"id":"2","battery_id":"2","last_tracked_time":"2022-03-03 09:38:06","next_estimated_charge_time":"2999-12-31
23:59:59"},{"id":"3","battery_id":"3","last_tracked_time":"2022-02-16 09:38:06","next_estimated_charge_time":"2022-04-17
09:38:06"},{"id":"4","battery_id":"4","last_tracked_time":"2022-02-25 09:38:06","next_estimated_charge_time":"2022-04-26
09:38:06"}]'
headers:
Access-Control-Allow-Headers:
- '*'
Expand All @@ -28,18 +28,16 @@ interactions:
- '*'
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Sat, 21 Aug 2021 19:42:48 GMT
- Fri, 22 Apr 2022 08:40:13 GMT
Server:
- nginx/1.20.1
- nginx/1.20.2
Transfer-Encoding:
- chunked
X-Powered-By:
- PHP/8.0.9
- PHP/8.0.13
status:
code: 200
message: OK
Expand Down

0 comments on commit bf1fb03

Please sign in to comment.