# Standars and best practices
[TOC]
## git
### General recommendations
* **Good branch names**
* **wip**: Works in progress; stuff I know won't be finished soon
* **feat**: Feature I'm adding or expanding
* **bug**: Bug fix or experiment
* **junk**: Throwaway branch created to experiment
* **new**/frabnotz
* **new**/foo
* **new**/bar
* **test**/foo
* **test**/frabnotz
```sh
$ git branch --list "test/*"
test/foo
test/frabnotz
$ git branch --list "*/foo"
new/foo
test/foo
```

* **Good commits messages**
* A good commit should contain a set of homogeneus and relevant changes.
* **Subject**
* Simple but explanatory
* Capitalized, without period at the end of line.
* Max of 50 characters!!
* Imperative mode
* Clean your room
* Close the door
* Take out the trash
* **Body**
* Just if it is necessary
* Max of 72 characters!!
* Explain what and why vs. how
> Fix typo in introduction to user guide
> Accelerate to 88 miles per hour
> Open the pod bay doors
> Derezz the master control program
> MCP turned out to be evil and had become intent on world domination.
This commit throws Tron's disc into MCP (causing its deresolution)
and turns it back into a chess game.
[Más info: Pro git](https://git-scm.com/book/en/v2)
## Repo structure
```
name-of-repo
├── .env.example
├── .gitignore
├── docs
├── dev
│ └── Dockerfile
├── prod
│ └── Dockerfile
├── docker-compose.yml
├── name-of-repo
│ └── __init__.py
│ └── file_1.py
│ └── file_2.py
│ └── utils.py
│ └── module_3
│ └── __init__.py
├── README.md
├── requirements.txt
└── tests
├── __init__.py
└── test_name-of-repo.py
└── config.py
└── main_file.py
```
### README file
Template
``` markdown
# Name
Description
# Table of contents
[](#)
[TOC]
## Features
* feature 1
* feature 2
## Installation
(step by step)
\`\`\`
\`\`\`
## Python version
## Usage
\`\`\`
\`\`\`
## Examples
## Test
## Deployment scenario
## TODO
## License
[MIT](https://choosealicense.com/licenses/mit/)
```
### Plus: budges!
[A collection of budges](https://gist.github.com/tterb/982ae14a9307b80117dbf49f624ce0e8)
[]()
`[]()`
[](https://pypi.python.org/pypi/yt2mp3/)
`[](https://pypi.python.org/pypi/yt2mp3/)`
### .env
**IT MUST NOT** be added!
Add and example.
Environmental variables text file with key, value pairs:
```
key_1 = value_1
key_2 = value_2
```
They could be acceded by:
```python
import os
value_1 = os.getenv('key_1')
```
### .gitignore
Default python .gitignore
```gitignore
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# vscode
.idea/
.vscode/
# C extensions
*.so
# Distribution / packaging
.Python
.env
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
```
#### Generate a repo from scratch
*with poetry*
```python
pip install poetry
poetry new <name>
```
## Python code
### Great names, code doc and comments
* Function: lowercase words separated by underscores, ex: `my_function`.
* Choose a self explanatory name with infinitive verbs, ex:
```python
def multiply_by_two(x):
return x * 2
```
* Variable: lowercase, single words separated by underscore, ex: `my_variable`
* Choose good names! Avoid n, x, y, a.
* Class: capitalize words, ex: `MyClass`.
* Method: lowercase words separated by underscores, ex: `class_method`.
* Constant: Uppercase single words separated by underscores, ex: `MY_CONSTANT`.
* Module: Short, lowercase words separated by underscores: `my_module.py`
* Package: Short, lowercase words separated: `mypackage`
* Comments: Capitalize, double space after a line.
#### Docstring
reST style
##### functions
```python
def my_function(param_1, param_2):
"""
Return / Description
:param param_1: (type) description
:param param_2: (type) description
:return: (type) description
"""
```
###### Example
```python
def def multiply_by_two(x):
"""
Return the result of multiplying by two the x param
:param x: (float) number to multiply
:return: (float) param x multiplied by two
"""
return x * 2
```
##### classes
```python
class Keeper(Storer):
"""
Keep data fresher longer.
Extend `Storer`. Class attribute `instances` keeps track
of the number of `Keeper` objects instantiated.
"""
instances = 0
"""How many `Keeper` objects are there?"""
def __init__(self):
"""
Extend `Storer.__init__()` to keep track of
instances. Keep count in `self.instances` and data
in `self.data`.
"""
Storer.__init__(self)
self.instances += 1
self.data = []
"""Store data in a list, most recent last."""
def storedata(self, data):
"""
Extend `Storer.storedata()`; append new `data` to a
list (in `self.data`).
"""
self.data = data
```
###### example
```python
class MyClass(BaseClass):
"""
Short class description.
Extend `BaseClass`. Important notes about attributes/methods/instances.
"""
def __init__(self):
"""
Extend `BaseClass.__init__()`.
"""
BaseClass.__init__(self)
self.attr_1 += 1
self.attr_2 = []
def method_1(self, param):
"""
Extend `BaseClass.method_1()`; short method description
"""
self.attr_2 = param
```
#### Inline comments
They must add valuable information
* In the same line of code that it is refering to
* Capitalize
* 2 spaces and # before the comment
```python
x = 5 # This is an inline comment
```
### Whitespaces
* Operators
```python
x = 5
y = x * 2
if z >= x:
# ...
if z is not None:
# ...
```
* Exceptions
```python
y = x**2 + 5
z = (x+y) * (x-y)
if x>5 and x%2==0:
# ...
list[x+1 : x+2]
def function(default_parameter=5):
# ...
```
When DO NOT add whitespaces:
```python
print(x, y)
double(3)
list[3]
tuple = (1,)
```
### Blank lines
* **2** before a class/function definition.
```python
class MyFirstClass:
pass
class MySecondClass:
pass
def top_level_function():
return None
```
* **1** between class method definitions
```python
class MyClass:
def first_method(self):
return None
def second_method(self):
return None
```
* **1** between group of streps inside functions
```python
def calculate_variance(number_list):
sum_list = 0
for number in number_list:
sum_list = sum_list + number
mean = sum_list / len(number_list)
sum_squares = 0
for number in number_list:
sum_squares = sum_squares + number**2
mean_squares = sum_squares / len(number_list)
return mean_squares - mean**2
```
### Lenght of line: 79!!!
Ways to guarantee max length of lines:
```python
def function(arg_one, arg_two,
arg_three, arg_four):
return arg_one
from mypkg import example1, \
example2, example3
total = (first_variable
+ second_variable
- third_variable)
```
### Identations
```python
def function(arg_one, arg_two,
arg_three, arg_four):
return arg_one
x = 5
if (x > 3 and
x < 10):
print(x)
list_of_numbers = [
1, 2, 3,
4, 5, 6,
7, 8, 9
]
```
### imports
1. standard library imports
2. related third party imports
3. local application/library specific imports
* linter: isort
### Recommendations
* Don’t compare Boolean values to True or False using the equivalence operator.
```python
# if my_bool == True:
if my_bool:
return '6 is bigger than 5'
```
* Empty list should be interpreted as a False statement:
```python
my_list = []
# if not len(my_list):
if not my_list:
print('List is empty!')
```
* Use is not instead of not None
```python
# if not x is None:
if x is not None:
return 'x exists!'
```
### List of PEP
* [PEP8](https://www.python.org/dev/peps/pep-0008/)
* [PEP257](https://www.python.org/dev/peps/pep-0257/)
### Linters
* isort
```bash
$ pip install isort
$ isort code.py
```
* black
```bash
$ pip install black
$ black code.py
```
* flake8
```bash
$ pip install flake8
$ flake8 code.py
```
* pycodestyle
```bash
$ pip install pycodestyle
$ pycodestyle code.py
```
#### Extra python code recommendations
##### Check edge cases
##### Arg parser
``` python
import argparse
parser = argparse.ArgumentParser(description='Description.')
parser.add_argument('--foo', type=int, default=42, help='FOO!')
args = parser.parse_args()
```
##### unittest
```python
import unittest
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def test_default_widget_size(self):
self.assertEqual(self.widget.size(), (50,50),
'incorrect default size')
def test_widget_resize(self):
self.widget.resize(100,150)
self.assertEqual(self.widget.size(), (100,150),
'wrong size after resize')
```
`python -m unittest tests/test_something.py`
##### typehints
```python
def greeting(name: str) -> str:
return 'Hello ' + name
Vector = list[float]
def scale(scalar: float, vector: Vector) -> Vector:
ConnectionOptions = dict[str, str]
Address = tuple[str, int]
Server = tuple[Address, ConnectionOptions]
def broadcast_message(message: str, servers: Sequence[Server])
```
##### dataclasses
```python
from dataclasses import dataclass
@dataclass
class InventoryItem:
"""Class for keeping track of an item in inventory."""
name: str
unit_price: float
quantity_on_hand: int = 0
def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand
# Will add, among other things, a __init__() that looks like:
def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
self.name = name
self.unit_price = unit_price
self.quantity_on_hand = quantity_on_hand
```
## Documentation