Skip to content

Commit

Permalink
Merge pull request #42 from sudiptob2/refactor/41-strict-flake8
Browse files Browse the repository at this point in the history
Refactor/41 strict flake8
  • Loading branch information
sudiptob2 authored Oct 6, 2022
2 parents 2be4b17 + be7182a commit 1690263
Show file tree
Hide file tree
Showing 12 changed files with 444 additions and 78 deletions.
40 changes: 40 additions & 0 deletions .github/check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env sh

set -o errexit
set -o nounset

pyclean() {
# Cleaning cache:
find . |
grep -E '(__pycache__|\.hypothesis|\.perm|\.cache|\.static|\.py[cod]$)' |
xargs rm -rf
}

run_checks() {
echo '[Check Started]'
set -x # we want to print commands during the CI process.

# Running linting for all python files in the project:
python -m flake8

# Running type checking, see https://github.com/typeddjango/django-stubs
python -m mypy leeteasy tests

# Running tests:
python -m pytest --cov

# Checking dependencies status:
python -m pip check

set +x
echo '[checks completed]'
}

# Remove any cache before the script:
pyclean

# Clean everything up:
trap pyclean EXIT INT TERM

# Run the CI process:
run_checks
7 changes: 7 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ notify-py = "*"
flake8 = "*"
pytest = "*"
pytest-mock = "*"
mypy = "*"
darglint = "*"
flake8-docstrings = "*"
wemake-python-styleguide = "*"
flake8-pytest-style = "*"
flake8-bandit = "*"
pytest-cov = "*"

[requires]
python_version = "3.8"
281 changes: 275 additions & 6 deletions Pipfile.lock

Large diffs are not rendered by default.

27 changes: 15 additions & 12 deletions leeteasy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import click
import schedule

from leeteasy.constant import Constant
from leeteasy.services.notification_service import Notifier
from leeteasy.utils.validatiors import TimeValidator

Expand All @@ -14,13 +15,13 @@
'-d',
'--difficulty',
type=click.Choice(['Medium', 'Hard'], case_sensitive=False),
help='Additional problem difficulty for notification.'
help='Additional problem difficulty for notification.',
)
@click.option(
"--sleep_duration",
default=3600,
type=click.IntRange(1, 3600, clamp=True),
help='Sleep duration in seconds.'
'--sleep_duration',
default=Constant.DEFAULT_SLEEP,
type=click.IntRange(1, Constant.DEFAULT_SLEEP, clamp=True),
help='Sleep duration in seconds.',
)
@click.argument('time')
def execute_start(time, difficulty, sleep_duration) -> None:
Expand All @@ -33,30 +34,32 @@ def execute_start(time, difficulty, sleep_duration) -> None:
Notifier.target_difficulty.append(difficulty)
schedule.every().day.at(time).do(Notifier.notify)

while True:
while True: # NOQA: WPS457
schedule.run_pending()
clock.sleep(sleep_duration)


@click.command('stop')
def execute_stop() -> None:
"""Stops leeteasy process"""
"""Stop leeteasy process."""
os.system('pkill -9 -f leeteasy')


@click.group('leeteasy')
def execute_root():
"""v0.4.0 | supported version strings: 0.7.2"""
pass
"""Group child command."""


execute_root.add_command(execute_start)
execute_root.add_command(execute_stop)

if __name__ == '__main__':
if platform != 'win32':
import pwd
import pwd # NOQA: WPS433

os.environ[
'DBUS_SESSION_BUS_ADDRESS'] = f'unix:path=/run/user/{pwd.getpwuid(os.getuid()).pw_uid}/bus' # NOQA: E501
bus_addr = 'unix:path=/run/user/{0}/bus'.format(
pwd.getpwuid(os.getuid()).pw_uid,
)

os.environ['DBUS_SESSION_BUS_ADDRESS'] = bus_addr
execute_root()
3 changes: 3 additions & 0 deletions leeteasy/constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ class Constant:

# http call retries
HTTP_CALL_RETRIES = 3

# default sleep duration
DEFAULT_SLEEP = 3600
33 changes: 19 additions & 14 deletions leeteasy/models/challenge.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,44 @@
from typing import List
from typing import Dict, List, Optional


class Challenge:
"""Singleton Model class for daily challenge."""

title: str = ''
raw_tags: List[dict] = None
raw_tags: List[Dict[str, str]] = []
ac_rate: float = 0
difficulty: str = None
question_id: int = None
difficulty: str = ''
question_id: int = 0
title_slug: str = ''
date: str = None
date: str = ''

def __new__(cls):
if not hasattr(cls, 'instance'):
cls.instance = super(Challenge, cls).__new__(cls)
"""Override default class creation logic."""
if not hasattr(cls, 'instance'): # NOQA : WPS421
cls.instance = super(Challenge, cls).__new__(cls) # NOQA: WPS608
return cls.instance

@property
def problem_link(self) -> str:
"""Returns the link of the problem."""
"""Return the link of the problem."""
return 'https://leetcode.com/problems/{0}/'.format(
self.title_slug,
)

@property
def tags(self) -> List[str]:
"""Returns the link of the problem."""
def tags(self) -> List[Optional[str]]:
"""Return the link of the problem."""
tags = []
for tag in self.raw_tags:
tags.append(tag.get('name'))
return tags

def __str__(self):
"""Returns the string rep of the class."""
return f"Title: {self.title}\nAcceptance Rate: {self.ac_rate}" \
f"\nDifficulty: {self.difficulty}\n" + \
f"id: {self.question_id}\nTags: {self.tags}"
"""Return the string rep of the class."""
return 'Title: {0}\nAcceptance: {1}\nDifficulty: {2}\nID: {3}\nTags: {4}\n'.format(
self.title,
self.ac_rate,
self.difficulty,
self.question_id,
self.tags,
)
13 changes: 8 additions & 5 deletions leeteasy/services/notification_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from notifypy import Notify

from leeteasy.models.challenge import Challenge
from leeteasy.services.request_handler import RequestHandler
from leeteasy.services.request_parser import RequestParser

Expand All @@ -11,11 +12,11 @@ class Notifier:

target_difficulty = ['Easy']
app_name = 'LeetEasy'
challenge = None
challenge: Challenge

@classmethod
def prepare_notification(cls):
"""Prepares notification msg and triggers notification."""
"""Prepare notification msg and triggers notification."""
challenge_info = RequestHandler.get_challenge_info()
cls.challenge = RequestParser.parse(challenge_info)
if cls.challenge.difficulty in cls.target_difficulty:
Expand All @@ -27,14 +28,16 @@ def prepare_notification(cls):
@classmethod
def notify(cls):
"""Send desktop notification."""
app_name_with_subtitle = f'{cls.app_name} - Easy Problem Notification'
app_name_with_subtitle = '{0} - Easy Problem Notification'.format(cls.app_name)
icon_path = Path(__file__).parent.parent / 'assets/leetcoin.png'
notification = Notify(
default_notification_application_name=app_name_with_subtitle,
default_notification_icon=icon_path,
)
notification.message = cls.prepare_notification()
notification.title = f'{cls.app_name} - {cls.challenge.difficulty} ' \
f'Problem Alert \U0001F514'
notification.title = '{0} - {1} Problem Alert \U0001F514'.format(
cls.app_name,
cls.challenge.difficulty,
)
if notification.message:
notification.send()
28 changes: 11 additions & 17 deletions leeteasy/services/request_handler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import time
from typing import Union
from typing import Dict

import requests

Expand All @@ -9,23 +9,17 @@
class RequestHandler:
"""Provides services for requesting leetcode API."""

@classmethod
def get_challenge_info(cls) -> Union[dict, None]:
"""Gets daily challenge info from leetcode API."""
url = Constant.LEETCODE_API_ENDPOINT
query = Constant.DAILY_CODING_CHALLENGE_QUERY
max_retries = Constant.HTTP_CALL_RETRIES # Change HTTP_CALL_RETRIES for more retries
url = Constant.LEETCODE_API_ENDPOINT
query = Constant.DAILY_CODING_CHALLENGE_QUERY
max_retries = Constant.HTTP_CALL_RETRIES

for i in range(max_retries):
@classmethod
def get_challenge_info(cls) -> Dict:
"""Get daily challenge info from leetcode API."""
for iteration in range(cls.max_retries):
try:
response = requests.post(url, json={'query': query})
response = requests.post(cls.url, json={'query': cls.query})
return response.json().get('data').get('activeDailyCodingChallengeQuestion')
except Exception:
"""
On first hit sleep 10 minutes.
On second hit sleep 20 minutes.
On third hit sleep 30 minutes.
"""
time.sleep(((i+1)*10)*60)
raise SystemExit(f"""Connection to leetcode failed and max retries have been exceeded.
Total attempts made: {max_retries}. Try again""")
time.sleep(((iteration + 1) * 10) * 60)
raise SystemExit('Could not connect to the leetcode server.')
19 changes: 11 additions & 8 deletions leeteasy/services/request_parser.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
from typing import Dict

from leeteasy.models.challenge import Challenge


class RequestParser:
"""Parse responses of leetcode API."""

@classmethod
def parse(cls, challenge_info: dict) -> Challenge:
def parse(cls, challenge_info: Dict) -> Challenge:
"""Parse API data ans update challenge model."""
return RequestParser._parse_challenge_info(challenge_info)
return cls._parse_challenge_info(challenge_info)

@classmethod
def _parse_challenge_info(cls, challenge_info) -> Challenge:
"""Parse and update challenge model."""
question = challenge_info.get('question')
challenge = Challenge()
challenge.title = challenge_info.get('question').get('title')
challenge.ac_rate = challenge_info.get('question').get('acRate')
challenge.difficulty = challenge_info.get('question').get('difficulty')
challenge.question_id = challenge_info.get('question').get('frontendQuestionId')
challenge.title = question.get('title')
challenge.ac_rate = question.get('acRate')
challenge.difficulty = question.get('difficulty')
challenge.question_id = question.get('frontendQuestionId')
challenge.date = challenge_info.get('date')
challenge.title_slug = challenge_info.get('question').get('titleSlug')
challenge.raw_tags = challenge_info.get('question').get('topicTags')
challenge.title_slug = question.get('titleSlug')
challenge.raw_tags = question.get('topicTags')
return challenge
2 changes: 1 addition & 1 deletion leeteasy/utils/validatiors.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class TimeValidator:

@classmethod
def validate(cls, time: str):
"""Validates the given string is in valid time format."""
"""Validate the given string is in valid time format."""
try:
return datetime.strptime(time, cls.time_format).time()
except ValueError:
Expand Down
54 changes: 54 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,61 @@ exclude =
docs.*

[flake8]
# flake8 configuration:
# https://flake8.pycqa.org/en/latest/user/configuration.html
max-line-length = 99
max-complexity = 6
format = wemake
show-source = True
statistics = True
doctests = True
enable-extensions = G

# Excluding some directories:
exclude = .git,__pycache__,.venv,.eggs,*.egg, tests

# darglint configuration:
# https://github.com/terrencepreilly/darglint
strictness = long
docstring-style = numpy

# Disable some checks:
ignore =
D100,
S605,
S607,
WPS115,
WPS306,
D104,
WPS201, # Found module with too many imports
WPS229, # Found too long try body length



[mypy]
# Mypy configuration:
# https://mypy.readthedocs.io/en/latest/config_file.html
allow_redefinition = False
check_untyped_defs = True
disallow_untyped_decorators = True
disallow_any_explicit = True
;disallow_any_generics = True
;disallow_untyped_calls = True
ignore_errors = False
ignore_missing_imports = True
implicit_reexport = False
local_partial_types = True
# strict_optional = True
strict_equality = True
no_implicit_optional = True
warn_unused_ignores = True
warn_redundant_casts = True
warn_unused_configs = True
warn_unreachable = True
warn_no_return = True
show_error_codes = True


[options.entry_points]
console_scripts =
leeteasy = leeteasy.__main__:main
15 changes: 0 additions & 15 deletions tests/test_services/test_request_handler.py

This file was deleted.

0 comments on commit 1690263

Please sign in to comment.