Skip to content

Commit

Permalink
Merge 1f8faed into d465979
Browse files Browse the repository at this point in the history
  • Loading branch information
HazardDede committed Oct 6, 2022
2 parents d465979 + 1f8faed commit 7651c60
Show file tree
Hide file tree
Showing 13 changed files with 49 additions and 122 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ python:
- 3.5
- 3.6
- 3.7
before_install:
- pip install --upgrade pip
- pip install --upgrade importlib-metadata
install:
- pip install -r requirements.txt
script:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ docs: README.mdpp
lint:
flake8 $(SOURCE_PATH)
pylint $(SOURCE_PATH)
mypy --strict $(SOURCE_PATH)
mypy --strict --no-warn-unused-ignores $(SOURCE_PATH)

test:
pytest --verbose --color=yes -s \
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ if __name__ == '__main__':

## Changelog

**0.3.7**
* Adapts the vacation date parsing logic to the new ferien-api standard (#8)
* Fixes some linting errors using the latest linter / mypy (#8)

**0.3.6**
* Removes unnecessary print statement in utils.py

Expand Down
4 changes: 4 additions & 0 deletions README.mdpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ an asyncio enthusiast) and save a lot of time:

## Changelog

**0.3.7**
* Adapts the vacation date parsing logic to the new ferien-api standard (#8)
* Fixes some linting errors using the latest linter / mypy (#8)

**0.3.6**
* Removes unnecessary print statement in utils.py

Expand Down
7 changes: 6 additions & 1 deletion ferien/async_.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@


async def _make_api_request(api_url: APIUrl) -> APIResponse:
# pylint: disable=import-outside-toplevel
import aiohttp
import certifi # type: ignore
import ssl # type: ignore
sslcontext = ssl.create_default_context(cafile=certifi.where())
async with aiohttp.ClientSession() as session:
async with session.get(api_url) as resp:
async with session.get(api_url, ssl=sslcontext) as resp:
if resp.status != 200:
# pylint: disable=consider-using-f-string
raise RuntimeError(
"ferien-api.de failed with http code = '{}'\n"
"Error: {}".format(
Expand Down
2 changes: 2 additions & 0 deletions ferien/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@
API_STATE_YEAR_URL = 'https://ferien-api.de/api/v1/holidays/' \
'{state_code}/{year}'

API_TIMEOUT = 5

TZ_GERMANY = pytz.timezone("Europe/Berlin")
13 changes: 4 additions & 9 deletions ferien/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,7 @@ class Vacation:
@staticmethod
def _parse_date(candidate: str) -> datetime:
# Parse iso format
try:
# Date format before 2020-09-07
# Keep it if the Z remainder was introduced by accident
dt = datetime.strptime(candidate, '%Y-%m-%dT%H:%M')
except ValueError:
# Date format after 2020-09-07
dt = datetime.strptime(candidate, '%Y-%m-%dT%H:%MZ')
# All dates from the api are Europe/Berlin (CET/CEST)
dt = datetime.fromisoformat(candidate)
return TZ_GERMANY.localize(dt)

@classmethod
Expand All @@ -69,7 +62,9 @@ def from_dict(cls, dct: APIItem) -> 'Vacation':

return cls(
start=cls._parse_date(dct['start']),
end=cls._parse_date(dct['end']),
end=cls._parse_date(
dct['end']
).replace(hour=23, minute=59, second=59),
year=dct['year'],
state_code=dct['stateCode'],
name=dct.get('name', 'none'),
Expand Down
13 changes: 8 additions & 5 deletions ferien/sync_.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
"""Synchronous implementation using requests."""
import copy
from datetime import datetime
from typing import cast, List, Iterable, Optional, Callable

from .const import (ALL_STATE_CODES, API_ALL_URL,
API_STATE_URL, API_STATE_YEAR_URL)
from .const import (
ALL_STATE_CODES, API_ALL_URL, API_STATE_URL, API_STATE_YEAR_URL,
API_TIMEOUT
)
from .model import Vacation
from .types import APIResponse, APIUrl, StateCode
from .util import parse_state_code, parse_year, find_current, find_next


def _make_api_request(api_url: APIUrl) -> APIResponse:
import requests
resp = requests.get(api_url)
import requests # pylint: disable=import-outside-toplevel
resp = requests.get(api_url, timeout=API_TIMEOUT)
if resp.status_code != 200:
# pylint: disable=consider-using-f-string
raise RuntimeError("ferien-api.de failed with http code = '{}'\n"
"Error: {}".format(resp.status_code, resp.text))
return cast(APIResponse, resp.json())
Expand All @@ -24,7 +28,6 @@ def _convert_json(resp: APIResponse) -> List[Vacation]:

def state_codes() -> List[StateCode]:
"""Returns all known and valid state codes."""
import copy
return copy.copy(ALL_STATE_CODES)


Expand Down
8 changes: 7 additions & 1 deletion ferien/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def make_tz_aware_timestamp(dt: Optional[datetime]) -> datetime:
3. dt has no timezone: Assume that is german tz and set it."""
dt = dt or datetime.now(tz=TZ_GERMANY)
if not is_tz_aware_timestamp(dt):
dt = dt.replace(tzinfo=TZ_GERMANY)
dt = TZ_GERMANY.localize(dt)
return dt


Expand All @@ -27,6 +27,7 @@ def parse_state_code(candidate: Any) -> str:
when the candidate is not a valid state code."""
state_code = str(candidate)
if state_code not in ALL_STATE_CODES:
# pylint: disable=consider-using-f-string
raise ValueError("Argument state_code (current: '{}') is expected "
"to be one of {}".format(state_code, ALL_STATE_CODES))
return state_code
Expand All @@ -36,6 +37,7 @@ def parse_year(candidate: Any) -> int:
"""Parses the given candidate as a year literal. Raises a ValueError
when the candidate is not a valid year."""
if candidate is not None and not isinstance(candidate, int):
# pylint: disable=consider-using-f-string
raise TypeError("Argument year is expected to be an int, "
"but is {}".format(type(candidate)))
return cast(int, candidate)
Expand All @@ -53,17 +55,20 @@ def is_iterable_but_no_str(candidate: Any) -> bool:
def check_vac_list(vacs: Iterable[Vacation]) -> None:
"""Checks if the given list is an actual list of vacations."""
if not is_iterable_but_no_str(vacs):
# pylint: disable=consider-using-f-string
raise TypeError("Argument 'vacs' is expected to an iterable, "
"but is {}".format(type(vacs)))
for i, val in enumerate(vacs):
if not isinstance(val, Vacation):
# pylint: disable=consider-using-f-string
raise TypeError("Item {} of argument 'vacs' is expected to be of "
"type 'Vacation', but is {}".format(i, type(val)))


def check_datetime(dt: Any) -> None:
"""Checks if the argument dt is a valid datetime."""
if dt and not isinstance(dt, datetime):
# pylint: disable=consider-using-f-string
raise TypeError("Argument 'dt' is expected to be of type 'datetime', "
"but is {}".format(type(dt)))

Expand Down Expand Up @@ -91,6 +96,7 @@ def find_next(vacs: Iterable[Vacation],
check_datetime(dt)

dt = make_tz_aware_timestamp(dt)

res = sorted([i for i in vacs if i.start >= dt], key=lambda i: i.start)
if not res:
return None
Expand Down
97 changes: 0 additions & 97 deletions pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -51,96 +51,6 @@ unsafe-load-any-extension=no
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
confidence=

# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=print-statement,
parameter-unpacking,
unpacking-in-except,
old-raise-syntax,
backtick,
long-suffix,
old-ne-operator,
old-octal-literal,
import-star-module-level,
non-ascii-bytes-literal,
raw-checker-failed,
bad-inline-option,
locally-disabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
apply-builtin,
basestring-builtin,
buffer-builtin,
cmp-builtin,
coerce-builtin,
execfile-builtin,
file-builtin,
long-builtin,
raw_input-builtin,
reduce-builtin,
standarderror-builtin,
unicode-builtin,
xrange-builtin,
coerce-method,
delslice-method,
getslice-method,
setslice-method,
no-absolute-import,
old-division,
dict-iter-method,
dict-view-method,
next-method-called,
metaclass-assignment,
indexing-exception,
raising-string,
reload-builtin,
oct-method,
hex-method,
nonzero-method,
cmp-method,
input-builtin,
round-builtin,
intern-builtin,
unichr-builtin,
map-builtin-not-iterating,
zip-builtin-not-iterating,
range-builtin-not-iterating,
filter-builtin-not-iterating,
using-cmp-argument,
eq-without-hash,
div-method,
idiv-method,
rdiv-method,
exception-message-attribute,
invalid-str-codec,
sys-max-int,
bad-python3-import,
deprecated-string-function,
deprecated-str-translate-call,
deprecated-itertools-function,
deprecated-types-field,
next-method-defined,
dict-items-not-iterating,
dict-keys-not-iterating,
dict-values-not-iterating,
deprecated-operator-function,
deprecated-urllib-function,
xreadlines-attribute,
deprecated-sys-function,
exception-escape,
comprehension-escape,
import-outside-toplevel

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
Expand Down Expand Up @@ -316,13 +226,6 @@ max-line-length=100
# Maximum number of lines in a module.
max-module-lines=1000

# List of optional constructs for which whitespace checking is disabled. `dict-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
no-space-check=trailing-comma,
dict-separator

# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ pytest>=5.4.0
pytest-asyncio
pytest-cov
twine
types-pytz
types-requests
setuptools
MarkdownPP
8 changes: 4 additions & 4 deletions tests/test_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@

DUMMY_RESP = [
{
"start": "2017-01-29T23:00",
"end": "2017-01-31T23:00",
"start": "2017-01-29",
"end": "2017-01-31",
"year": 2017,
"stateCode": "HB",
"name": "winterferien",
"slug": "winterferien-2017-HB"
},
{
"start": "2017-04-09T22:00",
"end": "2017-04-22T22:00",
"start": "2017-04-09",
"end": "2017-04-22",
"year": 2017,
"stateCode": "HB",
"name": "osterferien",
Expand Down
8 changes: 4 additions & 4 deletions tests/test_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@

DUMMY_RESP = [
{
"start": "2017-01-29T23:00",
"end": "2017-01-31T23:00",
"start": "2017-01-29",
"end": "2017-01-31",
"year": 2017,
"stateCode": "HB",
"name": "winterferien",
"slug": "winterferien-2017-HB"
},
{
"start": "2017-04-09T22:00Z",
"end": "2017-04-22T22:00Z",
"start": "2017-04-09",
"end": "2017-04-22",
"year": 2017,
"stateCode": "HB",
"name": "osterferien",
Expand Down

0 comments on commit 7651c60

Please sign in to comment.