Skip to content

Commit

Permalink
Merge pull request #38 from miraisolutions/issue-29/documentation-and…
Browse files Browse the repository at this point in the history
…-mypy-improvements

Issue 29/documentation and mypy improvements
  • Loading branch information
mirai-mjelavic authored Feb 29, 2024
2 parents 69de0fb + 78ab051 commit 54c9030
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 55 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down Expand Up @@ -223,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}
Expand Down
6 changes: 4 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -37,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.
Expand Down Expand Up @@ -82,7 +84,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.
Expand Down Expand Up @@ -212,7 +214,7 @@

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
latex_elements: Dict[str, str] = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',

Expand Down
6 changes: 6 additions & 0 deletions docs/source/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ Modules

.. automodule:: secretsanta.main.funs
:members:

``secretsanta.main.utils``
-------------------------

.. automodule:: secretsanta.main.utils
:members:
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,7 @@ norecursedirs = [
".tox",
".git",
"docs"
]
]

[tool.mypy]
exclude = ["venv"]
1 change: 0 additions & 1 deletion requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 1 addition & 3 deletions run/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
36 changes: 19 additions & 17 deletions secretsanta/main/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,36 @@ 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('[email protected]', 'you')
>>> obj.helper
'rudolph'
Example:
>>> obj = SecretSanta('[email protected]', 'you')
>>> obj.helper
'rudolph'
"""
self.email = email
self.person = person

# 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'
Expand Down
37 changes: 19 additions & 18 deletions secretsanta/main/funs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions secretsanta/main/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions tests/main/test_funs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 54c9030

Please sign in to comment.