├── .gitignore ├── .pre-commit-config.yaml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── clean_code ├── comments.py ├── functions_methods │ ├── one_level_of_abstraction_per_method.py │ ├── reduce_if.py │ ├── small_blocks.py │ └── the_stepdown_rule.py └── naming.py ├── docs ├── .nojekyll ├── Makefile ├── build │ ├── doctrees │ │ ├── environment.pickle │ │ ├── index.doctree │ │ ├── modules.doctree │ │ └── testing.doctree │ └── html │ │ ├── .buildinfo │ │ ├── _sources │ │ ├── index.rst.txt │ │ ├── modules.rst.txt │ │ └── testing.rst.txt │ │ ├── _static │ │ ├── basic.css │ │ ├── css │ │ │ ├── badge_only.css │ │ │ ├── fonts │ │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ ├── lato-bold-italic.woff │ │ │ │ ├── lato-bold-italic.woff2 │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-normal-italic.woff │ │ │ │ ├── lato-normal-italic.woff2 │ │ │ │ ├── lato-normal.woff │ │ │ │ └── lato-normal.woff2 │ │ │ └── theme.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── js │ │ │ ├── badge_only.js │ │ │ ├── html5shiv-printshiv.min.js │ │ │ ├── html5shiv.min.js │ │ │ └── theme.js │ │ ├── language_data.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ └── sphinx_highlight.js │ │ ├── genindex.html │ │ ├── index.html │ │ ├── modules.html │ │ ├── objects.inv │ │ ├── py-modindex.html │ │ ├── search.html │ │ ├── searchindex.js │ │ └── testing.html ├── make.bat └── source │ ├── conf.py │ ├── index.rst │ ├── modules.rst │ └── testing.rst ├── documenting └── documenting.py ├── flask_examples ├── flask_basic.py └── flask_basic_test.py ├── pep8 ├── blank_lines.py ├── blank_lines_test.py └── line_breaking.py ├── requirements.txt ├── solid ├── dependency_inversion_principle_dip.py ├── interface_segregation_principle_isp.py ├── liskov_substitution_principle_lsp.py ├── open_closed_principle_ocp │ ├── classes_that_violate_srp.py │ ├── classes_tightly_coupled.py │ └── classes_with_conditional_logic.py └── single_responsibility_principle_srp.py ├── testing ├── __init__.py ├── conftest.py ├── mocking.py ├── mocking_pytest-mock_test.py ├── mocking_test.py ├── pytesting.py └── pytesting_test.py └── validation └── validation.py /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | .pytest_cache 3 | venv 4 | .env 5 | 6 | # Sphinx documentation. Uncomment these if don't want to commit the build of Sphinx. 7 | # docs/_build 8 | # docs/build 9 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://gitlab.com/pycqa/flake8 3 | rev: 3.7.9 4 | hooks: 5 | - id: flake8 6 | stages: [commit] 7 | - repo: local 8 | hooks: 9 | - id: pytest 10 | name: pytest 11 | language: system 12 | entry: pytest -v -s 13 | always_run: true 14 | pass_filenames: false 15 | stages: [commit] 16 | - repo: local 17 | hooks: 18 | - id: requirements 19 | name: requirements 20 | entry: bash -c 'pip3 freeze > requirements.txt; git add requirements.txt' 21 | language: system 22 | pass_filenames: false 23 | stages: [commit] 24 | - repo: local 25 | hooks: 26 | - id: mypy 27 | name: mypy 28 | entry: "mypy --exclude '(docs|venv)' ./ " 29 | language: system 30 | pass_filenames: false 31 | # trigger for commits changing Python files 32 | types: [python] 33 | # use require_serial so that script 34 | # is only called once per commit 35 | require_serial: true 36 | # print the number of files as a sanity-check 37 | verbose: true 38 | exclude: 'docs/.*|venv/.*' -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.enabled": true, 3 | "python.linting.pylintEnabled": true, 4 | "python.linting.pylintArgs": [ 5 | "--max-line-length=79", 6 | "--load-plugins", 7 | "pylint_pytest" 8 | ], 9 | "python.linting.mypyEnabled": true, 10 | "files.exclude": { 11 | "**/*.pyc": {"when": "$(basename).py"}, 12 | "**/__pycache__": true, 13 | "**/*.pytest_cache": true, 14 | "**/venv": true 15 | }, 16 | "[python]": { 17 | "editor.rulers": [ 18 | 79 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Claudio Shigueo Watanabe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | This repository has examples to help developers understand and improve their Python coding practices. 4 | 5 | Related to this, there is a series of Linkedin posts that will be a suitable complement: 6 | 7 | 1. [Python Recommended coding practices - part 1: PEP8](https://www.linkedin.com/pulse/python-recommended-coding-practices-part-1-pep8-watanabe/) explains about PEP8, its name conventions, code layout and linters. 8 | 2. [Python Recommended coding practices - part 2: Clean Code](https://www.linkedin.com/pulse/python-recommended-coding-practices-part2-clean-code-watanabe/): In [this article](https://www.linkedin.com/pulse/python-recommended-coding-practices-part2-clean-code-watanabe/), I wrote about Python Clean Code practices. 9 | 3. [Python Recommended coding practices - part 3: SOLID Principles](https://www.linkedin.com/pulse/python-recommended-coding-practices-part-3-solid-watanabe/): In [this article](https://www.linkedin.com/pulse/python-recommended-coding-practices-part-3-solid-watanabe/), I wrote about the SOLID principles with Python examples. 10 | 4. [Testing in Python using Pytest and Mock](https://www.linkedin.com/pulse/testing-python-using-pytest-mock-claudio-shigueo-watanabe/): [In this article](https://www.linkedin.com/pulse/testing-python-using-pytest-mock-claudio-shigueo-watanabe/), I wrote about Pytest and Mock. I discussed basic use, fixtures, markers, parametrization and duration. 11 | 5. [Good Practices for Documenting Python Code](https://www.linkedin.com/pulse/good-practices-documenting-python-code-claudio-shigueo-watanabe/): [In this article](https://www.linkedin.com/pulse/good-practices-documenting-python-code-claudio-shigueo-watanabe/) I wrote about documentation in Python, including Type Hinting and Docstrings. And how to use Sphinx to generate HTML (or other format) documentation from the Docstrings. 12 | 13 | Each folder of this project is related to one specific topic: 14 | * .vscode: This folder contain example of VSCode configuration. 15 | * clean_code: This folder contains examples for "Clean Code" principles. 16 | * documenting: This folder contains examples for code documentation. 17 | * pep8: This folder contains examples for PEP8 style guidelines. 18 | * solid: This folder contains examples for SOLID principles. 19 | * testing: This folder contains examples for testing, including Pytest examples and using mocking. 20 | * .gitignore: This file contain patterns for ignoring files and folders on your local computer to GitHub. 21 | * .pre-commit-config.yaml: This file containes configuration for all the pre-commit tasks. 22 | 23 | 24 | # Instalation 25 | 26 | ## On prompt, acess the directory that want to download the project 27 | ``` 28 | git clone https://github.com/claudiosw/python-best-practices.git 29 | ``` 30 | 31 | ## Create the virtual environment: 32 | ``` 33 | python -m venv venv 34 | 35 | ``` 36 | 37 | ## Run the virtual environment: 38 | ### Windows 39 | ``` 40 | venv\Scripts\activate 41 | 42 | ``` 43 | ### Linux/MacOS 44 | ``` 45 | source venv/bin/activate 46 | ``` 47 | 48 | ## Install the required Python packages: 49 | ``` 50 | pip install -r requirements.txt 51 | pre-commit install 52 | ``` 53 | 54 | ## If you need to regenerate the Docs, execute: (You may need to run “./make.bat html” on Windows) 55 | ``` 56 | sphinx-apidoc -f -o docs/source . 57 | make clean html 58 | make html 59 | ``` 60 | 61 | ## Documentation 62 | You can see the documentation of this project generated for Sphinx in [here](https://claudiosw.github.io/python-best-practices/build/html/index.html). 63 | -------------------------------------------------------------------------------- /clean_code/comments.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=C0116 2 | 3 | """ This module contains examples of clean code regarding comments 4 | """ 5 | 6 | 7 | # Bad example 8 | def calculate_sale_price_bad(cost_price): 9 | return cost_price * (1 + 50 / 100) # 50 is margin % 10 | 11 | 12 | # Good example 13 | # Notice that when we name the constant, it is self-explanatory. We don't need 14 | # to add a comment. 15 | DEFAULT_MARGIN_PERCENT = 50 16 | 17 | 18 | def calculate_sale_price_good(cost_price): 19 | return cost_price * (1 + DEFAULT_MARGIN_PERCENT / 100) 20 | 21 | 22 | # Bad example 23 | # Don't comment out old code 24 | # def calculate_sale_price_bad_old_version(cost_price): 25 | # return cost_price * (1 + 50 / 100) # 50 is margin % 26 | 27 | 28 | # Explain why the code was written this way 29 | # I decided to use this approach because ... 30 | 31 | 32 | # You can write TODO comments to take note of things that should be done in 33 | # the future. However, you must organize yourself so that you can resolve the 34 | # issues and delete the comments. 35 | # Example of comment that we want to do in the future 36 | # This is to disable Pylint TODO error => pylint: disable=fixme 37 | # TODO: Add more examples here # pylint: disable=fixme 38 | -------------------------------------------------------------------------------- /clean_code/functions_methods/one_level_of_abstraction_per_method.py: -------------------------------------------------------------------------------- 1 | """ This module shows Python One Level of Abstraction per Function example 2 | """ 3 | # Try to write small functions/methods that do one thing and one thing well. 4 | # They should hardly ever be 20 lines long and the ideal goal would be less 5 | # than 5 lines. Having small code blocks and each one having a name helps to 6 | # have cleaner code that is easier to understand. 7 | 8 | 9 | DEFAULT_MARGIN_PERCENT = 50 10 | DEFAULT_IMPORT_TAX = 10 11 | DEFAULT_TAX = 10 12 | 13 | 14 | # This example show a method that could be smaller 15 | class ElectricCarShortVersion(): 16 | """ This is a class example to show naming best practices 17 | """ 18 | def __init__(self, name, cost_price, imported) -> None: 19 | self.name = name 20 | self.cost_price = cost_price 21 | self.imported = imported 22 | 23 | def __calculate_taxes_factor(self): 24 | """ This method calculates the taxes in percent 25 | """ 26 | if self.imported: 27 | taxes_percent = DEFAULT_TAX + DEFAULT_IMPORT_TAX 28 | else: 29 | taxes_percent = DEFAULT_TAX 30 | return 1 + taxes_percent / 100 31 | 32 | def calculate_sale_price(self): 33 | """ This method calculates the sale price of the car 34 | """ 35 | price_before_tax = self.cost_price * (1 + DEFAULT_MARGIN_PERCENT / 100) 36 | return (price_before_tax 37 | * self.__calculate_taxes_factor()) 38 | 39 | 40 | # This example show shorter methods 41 | class ElectricCarEvenShorterVersion(): 42 | """ This is a class example to show naming best practices 43 | """ 44 | def __init__(self, name, cost_price, imported) -> None: 45 | self.name = name 46 | self.cost_price = cost_price 47 | self.imported = imported 48 | 49 | def __calculate_price_before_tax(self): 50 | """ This method calculates the price before tax 51 | """ 52 | return self.cost_price * (1 + DEFAULT_MARGIN_PERCENT / 100) 53 | 54 | def __calculate_taxes_factor(self): 55 | """ This method calculates the taxes in percent 56 | """ 57 | if self.imported: 58 | taxes_percent = DEFAULT_TAX + DEFAULT_IMPORT_TAX 59 | else: 60 | taxes_percent = DEFAULT_TAX 61 | return 1 + taxes_percent / 100 62 | 63 | def calculate_sale_price(self): 64 | """ This method calculates the sale price of the car 65 | """ 66 | return (self.__calculate_price_before_tax() 67 | * self.__calculate_taxes_factor()) 68 | -------------------------------------------------------------------------------- /clean_code/functions_methods/reduce_if.py: -------------------------------------------------------------------------------- 1 | """ This module shows Python example of how to reduce if statement 2 | """ 3 | 4 | 5 | # Long if example 6 | def is_car_ok_long(oil_level, fuel_level, right_door_status, left_door_status): 7 | """ This method checks the car status 8 | """ 9 | if ( 10 | oil_level > 3 11 | and fuel_level > 5 12 | and right_door_status == "closed" 13 | and left_door_status == "closed" 14 | ): 15 | return True 16 | return False 17 | 18 | 19 | # Shorter if example using functions and variables 20 | def is_car_ok(oil_level, fuel_level, right_door_status, left_door_status): 21 | """ This method checks the car status 22 | """ 23 | if ( 24 | is_car_levels_ok(oil_level, fuel_level) 25 | and is_car_doors_closed(right_door_status, left_door_status) 26 | ): 27 | return True 28 | return False 29 | 30 | 31 | def is_car_doors_closed(right_door_status, left_door_status): 32 | """ This method checks the car doors status 33 | """ 34 | if ( 35 | right_door_status == "closed" 36 | and left_door_status == "closed" 37 | ): 38 | return True 39 | return False 40 | 41 | 42 | def is_car_levels_ok(oil_level, fuel_level): 43 | """ This method checks the car levels status 44 | """ 45 | # We can also use variables to store conditions 46 | oil_level_above_minimum = oil_level > 3 47 | fuel_level_above_minimum = fuel_level > 5 48 | if ( 49 | oil_level_above_minimum 50 | and fuel_level_above_minimum 51 | ): 52 | return True 53 | return False 54 | -------------------------------------------------------------------------------- /clean_code/functions_methods/small_blocks.py: -------------------------------------------------------------------------------- 1 | """ This module shows Python small block best practice example 2 | """ 3 | # Try to write small functions/methods that do one thing and one thing well. 4 | # They should hardly ever be 20 lines long and the ideal goal would be less 5 | # than 5 lines. Having small code blocks and each one having a name helps to 6 | # have cleaner code that is easier to understand. 7 | 8 | 9 | DEFAULT_MARGIN_PERCENT = 50 10 | DEFAULT_IMPORT_TAX = 10 11 | DEFAULT_TAX = 10 12 | 13 | 14 | # This example show a method that could be smaller 15 | class ElectricCarLongVersion(): 16 | """ This is a class example to show naming best practices 17 | """ 18 | def __init__(self, name, cost_price, imported) -> None: 19 | self.name = name 20 | self.cost_price = cost_price 21 | self.imported = imported 22 | 23 | def calculate_sale_price(self): 24 | """ This method calculates the sale price of the car 25 | """ 26 | price_before_tax = self.cost_price * (1 + DEFAULT_MARGIN_PERCENT / 100) 27 | if self.imported: 28 | taxes_percent = DEFAULT_TAX + DEFAULT_IMPORT_TAX 29 | else: 30 | taxes_percent = DEFAULT_TAX 31 | return price_before_tax * (1 + taxes_percent / 100) 32 | 33 | 34 | # This example show shorter methods 35 | class ElectricCarShortVersion(): 36 | """ This is a class example to show naming best practices 37 | """ 38 | def __init__(self, name, cost_price, imported) -> None: 39 | self.name = name 40 | self.cost_price = cost_price 41 | self.imported = imported 42 | 43 | def __calculate_price_before_tax(self): 44 | """ This method calculates the price before tax 45 | """ 46 | return self.cost_price * (1 + DEFAULT_MARGIN_PERCENT / 100) 47 | 48 | def __calculate_taxes_factor(self): 49 | """ This method calculates the taxes in percent 50 | """ 51 | if self.imported: 52 | taxes_percent = DEFAULT_TAX + DEFAULT_IMPORT_TAX 53 | else: 54 | taxes_percent = DEFAULT_TAX 55 | return 1 + taxes_percent / 100 56 | 57 | def calculate_sale_price(self): 58 | """ This method calculates the sale price of the car 59 | """ 60 | return (self.__calculate_price_before_tax() 61 | * self.__calculate_taxes_factor()) 62 | -------------------------------------------------------------------------------- /clean_code/functions_methods/the_stepdown_rule.py: -------------------------------------------------------------------------------- 1 | """ This module shows StepDown Rule Python example 2 | """ 3 | 4 | 5 | DEFAULT_MARGIN_PERCENT = 50 6 | DEFAULT_IMPORT_TAX = 10 7 | DEFAULT_TAX = 10 8 | 9 | 10 | # This example is NOT following the StepDown Rule example because the highest 11 | # level of abstraction is at the bottom of the class. 12 | class ElectricCarEvenShorterVersion(): 13 | """ This is a class example to show naming best practices 14 | """ 15 | def __init__(self, name, cost_price, imported) -> None: 16 | self.name = name 17 | self.cost_price = cost_price 18 | self.imported = imported 19 | 20 | def __calculate_price_before_tax(self): 21 | """ This method calculates the price before tax 22 | """ 23 | return self.cost_price * (1 + DEFAULT_MARGIN_PERCENT / 100) 24 | 25 | def __calculate_taxes_factor(self): 26 | """ This method calculates the taxes in percent 27 | """ 28 | if self.imported: 29 | taxes_percent = DEFAULT_TAX + DEFAULT_IMPORT_TAX 30 | else: 31 | taxes_percent = DEFAULT_TAX 32 | return 1 + taxes_percent / 100 33 | 34 | def calculate_sale_price(self): 35 | """ This method calculates the sale price of the car 36 | """ 37 | return (self.__calculate_price_before_tax() 38 | * self.__calculate_taxes_factor()) 39 | 40 | 41 | # This rule states that when we read a code, the higher level of abstraction 42 | # should be at the top of the file, not at the bottom. So, in the previous 43 | # code, the calculate_sale_price method should be at the top of the class like 44 | # this: 45 | class ElectricCarStepDownRule(): 46 | """ This is a class example to show naming best practices 47 | """ 48 | def __init__(self, name, cost_price, imported) -> None: 49 | self.name = name 50 | self.cost_price = cost_price 51 | self.imported = imported 52 | 53 | def calculate_sale_price(self): 54 | """ This method calculates the sale price of the car 55 | """ 56 | return (self.__calculate_price_before_tax() 57 | * self.__calculate_taxes_factor()) 58 | 59 | def __calculate_price_before_tax(self): 60 | """ This method calculates the price before tax 61 | """ 62 | return self.cost_price * (1 + DEFAULT_MARGIN_PERCENT / 100) 63 | 64 | def __calculate_taxes_factor(self): 65 | """ This method calculates the taxes in percent 66 | """ 67 | if self.imported: 68 | taxes_percent = DEFAULT_TAX + DEFAULT_IMPORT_TAX 69 | else: 70 | taxes_percent = DEFAULT_TAX 71 | return 1 + taxes_percent / 100 72 | -------------------------------------------------------------------------------- /clean_code/naming.py: -------------------------------------------------------------------------------- 1 | """ This module shows Python naming best practice examples 2 | """ 3 | 4 | # Notice that the constant name is self explanatory and is a noun. 5 | # Don't try to cut back letters like DEF_MARG_PERC or DMP 6 | DEFAULT_MARGIN_PERCENT = 50 7 | 8 | 9 | class ElectricCar(): 10 | """ This is a class example to show naming best practices 11 | """ 12 | # Notice that the class is named as a noun, not a verb. 13 | # Following PEP8 naming convention, it uses camel case. 14 | def __init__(self, name, cost_price) -> None: 15 | self.name = name 16 | self.cost_price = cost_price 17 | 18 | def calculate_sale_price(self): 19 | """ This method calculates the sale price of the car 20 | """ 21 | # Notice that the method is named as a verb. 22 | # Following PEP8, it's name uses lowercases and underscores. 23 | # Notice that the constant DEFAULT_MARGIN_PERCENT helps to understand 24 | # the calculation logic. If we use 50 instead, 50 would be a magic 25 | # number and it would be more difficult to understand. 26 | return self.cost_price * (1 + DEFAULT_MARGIN_PERCENT / 100) 27 | 28 | def get_name(self): 29 | """ This method returns the name of the car 30 | """ 31 | return self.name 32 | 33 | def get_cost_price(self): 34 | """ This method returns the cost price of the car 35 | """ 36 | # Notice that both get_name and get_cost_price use the same verb. 37 | # We should not name them get_name and fetch_cost_price, for instance 38 | return self.cost_price 39 | 40 | 41 | def explode_word(word): 42 | """ This function is a function to explode a word 43 | """ 44 | # Notice that the function is named as a verb. 45 | # Following PEP8, it's name uses lowercases and underscores. 46 | # The character could be named as i in loops. But naming it as character is 47 | # probably clearer. 48 | for character in word: 49 | print(character) 50 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/.nojekyll -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/modules.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/doctrees/modules.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/testing.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/doctrees/testing.doctree -------------------------------------------------------------------------------- /docs/build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: f135345770005f44ad30cd3ce7794139 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/build/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. Python Best Practices documentation master file, created by 2 | sphinx-quickstart on Mon Apr 10 12:54:46 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Python Best Practices's documentation! 7 | ================================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/build/html/_sources/modules.rst.txt: -------------------------------------------------------------------------------- 1 | python-best-practices 2 | ===================== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | testing 8 | -------------------------------------------------------------------------------- /docs/build/html/_sources/testing.rst.txt: -------------------------------------------------------------------------------- 1 | testing package 2 | =============== 3 | 4 | Submodules 5 | ---------- 6 | 7 | testing.conftest module 8 | ----------------------- 9 | 10 | .. automodule:: testing.conftest 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | testing.mocking module 16 | ---------------------- 17 | 18 | .. automodule:: testing.mocking 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | testing.mocking\_test module 24 | ---------------------------- 25 | 26 | .. automodule:: testing.mocking_test 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | testing.pytesting module 32 | ------------------------ 33 | 34 | .. automodule:: testing.pytesting 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | testing.pytesting\_test module 40 | ------------------------------ 41 | 42 | .. automodule:: testing.pytesting_test 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | Module contents 48 | --------------- 49 | 50 | .. automodule:: testing 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /docs/build/html/_static/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- main layout ----------------------------------------------------------- */ 13 | 14 | div.clearer { 15 | clear: both; 16 | } 17 | 18 | div.section::after { 19 | display: block; 20 | content: ''; 21 | clear: left; 22 | } 23 | 24 | /* -- relbar ---------------------------------------------------------------- */ 25 | 26 | div.related { 27 | width: 100%; 28 | font-size: 90%; 29 | } 30 | 31 | div.related h3 { 32 | display: none; 33 | } 34 | 35 | div.related ul { 36 | margin: 0; 37 | padding: 0 0 0 10px; 38 | list-style: none; 39 | } 40 | 41 | div.related li { 42 | display: inline; 43 | } 44 | 45 | div.related li.right { 46 | float: right; 47 | margin-right: 5px; 48 | } 49 | 50 | /* -- sidebar --------------------------------------------------------------- */ 51 | 52 | div.sphinxsidebarwrapper { 53 | padding: 10px 5px 0 10px; 54 | } 55 | 56 | div.sphinxsidebar { 57 | float: left; 58 | width: 230px; 59 | margin-left: -100%; 60 | font-size: 90%; 61 | word-wrap: break-word; 62 | overflow-wrap : break-word; 63 | } 64 | 65 | div.sphinxsidebar ul { 66 | list-style: none; 67 | } 68 | 69 | div.sphinxsidebar ul ul, 70 | div.sphinxsidebar ul.want-points { 71 | margin-left: 20px; 72 | list-style: square; 73 | } 74 | 75 | div.sphinxsidebar ul ul { 76 | margin-top: 0; 77 | margin-bottom: 0; 78 | } 79 | 80 | div.sphinxsidebar form { 81 | margin-top: 10px; 82 | } 83 | 84 | div.sphinxsidebar input { 85 | border: 1px solid #98dbcc; 86 | font-family: sans-serif; 87 | font-size: 1em; 88 | } 89 | 90 | div.sphinxsidebar #searchbox form.search { 91 | overflow: hidden; 92 | } 93 | 94 | div.sphinxsidebar #searchbox input[type="text"] { 95 | float: left; 96 | width: 80%; 97 | padding: 0.25em; 98 | box-sizing: border-box; 99 | } 100 | 101 | div.sphinxsidebar #searchbox input[type="submit"] { 102 | float: left; 103 | width: 20%; 104 | border-left: none; 105 | padding: 0.25em; 106 | box-sizing: border-box; 107 | } 108 | 109 | 110 | img { 111 | border: 0; 112 | max-width: 100%; 113 | } 114 | 115 | /* -- search page ----------------------------------------------------------- */ 116 | 117 | ul.search { 118 | margin: 10px 0 0 20px; 119 | padding: 0; 120 | } 121 | 122 | ul.search li { 123 | padding: 5px 0 5px 20px; 124 | background-image: url(file.png); 125 | background-repeat: no-repeat; 126 | background-position: 0 7px; 127 | } 128 | 129 | ul.search li a { 130 | font-weight: bold; 131 | } 132 | 133 | ul.search li p.context { 134 | color: #888; 135 | margin: 2px 0 0 30px; 136 | text-align: left; 137 | } 138 | 139 | ul.keywordmatches li.goodmatch a { 140 | font-weight: bold; 141 | } 142 | 143 | /* -- index page ------------------------------------------------------------ */ 144 | 145 | table.contentstable { 146 | width: 90%; 147 | margin-left: auto; 148 | margin-right: auto; 149 | } 150 | 151 | table.contentstable p.biglink { 152 | line-height: 150%; 153 | } 154 | 155 | a.biglink { 156 | font-size: 1.3em; 157 | } 158 | 159 | span.linkdescr { 160 | font-style: italic; 161 | padding-top: 5px; 162 | font-size: 90%; 163 | } 164 | 165 | /* -- general index --------------------------------------------------------- */ 166 | 167 | table.indextable { 168 | width: 100%; 169 | } 170 | 171 | table.indextable td { 172 | text-align: left; 173 | vertical-align: top; 174 | } 175 | 176 | table.indextable ul { 177 | margin-top: 0; 178 | margin-bottom: 0; 179 | list-style-type: none; 180 | } 181 | 182 | table.indextable > tbody > tr > td > ul { 183 | padding-left: 0em; 184 | } 185 | 186 | table.indextable tr.pcap { 187 | height: 10px; 188 | } 189 | 190 | table.indextable tr.cap { 191 | margin-top: 10px; 192 | background-color: #f2f2f2; 193 | } 194 | 195 | img.toggler { 196 | margin-right: 3px; 197 | margin-top: 3px; 198 | cursor: pointer; 199 | } 200 | 201 | div.modindex-jumpbox { 202 | border-top: 1px solid #ddd; 203 | border-bottom: 1px solid #ddd; 204 | margin: 1em 0 1em 0; 205 | padding: 0.4em; 206 | } 207 | 208 | div.genindex-jumpbox { 209 | border-top: 1px solid #ddd; 210 | border-bottom: 1px solid #ddd; 211 | margin: 1em 0 1em 0; 212 | padding: 0.4em; 213 | } 214 | 215 | /* -- domain module index --------------------------------------------------- */ 216 | 217 | table.modindextable td { 218 | padding: 2px; 219 | border-collapse: collapse; 220 | } 221 | 222 | /* -- general body styles --------------------------------------------------- */ 223 | 224 | div.body { 225 | min-width: 360px; 226 | max-width: 800px; 227 | } 228 | 229 | div.body p, div.body dd, div.body li, div.body blockquote { 230 | -moz-hyphens: auto; 231 | -ms-hyphens: auto; 232 | -webkit-hyphens: auto; 233 | hyphens: auto; 234 | } 235 | 236 | a.headerlink { 237 | visibility: hidden; 238 | } 239 | 240 | h1:hover > a.headerlink, 241 | h2:hover > a.headerlink, 242 | h3:hover > a.headerlink, 243 | h4:hover > a.headerlink, 244 | h5:hover > a.headerlink, 245 | h6:hover > a.headerlink, 246 | dt:hover > a.headerlink, 247 | caption:hover > a.headerlink, 248 | p.caption:hover > a.headerlink, 249 | div.code-block-caption:hover > a.headerlink { 250 | visibility: visible; 251 | } 252 | 253 | div.body p.caption { 254 | text-align: inherit; 255 | } 256 | 257 | div.body td { 258 | text-align: left; 259 | } 260 | 261 | .first { 262 | margin-top: 0 !important; 263 | } 264 | 265 | p.rubric { 266 | margin-top: 30px; 267 | font-weight: bold; 268 | } 269 | 270 | img.align-left, figure.align-left, .figure.align-left, object.align-left { 271 | clear: left; 272 | float: left; 273 | margin-right: 1em; 274 | } 275 | 276 | img.align-right, figure.align-right, .figure.align-right, object.align-right { 277 | clear: right; 278 | float: right; 279 | margin-left: 1em; 280 | } 281 | 282 | img.align-center, figure.align-center, .figure.align-center, object.align-center { 283 | display: block; 284 | margin-left: auto; 285 | margin-right: auto; 286 | } 287 | 288 | img.align-default, figure.align-default, .figure.align-default { 289 | display: block; 290 | margin-left: auto; 291 | margin-right: auto; 292 | } 293 | 294 | .align-left { 295 | text-align: left; 296 | } 297 | 298 | .align-center { 299 | text-align: center; 300 | } 301 | 302 | .align-default { 303 | text-align: center; 304 | } 305 | 306 | .align-right { 307 | text-align: right; 308 | } 309 | 310 | /* -- sidebars -------------------------------------------------------------- */ 311 | 312 | div.sidebar, 313 | aside.sidebar { 314 | margin: 0 0 0.5em 1em; 315 | border: 1px solid #ddb; 316 | padding: 7px; 317 | background-color: #ffe; 318 | width: 40%; 319 | float: right; 320 | clear: right; 321 | overflow-x: auto; 322 | } 323 | 324 | p.sidebar-title { 325 | font-weight: bold; 326 | } 327 | 328 | nav.contents, 329 | aside.topic, 330 | div.admonition, div.topic, blockquote { 331 | clear: left; 332 | } 333 | 334 | /* -- topics ---------------------------------------------------------------- */ 335 | 336 | nav.contents, 337 | aside.topic, 338 | div.topic { 339 | border: 1px solid #ccc; 340 | padding: 7px; 341 | margin: 10px 0 10px 0; 342 | } 343 | 344 | p.topic-title { 345 | font-size: 1.1em; 346 | font-weight: bold; 347 | margin-top: 10px; 348 | } 349 | 350 | /* -- admonitions ----------------------------------------------------------- */ 351 | 352 | div.admonition { 353 | margin-top: 10px; 354 | margin-bottom: 10px; 355 | padding: 7px; 356 | } 357 | 358 | div.admonition dt { 359 | font-weight: bold; 360 | } 361 | 362 | p.admonition-title { 363 | margin: 0px 10px 5px 0px; 364 | font-weight: bold; 365 | } 366 | 367 | div.body p.centered { 368 | text-align: center; 369 | margin-top: 25px; 370 | } 371 | 372 | /* -- content of sidebars/topics/admonitions -------------------------------- */ 373 | 374 | div.sidebar > :last-child, 375 | aside.sidebar > :last-child, 376 | nav.contents > :last-child, 377 | aside.topic > :last-child, 378 | div.topic > :last-child, 379 | div.admonition > :last-child { 380 | margin-bottom: 0; 381 | } 382 | 383 | div.sidebar::after, 384 | aside.sidebar::after, 385 | nav.contents::after, 386 | aside.topic::after, 387 | div.topic::after, 388 | div.admonition::after, 389 | blockquote::after { 390 | display: block; 391 | content: ''; 392 | clear: both; 393 | } 394 | 395 | /* -- tables ---------------------------------------------------------------- */ 396 | 397 | table.docutils { 398 | margin-top: 10px; 399 | margin-bottom: 10px; 400 | border: 0; 401 | border-collapse: collapse; 402 | } 403 | 404 | table.align-center { 405 | margin-left: auto; 406 | margin-right: auto; 407 | } 408 | 409 | table.align-default { 410 | margin-left: auto; 411 | margin-right: auto; 412 | } 413 | 414 | table caption span.caption-number { 415 | font-style: italic; 416 | } 417 | 418 | table caption span.caption-text { 419 | } 420 | 421 | table.docutils td, table.docutils th { 422 | padding: 1px 8px 1px 5px; 423 | border-top: 0; 424 | border-left: 0; 425 | border-right: 0; 426 | border-bottom: 1px solid #aaa; 427 | } 428 | 429 | th { 430 | text-align: left; 431 | padding-right: 5px; 432 | } 433 | 434 | table.citation { 435 | border-left: solid 1px gray; 436 | margin-left: 1px; 437 | } 438 | 439 | table.citation td { 440 | border-bottom: none; 441 | } 442 | 443 | th > :first-child, 444 | td > :first-child { 445 | margin-top: 0px; 446 | } 447 | 448 | th > :last-child, 449 | td > :last-child { 450 | margin-bottom: 0px; 451 | } 452 | 453 | /* -- figures --------------------------------------------------------------- */ 454 | 455 | div.figure, figure { 456 | margin: 0.5em; 457 | padding: 0.5em; 458 | } 459 | 460 | div.figure p.caption, figcaption { 461 | padding: 0.3em; 462 | } 463 | 464 | div.figure p.caption span.caption-number, 465 | figcaption span.caption-number { 466 | font-style: italic; 467 | } 468 | 469 | div.figure p.caption span.caption-text, 470 | figcaption span.caption-text { 471 | } 472 | 473 | /* -- field list styles ----------------------------------------------------- */ 474 | 475 | table.field-list td, table.field-list th { 476 | border: 0 !important; 477 | } 478 | 479 | .field-list ul { 480 | margin: 0; 481 | padding-left: 1em; 482 | } 483 | 484 | .field-list p { 485 | margin: 0; 486 | } 487 | 488 | .field-name { 489 | -moz-hyphens: manual; 490 | -ms-hyphens: manual; 491 | -webkit-hyphens: manual; 492 | hyphens: manual; 493 | } 494 | 495 | /* -- hlist styles ---------------------------------------------------------- */ 496 | 497 | table.hlist { 498 | margin: 1em 0; 499 | } 500 | 501 | table.hlist td { 502 | vertical-align: top; 503 | } 504 | 505 | /* -- object description styles --------------------------------------------- */ 506 | 507 | .sig { 508 | font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 509 | } 510 | 511 | .sig-name, code.descname { 512 | background-color: transparent; 513 | font-weight: bold; 514 | } 515 | 516 | .sig-name { 517 | font-size: 1.1em; 518 | } 519 | 520 | code.descname { 521 | font-size: 1.2em; 522 | } 523 | 524 | .sig-prename, code.descclassname { 525 | background-color: transparent; 526 | } 527 | 528 | .optional { 529 | font-size: 1.3em; 530 | } 531 | 532 | .sig-paren { 533 | font-size: larger; 534 | } 535 | 536 | .sig-param.n { 537 | font-style: italic; 538 | } 539 | 540 | /* C++ specific styling */ 541 | 542 | .sig-inline.c-texpr, 543 | .sig-inline.cpp-texpr { 544 | font-family: unset; 545 | } 546 | 547 | .sig.c .k, .sig.c .kt, 548 | .sig.cpp .k, .sig.cpp .kt { 549 | color: #0033B3; 550 | } 551 | 552 | .sig.c .m, 553 | .sig.cpp .m { 554 | color: #1750EB; 555 | } 556 | 557 | .sig.c .s, .sig.c .sc, 558 | .sig.cpp .s, .sig.cpp .sc { 559 | color: #067D17; 560 | } 561 | 562 | 563 | /* -- other body styles ----------------------------------------------------- */ 564 | 565 | ol.arabic { 566 | list-style: decimal; 567 | } 568 | 569 | ol.loweralpha { 570 | list-style: lower-alpha; 571 | } 572 | 573 | ol.upperalpha { 574 | list-style: upper-alpha; 575 | } 576 | 577 | ol.lowerroman { 578 | list-style: lower-roman; 579 | } 580 | 581 | ol.upperroman { 582 | list-style: upper-roman; 583 | } 584 | 585 | :not(li) > ol > li:first-child > :first-child, 586 | :not(li) > ul > li:first-child > :first-child { 587 | margin-top: 0px; 588 | } 589 | 590 | :not(li) > ol > li:last-child > :last-child, 591 | :not(li) > ul > li:last-child > :last-child { 592 | margin-bottom: 0px; 593 | } 594 | 595 | ol.simple ol p, 596 | ol.simple ul p, 597 | ul.simple ol p, 598 | ul.simple ul p { 599 | margin-top: 0; 600 | } 601 | 602 | ol.simple > li:not(:first-child) > p, 603 | ul.simple > li:not(:first-child) > p { 604 | margin-top: 0; 605 | } 606 | 607 | ol.simple p, 608 | ul.simple p { 609 | margin-bottom: 0; 610 | } 611 | 612 | aside.footnote > span, 613 | div.citation > span { 614 | float: left; 615 | } 616 | aside.footnote > span:last-of-type, 617 | div.citation > span:last-of-type { 618 | padding-right: 0.5em; 619 | } 620 | aside.footnote > p { 621 | margin-left: 2em; 622 | } 623 | div.citation > p { 624 | margin-left: 4em; 625 | } 626 | aside.footnote > p:last-of-type, 627 | div.citation > p:last-of-type { 628 | margin-bottom: 0em; 629 | } 630 | aside.footnote > p:last-of-type:after, 631 | div.citation > p:last-of-type:after { 632 | content: ""; 633 | clear: both; 634 | } 635 | 636 | dl.field-list { 637 | display: grid; 638 | grid-template-columns: fit-content(30%) auto; 639 | } 640 | 641 | dl.field-list > dt { 642 | font-weight: bold; 643 | word-break: break-word; 644 | padding-left: 0.5em; 645 | padding-right: 5px; 646 | } 647 | 648 | dl.field-list > dd { 649 | padding-left: 0.5em; 650 | margin-top: 0em; 651 | margin-left: 0em; 652 | margin-bottom: 0em; 653 | } 654 | 655 | dl { 656 | margin-bottom: 15px; 657 | } 658 | 659 | dd > :first-child { 660 | margin-top: 0px; 661 | } 662 | 663 | dd ul, dd table { 664 | margin-bottom: 10px; 665 | } 666 | 667 | dd { 668 | margin-top: 3px; 669 | margin-bottom: 10px; 670 | margin-left: 30px; 671 | } 672 | 673 | dl > dd:last-child, 674 | dl > dd:last-child > :last-child { 675 | margin-bottom: 0; 676 | } 677 | 678 | dt:target, span.highlighted { 679 | background-color: #fbe54e; 680 | } 681 | 682 | rect.highlighted { 683 | fill: #fbe54e; 684 | } 685 | 686 | dl.glossary dt { 687 | font-weight: bold; 688 | font-size: 1.1em; 689 | } 690 | 691 | .versionmodified { 692 | font-style: italic; 693 | } 694 | 695 | .system-message { 696 | background-color: #fda; 697 | padding: 5px; 698 | border: 3px solid red; 699 | } 700 | 701 | .footnote:target { 702 | background-color: #ffa; 703 | } 704 | 705 | .line-block { 706 | display: block; 707 | margin-top: 1em; 708 | margin-bottom: 1em; 709 | } 710 | 711 | .line-block .line-block { 712 | margin-top: 0; 713 | margin-bottom: 0; 714 | margin-left: 1.5em; 715 | } 716 | 717 | .guilabel, .menuselection { 718 | font-family: sans-serif; 719 | } 720 | 721 | .accelerator { 722 | text-decoration: underline; 723 | } 724 | 725 | .classifier { 726 | font-style: oblique; 727 | } 728 | 729 | .classifier:before { 730 | font-style: normal; 731 | margin: 0 0.5em; 732 | content: ":"; 733 | display: inline-block; 734 | } 735 | 736 | abbr, acronym { 737 | border-bottom: dotted 1px; 738 | cursor: help; 739 | } 740 | 741 | /* -- code displays --------------------------------------------------------- */ 742 | 743 | pre { 744 | overflow: auto; 745 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 746 | } 747 | 748 | pre, div[class*="highlight-"] { 749 | clear: both; 750 | } 751 | 752 | span.pre { 753 | -moz-hyphens: none; 754 | -ms-hyphens: none; 755 | -webkit-hyphens: none; 756 | hyphens: none; 757 | white-space: nowrap; 758 | } 759 | 760 | div[class*="highlight-"] { 761 | margin: 1em 0; 762 | } 763 | 764 | td.linenos pre { 765 | border: 0; 766 | background-color: transparent; 767 | color: #aaa; 768 | } 769 | 770 | table.highlighttable { 771 | display: block; 772 | } 773 | 774 | table.highlighttable tbody { 775 | display: block; 776 | } 777 | 778 | table.highlighttable tr { 779 | display: flex; 780 | } 781 | 782 | table.highlighttable td { 783 | margin: 0; 784 | padding: 0; 785 | } 786 | 787 | table.highlighttable td.linenos { 788 | padding-right: 0.5em; 789 | } 790 | 791 | table.highlighttable td.code { 792 | flex: 1; 793 | overflow: hidden; 794 | } 795 | 796 | .highlight .hll { 797 | display: block; 798 | } 799 | 800 | div.highlight pre, 801 | table.highlighttable pre { 802 | margin: 0; 803 | } 804 | 805 | div.code-block-caption + div { 806 | margin-top: 0; 807 | } 808 | 809 | div.code-block-caption { 810 | margin-top: 1em; 811 | padding: 2px 5px; 812 | font-size: small; 813 | } 814 | 815 | div.code-block-caption code { 816 | background-color: transparent; 817 | } 818 | 819 | table.highlighttable td.linenos, 820 | span.linenos, 821 | div.highlight span.gp { /* gp: Generic.Prompt */ 822 | user-select: none; 823 | -webkit-user-select: text; /* Safari fallback only */ 824 | -webkit-user-select: none; /* Chrome/Safari */ 825 | -moz-user-select: none; /* Firefox */ 826 | -ms-user-select: none; /* IE10+ */ 827 | } 828 | 829 | div.code-block-caption span.caption-number { 830 | padding: 0.1em 0.3em; 831 | font-style: italic; 832 | } 833 | 834 | div.code-block-caption span.caption-text { 835 | } 836 | 837 | div.literal-block-wrapper { 838 | margin: 1em 0; 839 | } 840 | 841 | code.xref, a code { 842 | background-color: transparent; 843 | font-weight: bold; 844 | } 845 | 846 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 847 | background-color: transparent; 848 | } 849 | 850 | .viewcode-link { 851 | float: right; 852 | } 853 | 854 | .viewcode-back { 855 | float: right; 856 | font-family: sans-serif; 857 | } 858 | 859 | div.viewcode-block:target { 860 | margin: -1px -10px; 861 | padding: 0 10px; 862 | } 863 | 864 | /* -- math display ---------------------------------------------------------- */ 865 | 866 | img.math { 867 | vertical-align: middle; 868 | } 869 | 870 | div.body div.math p { 871 | text-align: center; 872 | } 873 | 874 | span.eqno { 875 | float: right; 876 | } 877 | 878 | span.eqno a.headerlink { 879 | position: absolute; 880 | z-index: 1; 881 | } 882 | 883 | div.math:hover a.headerlink { 884 | visibility: visible; 885 | } 886 | 887 | /* -- printout stylesheet --------------------------------------------------- */ 888 | 889 | @media print { 890 | div.document, 891 | div.documentwrapper, 892 | div.bodywrapper { 893 | margin: 0 !important; 894 | width: 100%; 895 | } 896 | 897 | div.sphinxsidebar, 898 | div.related, 899 | div.footer, 900 | #top-link { 901 | display: none; 902 | } 903 | } -------------------------------------------------------------------------------- /docs/build/html/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Base JavaScript utilities for all Sphinx HTML documentation. 6 | * 7 | * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | "use strict"; 12 | 13 | const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ 14 | "TEXTAREA", 15 | "INPUT", 16 | "SELECT", 17 | "BUTTON", 18 | ]); 19 | 20 | const _ready = (callback) => { 21 | if (document.readyState !== "loading") { 22 | callback(); 23 | } else { 24 | document.addEventListener("DOMContentLoaded", callback); 25 | } 26 | }; 27 | 28 | /** 29 | * Small JavaScript module for the documentation. 30 | */ 31 | const Documentation = { 32 | init: () => { 33 | Documentation.initDomainIndexTable(); 34 | Documentation.initOnKeyListeners(); 35 | }, 36 | 37 | /** 38 | * i18n support 39 | */ 40 | TRANSLATIONS: {}, 41 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 42 | LOCALE: "unknown", 43 | 44 | // gettext and ngettext don't access this so that the functions 45 | // can safely bound to a different name (_ = Documentation.gettext) 46 | gettext: (string) => { 47 | const translated = Documentation.TRANSLATIONS[string]; 48 | switch (typeof translated) { 49 | case "undefined": 50 | return string; // no translation 51 | case "string": 52 | return translated; // translation exists 53 | default: 54 | return translated[0]; // (singular, plural) translation tuple exists 55 | } 56 | }, 57 | 58 | ngettext: (singular, plural, n) => { 59 | const translated = Documentation.TRANSLATIONS[singular]; 60 | if (typeof translated !== "undefined") 61 | return translated[Documentation.PLURAL_EXPR(n)]; 62 | return n === 1 ? singular : plural; 63 | }, 64 | 65 | addTranslations: (catalog) => { 66 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 67 | Documentation.PLURAL_EXPR = new Function( 68 | "n", 69 | `return (${catalog.plural_expr})` 70 | ); 71 | Documentation.LOCALE = catalog.locale; 72 | }, 73 | 74 | /** 75 | * helper function to focus on search bar 76 | */ 77 | focusSearchBar: () => { 78 | document.querySelectorAll("input[name=q]")[0]?.focus(); 79 | }, 80 | 81 | /** 82 | * Initialise the domain index toggle buttons 83 | */ 84 | initDomainIndexTable: () => { 85 | const toggler = (el) => { 86 | const idNumber = el.id.substr(7); 87 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 88 | if (el.src.substr(-9) === "minus.png") { 89 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 90 | toggledRows.forEach((el) => (el.style.display = "none")); 91 | } else { 92 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 93 | toggledRows.forEach((el) => (el.style.display = "")); 94 | } 95 | }; 96 | 97 | const togglerElements = document.querySelectorAll("img.toggler"); 98 | togglerElements.forEach((el) => 99 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 100 | ); 101 | togglerElements.forEach((el) => (el.style.display = "")); 102 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 103 | }, 104 | 105 | initOnKeyListeners: () => { 106 | // only install a listener if it is really needed 107 | if ( 108 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 109 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 110 | ) 111 | return; 112 | 113 | document.addEventListener("keydown", (event) => { 114 | // bail for input elements 115 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 116 | // bail with special keys 117 | if (event.altKey || event.ctrlKey || event.metaKey) return; 118 | 119 | if (!event.shiftKey) { 120 | switch (event.key) { 121 | case "ArrowLeft": 122 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 123 | 124 | const prevLink = document.querySelector('link[rel="prev"]'); 125 | if (prevLink && prevLink.href) { 126 | window.location.href = prevLink.href; 127 | event.preventDefault(); 128 | } 129 | break; 130 | case "ArrowRight": 131 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 132 | 133 | const nextLink = document.querySelector('link[rel="next"]'); 134 | if (nextLink && nextLink.href) { 135 | window.location.href = nextLink.href; 136 | event.preventDefault(); 137 | } 138 | break; 139 | } 140 | } 141 | 142 | // some keyboard layouts may need Shift to get / 143 | switch (event.key) { 144 | case "/": 145 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 146 | Documentation.focusSearchBar(); 147 | event.preventDefault(); 148 | } 149 | }); 150 | }, 151 | }; 152 | 153 | // quick alias for translations 154 | const _ = Documentation.gettext; 155 | 156 | _ready(Documentation.init); 157 | -------------------------------------------------------------------------------- /docs/build/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '1.0.0', 4 | LANGUAGE: 'en', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: true, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false, 12 | SHOW_SEARCH_SUMMARY: true, 13 | ENABLE_SEARCH_SHORTCUTS: true, 14 | }; -------------------------------------------------------------------------------- /docs/build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/file.png -------------------------------------------------------------------------------- /docs/build/html/_static/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); -------------------------------------------------------------------------------- /docs/build/html/_static/js/html5shiv-printshiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /docs/build/html/_static/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /docs/build/html/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 63 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 64 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 65 | var s_v = "^(" + C + ")?" + v; // vowel in stem 66 | 67 | this.stemWord = function (w) { 68 | var stem; 69 | var suffix; 70 | var firstch; 71 | var origword = w; 72 | 73 | if (w.length < 3) 74 | return w; 75 | 76 | var re; 77 | var re2; 78 | var re3; 79 | var re4; 80 | 81 | firstch = w.substr(0,1); 82 | if (firstch == "y") 83 | w = firstch.toUpperCase() + w.substr(1); 84 | 85 | // Step 1a 86 | re = /^(.+?)(ss|i)es$/; 87 | re2 = /^(.+?)([^s])s$/; 88 | 89 | if (re.test(w)) 90 | w = w.replace(re,"$1$2"); 91 | else if (re2.test(w)) 92 | w = w.replace(re2,"$1$2"); 93 | 94 | // Step 1b 95 | re = /^(.+?)eed$/; 96 | re2 = /^(.+?)(ed|ing)$/; 97 | if (re.test(w)) { 98 | var fp = re.exec(w); 99 | re = new RegExp(mgr0); 100 | if (re.test(fp[1])) { 101 | re = /.$/; 102 | w = w.replace(re,""); 103 | } 104 | } 105 | else if (re2.test(w)) { 106 | var fp = re2.exec(w); 107 | stem = fp[1]; 108 | re2 = new RegExp(s_v); 109 | if (re2.test(stem)) { 110 | w = stem; 111 | re2 = /(at|bl|iz)$/; 112 | re3 = new RegExp("([^aeiouylsz])\\1$"); 113 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 114 | if (re2.test(w)) 115 | w = w + "e"; 116 | else if (re3.test(w)) { 117 | re = /.$/; 118 | w = w.replace(re,""); 119 | } 120 | else if (re4.test(w)) 121 | w = w + "e"; 122 | } 123 | } 124 | 125 | // Step 1c 126 | re = /^(.+?)y$/; 127 | if (re.test(w)) { 128 | var fp = re.exec(w); 129 | stem = fp[1]; 130 | re = new RegExp(s_v); 131 | if (re.test(stem)) 132 | w = stem + "i"; 133 | } 134 | 135 | // Step 2 136 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 137 | if (re.test(w)) { 138 | var fp = re.exec(w); 139 | stem = fp[1]; 140 | suffix = fp[2]; 141 | re = new RegExp(mgr0); 142 | if (re.test(stem)) 143 | w = stem + step2list[suffix]; 144 | } 145 | 146 | // Step 3 147 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 148 | if (re.test(w)) { 149 | var fp = re.exec(w); 150 | stem = fp[1]; 151 | suffix = fp[2]; 152 | re = new RegExp(mgr0); 153 | if (re.test(stem)) 154 | w = stem + step3list[suffix]; 155 | } 156 | 157 | // Step 4 158 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 159 | re2 = /^(.+?)(s|t)(ion)$/; 160 | if (re.test(w)) { 161 | var fp = re.exec(w); 162 | stem = fp[1]; 163 | re = new RegExp(mgr1); 164 | if (re.test(stem)) 165 | w = stem; 166 | } 167 | else if (re2.test(w)) { 168 | var fp = re2.exec(w); 169 | stem = fp[1] + fp[2]; 170 | re2 = new RegExp(mgr1); 171 | if (re2.test(stem)) 172 | w = stem; 173 | } 174 | 175 | // Step 5 176 | re = /^(.+?)e$/; 177 | if (re.test(w)) { 178 | var fp = re.exec(w); 179 | stem = fp[1]; 180 | re = new RegExp(mgr1); 181 | re2 = new RegExp(meq1); 182 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 183 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 184 | w = stem; 185 | } 186 | re = /ll$/; 187 | re2 = new RegExp(mgr1); 188 | if (re.test(w) && re2.test(w)) { 189 | re = /.$/; 190 | w = w.replace(re,""); 191 | } 192 | 193 | // and turn initial Y back to y 194 | if (firstch == "y") 195 | w = firstch.toLowerCase() + w.substr(1); 196 | return w; 197 | } 198 | } 199 | 200 | -------------------------------------------------------------------------------- /docs/build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | .highlight .hll { background-color: #ffffcc } 7 | .highlight { background: #f8f8f8; } 8 | .highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ 9 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 10 | .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 11 | .highlight .o { color: #666666 } /* Operator */ 12 | .highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ 13 | .highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ 14 | .highlight .cp { color: #9C6500 } /* Comment.Preproc */ 15 | .highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ 16 | .highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ 17 | .highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ 18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 19 | .highlight .ge { font-style: italic } /* Generic.Emph */ 20 | .highlight .gr { color: #E40000 } /* Generic.Error */ 21 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 22 | .highlight .gi { color: #008400 } /* Generic.Inserted */ 23 | .highlight .go { color: #717171 } /* Generic.Output */ 24 | .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 25 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 26 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 27 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 28 | .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 29 | .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 30 | .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 31 | .highlight .kp { color: #008000 } /* Keyword.Pseudo */ 32 | .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 33 | .highlight .kt { color: #B00040 } /* Keyword.Type */ 34 | .highlight .m { color: #666666 } /* Literal.Number */ 35 | .highlight .s { color: #BA2121 } /* Literal.String */ 36 | .highlight .na { color: #687822 } /* Name.Attribute */ 37 | .highlight .nb { color: #008000 } /* Name.Builtin */ 38 | .highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 39 | .highlight .no { color: #880000 } /* Name.Constant */ 40 | .highlight .nd { color: #AA22FF } /* Name.Decorator */ 41 | .highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ 42 | .highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ 43 | .highlight .nf { color: #0000FF } /* Name.Function */ 44 | .highlight .nl { color: #767600 } /* Name.Label */ 45 | .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 46 | .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ 47 | .highlight .nv { color: #19177C } /* Name.Variable */ 48 | .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 49 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 50 | .highlight .mb { color: #666666 } /* Literal.Number.Bin */ 51 | .highlight .mf { color: #666666 } /* Literal.Number.Float */ 52 | .highlight .mh { color: #666666 } /* Literal.Number.Hex */ 53 | .highlight .mi { color: #666666 } /* Literal.Number.Integer */ 54 | .highlight .mo { color: #666666 } /* Literal.Number.Oct */ 55 | .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ 56 | .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ 57 | .highlight .sc { color: #BA2121 } /* Literal.String.Char */ 58 | .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ 59 | .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 60 | .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ 61 | .highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ 62 | .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ 63 | .highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ 64 | .highlight .sx { color: #008000 } /* Literal.String.Other */ 65 | .highlight .sr { color: #A45A77 } /* Literal.String.Regex */ 66 | .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ 67 | .highlight .ss { color: #19177C } /* Literal.String.Symbol */ 68 | .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ 69 | .highlight .fm { color: #0000FF } /* Name.Function.Magic */ 70 | .highlight .vc { color: #19177C } /* Name.Variable.Class */ 71 | .highlight .vg { color: #19177C } /* Name.Variable.Global */ 72 | .highlight .vi { color: #19177C } /* Name.Variable.Instance */ 73 | .highlight .vm { color: #19177C } /* Name.Variable.Magic */ 74 | .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/build/html/_static/searchtools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * searchtools.js 3 | * ~~~~~~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for the full-text search. 6 | * 7 | * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | "use strict"; 12 | 13 | /** 14 | * Simple result scoring code. 15 | */ 16 | if (typeof Scorer === "undefined") { 17 | var Scorer = { 18 | // Implement the following function to further tweak the score for each result 19 | // The function takes a result array [docname, title, anchor, descr, score, filename] 20 | // and returns the new score. 21 | /* 22 | score: result => { 23 | const [docname, title, anchor, descr, score, filename] = result 24 | return score 25 | }, 26 | */ 27 | 28 | // query matches the full name of an object 29 | objNameMatch: 11, 30 | // or matches in the last dotted part of the object name 31 | objPartialMatch: 6, 32 | // Additive scores depending on the priority of the object 33 | objPrio: { 34 | 0: 15, // used to be importantResults 35 | 1: 5, // used to be objectResults 36 | 2: -5, // used to be unimportantResults 37 | }, 38 | // Used when the priority is not in the mapping. 39 | objPrioDefault: 0, 40 | 41 | // query found in title 42 | title: 15, 43 | partialTitle: 7, 44 | // query found in terms 45 | term: 5, 46 | partialTerm: 2, 47 | }; 48 | } 49 | 50 | const _removeChildren = (element) => { 51 | while (element && element.lastChild) element.removeChild(element.lastChild); 52 | }; 53 | 54 | /** 55 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping 56 | */ 57 | const _escapeRegExp = (string) => 58 | string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string 59 | 60 | const _displayItem = (item, searchTerms) => { 61 | const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; 62 | const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; 63 | const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; 64 | const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; 65 | const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; 66 | 67 | const [docName, title, anchor, descr, score, _filename] = item; 68 | 69 | let listItem = document.createElement("li"); 70 | let requestUrl; 71 | let linkUrl; 72 | if (docBuilder === "dirhtml") { 73 | // dirhtml builder 74 | let dirname = docName + "/"; 75 | if (dirname.match(/\/index\/$/)) 76 | dirname = dirname.substring(0, dirname.length - 6); 77 | else if (dirname === "index/") dirname = ""; 78 | requestUrl = docUrlRoot + dirname; 79 | linkUrl = requestUrl; 80 | } else { 81 | // normal html builders 82 | requestUrl = docUrlRoot + docName + docFileSuffix; 83 | linkUrl = docName + docLinkSuffix; 84 | } 85 | let linkEl = listItem.appendChild(document.createElement("a")); 86 | linkEl.href = linkUrl + anchor; 87 | linkEl.dataset.score = score; 88 | linkEl.innerHTML = title; 89 | if (descr) 90 | listItem.appendChild(document.createElement("span")).innerHTML = 91 | " (" + descr + ")"; 92 | else if (showSearchSummary) 93 | fetch(requestUrl) 94 | .then((responseData) => responseData.text()) 95 | .then((data) => { 96 | if (data) 97 | listItem.appendChild( 98 | Search.makeSearchSummary(data, searchTerms) 99 | ); 100 | }); 101 | Search.output.appendChild(listItem); 102 | }; 103 | const _finishSearch = (resultCount) => { 104 | Search.stopPulse(); 105 | Search.title.innerText = _("Search Results"); 106 | if (!resultCount) 107 | Search.status.innerText = Documentation.gettext( 108 | "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." 109 | ); 110 | else 111 | Search.status.innerText = _( 112 | `Search finished, found ${resultCount} page(s) matching the search query.` 113 | ); 114 | }; 115 | const _displayNextItem = ( 116 | results, 117 | resultCount, 118 | searchTerms 119 | ) => { 120 | // results left, load the summary and display it 121 | // this is intended to be dynamic (don't sub resultsCount) 122 | if (results.length) { 123 | _displayItem(results.pop(), searchTerms); 124 | setTimeout( 125 | () => _displayNextItem(results, resultCount, searchTerms), 126 | 5 127 | ); 128 | } 129 | // search finished, update title and status message 130 | else _finishSearch(resultCount); 131 | }; 132 | 133 | /** 134 | * Default splitQuery function. Can be overridden in ``sphinx.search`` with a 135 | * custom function per language. 136 | * 137 | * The regular expression works by splitting the string on consecutive characters 138 | * that are not Unicode letters, numbers, underscores, or emoji characters. 139 | * This is the same as ``\W+`` in Python, preserving the surrogate pair area. 140 | */ 141 | if (typeof splitQuery === "undefined") { 142 | var splitQuery = (query) => query 143 | .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) 144 | .filter(term => term) // remove remaining empty strings 145 | } 146 | 147 | /** 148 | * Search Module 149 | */ 150 | const Search = { 151 | _index: null, 152 | _queued_query: null, 153 | _pulse_status: -1, 154 | 155 | htmlToText: (htmlString) => { 156 | const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); 157 | htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); 158 | const docContent = htmlElement.querySelector('[role="main"]'); 159 | if (docContent !== undefined) return docContent.textContent; 160 | console.warn( 161 | "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." 162 | ); 163 | return ""; 164 | }, 165 | 166 | init: () => { 167 | const query = new URLSearchParams(window.location.search).get("q"); 168 | document 169 | .querySelectorAll('input[name="q"]') 170 | .forEach((el) => (el.value = query)); 171 | if (query) Search.performSearch(query); 172 | }, 173 | 174 | loadIndex: (url) => 175 | (document.body.appendChild(document.createElement("script")).src = url), 176 | 177 | setIndex: (index) => { 178 | Search._index = index; 179 | if (Search._queued_query !== null) { 180 | const query = Search._queued_query; 181 | Search._queued_query = null; 182 | Search.query(query); 183 | } 184 | }, 185 | 186 | hasIndex: () => Search._index !== null, 187 | 188 | deferQuery: (query) => (Search._queued_query = query), 189 | 190 | stopPulse: () => (Search._pulse_status = -1), 191 | 192 | startPulse: () => { 193 | if (Search._pulse_status >= 0) return; 194 | 195 | const pulse = () => { 196 | Search._pulse_status = (Search._pulse_status + 1) % 4; 197 | Search.dots.innerText = ".".repeat(Search._pulse_status); 198 | if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); 199 | }; 200 | pulse(); 201 | }, 202 | 203 | /** 204 | * perform a search for something (or wait until index is loaded) 205 | */ 206 | performSearch: (query) => { 207 | // create the required interface elements 208 | const searchText = document.createElement("h2"); 209 | searchText.textContent = _("Searching"); 210 | const searchSummary = document.createElement("p"); 211 | searchSummary.classList.add("search-summary"); 212 | searchSummary.innerText = ""; 213 | const searchList = document.createElement("ul"); 214 | searchList.classList.add("search"); 215 | 216 | const out = document.getElementById("search-results"); 217 | Search.title = out.appendChild(searchText); 218 | Search.dots = Search.title.appendChild(document.createElement("span")); 219 | Search.status = out.appendChild(searchSummary); 220 | Search.output = out.appendChild(searchList); 221 | 222 | const searchProgress = document.getElementById("search-progress"); 223 | // Some themes don't use the search progress node 224 | if (searchProgress) { 225 | searchProgress.innerText = _("Preparing search..."); 226 | } 227 | Search.startPulse(); 228 | 229 | // index already loaded, the browser was quick! 230 | if (Search.hasIndex()) Search.query(query); 231 | else Search.deferQuery(query); 232 | }, 233 | 234 | /** 235 | * execute search (requires search index to be loaded) 236 | */ 237 | query: (query) => { 238 | const filenames = Search._index.filenames; 239 | const docNames = Search._index.docnames; 240 | const titles = Search._index.titles; 241 | const allTitles = Search._index.alltitles; 242 | const indexEntries = Search._index.indexentries; 243 | 244 | // stem the search terms and add them to the correct list 245 | const stemmer = new Stemmer(); 246 | const searchTerms = new Set(); 247 | const excludedTerms = new Set(); 248 | const highlightTerms = new Set(); 249 | const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); 250 | splitQuery(query.trim()).forEach((queryTerm) => { 251 | const queryTermLower = queryTerm.toLowerCase(); 252 | 253 | // maybe skip this "word" 254 | // stopwords array is from language_data.js 255 | if ( 256 | stopwords.indexOf(queryTermLower) !== -1 || 257 | queryTerm.match(/^\d+$/) 258 | ) 259 | return; 260 | 261 | // stem the word 262 | let word = stemmer.stemWord(queryTermLower); 263 | // select the correct list 264 | if (word[0] === "-") excludedTerms.add(word.substr(1)); 265 | else { 266 | searchTerms.add(word); 267 | highlightTerms.add(queryTermLower); 268 | } 269 | }); 270 | 271 | if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js 272 | localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) 273 | } 274 | 275 | // console.debug("SEARCH: searching for:"); 276 | // console.info("required: ", [...searchTerms]); 277 | // console.info("excluded: ", [...excludedTerms]); 278 | 279 | // array of [docname, title, anchor, descr, score, filename] 280 | let results = []; 281 | _removeChildren(document.getElementById("search-progress")); 282 | 283 | const queryLower = query.toLowerCase(); 284 | for (const [title, foundTitles] of Object.entries(allTitles)) { 285 | if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { 286 | for (const [file, id] of foundTitles) { 287 | let score = Math.round(100 * queryLower.length / title.length) 288 | results.push([ 289 | docNames[file], 290 | titles[file] !== title ? `${titles[file]} > ${title}` : title, 291 | id !== null ? "#" + id : "", 292 | null, 293 | score, 294 | filenames[file], 295 | ]); 296 | } 297 | } 298 | } 299 | 300 | // search for explicit entries in index directives 301 | for (const [entry, foundEntries] of Object.entries(indexEntries)) { 302 | if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { 303 | for (const [file, id] of foundEntries) { 304 | let score = Math.round(100 * queryLower.length / entry.length) 305 | results.push([ 306 | docNames[file], 307 | titles[file], 308 | id ? "#" + id : "", 309 | null, 310 | score, 311 | filenames[file], 312 | ]); 313 | } 314 | } 315 | } 316 | 317 | // lookup as object 318 | objectTerms.forEach((term) => 319 | results.push(...Search.performObjectSearch(term, objectTerms)) 320 | ); 321 | 322 | // lookup as search terms in fulltext 323 | results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); 324 | 325 | // let the scorer override scores with a custom scoring function 326 | if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); 327 | 328 | // now sort the results by score (in opposite order of appearance, since the 329 | // display function below uses pop() to retrieve items) and then 330 | // alphabetically 331 | results.sort((a, b) => { 332 | const leftScore = a[4]; 333 | const rightScore = b[4]; 334 | if (leftScore === rightScore) { 335 | // same score: sort alphabetically 336 | const leftTitle = a[1].toLowerCase(); 337 | const rightTitle = b[1].toLowerCase(); 338 | if (leftTitle === rightTitle) return 0; 339 | return leftTitle > rightTitle ? -1 : 1; // inverted is intentional 340 | } 341 | return leftScore > rightScore ? 1 : -1; 342 | }); 343 | 344 | // remove duplicate search results 345 | // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept 346 | let seen = new Set(); 347 | results = results.reverse().reduce((acc, result) => { 348 | let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); 349 | if (!seen.has(resultStr)) { 350 | acc.push(result); 351 | seen.add(resultStr); 352 | } 353 | return acc; 354 | }, []); 355 | 356 | results = results.reverse(); 357 | 358 | // for debugging 359 | //Search.lastresults = results.slice(); // a copy 360 | // console.info("search results:", Search.lastresults); 361 | 362 | // print the results 363 | _displayNextItem(results, results.length, searchTerms); 364 | }, 365 | 366 | /** 367 | * search for object names 368 | */ 369 | performObjectSearch: (object, objectTerms) => { 370 | const filenames = Search._index.filenames; 371 | const docNames = Search._index.docnames; 372 | const objects = Search._index.objects; 373 | const objNames = Search._index.objnames; 374 | const titles = Search._index.titles; 375 | 376 | const results = []; 377 | 378 | const objectSearchCallback = (prefix, match) => { 379 | const name = match[4] 380 | const fullname = (prefix ? prefix + "." : "") + name; 381 | const fullnameLower = fullname.toLowerCase(); 382 | if (fullnameLower.indexOf(object) < 0) return; 383 | 384 | let score = 0; 385 | const parts = fullnameLower.split("."); 386 | 387 | // check for different match types: exact matches of full name or 388 | // "last name" (i.e. last dotted part) 389 | if (fullnameLower === object || parts.slice(-1)[0] === object) 390 | score += Scorer.objNameMatch; 391 | else if (parts.slice(-1)[0].indexOf(object) > -1) 392 | score += Scorer.objPartialMatch; // matches in last name 393 | 394 | const objName = objNames[match[1]][2]; 395 | const title = titles[match[0]]; 396 | 397 | // If more than one term searched for, we require other words to be 398 | // found in the name/title/description 399 | const otherTerms = new Set(objectTerms); 400 | otherTerms.delete(object); 401 | if (otherTerms.size > 0) { 402 | const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); 403 | if ( 404 | [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) 405 | ) 406 | return; 407 | } 408 | 409 | let anchor = match[3]; 410 | if (anchor === "") anchor = fullname; 411 | else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; 412 | 413 | const descr = objName + _(", in ") + title; 414 | 415 | // add custom score for some objects according to scorer 416 | if (Scorer.objPrio.hasOwnProperty(match[2])) 417 | score += Scorer.objPrio[match[2]]; 418 | else score += Scorer.objPrioDefault; 419 | 420 | results.push([ 421 | docNames[match[0]], 422 | fullname, 423 | "#" + anchor, 424 | descr, 425 | score, 426 | filenames[match[0]], 427 | ]); 428 | }; 429 | Object.keys(objects).forEach((prefix) => 430 | objects[prefix].forEach((array) => 431 | objectSearchCallback(prefix, array) 432 | ) 433 | ); 434 | return results; 435 | }, 436 | 437 | /** 438 | * search for full-text terms in the index 439 | */ 440 | performTermsSearch: (searchTerms, excludedTerms) => { 441 | // prepare search 442 | const terms = Search._index.terms; 443 | const titleTerms = Search._index.titleterms; 444 | const filenames = Search._index.filenames; 445 | const docNames = Search._index.docnames; 446 | const titles = Search._index.titles; 447 | 448 | const scoreMap = new Map(); 449 | const fileMap = new Map(); 450 | 451 | // perform the search on the required terms 452 | searchTerms.forEach((word) => { 453 | const files = []; 454 | const arr = [ 455 | { files: terms[word], score: Scorer.term }, 456 | { files: titleTerms[word], score: Scorer.title }, 457 | ]; 458 | // add support for partial matches 459 | if (word.length > 2) { 460 | const escapedWord = _escapeRegExp(word); 461 | Object.keys(terms).forEach((term) => { 462 | if (term.match(escapedWord) && !terms[word]) 463 | arr.push({ files: terms[term], score: Scorer.partialTerm }); 464 | }); 465 | Object.keys(titleTerms).forEach((term) => { 466 | if (term.match(escapedWord) && !titleTerms[word]) 467 | arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); 468 | }); 469 | } 470 | 471 | // no match but word was a required one 472 | if (arr.every((record) => record.files === undefined)) return; 473 | 474 | // found search word in contents 475 | arr.forEach((record) => { 476 | if (record.files === undefined) return; 477 | 478 | let recordFiles = record.files; 479 | if (recordFiles.length === undefined) recordFiles = [recordFiles]; 480 | files.push(...recordFiles); 481 | 482 | // set score for the word in each file 483 | recordFiles.forEach((file) => { 484 | if (!scoreMap.has(file)) scoreMap.set(file, {}); 485 | scoreMap.get(file)[word] = record.score; 486 | }); 487 | }); 488 | 489 | // create the mapping 490 | files.forEach((file) => { 491 | if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) 492 | fileMap.get(file).push(word); 493 | else fileMap.set(file, [word]); 494 | }); 495 | }); 496 | 497 | // now check if the files don't contain excluded terms 498 | const results = []; 499 | for (const [file, wordList] of fileMap) { 500 | // check if all requirements are matched 501 | 502 | // as search terms with length < 3 are discarded 503 | const filteredTermCount = [...searchTerms].filter( 504 | (term) => term.length > 2 505 | ).length; 506 | if ( 507 | wordList.length !== searchTerms.size && 508 | wordList.length !== filteredTermCount 509 | ) 510 | continue; 511 | 512 | // ensure that none of the excluded terms is in the search result 513 | if ( 514 | [...excludedTerms].some( 515 | (term) => 516 | terms[term] === file || 517 | titleTerms[term] === file || 518 | (terms[term] || []).includes(file) || 519 | (titleTerms[term] || []).includes(file) 520 | ) 521 | ) 522 | break; 523 | 524 | // select one (max) score for the file. 525 | const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); 526 | // add result to the result list 527 | results.push([ 528 | docNames[file], 529 | titles[file], 530 | "", 531 | null, 532 | score, 533 | filenames[file], 534 | ]); 535 | } 536 | return results; 537 | }, 538 | 539 | /** 540 | * helper function to return a node containing the 541 | * search summary for a given text. keywords is a list 542 | * of stemmed words. 543 | */ 544 | makeSearchSummary: (htmlText, keywords) => { 545 | const text = Search.htmlToText(htmlText); 546 | if (text === "") return null; 547 | 548 | const textLower = text.toLowerCase(); 549 | const actualStartPosition = [...keywords] 550 | .map((k) => textLower.indexOf(k.toLowerCase())) 551 | .filter((i) => i > -1) 552 | .slice(-1)[0]; 553 | const startWithContext = Math.max(actualStartPosition - 120, 0); 554 | 555 | const top = startWithContext === 0 ? "" : "..."; 556 | const tail = startWithContext + 240 < text.length ? "..." : ""; 557 | 558 | let summary = document.createElement("p"); 559 | summary.classList.add("context"); 560 | summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; 561 | 562 | return summary; 563 | }, 564 | }; 565 | 566 | _ready(Search.init); 567 | -------------------------------------------------------------------------------- /docs/build/html/_static/sphinx_highlight.js: -------------------------------------------------------------------------------- 1 | /* Highlighting utilities for Sphinx HTML documentation. */ 2 | "use strict"; 3 | 4 | const SPHINX_HIGHLIGHT_ENABLED = true 5 | 6 | /** 7 | * highlight a given string on a node by wrapping it in 8 | * span elements with the given class name. 9 | */ 10 | const _highlight = (node, addItems, text, className) => { 11 | if (node.nodeType === Node.TEXT_NODE) { 12 | const val = node.nodeValue; 13 | const parent = node.parentNode; 14 | const pos = val.toLowerCase().indexOf(text); 15 | if ( 16 | pos >= 0 && 17 | !parent.classList.contains(className) && 18 | !parent.classList.contains("nohighlight") 19 | ) { 20 | let span; 21 | 22 | const closestNode = parent.closest("body, svg, foreignObject"); 23 | const isInSVG = closestNode && closestNode.matches("svg"); 24 | if (isInSVG) { 25 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 26 | } else { 27 | span = document.createElement("span"); 28 | span.classList.add(className); 29 | } 30 | 31 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 32 | parent.insertBefore( 33 | span, 34 | parent.insertBefore( 35 | document.createTextNode(val.substr(pos + text.length)), 36 | node.nextSibling 37 | ) 38 | ); 39 | node.nodeValue = val.substr(0, pos); 40 | 41 | if (isInSVG) { 42 | const rect = document.createElementNS( 43 | "http://www.w3.org/2000/svg", 44 | "rect" 45 | ); 46 | const bbox = parent.getBBox(); 47 | rect.x.baseVal.value = bbox.x; 48 | rect.y.baseVal.value = bbox.y; 49 | rect.width.baseVal.value = bbox.width; 50 | rect.height.baseVal.value = bbox.height; 51 | rect.setAttribute("class", className); 52 | addItems.push({ parent: parent, target: rect }); 53 | } 54 | } 55 | } else if (node.matches && !node.matches("button, select, textarea")) { 56 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 57 | } 58 | }; 59 | const _highlightText = (thisNode, text, className) => { 60 | let addItems = []; 61 | _highlight(thisNode, addItems, text, className); 62 | addItems.forEach((obj) => 63 | obj.parent.insertAdjacentElement("beforebegin", obj.target) 64 | ); 65 | }; 66 | 67 | /** 68 | * Small JavaScript module for the documentation. 69 | */ 70 | const SphinxHighlight = { 71 | 72 | /** 73 | * highlight the search words provided in localstorage in the text 74 | */ 75 | highlightSearchWords: () => { 76 | if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight 77 | 78 | // get and clear terms from localstorage 79 | const url = new URL(window.location); 80 | const highlight = 81 | localStorage.getItem("sphinx_highlight_terms") 82 | || url.searchParams.get("highlight") 83 | || ""; 84 | localStorage.removeItem("sphinx_highlight_terms") 85 | url.searchParams.delete("highlight"); 86 | window.history.replaceState({}, "", url); 87 | 88 | // get individual terms from highlight string 89 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); 90 | if (terms.length === 0) return; // nothing to do 91 | 92 | // There should never be more than one element matching "div.body" 93 | const divBody = document.querySelectorAll("div.body"); 94 | const body = divBody.length ? divBody[0] : document.querySelector("body"); 95 | window.setTimeout(() => { 96 | terms.forEach((term) => _highlightText(body, term, "highlighted")); 97 | }, 10); 98 | 99 | const searchBox = document.getElementById("searchbox"); 100 | if (searchBox === null) return; 101 | searchBox.appendChild( 102 | document 103 | .createRange() 104 | .createContextualFragment( 105 | '" 109 | ) 110 | ); 111 | }, 112 | 113 | /** 114 | * helper function to hide the search marks again 115 | */ 116 | hideSearchWords: () => { 117 | document 118 | .querySelectorAll("#searchbox .highlight-link") 119 | .forEach((el) => el.remove()); 120 | document 121 | .querySelectorAll("span.highlighted") 122 | .forEach((el) => el.classList.remove("highlighted")); 123 | localStorage.removeItem("sphinx_highlight_terms") 124 | }, 125 | 126 | initEscapeListener: () => { 127 | // only install a listener if it is really needed 128 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; 129 | 130 | document.addEventListener("keydown", (event) => { 131 | // bail for input elements 132 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 133 | // bail with special keys 134 | if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; 135 | if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { 136 | SphinxHighlight.hideSearchWords(); 137 | event.preventDefault(); 138 | } 139 | }); 140 | }, 141 | }; 142 | 143 | _ready(SphinxHighlight.highlightSearchWords); 144 | _ready(SphinxHighlight.initEscapeListener); 145 | -------------------------------------------------------------------------------- /docs/build/html/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Index — Python Best Practices 1.0.0 documentation 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 45 | 46 |
50 | 51 |
52 |
53 |
54 |
    55 |
  • 56 | 57 |
  • 58 |
  • 59 |
60 |
61 |
62 |
63 |
64 | 65 | 66 |

Index

67 | 68 |
69 | A 70 | | F 71 | | I 72 | | M 73 | | N 74 | | P 75 | | R 76 | | T 77 | 78 |
79 |

A

80 | 81 | 85 |
86 | 87 |

F

88 | 89 | 93 | 97 |
98 | 99 |

I

100 | 101 | 105 |
106 | 107 |

M

108 | 109 | 128 |
129 | 130 |

N

131 | 132 | 136 |
137 | 138 |

P

139 | 140 | 144 | 150 |
151 | 152 |

R

153 | 154 | 158 |
159 | 160 |

T

161 | 162 | 188 | 222 |
223 | 224 | 225 | 226 |
227 |
228 |
229 | 230 |
231 | 232 |
233 |

© Copyright 2023, Claudio Shigueo Watanabe.

234 |
235 | 236 | Built with Sphinx using a 237 | theme 238 | provided by Read the Docs. 239 | 240 | 241 |
242 |
243 |
244 |
245 |
246 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /docs/build/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Welcome to Python Best Practices’s documentation! — Python Best Practices 1.0.0 documentation 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 50 | 51 |
55 | 56 |
57 |
58 |
59 |
    60 |
  • 61 | 62 |
  • 63 | View page source 64 |
  • 65 |
66 |
67 |
68 |
69 |
70 | 71 |
72 |

Welcome to Python Best Practices’s documentation!

73 |
74 |
75 |
76 |
77 |

Indices and tables

78 | 83 |
84 | 85 | 86 |
87 |
88 |
89 | 90 |
91 | 92 |
93 |

© Copyright 2023, Claudio Shigueo Watanabe.

94 |
95 | 96 | Built with Sphinx using a 97 | theme 98 | provided by Read the Docs. 99 | 100 | 101 |
102 |
103 |
104 |
105 |
106 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /docs/build/html/modules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | python-best-practices — Python Best Practices 1.0.0 documentation 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 49 | 50 |
54 | 55 |
56 |
57 |
58 | 65 |
66 |
67 |
68 |
69 | 70 |
71 |

python-best-practices

72 | 116 |
117 | 118 | 119 |
120 |
121 |
122 | 123 |
124 | 125 |
126 |

© Copyright 2023, Claudio Shigueo Watanabe.

127 |
128 | 129 | Built with Sphinx using a 130 | theme 131 | provided by Read the Docs. 132 | 133 | 134 |
135 |
136 |
137 |
138 |
139 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /docs/build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/docs/build/html/objects.inv -------------------------------------------------------------------------------- /docs/build/html/py-modindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Python Module Index — Python Best Practices 1.0.0 documentation 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 48 | 49 |
53 | 54 |
55 |
56 |
57 |
    58 |
  • 59 | 60 |
  • 61 |
  • 62 |
63 |
64 |
65 |
66 |
67 | 68 | 69 |

Python Module Index

70 | 71 |
72 | t 73 |
74 | 75 | 76 | 77 | 79 | 80 | 82 | 85 | 86 | 87 | 90 | 91 | 92 | 95 | 96 | 97 | 100 | 101 | 102 | 105 | 106 | 107 | 110 |
 
78 | t
83 | testing 84 |
    88 | testing.conftest 89 |
    93 | testing.mocking 94 |
    98 | testing.mocking_test 99 |
    103 | testing.pytesting 104 |
    108 | testing.pytesting_test 109 |
111 | 112 | 113 |
114 |
115 |
116 | 117 |
118 | 119 |
120 |

© Copyright 2023, Claudio Shigueo Watanabe.

121 |
122 | 123 | Built with Sphinx using a 124 | theme 125 | provided by Read the Docs. 126 | 127 | 128 |
129 |
130 |
131 |
132 |
133 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /docs/build/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Search — Python Best Practices 1.0.0 documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 48 | 49 |
53 | 54 |
55 |
56 |
57 |
    58 |
  • 59 | 60 |
  • 61 |
  • 62 |
63 |
64 |
65 |
66 |
67 | 68 | 75 | 76 | 77 |
78 | 79 |
80 | 81 |
82 |
83 |
84 | 85 |
86 | 87 |
88 |

© Copyright 2023, Claudio Shigueo Watanabe.

89 |
90 | 91 | Built with Sphinx using a 92 | theme 93 | provided by Read the Docs. 94 | 95 | 96 |
97 |
98 |
99 |
100 |
101 | 106 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /docs/build/html/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({"docnames": ["index", "modules", "testing"], "filenames": ["index.rst", "modules.rst", "testing.rst"], "titles": ["Welcome to Python Best Practices\u2019s documentation!", "python-best-practices", "testing package"], "terms": {"index": 0, "modul": [0, 1], "search": 0, "page": 0, "test": 1, "packag": 1, "submodul": 1, "conftest": 1, "mock": 1, "mocking_test": 1, "pytest": 1, "pytesting_test": 1, "content": 1, "fixture_product_1": [1, 2], "fixture_product_2": [1, 2], "product_fixture_list": [1, 2], "time_calcul": [1, 2], "run_dic": [1, 2], "testrundic": [1, 2], "test_mock_function_with_dic": [1, 2], "test_mock_funct": [1, 2], "product": [1, 2], "id": [1, 2], "name": [1, 2], "price": [1, 2], "add": [1, 2], "test_add": [1, 2], "test_add_parametr": [1, 2], "test_product_list": [1, 2], "thi": 2, "file": 2, "ha": 2, "fixtur": 2, "includ": 2, "directori": 2, "exampl": 2, "one": 2, "anoth": 2, "compos": 2, "two": 2, "other": 2, "an": 2, "autous": 2, "support": 2, "code": 2, "demonstr": 2, "str": 2, "simul": 2, "dice": 2, "roll": 2, "return": 2, "lucki": 2, "unlucki": 2, "class": 2, "methodnam": 2, "runtest": 2, "base": 2, "testcas": 2, "function": 2, "isol": 2, "from": 2, "random": 2, "librari": 2, "mock_random": 2, "i": 2, "call": 2, "show": 2, "featur": 2, "uuid": 2, "float": 2, "object": 2, "number_1": 2, "int": 2, "number_2": 2, "number": 2, "paramet": 2, "first": 2, "second": 2, "sum": 2, "restat": 2, "do": 2, "simpl": 2, "parameter_1": 2, "parameter_2": 2, "expected_result": 2, "us": 2, "parametr": 2}, "objects": {"": [[2, 0, 0, "-", "testing"]], "testing": [[2, 0, 0, "-", "conftest"], [2, 0, 0, "-", "mocking"], [2, 0, 0, "-", "mocking_test"], [2, 0, 0, "-", "pytesting"], [2, 0, 0, "-", "pytesting_test"]], "testing.conftest": [[2, 1, 1, "", "fixture_product_1"], [2, 1, 1, "", "fixture_product_2"], [2, 1, 1, "", "product_fixture_list"], [2, 1, 1, "", "time_calculator"]], "testing.mocking": [[2, 1, 1, "", "run_dice"]], "testing.mocking_test": [[2, 2, 1, "", "TestRunDice"], [2, 1, 1, "", "test_mock_function"]], "testing.mocking_test.TestRunDice": [[2, 3, 1, "", "test_mock_function_with_dice"]], "testing.pytesting": [[2, 2, 1, "", "Product"], [2, 1, 1, "", "add"]], "testing.pytesting.Product": [[2, 4, 1, "", "id"], [2, 4, 1, "", "name"], [2, 4, 1, "", "price"]], "testing.pytesting_test": [[2, 1, 1, "", "test_add"], [2, 1, 1, "", "test_add_parametrize"], [2, 1, 1, "", "test_product_list"]]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:class", "3": "py:method", "4": "py:attribute"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "class", "Python class"], "3": ["py", "method", "Python method"], "4": ["py", "attribute", "Python attribute"]}, "titleterms": {"welcom": 0, "python": [0, 1], "best": [0, 1], "practic": [0, 1], "": 0, "document": 0, "indic": 0, "tabl": 0, "test": 2, "packag": 2, "submodul": 2, "conftest": 2, "modul": 2, "mock": 2, "mocking_test": 2, "pytest": 2, "pytesting_test": 2, "content": 2}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 57}, "alltitles": {"Welcome to Python Best Practices\u2019s documentation!": [[0, "welcome-to-python-best-practices-s-documentation"]], "Indices and tables": [[0, "indices-and-tables"]], "python-best-practices": [[1, "python-best-practices"]], "testing package": [[2, "testing-package"]], "Submodules": [[2, "submodules"]], "testing.conftest module": [[2, "module-testing.conftest"]], "testing.mocking module": [[2, "module-testing.mocking"]], "testing.mocking_test module": [[2, "module-testing.mocking_test"]], "testing.pytesting module": [[2, "module-testing.pytesting"]], "testing.pytesting_test module": [[2, "module-testing.pytesting_test"]], "Module contents": [[2, "module-testing"]]}, "indexentries": {"product (class in testing.pytesting)": [[2, "testing.pytesting.Product"]], "testrundice (class in testing.mocking_test)": [[2, "testing.mocking_test.TestRunDice"]], "add() (in module testing.pytesting)": [[2, "testing.pytesting.add"]], "fixture_product_1() (in module testing.conftest)": [[2, "testing.conftest.fixture_product_1"]], "fixture_product_2() (in module testing.conftest)": [[2, "testing.conftest.fixture_product_2"]], "id (testing.pytesting.product attribute)": [[2, "testing.pytesting.Product.id"]], "module": [[2, "module-testing"], [2, "module-testing.conftest"], [2, "module-testing.mocking"], [2, "module-testing.mocking_test"], [2, "module-testing.pytesting"], [2, "module-testing.pytesting_test"]], "name (testing.pytesting.product attribute)": [[2, "testing.pytesting.Product.name"]], "price (testing.pytesting.product attribute)": [[2, "testing.pytesting.Product.price"]], "product_fixture_list() (in module testing.conftest)": [[2, "testing.conftest.product_fixture_list"]], "run_dice() (in module testing.mocking)": [[2, "testing.mocking.run_dice"]], "test_add() (in module testing.pytesting_test)": [[2, "testing.pytesting_test.test_add"]], "test_add_parametrize() (in module testing.pytesting_test)": [[2, "testing.pytesting_test.test_add_parametrize"]], "test_mock_function() (in module testing.mocking_test)": [[2, "testing.mocking_test.test_mock_function"]], "test_mock_function_with_dice() (testing.mocking_test.testrundice method)": [[2, "testing.mocking_test.TestRunDice.test_mock_function_with_dice"]], "test_product_list() (in module testing.pytesting_test)": [[2, "testing.pytesting_test.test_product_list"]], "testing": [[2, "module-testing"]], "testing.conftest": [[2, "module-testing.conftest"]], "testing.mocking": [[2, "module-testing.mocking"]], "testing.mocking_test": [[2, "module-testing.mocking_test"]], "testing.pytesting": [[2, "module-testing.pytesting"]], "testing.pytesting_test": [[2, "module-testing.pytesting_test"]], "time_calculator() (in module testing.conftest)": [[2, "testing.conftest.time_calculator"]]}}) -------------------------------------------------------------------------------- /docs/build/html/testing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | testing package — Python Best Practices 1.0.0 documentation 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 88 | 89 |
93 | 94 |
95 |
96 |
97 | 104 |
105 |
106 |
107 |
108 | 109 |
110 |

testing package

111 |
112 |

Submodules

113 |
114 |
115 |

testing.conftest module

116 |

This file has fixtures for the tests included in this directory

117 |
118 |
119 | testing.conftest.fixture_product_1()
120 |

Example of a fixture of one product

121 |
122 | 123 |
124 |
125 | testing.conftest.fixture_product_2()
126 |

Example of a fixture of another product

127 |
128 | 129 |
130 |
131 | testing.conftest.product_fixture_list(fixture_product_1, fixture_product_2)
132 |

Example of a fixture composed of two other fixtures

133 |
134 | 135 |
136 |
137 | testing.conftest.time_calculator()
138 |

Example of an autouse fixture

139 |
140 | 141 |
142 |
143 |

testing.mocking module

144 |

This module has supporting code to demonstrate testing

145 |
146 |
147 | testing.mocking.run_dice() str
148 |

Simulate a dice roll 149 | :returns: ‘Lucky’ or ‘Unlucky’

150 |
151 | 152 |
153 |
154 |

testing.mocking_test module

155 |

Tests to demonstrate testing.

156 |
157 |
158 | class testing.mocking_test.TestRunDice(methodName='runTest')
159 |

Bases: TestCase

160 |

Tests for the run_dice function isolating it from random library

161 |
162 |
163 | test_mock_function_with_dice(mock_random)
164 |

Testing run_dice function

165 |
166 | 167 |
168 | 169 |
170 |
171 | testing.mocking_test.test_mock_function()
172 |

Test that a mock function is called

173 |
174 | 175 |
176 |
177 |

testing.pytesting module

178 |

Example code to show Pytest features

179 |
180 |
181 | class testing.pytesting.Product(id: UUID, price: float, name: str)
182 |

Bases: object

183 |

Class Product example

184 |
185 |
186 | id: UUID
187 |
188 | 189 |
190 |
191 | name: str
192 |
193 | 194 |
195 |
196 | price: float
197 |
198 | 199 |
200 | 201 |
202 |
203 | testing.pytesting.add(number_1: int, number_2: int) int
204 |

Add two numbers.

205 |
206 |
Parameters:
207 |
    208 |
  • number_1 – First number.

  • 209 |
  • number_2 – Second number.

  • 210 |
211 |
212 |
Returns:
213 |

Sum of two numbers.

214 |
215 |
216 |
217 | 218 |
219 |
220 |

testing.pytesting_test module

221 |

Tests restated to Pytest

222 |
223 |
224 | testing.pytesting_test.test_add()
225 |

Doing simple tests

226 |
227 | 228 |
229 |
230 | testing.pytesting_test.test_add_parametrize(parameter_1, parameter_2, expected_result)
231 |

Test example using parametrize feature of Pytest

232 |
233 | 234 |
235 |
236 | testing.pytesting_test.test_product_list(product_fixture_list)
237 |

Test example using fixture

238 |
239 | 240 |
241 |
242 |

Module contents

243 |
244 |
245 | 246 | 247 |
248 |
249 |
250 | 251 |
252 | 253 |
254 |

© Copyright 2023, Claudio Shigueo Watanabe.

255 |
256 | 257 | Built with Sphinx using a 258 | theme 259 | provided by Read the Docs. 260 | 261 | 262 |
263 |
264 |
265 |
266 |
267 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | import os 10 | import sys 11 | # Use this line if you chose to separate source and build directories 12 | sys.path.insert(0, os.path.abspath('../..')) 13 | # Use this line if you chose to NOT separate source and build directories 14 | # sys.path.insert(0, os.path.abspath('..')) 15 | 16 | project = 'Python Best Practices' 17 | copyright = '2023, Claudio Shigueo Watanabe' 18 | author = 'Claudio Shigueo Watanabe' 19 | release = '1.0.0' 20 | 21 | # -- General configuration --------------------------------------------------- 22 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 23 | 24 | extensions = ['sphinx.ext.autodoc'] 25 | 26 | templates_path = ['_templates'] 27 | exclude_patterns = [] 28 | 29 | 30 | 31 | # -- Options for HTML output ------------------------------------------------- 32 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 33 | 34 | html_theme = 'sphinx_rtd_theme' 35 | html_static_path = ['_static'] 36 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Python Best Practices documentation master file, created by 2 | sphinx-quickstart on Mon Apr 10 12:54:46 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Python Best Practices's documentation! 7 | ================================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | python-best-practices 2 | ===================== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | testing 8 | -------------------------------------------------------------------------------- /docs/source/testing.rst: -------------------------------------------------------------------------------- 1 | testing package 2 | =============== 3 | 4 | Submodules 5 | ---------- 6 | 7 | testing.conftest module 8 | ----------------------- 9 | 10 | .. automodule:: testing.conftest 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | testing.mocking module 16 | ---------------------- 17 | 18 | .. automodule:: testing.mocking 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | testing.mocking\_test module 24 | ---------------------------- 25 | 26 | .. automodule:: testing.mocking_test 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | testing.pytesting module 32 | ------------------------ 33 | 34 | .. automodule:: testing.pytesting 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | testing.pytesting\_test module 40 | ------------------------------ 41 | 42 | .. automodule:: testing.pytesting_test 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | Module contents 48 | --------------- 49 | 50 | .. automodule:: testing 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /documenting/documenting.py: -------------------------------------------------------------------------------- 1 | """ This class is an example on how to document Python code 2 | """ 3 | 4 | 5 | def transform_int_to_str(number: int) -> str: 6 | """ Transform a int into a string 7 | 8 | :param number: number to transform 9 | :returns: A string with 'The input was ' concatenated with the parameter 10 | """ 11 | return f"The input was {number}" 12 | 13 | 14 | class Transformer(): 15 | """ 16 | Small description of the class 17 | 18 | Larger description of the class. This class is just an example of how to 19 | document Python code. 20 | """ 21 | def transform_float_less_than_100_to_int(self, number: float) -> int: 22 | """ Transform a number into a string 23 | 24 | :param number: number to transform 25 | :raises ValueError: if the parameter is greater than or equal to 100 26 | :returns: A string with 'The input was ' concatenated with the 27 | parameter 28 | """ 29 | if number < 100: 30 | return int(number) 31 | else: 32 | raise ValueError(f"{number} is greater than or equal to 100") 33 | -------------------------------------------------------------------------------- /flask_examples/flask_basic.py: -------------------------------------------------------------------------------- 1 | """ Basic Flask Example 2 | """ 3 | 4 | 5 | from flask import Flask 6 | 7 | 8 | def create_basic_flask_app(): 9 | """ Basic Flask Example 10 | """ 11 | app_basic = Flask(__name__) 12 | 13 | @app_basic.route("/") 14 | def index(): 15 | """ Index route 16 | """ 17 | return "Hello World!" 18 | 19 | return app_basic 20 | 21 | 22 | if __name__ == "__main__": 23 | app = create_basic_flask_app() 24 | app.run(debug=True) 25 | -------------------------------------------------------------------------------- /flask_examples/flask_basic_test.py: -------------------------------------------------------------------------------- 1 | """ Basic Flask Example Test 2 | """ 3 | 4 | 5 | import pytest 6 | from flask_basic import create_basic_flask_app 7 | 8 | 9 | @pytest.fixture() 10 | def app_flask(): 11 | """ Instance Basic Flask App to test 12 | """ 13 | app = create_basic_flask_app() 14 | app.config.update({ 15 | "TESTING": True, 16 | }) 17 | 18 | yield app 19 | 20 | 21 | @pytest.fixture() 22 | def client(app_flask): 23 | """ Client test for Basic Flask App 24 | """ 25 | return app_flask.test_client() 26 | 27 | 28 | def test_request_example(client): 29 | """ Test request example 30 | """ 31 | response = client.get("/") 32 | assert b"Hello World!" in response.data 33 | -------------------------------------------------------------------------------- /pep8/blank_lines.py: -------------------------------------------------------------------------------- 1 | """ This is a Python module example to show PEP 8 blank lines code layout. 2 | """ 3 | 4 | # importing os module 5 | import os 6 | 7 | 8 | class CurrentDirectory: 9 | """ This is a class example that shows the current directory 10 | See that there are two blank lines before a class 11 | """ 12 | def __init__(self) -> None: 13 | self.current_directory = os.getcwd() 14 | 15 | def show_directory(self): 16 | """ This is a method example that shows the current directory 17 | See that there is only one blank line before a method 18 | """ 19 | return self.current_directory 20 | 21 | def list_directory(self): 22 | """ This is a method example that lists the current directory 23 | """ 24 | directory_listing = os.listdir(self.current_directory) 25 | 26 | # This blank line above is optional. 27 | # We can use these optional blank lines to organize the code. 28 | return directory_listing 29 | 30 | 31 | def top_level_function(): 32 | """ This is a function example of a top level function. 33 | See that there are two blank lines before a top level function 34 | """ 35 | return None 36 | -------------------------------------------------------------------------------- /pep8/blank_lines_test.py: -------------------------------------------------------------------------------- 1 | """ Tests for blank_lines module 2 | """ 3 | 4 | # importing os module 5 | import os 6 | from blank_lines import CurrentDirectory 7 | 8 | 9 | def test_current_directory(): 10 | """ Test for CurrentDirectory class 11 | """ 12 | current_directory = CurrentDirectory() 13 | 14 | assert current_directory.show_directory() == os.getcwd() 15 | -------------------------------------------------------------------------------- /pep8/line_breaking.py: -------------------------------------------------------------------------------- 1 | """ This module shows examples of how to break likes when our code passes the 2 | 79 character limit. 3 | """ 4 | 5 | 6 | def first_function_with_very_long_name( 7 | first_argument: str, 8 | second_argument: int) -> str: 9 | """ This function would pass the 79 character limit. 10 | """ 11 | return f"{first_argument} {second_argument}" 12 | 13 | 14 | def second_function_with_very_long_name( 15 | first_argument: str, 16 | second_argument: int 17 | ) -> str: 18 | """ This function would pass the 79 character limit. 19 | """ 20 | return f"{first_argument} {second_argument}" 21 | 22 | 23 | def third_function_with_very_long_name( 24 | first_argument: str, second_argument: int) -> str: 25 | """ This function would pass the 79 character limit. 26 | """ 27 | return f"{first_argument} {second_argument}" 28 | 29 | 30 | def fourth_function_with_very_long_name( 31 | first_argument: str, 32 | second_argument: int) -> str: 33 | """ This function would pass the 79 character limit. 34 | """ 35 | return f"{first_argument} {second_argument}" 36 | 37 | 38 | def fifth_function_with_very_long_name(first_argument: str, 39 | second_argument: int) -> str: 40 | """ This function would pass the 79 character limit. 41 | """ 42 | return f"{first_argument} {second_argument}" 43 | 44 | 45 | def function_with_math_operators( 46 | first_long_argument, second_long_argument, 47 | third_long_argument, fourth_long_argument): 48 | """ This function content would pass the 79 character limit. 49 | """ 50 | # It is recommended that we break we place the math operators at the 51 | # beginning of the line, not at the end of the line. 52 | return (first_long_argument 53 | + second_long_argument 54 | - third_long_argument 55 | * fourth_long_argument) 56 | 57 | 58 | def function_example_with_backslash(number_to_check): 59 | """ This function content would pass the 79 character limit. 60 | """ 61 | return number_to_check < 10 or 21 <= number_to_check < 20 or \ 62 | number_to_check > 100 63 | 64 | 65 | def function_with_if_examples(number_to_check): 66 | """ This function content would pass the 79 character limit. 67 | """ 68 | # Notice that the conditions aren't at the same level of parenthesis 69 | if ( 70 | number_to_check < 10 or 71 | 21 <= number_to_check < 20 or 72 | number_to_check > 100): 73 | return True 74 | # Notice that the closing parenthesis is at the same level of the oppening 75 | # parenthesis 76 | if ( 77 | number_to_check < 10 or 78 | 21 <= number_to_check < 20 or 79 | number_to_check > 100 80 | ): 81 | return True 82 | # Notice that the closing parenthesis is at the same level of the if clause 83 | if ( 84 | number_to_check < 10 or 85 | 21 <= number_to_check < 20 or 86 | number_to_check > 100 87 | ): 88 | return True 89 | # This is my least preferred one 90 | if (number_to_check < 10 or 91 | 21 <= number_to_check < 20 or 92 | number_to_check > 100): 93 | return True 94 | 95 | 96 | # This is an example of a long string 97 | LONG_STRING = ("This is an example of a very long Python string. We can use " 98 | "parenthesis, and single or double quotes for that." 99 | ) 100 | 101 | 102 | # This is an example of a long string using f-strings 103 | a_value = input() 104 | long_fstring = ("This is an example of a very long Python string. We can use " 105 | f"parenthesis, and single or double quotes for that. {a_value}" 106 | ) 107 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.13 2 | astroid==2.14.2 3 | attrs==22.2.0 4 | Babel==2.12.1 5 | blinker==1.6.2 6 | Cerberus==1.3.4 7 | certifi==2022.12.7 8 | cfgv==3.3.1 9 | charset-normalizer==3.1.0 10 | click==8.1.3 11 | colorama==0.4.6 12 | dill==0.3.6 13 | distlib==0.3.6 14 | docutils==0.18.1 15 | exceptiongroup==1.1.0 16 | filelock==3.9.0 17 | Flask==2.3.2 18 | identify==2.5.18 19 | idna==3.4 20 | imagesize==1.4.1 21 | importlib-metadata==6.3.0 22 | iniconfig==2.0.0 23 | isort==5.12.0 24 | itsdangerous==2.1.2 25 | Jinja2==3.1.2 26 | lazy-object-proxy==1.9.0 27 | MarkupSafe==2.1.2 28 | mccabe==0.7.0 29 | mypy==1.1.1 30 | mypy-extensions==1.0.0 31 | nodeenv==1.7.0 32 | packaging==23.0 33 | pep8==1.7.1 34 | platformdirs==3.0.0 35 | pluggy==1.0.0 36 | pre-commit==3.1.1 37 | pycodestyle==2.10.0 38 | Pygments==2.15.0 39 | pylint==2.16.2 40 | pylint-pytest==1.1.2 41 | pytest==7.2.1 42 | pytest-mock==3.10.0 43 | PyYAML==6.0 44 | requests==2.28.2 45 | snowballstemmer==2.2.0 46 | Sphinx==6.1.3 47 | sphinx-rtd-theme==1.2.0 48 | sphinxcontrib-applehelp==1.0.4 49 | sphinxcontrib-devhelp==1.0.2 50 | sphinxcontrib-htmlhelp==2.0.1 51 | sphinxcontrib-jquery==4.1 52 | sphinxcontrib-jsmath==1.0.1 53 | sphinxcontrib-qthelp==1.0.3 54 | sphinxcontrib-serializinghtml==1.1.5 55 | tomli==2.0.1 56 | tomlkit==0.11.6 57 | types-colorama==0.4.15.11 58 | types-docutils==0.20.0.1 59 | types-Pygments==2.15.0.1 60 | types-setuptools==67.7.0.2 61 | typing_extensions==4.5.0 62 | urllib3==1.26.15 63 | virtualenv==20.19.0 64 | Werkzeug==2.3.4 65 | wrapt==1.15.0 66 | zipp==3.15.0 67 | -------------------------------------------------------------------------------- /solid/dependency_inversion_principle_dip.py: -------------------------------------------------------------------------------- 1 | """ This module has an example of the Dependency Inversion Principle(DIP) in 2 | Python. 3 | """ 4 | 5 | 6 | from abc import ABC, abstractmethod 7 | 8 | 9 | # This is a tightly coupled example that violates the Dependency Inversion 10 | # Principle (DIP) as the class MySQLStorageBadExample is called directly 11 | # without using an abstraction. 12 | class MySQLStorageBadExample(): 13 | """ This is the implementation of MySQL storage. 14 | """ 15 | 16 | def get(self): 17 | """ Get data 18 | """ 19 | print("Get data from MySQL database") 20 | 21 | def set(self, data): 22 | """ Set data 23 | """ 24 | print(f"Set {data} into MySQL database") 25 | 26 | 27 | class UseCaseBadExample(): 28 | """ This is the main class of the application. 29 | """ 30 | def run(self): 31 | """ This is the main method of the use case. 32 | """ 33 | bad_storage = MySQLStorageBadExample() 34 | bad_storage.get() 35 | bad_storage.set({"data": "Hello World"}) 36 | 37 | 38 | if __name__ == "__main__": 39 | use_case_bad_example = UseCaseBadExample() 40 | use_case_bad_example.run() 41 | 42 | 43 | # This is a loosely coupled example that follows the Dependency Inversion 44 | # Principle (DIP) as the class MySQLStorage is called using the 45 | # abstract class Storage. 46 | class Storage (ABC): 47 | """ This is the abstract class for the storage. 48 | """ 49 | 50 | @abstractmethod 51 | def get(self): 52 | """ Get data 53 | """ 54 | 55 | @abstractmethod 56 | def set(self, data): 57 | """ Set data 58 | """ 59 | 60 | 61 | class MySQLStorage(Storage): 62 | """ This is the implementation of MySQL storage. 63 | """ 64 | 65 | def get(self): 66 | print("Get data from MySQL database") 67 | 68 | def set(self, data): 69 | print(f"Set {data} into MySQL database") 70 | 71 | 72 | class CSVFileStorage(Storage): 73 | """ This is the implementation of CSV storage. 74 | """ 75 | 76 | def get(self): 77 | print("Get data from CSV storage") 78 | 79 | def set(self, data): 80 | print(f"Set {data} into CSV storage") 81 | 82 | 83 | class UseCase(): 84 | """ This is the main class of the application. 85 | """ 86 | def run(self, storage: Storage): 87 | """ This is the main method of the use case. 88 | """ 89 | storage.get() 90 | storage.set({"data": "Hello World"}) 91 | 92 | 93 | if __name__ == "__main__": 94 | use_case = UseCase() 95 | use_case.run(MySQLStorage()) 96 | use_case.run(CSVFileStorage()) 97 | 98 | # Consequently, following the Dependency Inversion Principle we can improve our 99 | # code maintainability and flexibility because it will depend less on lower 100 | # classes (like MySQLStorage in our example) and we can even use more than one 101 | # lower class without changing the higher class (eg UseCase). 102 | -------------------------------------------------------------------------------- /solid/interface_segregation_principle_isp.py: -------------------------------------------------------------------------------- 1 | """ This module has examples of the Interface Segregation Principle (ISP) 2 | in Python. 3 | """ 4 | 5 | 6 | from abc import ABC, abstractmethod 7 | 8 | 9 | class EmployeeBadExample(ABC): 10 | """ An abstract class example that doesn't follow the Interface Segregation 11 | Principle as it has methods that aren't used by its child classes 12 | """ 13 | 14 | @abstractmethod 15 | def write_code(self): 16 | """ Write code method 17 | """ 18 | 19 | @abstractmethod 20 | def cook(self): 21 | """ Cook method 22 | """ 23 | 24 | @abstractmethod 25 | def lead(self): 26 | """ Lead method 27 | """ 28 | 29 | 30 | class ChefBadExample(EmployeeBadExample): 31 | """ Chef Class 32 | """ 33 | def write_code(self): 34 | raise NotImplementedError("Chefs don't write code.") 35 | 36 | def cook(self): 37 | print("I am cooking") 38 | 39 | def lead(self): 40 | print("I am leading") 41 | 42 | 43 | # Those classes from now on follow the Interface Segregation Principle 44 | # Instead of having one abstract class for all employees, we divided them by 45 | # roles and the child class uses multiple inheritance. 46 | # If you don’t know what multiple inheritance is, the Chef class has multiple 47 | # inheritance as it has two parent classes. Chef class inherits the lead method 48 | # from the Leader class and the cook method from the Cook class. 49 | class Cook(ABC): 50 | """ An abstract class representing a cook (person who cooks) 51 | """ 52 | @abstractmethod 53 | def cook(self): 54 | """ Cook method 55 | 56 | """ 57 | 58 | 59 | class Leader(ABC): 60 | """ An abstract class representing a leader 61 | """ 62 | 63 | @abstractmethod 64 | def lead(self): 65 | """ Lead method 66 | """ 67 | 68 | 69 | class Chef(Cook, Leader): 70 | """ Chef Class 71 | """ 72 | def lead(self): 73 | print("I am leading") 74 | 75 | def cook(self): 76 | print("I am cooking") 77 | 78 | 79 | # Alternatively, the method could be implemented directly on the child class 80 | # without relating it to the parent class. The fly method of the Bird class 81 | # below is an example of this approach. 82 | class Animal(ABC): 83 | """ An abstract class representing a leader 84 | """ 85 | 86 | @abstractmethod 87 | def eat(self): 88 | """ Eat method 89 | """ 90 | 91 | 92 | class Bird(Animal): 93 | """ Bird Class 94 | """ 95 | def eat(self): 96 | print("I am eating") 97 | 98 | def fly(self): 99 | """ Fly method 100 | """ 101 | print("I am flying") 102 | -------------------------------------------------------------------------------- /solid/liskov_substitution_principle_lsp.py: -------------------------------------------------------------------------------- 1 | """ This module has an example of the Liskov Substitution Principle(LSP) in 2 | Python. 3 | """ 4 | 5 | 6 | # The example below violates The Liskov Substitution Principle as the calculate 7 | # Father class returns a value and the method from SonBadExample class prints 8 | # it. 9 | class Father(): 10 | """ Example of a base class 11 | """ 12 | def add(self, first: float, second: float) -> float: 13 | """ Do a calculation 14 | """ 15 | return first + second 16 | 17 | 18 | class SonBadExample(Father): 19 | """ Example of a child class that violates the Liskov Substitution 20 | Principle 21 | """ 22 | def add(self, first: float, second: float) -> None: # type: ignore 23 | """ Do a calculation 24 | """ 25 | print(first + second) 26 | 27 | 28 | # On the other hand, the class below doesn't do polymorphism but creates a new 29 | # method which follows the Liskov Substitution Principle. 30 | # If you don’t know what polymorphism is, we did polymorphism in the previous 31 | # example when we overrided the method. This is, we redefine in the child class 32 | # a method (add) already defined in the parent class. 33 | class Son(Father): 34 | """ Example of a child class that follows the Liskov Substitution Principle 35 | """ 36 | def print_addition(self, first: float, second: float) -> None: 37 | """ Do a calculation 38 | """ 39 | print(self.add(first, second)) 40 | -------------------------------------------------------------------------------- /solid/open_closed_principle_ocp/classes_that_violate_srp.py: -------------------------------------------------------------------------------- 1 | """ This module has an example of the Open/Closed Principle in Python where 2 | classes DON'T follow the Single Responsibility Principle 3 | """ 4 | 5 | 6 | # This UserBad class is not following the Open/Closed Principle because if 7 | # we need to add a responsibility, we would need to modify the class. 8 | # For example, if we add the feature of sending email like the example shown on 9 | # the SRP example, we need to modify the UserBad class. 10 | class UserBad(): 11 | """ This is an example of a class in Python that is NOT following the 12 | Single Responsibility Principle and Open/Closed Principle 13 | """ 14 | def __init__(self, username, name): 15 | self.username = username 16 | self.name = name 17 | 18 | def save(self): 19 | """ This method would send a confirmation email 20 | """ 21 | print("Saving data into database") 22 | 23 | 24 | # Here we separated the responsibilities, each responsibility in your 25 | # respective class. So, this example follows the Open/Closed Principle and 26 | # Single Responsibility Principle. Before, we had two reasons to change the 27 | # UserBad class, changing user properties and saving to database. Now, each of 28 | # these has its own class and if we need an additional responsibility, we would 29 | # create a new class. 30 | class User(): 31 | """ This class is an example of the Open/Closed Principle in Python 32 | """ 33 | def __init__(self, username, name): 34 | self.username = username 35 | self.name = name 36 | 37 | 38 | class UserDatabase(): 39 | """ This class is an example of the Open/Closed Principle in Python 40 | """ 41 | def save(self, user: User): 42 | """ This method would save data to the database 43 | """ 44 | print(f"Saving {user} to database") 45 | -------------------------------------------------------------------------------- /solid/open_closed_principle_ocp/classes_tightly_coupled.py: -------------------------------------------------------------------------------- 1 | """ This module has an example of the Open/Close Principle in Python where 2 | classes are tightly coupled. 3 | """ 4 | 5 | 6 | class UserBadExample(): 7 | """ This is an example of a class in Python that is NOT following the 8 | Open/Closed Principle 9 | """ 10 | def __init__(self, username, name): 11 | self.username = username 12 | self.name = name 13 | 14 | 15 | # This function is tightly coupled to the UserBadExample class as we would need 16 | # to change the get_user_bad_example function if we need to change the 17 | # UserBadExample class. That also violates the Open/Close Principle. 18 | def get_user_bad_example(user_bad_example): 19 | """ This is an example of a Python function that is NOT following the 20 | Open/Closed Principle 21 | """ 22 | return { 23 | "username": user_bad_example.username, 24 | "name": user_bad_example.name 25 | } 26 | 27 | 28 | # Transforming the function into a method of the class solves the previous 29 | # problem and follows the Open/Close Principle. 30 | class User(): 31 | """ This is an example of a Python class that follows the 32 | Open/Closed Principle 33 | """ 34 | def __init__(self, username, name): 35 | self.username = username 36 | self.name = name 37 | 38 | def get(self): 39 | """ Get user data """ 40 | return {"username": self.username, "name": self.name} 41 | -------------------------------------------------------------------------------- /solid/open_closed_principle_ocp/classes_with_conditional_logic.py: -------------------------------------------------------------------------------- 1 | """ This module has example of Open/Closed Principle in Python where Classes 2 | have conditional logic 3 | """ 4 | from abc import ABC, abstractmethod 5 | 6 | 7 | class InterestRateBadExample(): 8 | """ This is an example of a class in Python that is NOT following the 9 | Open/Closed Principle 10 | """ 11 | def get_interest_rate(self, category): 12 | """ Method to get interest rate 13 | """ 14 | if category == 'standard': 15 | return 0.03 16 | elif category == 'premium': 17 | return 0.05 18 | 19 | 20 | class InterestRate(ABC): 21 | """ This is an example of an abstract class in Python that is following the 22 | Open/Closed Principle 23 | """ 24 | @abstractmethod 25 | def get_interest_rate(self): 26 | """ Method to get interest rate 27 | """ 28 | 29 | 30 | class StandardInterestRate(InterestRate): 31 | """ This is an example of a class in Python that is following the 32 | Open/Closed Principle 33 | """ 34 | def get_interest_rate(self): 35 | """ Method to get interest rate 36 | """ 37 | return 0.03 38 | 39 | 40 | class PremiumInterestRate(InterestRate): 41 | """ This is an example of a class in Python that is following the 42 | Open/Closed Principle 43 | """ 44 | def get_interest_rate(self): 45 | """ Method to get interest rate 46 | """ 47 | return 0.05 48 | -------------------------------------------------------------------------------- /solid/single_responsibility_principle_srp.py: -------------------------------------------------------------------------------- 1 | """ This module has examples of the Single Responsibility Principle in Python 2 | """ 3 | 4 | 5 | # This UserBad class is not following the Single Responsibility Principle as it 6 | # has two responsibilities: managing user properties and sending confirmation 7 | # email. 8 | class UserBad(): 9 | """ This is an example of a class in Python that is NOT following the 10 | Single Responsibility Principle 11 | """ 12 | def __init__(self, username, email_address): 13 | self.username = username 14 | self.email_address = email_address 15 | self.send_confirmation_email() 16 | 17 | def send_confirmation_email(self): 18 | """ This method would send confirmation email 19 | """ 20 | print(f"Sending confirmation email to {self.email_address}") 21 | 22 | 23 | # Here we separated the responsibilities, each responsibility in your 24 | # respective class. So, this example follows the Single Responsibility 25 | # Principle. Before, we had two reasons to change the UserBad class, changing 26 | # user properties and changing how the email is sent. Now, each of these has 27 | # its own class. 28 | class User(): 29 | """ This class is an example of the Single Responsibility Principle in Python 30 | """ 31 | def __init__(self, username, email_address): 32 | self.username = username 33 | self.email_address = email_address 34 | self.email = Email(email_address) 35 | self.email.send_confirmation_email() 36 | 37 | 38 | class Email(): 39 | """ This class is an example of the Single Responsibility Principle in Python 40 | """ 41 | def __init__(self, email_address): 42 | self.email_address = email_address 43 | 44 | def send_confirmation_email(self): 45 | """ This method would send confirmation email 46 | """ 47 | print(f"Sending confirmation email to {self.email_address}") 48 | -------------------------------------------------------------------------------- /testing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiosw/python-best-practices/898eb103bfe0bb3259637628dea8544cae9019f9/testing/__init__.py -------------------------------------------------------------------------------- /testing/conftest.py: -------------------------------------------------------------------------------- 1 | """ This file has fixtures for the tests included in this directory 2 | """ 3 | 4 | 5 | import uuid 6 | from datetime import datetime 7 | import pytest 8 | from testing.pytesting import Product 9 | 10 | 11 | @pytest.fixture(autouse=True) 12 | def time_calculator(): 13 | """ Example of an autouse fixture """ 14 | # To see this, execute pytest with "-s" option 15 | print(f" Test started: {datetime.now().strftime('%d-%m-%Y %H:%M')} ") 16 | yield 17 | print(f" Test ended: {datetime.now().strftime('%d-%m-%Y %H:%M')} ") 18 | 19 | 20 | @pytest.fixture() 21 | def fixture_product_1(): 22 | """ Example of a fixture of one product """ 23 | return Product( 24 | id=uuid.uuid4(), 25 | price=1.11, 26 | name="test 1" 27 | ) 28 | 29 | 30 | @pytest.fixture() 31 | def fixture_product_2(): 32 | """ Example of a fixture of another product """ 33 | return Product( 34 | id=uuid.uuid4(), 35 | price=11, 36 | name="test 2" 37 | ) 38 | 39 | 40 | @pytest.fixture() 41 | def product_fixture_list(fixture_product_1, fixture_product_2): 42 | """ Example of a fixture composed of two other fixtures """ 43 | return [fixture_product_1, fixture_product_2] 44 | -------------------------------------------------------------------------------- /testing/mocking.py: -------------------------------------------------------------------------------- 1 | """ This module has supporting code to demonstrate testing 2 | """ 3 | 4 | 5 | import random 6 | from abc import ABC, abstractmethod 7 | 8 | 9 | def run_dice() -> str: 10 | """ Simulate a dice roll 11 | :returns: 'Lucky' or 'Unlucky' 12 | """ 13 | dice = random.randint(1, 6) 14 | if dice < 4: 15 | return "Unlucky" 16 | return "Lucky" 17 | 18 | 19 | class ExampleRepositoryInterface(ABC): 20 | """ Abstract class for ExampleRepository 21 | """ 22 | @abstractmethod 23 | def create(self, name: str) -> str: 24 | """ Create Example 25 | :param name: Name 26 | :return: A String 27 | """ 28 | 29 | 30 | class CreateExampleUseCase(): 31 | """ Class CreateExampleUseCase 32 | """ 33 | def __init__(self, repository: ExampleRepositoryInterface) -> None: 34 | self.repository = repository 35 | 36 | def execute(self, name: str) -> str: 37 | """ Method to execute use case 38 | :param name: Name of example 39 | :return: Str 40 | """ 41 | return self.repository.create(name) 42 | -------------------------------------------------------------------------------- /testing/mocking_pytest-mock_test.py: -------------------------------------------------------------------------------- 1 | """ In this file we have tests that use pytest-mock 2 | """ 3 | from testing import mocking 4 | from testing.mocking import ExampleRepositoryInterface 5 | 6 | 7 | def test_mock_patch_function_with_dice(mocker): 8 | """ Testing run_dice function 9 | """ 10 | # We say randint should return 4 then we execute the run_dice function. 11 | mocker.patch( 12 | # api_call is from slow.py but imported to main.py 13 | 'testing.mocking.random.randint', 14 | return_value=4 15 | ) 16 | # Testing if run_dice returns "Lucky". 17 | assert mocking.run_dice() == "Lucky" 18 | assert mocking.random.randint.call_count == 1 # pylint: disable=E1101 19 | assert mocking.random.randint.call_once_with(1, 6) # pylint: disable=E1101 20 | assert mocking.random.randint.call_with(1, 6) # pylint: disable=E1101 21 | 22 | # We say randint should return 3 then we execute the run_dice function. 23 | mocker.patch( 24 | # api_call is from slow.py but imported to main.py 25 | 'testing.mocking.random.randint', 26 | return_value=3 27 | ) 28 | # Testing if run_dice returns "Unlucky". 29 | assert mocking.run_dice() == "Unlucky" 30 | assert mocking.random.randint.call_count == 1 # pylint: disable=E1101 31 | assert mocking.random.randint.call_with(1, 6) # pylint: disable=E1101 32 | 33 | assert mocking.run_dice() == "Unlucky" 34 | assert mocking.random.randint.call_count == 2 # pylint: disable=E1101 35 | # We disable the E1101 pylint message because pylint can't figure out what 36 | # mock is doing behind the scene in a sane way.This problem only occurs 37 | # when you're mocking a module. Pylint correctly finds the type of the mock 38 | # if you're mocking a function, class or method. 39 | 40 | 41 | def test_create_example(mocker): 42 | """ Example of a test passing a mock as a parameter 43 | """ 44 | # Mocking the object 45 | repository_mock = mocker.patch.object( 46 | ExampleRepositoryInterface, 47 | "create" 48 | ) 49 | # Defining the result it will return 50 | repository_mock.create.return_value = "Result Example created" 51 | # Executing the test 52 | use_case = mocking.CreateExampleUseCase(repository_mock) 53 | result = use_case.execute("Name Example") 54 | # Check if the mocked method of the object received the correct parameter 55 | repository_mock.create.assert_called_once_with("Name Example") 56 | # Check if the use case passed the result of the mocked object correctly 57 | assert result == "Result Example created" 58 | -------------------------------------------------------------------------------- /testing/mocking_test.py: -------------------------------------------------------------------------------- 1 | """ Tests to demonstrate testing. 2 | """ 3 | 4 | 5 | import unittest 6 | from unittest.mock import Mock, patch 7 | from testing import mocking 8 | 9 | 10 | def test_mock_function(): 11 | """ Test that a mock function is called 12 | """ 13 | object_to_mock = Mock() 14 | object_to_mock.method_example("parameter example") 15 | 16 | assert object_to_mock.method_example.call_count == 1 17 | 18 | object_to_mock.method_example.assert_called() 19 | object_to_mock.method_example.assert_called_once() 20 | object_to_mock.method_example.assert_called_with("parameter example") 21 | object_to_mock.method_example.assert_called_once_with("parameter example") 22 | 23 | object_to_mock.method_example() 24 | 25 | assert object_to_mock.method_example.call_count == 2 26 | 27 | object_to_mock.method_example.assert_called_with() 28 | 29 | 30 | class TestRunDice(unittest.TestCase): 31 | """ Tests for the run_dice function isolating it from random library 32 | """ 33 | # With @patch("testing.mocking.random"), we mock random from the mocking.py 34 | # file which is the file where the function run_dice is located. 35 | @patch("testing.mocking.random") 36 | # The mock_random parameter of the test_mock_function_with_dice method is 37 | # the name of the mock object we pass to the test method. 38 | def test_mock_function_with_dice(self, mock_random): 39 | """ Testing run_dice function 40 | """ 41 | # We say randint should return 4 then we execute the run_dice function. 42 | mock_random.randint.return_value = 4 43 | 44 | # Testing if run_dice returns "Lucky". 45 | assert mocking.run_dice() == "Lucky" 46 | 47 | # We say that randint should return 3. 48 | mock_random.randint.return_value = 3 49 | 50 | # We execute the run_dice function and test if it returns "Unlucky". 51 | assert mocking.run_dice() == "Unlucky" 52 | -------------------------------------------------------------------------------- /testing/pytesting.py: -------------------------------------------------------------------------------- 1 | """ Example code to show Pytest features """ 2 | 3 | 4 | import uuid 5 | import dataclasses 6 | import sys 7 | from typing import Dict 8 | 9 | 10 | @dataclasses.dataclass 11 | class Product: 12 | """ Class Product example """ 13 | id: uuid.UUID 14 | price: float 15 | name: str 16 | 17 | 18 | def add(number_1: int, number_2: int) -> int: 19 | """ 20 | Add two numbers. 21 | 22 | :param number_1: First number. 23 | :param number_2: Second number. 24 | :return: Sum of two numbers. 25 | """ 26 | return number_1 + number_2 27 | 28 | 29 | def get_user_inputs() -> Dict[str, str]: 30 | """ Get User Inputs 31 | :return: Dictionary with user inputs 32 | """ 33 | received_user_input1 = str(input("What is your first input?")) 34 | received_user_input2 = str(input("What is your second input?")) 35 | return { 36 | "user_input1": received_user_input1, 37 | "user_input2": received_user_input2 38 | } 39 | 40 | 41 | def print_hello_world_with_error(): 42 | """ Print Hello World with error """ 43 | print("Hello World") 44 | sys.stderr.write("This is an error") 45 | -------------------------------------------------------------------------------- /testing/pytesting_test.py: -------------------------------------------------------------------------------- 1 | """ Tests restated to Pytest """ 2 | 3 | 4 | import pytest 5 | from .pytesting import add, get_user_inputs, print_hello_world_with_error 6 | 7 | 8 | def test_add(): 9 | """ Doing simple tests """ 10 | assert add(1, 2) == 3 11 | assert add(1, 2) < 4 12 | 13 | 14 | def test_product_list(product_fixture_list): 15 | """ Test example using fixture """ 16 | assert len(product_fixture_list) == 2 17 | assert add( 18 | product_fixture_list[0].price, 19 | product_fixture_list[1].price 20 | ) == 12.11 21 | 22 | 23 | @pytest.mark.parametrize("parameter_1, parameter_2, expected_result", [ 24 | (1, 2, 3), 25 | (-1, 1, 0), 26 | (0, 1, 1)]) 27 | def test_add_parametrize(parameter_1, parameter_2, expected_result): 28 | """ Test example using parametrize feature of Pytest """ 29 | assert add(parameter_1, parameter_2) == expected_result 30 | 31 | 32 | def test_stdout_stderr(capsys): 33 | """ Test example using capsys to test stdout and stderr """ 34 | print_hello_world_with_error() 35 | captured = capsys.readouterr() 36 | # Asserting that the captured standard output (print) is what we expect 37 | # Notice that print adds a newline (/n) at the end of the string 38 | assert captured.out == "Hello World\n" 39 | # Asserting that the captured standard error (sys.stderr.write) is what we 40 | # expect 41 | assert captured.err == "This is an error" 42 | 43 | 44 | def test_user_input(monkeypatch): 45 | fake_user_input1 = "This is the first user input" 46 | fake_user_input2 = 42 47 | fake_user_inputs = iter([fake_user_input1, str(fake_user_input2)]) 48 | # This simulates user inputs in the terminal, each input as one variable: 49 | monkeypatch.setattr('builtins.input', lambda _: next(fake_user_inputs)) 50 | # Printing and generating error: 51 | received_user_inputs = get_user_inputs() 52 | # Asserting that the received user inputs are what we expect 53 | assert received_user_inputs["user_input1"] == fake_user_input1 54 | assert received_user_inputs["user_input2"] == str(fake_user_input2) 55 | -------------------------------------------------------------------------------- /validation/validation.py: -------------------------------------------------------------------------------- 1 | """ Validator for the data 2 | """ 3 | 4 | 5 | # It is necessary to ignore mypy Cerberus errors because it apparently misses 6 | # library stubs or py.typed marker 7 | from cerberus import Validator # type: ignore 8 | 9 | schema = { 10 | "name": { 11 | "type": "string", 12 | "minlength": 3, 13 | "maxlength": 80, 14 | "required": True, 15 | "empty": False 16 | } 17 | } 18 | 19 | 20 | validator = Validator(schema) 21 | data = {"name": "Jo"} 22 | if not validator.validate(data): 23 | print('Invalid Data:') 24 | print(validator.errors) 25 | --------------------------------------------------------------------------------