From a76a4f63b7787f05195c3911aa7be79cd7827ab5 Mon Sep 17 00:00:00 2001 From: Marko Jelavic Date: Tue, 23 Jan 2024 12:49:42 +0100 Subject: [PATCH 1/4] Excluding venv from mypy to prevent warnings from third party libraries, fixing mypy issues in our code --- docs/source/conf.py | 5 +++-- pyproject.toml | 5 ++++- run/run.py | 4 +--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 6e44988..d227652 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -15,6 +15,7 @@ import sys import os +from typing import List, Dict # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -82,7 +83,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = [] +exclude_patterns: List[str] = [] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -212,7 +213,7 @@ # -- Options for LaTeX output --------------------------------------------- -latex_elements = { +latex_elements: Dict[str, str] = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', diff --git a/pyproject.toml b/pyproject.toml index d395497..cd0ca22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,4 +53,7 @@ norecursedirs = [ ".tox", ".git", "docs" -] \ No newline at end of file +] + +[tool.mypy] +exclude = ["venv"] \ No newline at end of file diff --git a/run/run.py b/run/run.py index 0f702b8..f6a3e56 100644 --- a/run/run.py +++ b/run/run.py @@ -25,7 +25,5 @@ file_to_rm = list(Path("./log_files").iterdir()) file_to_rm[0].unlink() -# set logging level as environment variable -os.environ["level"] = "DEBUG" -log_level = os.environ.get('log_level', os.getenv("level")).upper() +log_level = os.environ.get('log_level', "DEBUG").upper() print(make_santa_dict(participants, seed=None, verbose=True, level=log_level)) From 3ebb5b70e8802b61032f08ec1556280eaad622ef Mon Sep 17 00:00:00 2001 From: Marko Jelavic Date: Tue, 23 Jan 2024 13:49:14 +0100 Subject: [PATCH 2/4] Removing instructions to install mypy as it comes with venv, removing pip tools from requirements as we have instructions to install pip-tools to later on install requirements --- README.md | 9 ++++----- requirements.in | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f34f9d8..f13c725 100644 --- a/README.md +++ b/README.md @@ -187,14 +187,13 @@ Alternatively, you can register the `*.ini` and `.coveragerc` patterns to the *e Type hints define what type function arguments and return values should be. They are both a source of documentation and testing framework to identify bugs more easily, see also [PEP 484](https://www.python.org/dev/peps/pep-0484/). -In order to use them, install [mypy](http://www.mypy-lang.org/) (inside the virtual environment, requires Python >= 3.6): -```{bash, eval=FALSE} -pip install mypy -``` -Then run something like below: +mypy comes installed in the virtual environment as it's part of requirements.in. + +Run something like below: ```{bash, eval=FALSE} mypy ./secretsanta/main/core.py mypy ./tests +mypy . ``` to test if the type hints of `.py` file(s) are correct (in which case it would typically output a "Success" message). diff --git a/requirements.in b/requirements.in index 4bd37d3..9f6e0cf 100644 --- a/requirements.in +++ b/requirements.in @@ -5,7 +5,6 @@ hypothesis jupyter keyring<23.10.0 # otherwise importlib-metadata dependency bites with flake8. this is a dependency of twine. mypy -pip-tools>=5.0.0 pytest pytest-cov Sphinx<4.4 # otherwise importlib-metadata dependency bites with flake8 From 29c75522d02fd27e3fa1546245fd31eca646d2e7 Mon Sep 17 00:00:00 2001 From: Marko Jelavic Date: Thu, 25 Jan 2024 13:37:07 +0100 Subject: [PATCH 3/4] Adding utils to sphinx, changing docstrings to Google style, adding napoleon for proper parsing of Google docstrings --- docs/source/conf.py | 1 + docs/source/modules.rst | 6 ++++++ secretsanta/main/core.py | 36 +++++++++++++++++++----------------- secretsanta/main/funs.py | 37 +++++++++++++++++++------------------ secretsanta/main/utils.py | 6 +++--- tests/main/test_funs.py | 9 +++++---- 6 files changed, 53 insertions(+), 42 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index d227652..86a60e7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -38,6 +38,7 @@ 'sphinx.ext.coverage', 'sphinx.ext.mathjax', 'sphinx.ext.viewcode', + 'sphinx.ext.napoleon', ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/source/modules.rst b/docs/source/modules.rst index dd5e63f..93dd305 100644 --- a/docs/source/modules.rst +++ b/docs/source/modules.rst @@ -13,3 +13,9 @@ Modules .. automodule:: secretsanta.main.funs :members: + +``secretsanta.main.utils`` +------------------------- + +.. automodule:: secretsanta.main.utils + :members: diff --git a/secretsanta/main/core.py b/secretsanta/main/core.py index 784c22c..93d96fe 100644 --- a/secretsanta/main/core.py +++ b/secretsanta/main/core.py @@ -25,18 +25,19 @@ def helper(self: "SecretSanta") -> str: # constructor def __init__(self: "SecretSanta", email: Union[str, List[str]], person: str) -> None: # https://stackoverflow.com/questions/5599254/how-to-use-sphinxs-autodoc-to-document-a-classs-init-self-method - """ - init method + """init method - :param email: email address(es) - :param person: person - :return: class instance + Args: + email: email address(es). + person: person. - :Example: + Returns: + Class instance. - >>> obj = SecretSanta('me@gmail.com', 'you') - >>> obj.helper - 'rudolph' + Example: + >>> obj = SecretSanta('me@gmail.com', 'you') + >>> obj.helper + 'rudolph' """ self.email = email self.person = person @@ -44,15 +45,16 @@ def __init__(self: "SecretSanta", email: Union[str, List[str]], person: str) -> # Note: Instead of returning a dictionary, it may also raise an error (see SMTP.sendmail documentation) def send(self: "SecretSanta", subject: str, from_address: str, message: str, mailserver: SMTP, test: bool = False) -> Dict[str, Tuple[int, bytes]]: - """ - send method + """ Method for sending out emails - :param subject: subject for email - :param from_address: sender email address - :param message: customizable part of email body (message), which is prepended to ``self.person`` - :param mailserver: SMTP server object (initialized via :func:`smtplib.SMTP`) - :param test: boolean to switch on extra subject and message text, indicating the email is sent for test purposes - :return: send-status of email as returned by :func:`smtplib.sendmail`, empty if all were successful + Args: + subject: subject for email + from_address: sender email address + message: customizable part of email body (message), which is prepended to ``self.person`` + mailserver: SMTP server object (initialized via :func:`smtplib.SMTP`) + test: boolean to switch on extra subject and message text, indicating the email is sent for test purposes + Returns: + Send-status of email as returned by :func:`smtplib.sendmail`, empty if all were successful """ message = ( f'Hi there!\n\n{message} {self.person}.\n\nHo Ho Ho,\n\nSanta\n\n' diff --git a/secretsanta/main/funs.py b/secretsanta/main/funs.py index a3460a2..0807644 100644 --- a/secretsanta/main/funs.py +++ b/secretsanta/main/funs.py @@ -16,14 +16,15 @@ def make_santa_dict(dictionary: Dict[str, str], seed: Optional[int] = None, verbose: bool = False, level: str = "ERROR") -> Dict[str, str]: # type triple-quotes and press enter to generate empty docstring stub - """ - creates a random secret santa assignment as a dictionary from an initial dictionary of the participants' names and\ - associated email addresses. - - :param dictionary: mapping names to email addresses - :param seed: seed for numpy random number generator - :param verbose: boolean to control print output - :return: shuffled (randomized) dictionary, guaranteed not to map any name to its original email in `dictionary` + """Creates a random secret santa assignment as a dictionary from an initial dictionary of the participants' names + and associated email addresses. + + Args: + dictionary: mapping names to email addresses + seed: seed for numpy random number generator + verbose: boolean to control print output + Returns: + Shuffled (randomized) dictionary, guaranteed not to map any name to its original email in `dictionary` """ ###### # Implementation note: the main challenge we have beyond simply shuffling a list is to ensure no one gets assigned @@ -114,16 +115,16 @@ def santa_builder(email: Union[str, List[str]], person: str): def internal_send_santa_dict(smtpserverwithport: str, sender: str, pwd: str, senddict: Dict[str, str], santabuilder, test: bool = False) -> Dict[str, Tuple[int, bytes]]: # "\" is used in the docstring to escape the line ending in sphinx output - """ - loops over a 'santa' dictionary and sends respective emails - - :param smtpserverwithport: SMTP server including port (colon-separated) - :param sender: email address from which to send emails - :param pwd: password for sender's email account - :param senddict: mapping of names to email addresses - :param test: boolean to allow test-run - :return: all failed email sending attempts as returned by :func:`smtplib.sendmail()`, empty if all\ - were successful + """Loops over a 'santa' dictionary and sends respective emails + + Args: + smtpserverwithport: SMTP server including port (colon-separated) + sender: email address from which to send emails + pwd: password for sender's email account + senddict: mapping of names to email addresses + test: boolean to allow test-run + Returns: + All failed email sending attempts as returned by :func:`smtplib.sendmail()`, empty if all were successful """ # create SMTP server object and connect server = smtplib.SMTP(smtpserverwithport) diff --git a/secretsanta/main/utils.py b/secretsanta/main/utils.py index 1798fee..4456b6e 100644 --- a/secretsanta/main/utils.py +++ b/secretsanta/main/utils.py @@ -5,10 +5,10 @@ def setup_logging(level: str = "ERROR"): - """ - sets up logging configuration. + """Sets up logging configuration. - :param level: logging level + Args: + level: logging level """ name_file = f'secretsanta_{time.strftime("%Y%m%d-%H%M%S")}.log' path_file = Path('./log_files/') / name_file diff --git a/tests/main/test_funs.py b/tests/main/test_funs.py index c042041..c783d49 100644 --- a/tests/main/test_funs.py +++ b/tests/main/test_funs.py @@ -15,11 +15,12 @@ class MakeSantaDict(unittest.TestCase): min_size=2, max_size=20), min_size=2, max_size=10, unique=True), integers(min_value=0, max_value=2**32 - 1)) def test_all_different_assign(self, test_list, seed): - """ - Test that a generated list of unique names, turned into a dictionary, are all assigned to one another, without + """Test that a generated list of unique names, turned into a dictionary, are all assigned to one another, without self-assignment. - :param test_list: list of names - :param seed: seed for random choice picking + + Args: + test_list: list of names + seed: seed for random choice picking """ test_dict = dict(zip(test_list, test_list)) assignment = funs.make_santa_dict(test_dict, seed) From 78ab051e4df0161c7f77d5a7dadf81b999a5daa1 Mon Sep 17 00:00:00 2001 From: mirai-mjelavic Date: Wed, 28 Feb 2024 13:05:45 +0100 Subject: [PATCH 4/4] Addressing review comments --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f13c725..80514c8 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,8 @@ a `@patch` decorator, which allows us to specify classes to be mocked within the *test_funs.py* and *test_core.py* for examples. ### Documentation -Documentation is done using [Sphinx](http://www.sphinx-doc.org/en/master/usage/quickstart.html). +Documentation is done using [Sphinx](http://www.sphinx-doc.org/en/master/usage/quickstart.html). We use Google style docstrings as that seems to be prevalent in the industry, +with the addition of `napoleon` Sphinx extension. Prerequisite: Installation. Open a terminal (outside of a virtual environment) and run below command: ```{bash, eval=FALSE}