From 2f924e20a498ec060637ab8c0e93e16d97e7dd5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Alloza=20Gonz=C3=A1lez?= Date: Sat, 6 Nov 2021 13:24:29 +0100 Subject: [PATCH] docs: document codebase and add CI (#17) * docs: document entire codebase * feat: add lint action * chore: improve CI * chore: fix ci * chore: apply black patch * chore: run only on pull request * chore: bump version --- .github/workflows/linter.yml | 38 ++ .pylintrc | 590 ++++++++++++++++++++++++++++++++ app/__init__.py | 2 + app/api/__init__.py | 2 + app/api/cron.py | 16 +- app/api/meals.py | 24 +- app/api/utils.py | 2 + app/core/aws.py | 7 +- app/core/config.py | 2 + app/core/notion.py | 4 + app/core/todoist.py | 3 + app/cron/__init__.py | 2 + app/cron/backup_database.py | 3 + app/cron/base.py | 3 +- app/cron/check_frozen_meals.py | 3 + app/cron/update_notion_meals.py | 3 + app/crud/__init__.py | 2 + app/crud/base.py | 18 +- app/crud/crud_meal.py | 47 ++- app/db/base.py | 2 + app/db/base_class.py | 2 + app/db/utils.py | 2 + app/deps/__init__.py | 1 + app/deps/security.py | 3 + app/main.py | 3 + app/models/__init__.py | 2 + app/models/meal.py | 4 + app/schemas/meal.py | 2 + poetry.lock | 134 +++++++- pyproject.toml | 3 +- scripts/ensure-database.py | 3 + scripts/import.py | 3 + scripts/load-docs.py | 5 + scripts/manage.py | 2 + 34 files changed, 907 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/linter.yml create mode 100644 .pylintrc diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 0000000..0b48af7 --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,38 @@ +name: Linter + +on: + pull_request: + release: + +jobs: + linter: + strategy: + fail-fast: false + matrix: + python-version: [3.7, 3.8, 3.9] + poetry-version: [1.1.11] + os: [ubuntu-18.04] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Setup poetry + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: ${{ matrix.poetry-version }} + + - name: Install + run: poetry install + + - name: Run black + run: poetry run black --check app + + - name: Check imports + run: poetry run isort --check app + + - name: Lint with pylint + run: poetry run pylint app diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..373f530 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,590 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist=pydantic,dialogflow + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS,_version.py + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# 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 + +# 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 +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + db, + id, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# 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 + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,Session + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules=dialogflow_v2.types + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/app/__init__.py b/app/__init__.py index e3fd173..2a908de 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +1,5 @@ +"""The meal-planner app.""" + from .utils.misc import get_version __version__ = get_version() diff --git a/app/api/__init__.py b/app/api/__init__.py index 531223c..ed1ad3d 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -1,3 +1,5 @@ +"""API module. Contains the API router.""" + from fastapi.routing import APIRouter from .cron import router as cron_router diff --git a/app/api/cron.py b/app/api/cron.py index 5fe3d71..ef64260 100644 --- a/app/api/cron.py +++ b/app/api/cron.py @@ -1,3 +1,5 @@ +"""Cron related API endpoints.""" + from enum import Enum from fastapi import APIRouter, BackgroundTasks, Depends @@ -14,15 +16,17 @@ class ValidCron(Enum): - backup_database = "backup-database" - check_frozen_meals = "check-frozen-meals" - update_notion_meals = "update-notion-meals" + """Defined crons that can be launched from the API.""" + + BACKUP_DATABASE = "backup-database" + CHECK_FROZEN_MEALS = "check-frozen-meals" + UPDATE_NOTION_MEALS = "update-notion-meals" CRON_MAP = { - ValidCron.backup_database: backup_database, - ValidCron.check_frozen_meals: check_frozen_meals, - ValidCron.update_notion_meals: update_notion_meals, + ValidCron.BACKUP_DATABASE: backup_database, + ValidCron.CHECK_FROZEN_MEALS: check_frozen_meals, + ValidCron.UPDATE_NOTION_MEALS: update_notion_meals, } diff --git a/app/api/meals.py b/app/api/meals.py index 8a0722a..7ebf0be 100644 --- a/app/api/meals.py +++ b/app/api/meals.py @@ -1,9 +1,11 @@ +"""Meal related API endpoints.""" + import datetime from enum import Enum from typing import Any, List, Union from fastapi import APIRouter, BackgroundTasks, Depends, Query -from pydantic.tools import parse_obj_as +from pydantic import parse_obj_as from starlette.responses import Response from .. import crud @@ -21,19 +23,24 @@ class OutputEnum(Enum): - simple = "simple" - normal = "normal" + """Output types for meals.""" + + SIMPLE = "simple" + NORMAL = "normal" def simplify_asked(output: OutputEnum = Query(None, description="Simplify output")): - return output == OutputEnum.simple + """Returns true if the output must be simplified.""" + return output == OutputEnum.SIMPLE def paginate(skip: int = 0, limit: int = 100): + """Returns the pagination.""" return {"skip": skip, "limit": limit} def simplify(input_data: Any, simplified_model: Any, simplify_flag: bool): + """Simplifies output if needed.""" if not simplify_flag: return input_data return parse_obj_as(simplified_model, input_data) @@ -198,9 +205,8 @@ def update_meal( ) def delete_current_week(*, db=Depends(get_db), background_tasks: BackgroundTasks): """Delete all meals of a week.""" - result = crud.meal.remove_current_week(db) + crud.meal.remove_current_week(db) background_tasks.add_task(update_notion_meals) - return result @router.delete( @@ -211,9 +217,8 @@ def delete_current_week(*, db=Depends(get_db), background_tasks: BackgroundTasks ) def delete_week(*, week: int, db=Depends(get_db), background_tasks: BackgroundTasks): """Delete all meals of a week.""" - result = crud.meal.remove_week(db, week=week) + crud.meal.remove_week(db, week=week) background_tasks.add_task(update_notion_meals) - return result @router.delete( @@ -227,6 +232,5 @@ def delete_single_meal( *, date: datetime.date, db=Depends(get_db), background_tasks: BackgroundTasks ): """Delete single meal.""" - result = crud.meal.remove(db, id=date) + crud.meal.remove(db, id=date) background_tasks.add_task(update_notion_meals) - return result diff --git a/app/api/utils.py b/app/api/utils.py index d570d46..9aba2e5 100644 --- a/app/api/utils.py +++ b/app/api/utils.py @@ -1,3 +1,5 @@ +"""Util related API endpoints.""" + from fastapi import APIRouter from ..utils.misc import get_version diff --git a/app/core/aws.py b/app/core/aws.py index a7b3bb4..0ed1517 100644 --- a/app/core/aws.py +++ b/app/core/aws.py @@ -1,3 +1,5 @@ +"""AWS Operations.""" + import tempfile from json import dumps from typing import List @@ -13,13 +15,14 @@ def get_meals() -> List[Meal]: + """Returns the meals saved in AWS.""" s3 = boto3.client("s3") with tempfile.TemporaryFile() as fp: try: s3.download_fileobj(settings.S3_BUCKET_NAME, settings.S3_FILE_NAME, fp) except ClientError as exc: if "404" in str(exc): - raise HTTPException(404, "Meal not found") + raise HTTPException(404, "Meals not found") raise fp.seek(0) @@ -27,6 +30,7 @@ def get_meals() -> List[Meal]: def save_meals(meals: List[Meal]): + """Saves meals in AWS.""" menu_json = dumps(jsonable_encoder(meals)) s3 = boto3.client("s3") with tempfile.TemporaryFile() as fp: @@ -42,5 +46,6 @@ def save_meals(meals: List[Meal]): def create_bucket(): + """Creates the AWS bucket.""" s3 = boto3.client("s3") s3.create_bucket(Bucket=settings.S3_BUCKET_NAME) diff --git a/app/core/config.py b/app/core/config.py index db14156..cef02f5 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1,3 +1,5 @@ +"""Config module.""" + from typing import Any, Dict, Optional from pydantic import UUID4, BaseSettings, validator diff --git a/app/core/notion.py b/app/core/notion.py index 4bc8be5..f574549 100644 --- a/app/core/notion.py +++ b/app/core/notion.py @@ -1,3 +1,5 @@ +"""Notion operations""" + from typing import Dict, List import requests @@ -6,6 +8,7 @@ def create_notion_block(content: str, bold=False, italics=False, code=False): + """Returns the json to create a notion block.""" block = { "type": "text", "text": {"content": content}, @@ -24,6 +27,7 @@ def create_notion_block(content: str, bold=False, italics=False, code=False): def update_notion_text(blocks: List[Dict]): + """Updates the notion text via its API.""" headers = { "Authorization": f"Bearer {settings.NOTION_KEY}", "Content-Type": "application/json", diff --git a/app/core/todoist.py b/app/core/todoist.py index dd0c014..7d2d4bc 100644 --- a/app/core/todoist.py +++ b/app/core/todoist.py @@ -1,9 +1,12 @@ +"""Todoist operations.""" + from todoist.api import TodoistAPI from .config import settings def add_task(msg: str, due: str = "today", priority: int = 1): + """Adds a todoist task.""" api = TodoistAPI(settings.TODOIST_TOKEN) api.items.add( msg, diff --git a/app/cron/__init__.py b/app/cron/__init__.py index ecce92d..5b89fb7 100644 --- a/app/cron/__init__.py +++ b/app/cron/__init__.py @@ -1,3 +1,5 @@ +"""Cron module for periodic tasks.""" + from .backup_database import backup_database from .base import scheduler from .check_frozen_meals import check_frozen_meals diff --git a/app/cron/backup_database.py b/app/cron/backup_database.py index 6e14af7..973a507 100644 --- a/app/cron/backup_database.py +++ b/app/cron/backup_database.py @@ -1,3 +1,5 @@ +"""Backup database cron script.""" + from typing import List from pydantic import parse_obj_as @@ -12,6 +14,7 @@ # Should fire everyday at 02:08 @scheduler.scheduled_job("cron", id="backup-database", hour="2", minute="08") def backup_database(): + """Backup database to AWS.""" with manual_db() as db: meals = crud.meal.get_multi(db, limit=1000) diff --git a/app/cron/base.py b/app/cron/base.py index 9a695fd..aeed1b8 100644 --- a/app/cron/base.py +++ b/app/cron/base.py @@ -1,6 +1,7 @@ +"""Apscheduler configuration.""" + from apscheduler.schedulers.background import BackgroundScheduler -# XXX: Consider using prod database instead of a disposable sqlite scheduler = BackgroundScheduler( { "apscheduler.jobstores.default": { diff --git a/app/cron/check_frozen_meals.py b/app/cron/check_frozen_meals.py index e7e53aa..b00a619 100644 --- a/app/cron/check_frozen_meals.py +++ b/app/cron/check_frozen_meals.py @@ -1,3 +1,5 @@ +"""Check frozen meals cron script.""" + from .. import crud from ..core.todoist import add_task from ..deps.database import manual_db @@ -8,6 +10,7 @@ # Should fire everyday at 16:00 @scheduler.scheduled_job("cron", id="check-frozen-meals", hour="16", minute="0") def check_frozen_meals(): + """Checks if tomorrow something frozen is on the menu.""" with manual_db() as db: meal_db = crud.meal.get_tomorrow(db) diff --git a/app/cron/update_notion_meals.py b/app/cron/update_notion_meals.py index 93cad95..97bf3a9 100644 --- a/app/cron/update_notion_meals.py +++ b/app/cron/update_notion_meals.py @@ -1,3 +1,5 @@ +"""Update notion meals cron sript.""" + from .. import crud from ..core.config import settings from ..core.notion import create_notion_block, update_notion_text @@ -9,6 +11,7 @@ # Should fire everyday at 05:00 @scheduler.scheduled_job("cron", id="update-notion-meals", hour="5", minute="0") def update_notion_meals(): + """Update meals in notion page.""" with manual_db() as db: today_meal = crud.meal.get_today(db) tomorrow_meal = crud.meal.get_tomorrow(db) diff --git a/app/crud/__init__.py b/app/crud/__init__.py index 27894aa..46aeb08 100644 --- a/app/crud/__init__.py +++ b/app/crud/__init__.py @@ -1 +1,3 @@ +"""CRUD operations""" + from .crud_meal import meal diff --git a/app/crud/base.py b/app/crud/base.py index 70c8778..a3c0559 100644 --- a/app/crud/base.py +++ b/app/crud/base.py @@ -1,3 +1,5 @@ +"""Base CRUD operations.""" + from typing import Any, Generic, List, Optional, Type, TypeVar from fastapi.exceptions import HTTPException @@ -10,13 +12,19 @@ CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel) UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel) +# pylint: disable=redefined-builtin + class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): + """Base CRUD class.""" + def raise_not_found_error(self, *, id: Any): + """Raise 404 NOT FOUND.""" detail = f"{self.model.__name__} with id={id} does not exist" raise HTTPException(404, detail) def raise_conflict_error(self, id: Any): + """Raise 409 CONFLICT.""" detail = f"{self.model.__name__} with id={id} already exists" raise HTTPException(409, detail) @@ -30,22 +38,28 @@ def __init__(self, model: Type[ModelType]): self.model = model def get(self, db: Session, id: Any) -> Optional[ModelType]: + """Get an object using its id.""" return db.query(self.model).filter(self.model.id == id).first() def get_or_404(self, db: Session, id: Any) -> ModelType: + """Get an object or return 404.""" obj = self.get(db, id=id) if obj is not None: return obj - self.raise_not_found_error(id=id) + return self.raise_not_found_error(id=id) def get_multi( self, db: Session, *, skip: int = 0, limit: int = 100 ) -> List[ModelType]: + """Get multiple objects.""" + return db.query(self.model).offset(skip).limit(limit).all() def create( self, db: Session, *, obj_in: CreateSchemaType, commit_refresh=True ) -> ModelType: + """Create an object.""" + obj_in_data = obj_in.dict() if "id" in obj_in_data and self.get(db, id=obj_in_data["id"]): self.raise_conflict_error(id=obj_in_data["id"]) @@ -64,6 +78,7 @@ def update( db_obj: ModelType, obj_in: UpdateSchemaType, ) -> ModelType: + """Update an object.""" obj_data = obj_in.dict(exclude_unset=True) for field in obj_data: @@ -75,6 +90,7 @@ def update( return db_obj def remove(self, db: Session, *, id: Any) -> None: + """Remove an object.""" obj = self.get_or_404(db, id=id) db.delete(obj) db.commit() diff --git a/app/crud/crud_meal.py b/app/crud/crud_meal.py index 83d8933..12939a8 100644 --- a/app/crud/crud_meal.py +++ b/app/crud/crud_meal.py @@ -1,3 +1,5 @@ +"""Meals CRUD operations.""" + from datetime import datetime, timedelta from typing import List @@ -11,7 +13,10 @@ class CRUDMeal(CRUDBase[Meal, MealCreate, MealUpdate]): + """Meal CRUD operations.""" + def create_multiple(self, db: Session, *, obj_in: List[MealCreate]) -> List[Meal]: + """Create multiple meals.""" out = [] for obj in obj_in: out.append(self.create(db, obj_in=obj, commit_refresh=False)) @@ -22,49 +27,63 @@ def create_multiple(self, db: Session, *, obj_in: List[MealCreate]) -> List[Meal return out def get_today_or_404(self, db: Session): - meal = self.get_today(db) - if not meal: - # FIXME: id should be today's date - self.raise_not_found_error(id="") - return meal + """Get today's menu or return 404.""" + meal_db = self.get_today(db) + if not meal_db: + self.raise_not_found_error(id=datetime.now().date()) + return meal_db def get_tomorrow_or_404(self, db: Session): - meal = self.get_tomorrow(db) - if not meal: - # FIXME: id should be tomorrow's date - self.raise_not_found_error(id="") - return meal + """Get tomorrow's menu or return 404.""" + meal_db = self.get_tomorrow(db) + if not meal_db: + self.raise_not_found_error(id=self.get_tomorrow_date) + return meal_db + + @staticmethod + def get_tomorrow_date(): + """Returns tomorrow's date.""" + return (datetime.now() + timedelta(days=1)).date() def get_by_date_delta(self, db: Session, *, delta_days: int): - tomorrow = (datetime.now() + timedelta(days=delta_days)).date() - return self.get(db, id=tomorrow) + """Get menu using a relative time delta.""" + date = (datetime.now() + timedelta(days=delta_days)).date() + return self.get(db, id=date) def get_today(self, db: Session): + """Get today's menu.""" return self.get_by_date_delta(db, delta_days=0) def get_tomorrow(self, db: Session): + """Get tomorrow's menu.""" return self.get_by_date_delta(db, delta_days=1) def get_day_after_tomorrow(self, db: Session): + """Get the day after tomorrow's menu.""" return self.get_by_date_delta(db, delta_days=2) def get_week(self, db: Session, *, week: int): + """Get week's menu.""" return db.query(self.model).filter(func.weekofyear(self.model.id) == week).all() def get_week_delta(self, db: Session, *, delta_weeks: int): + """Get week's menu using a relative time delta.""" week = get_current_week() + delta_weeks return self.get_week(db, week=week) def get_current_week(self, db: Session): + """Get current week's menu.""" week = get_current_week() return self.get_week(db, week=week) def remove_week(self, db: Session, *, week: int): + """Remove meals for the entire week.""" meals = self.get_week(db, week=week) - for meal in meals: - self.remove(db, id=meal.id) + for meal_db in meals: + self.remove(db, id=meal_db.id) def remove_current_week(self, db: Session): + """Remove current week's meals.""" week = get_current_week() self.remove_week(db, week=week) diff --git a/app/db/base.py b/app/db/base.py index 168416a..2f7e970 100644 --- a/app/db/base.py +++ b/app/db/base.py @@ -1,2 +1,4 @@ +"""Joined imports for alembic use.""" + from ..db.base_class import * # noqa from ..models import * # noqa diff --git a/app/db/base_class.py b/app/db/base_class.py index 9520713..c0dc03c 100644 --- a/app/db/base_class.py +++ b/app/db/base_class.py @@ -1,3 +1,5 @@ +"""Base class for sql models.""" + from typing import Any from sqlalchemy.ext.declarative import as_declarative, declared_attr diff --git a/app/db/utils.py b/app/db/utils.py index 5f31ab8..549375e 100644 --- a/app/db/utils.py +++ b/app/db/utils.py @@ -1,3 +1,5 @@ +"""Database utils.""" + from sqlalchemy_utils import create_database, database_exists from .base_class import Base diff --git a/app/deps/__init__.py b/app/deps/__init__.py index e69de29..8032477 100644 --- a/app/deps/__init__.py +++ b/app/deps/__init__.py @@ -0,0 +1 @@ +"""Dependencies module.""" diff --git a/app/deps/security.py b/app/deps/security.py index 35e6202..6d82a7a 100644 --- a/app/deps/security.py +++ b/app/deps/security.py @@ -1,3 +1,5 @@ +"""Security dependencies.""" + from typing import Optional from fastapi import Header, HTTPException @@ -6,6 +8,7 @@ def token_middleware(x_token: Optional[str] = Header(None)): + """Token validator.""" if x_token is None: raise HTTPException(401, "Missing API token") if x_token != settings.API_TOKEN: diff --git a/app/main.py b/app/main.py index 184703c..1f1ad91 100644 --- a/app/main.py +++ b/app/main.py @@ -1,3 +1,5 @@ +"""Main app generation.""" + from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from starlette_prometheus import PrometheusMiddleware, metrics @@ -14,6 +16,7 @@ def get_application(): + """Creates and sets up the application.""" _app = FastAPI( title="Meal Planner", docs_url=None, redoc_url="/docs", version=get_version() ) diff --git a/app/models/__init__.py b/app/models/__init__.py index 808917e..87fe963 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -1 +1,3 @@ +"""Models module.""" + from .meal import Meal diff --git a/app/models/meal.py b/app/models/meal.py index 6ea1d7d..6dc9801 100644 --- a/app/models/meal.py +++ b/app/models/meal.py @@ -1,3 +1,5 @@ +"""Meal database model""" + from sqlalchemy import Boolean, Column, Date, String from sqlalchemy.orm import synonym @@ -7,6 +9,8 @@ class Meal(Base): + """SQL model for meals.""" + id = Column(Date(), primary_key=True, index=True) date = synonym("id") diff --git a/app/schemas/meal.py b/app/schemas/meal.py index c411943..cef94ab 100644 --- a/app/schemas/meal.py +++ b/app/schemas/meal.py @@ -1,3 +1,5 @@ +"""Meal schemas.""" + from datetime import date from typing import List, Optional diff --git a/poetry.lock b/poetry.lock index 4fae9f3..c8e0bf8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -73,6 +73,20 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] +[[package]] +name = "astroid" +version = "2.8.4" +description = "An abstract syntax tree for Python with inference support." +category = "dev" +optional = false +python-versions = "~=3.6" + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} +typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} +wrapt = ">=1.11,<1.14" + [[package]] name = "atomicwrites" version = "1.4.0" @@ -354,6 +368,14 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "lazy-object-proxy" +version = "1.6.0" +description = "A fast and thorough lazy object proxy." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + [[package]] name = "mako" version = "1.1.5" @@ -589,6 +611,23 @@ category = "dev" optional = false python-versions = ">=3.5" +[[package]] +name = "pylint" +version = "2.11.1" +description = "python code static checker" +category = "dev" +optional = false +python-versions = "~=3.6" + +[package.dependencies] +astroid = ">=2.8.0,<2.9" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.7" +platformdirs = ">=2.2.0" +toml = ">=0.7.1" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + [[package]] name = "pymdown-extensions" version = "9.0" @@ -976,6 +1015,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "wrapt" +version = "1.13.3" +description = "Module for decorators, wrappers and monkey patching." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + [[package]] name = "zipp" version = "3.6.0" @@ -991,7 +1038,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = ">=3.7,<3.10" -content-hash = "ff8b4d91219b79a87a2a30abe7670a4a267d58b5b92229696331ed6b7792fcf9" +content-hash = "13479ee1b41372243c723ef7989dc438ba939ae2a81b4adb6a5ed71a657a68c5" [metadata.files] alembic = [ @@ -1010,6 +1057,10 @@ asgiref = [ {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, ] +astroid = [ + {file = "astroid-2.8.4-py3-none-any.whl", hash = "sha256:0755c998e7117078dcb7d0bda621391dd2a85da48052d948c7411ab187325346"}, + {file = "astroid-2.8.4.tar.gz", hash = "sha256:1e83a69fd51b013ebf5912d26b9338d6643a55fec2f20c787792680610eed4a2"}, +] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, @@ -1176,6 +1227,30 @@ jmespath = [ {file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"}, {file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"}, ] +lazy-object-proxy = [ + {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, +] mako = [ {file = "Mako-1.1.5-py2.py3-none-any.whl", hash = "sha256:6804ee66a7f6a6416910463b00d76a7b25194cd27f1918500c5bd7be2a088a23"}, {file = "Mako-1.1.5.tar.gz", hash = "sha256:169fa52af22a91900d852e937400e79f535496191c63712e3b9fda5a9bed6fc3"}, @@ -1344,6 +1419,10 @@ pygments = [ {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, ] +pylint = [ + {file = "pylint-2.11.1-py3-none-any.whl", hash = "sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126"}, + {file = "pylint-2.11.1.tar.gz", hash = "sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"}, +] pymdown-extensions = [ {file = "pymdown-extensions-9.0.tar.gz", hash = "sha256:01e4bec7f4b16beaba0087a74496401cf11afd69e3a11fe95cb593e5c698ef40"}, {file = "pymdown_extensions-9.0-py3-none-any.whl", hash = "sha256:430cc2fbb30cef2df70edac0b4f62614a6a4d2b06462e32da4ca96098b7c1dfb"}, @@ -1631,6 +1710,59 @@ wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] +wrapt = [ + {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, + {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, + {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, + {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, + {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, + {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, + {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, + {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, + {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, + {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, + {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, + {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, + {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, + {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, + {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, + {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, + {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, + {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, + {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, +] zipp = [ {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, diff --git a/pyproject.toml b/pyproject.toml index 281212e..ef4ab5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "meal-planner" -version = "1.2.2" +version = "1.3.0" description = "" authors = ["Diego Alloza González "] packages = [ @@ -35,6 +35,7 @@ mypy = "^0.790" pytest = "^5.2" pytest-cov = "^2.10.1" python-dateutil = "^2.8.2" +pylint = "^2.11.1" [build-system] diff --git a/scripts/ensure-database.py b/scripts/ensure-database.py index 3cd9af4..58055a9 100644 --- a/scripts/ensure-database.py +++ b/scripts/ensure-database.py @@ -1,3 +1,5 @@ +"""Script to ensure that the database exists.""" + import traceback import click @@ -7,6 +9,7 @@ def ensure_database() -> None: + """Creates the database if it doesn't exist.""" try: if not database_exists(engine.url): create_database(engine.url) diff --git a/scripts/import.py b/scripts/import.py index 84e3435..2a76fd5 100644 --- a/scripts/import.py +++ b/scripts/import.py @@ -1,3 +1,5 @@ +"""Script to import data from AWS into the database using the API.""" + from urllib.parse import urljoin import click @@ -11,6 +13,7 @@ @click.command("import") @click.argument("API_URL", envvar="MEAL_PLANNER_API_URL") def import_aws_db(api_url): + """Imports the data from AWS into the database using the API.""" click.confirm(f"Import AWS DB to {api_url!r}?", abort=True, default=True) headers = {"x-token": settings.API_TOKEN, "user-agent": "mealer"} diff --git a/scripts/load-docs.py b/scripts/load-docs.py index 3ad054a..7ba2768 100644 --- a/scripts/load-docs.py +++ b/scripts/load-docs.py @@ -1,3 +1,5 @@ +"""Load docs script.""" + import locale from itertools import groupby from pathlib import Path @@ -22,6 +24,7 @@ @click.command() @click.argument("source", type=click.Choice(["aws", "db"])) def load_docs(source: str): + """Load the docs from AWS or the database.""" if source == "aws": meals = get_meals() else: @@ -38,6 +41,7 @@ def load_docs(source: str): def create_md(week: int, weekly_meals: List[Meal]): + """Creates the markdown file.""" MD_DIR.mkdir(exist_ok=True) md_filepath = MD_DIR / f"{week}.md" content = "" @@ -57,6 +61,7 @@ def create_md(week: int, weekly_meals: List[Meal]): def rebuild_mkdocs_yml(weeks: List[int]): + """Rebuilds the mkdocs.yml file.""" with MKDOCS_YML_PATH.open("rt", encoding="utf8") as fh: yml_content = yml.load(fh) yml_content["nav"] = [{"Índice": "index.md"}] diff --git a/scripts/manage.py b/scripts/manage.py index de5a435..d68f944 100644 --- a/scripts/manage.py +++ b/scripts/manage.py @@ -1,3 +1,5 @@ +"""Script to create a full week's menu.""" + import datetime import json import locale