Skip to content

Commit d2cdb5d

Browse files
authored
Added english translation
1 parent 1405b21 commit d2cdb5d

File tree

1 file changed

+249
-0
lines changed

1 file changed

+249
-0
lines changed

EN.md

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
# Evrone Python Guidelines
2+
3+
4+
## About the code
5+
6+
### Basic principles
7+
- **Maintainability** (will you be able to understand your code in a year or two?)
8+
- **Simplicity** (between a complex and a simple solution, you should choose the simple one)
9+
- **Plainness** (when a new programmer joins, how clear it will be to them why this code is written in this way?)
10+
11+
12+
### Atomicity of operations
13+
**1 action ~ 1 line**
14+
15+
Try to do atomic operations in your code — there should be exactly one operation on each line.
16+
17+
Bad ❌:
18+
```python
19+
# 1. 3 actions on one line - 3 function calls
20+
foo_result = foo(bar(spam(x)))
21+
22+
# 2. 3 actions on one line - function call foo, get_c, from_b
23+
foo_result = foo(a=a, b=b, c=get_c(from_b())
24+
25+
# 3. 3 actions on one line - filtering by arguments, conditionally getting elements (via or), calling a method .value
26+
result = [(a.value() or A, b or B) for a, b in iterator if a < b]
27+
28+
# 4. 4 actions on one line - from library/variable foo comes bar attribute getting, spam attribute getting, hello attribute getting and calculate_weather call
29+
result = calculate_weather(foo.bar.spam.hello)
30+
```
31+
32+
Good ✅:
33+
```python
34+
# 1. make a call to each function in turn
35+
spam_result = spam(x)
36+
bar_result = bar(spam_result)
37+
foo_result = foo(bar_result)
38+
39+
# 2. call the functions one by one, write the result to a variable and use it when calling foo
40+
from_b_result = from_b()
41+
c = get_c(from_b_result)
42+
foo_result = foo(a=a, b=b, c=c)
43+
44+
# 3. sequentially perform actions on the list - first filter, then call the .value method of a, and choose between elements (or)
45+
filtered_result = ((a, b) for a, b in iterator if a < b)
46+
intermediate_result = ((a.value(), b) for a, b in filtered_result)
47+
result = [(a or A, b or B) for a, b in intermediate_result]
48+
49+
# 4 . sequentially read the attributes bar, spam, hello and call the function calculate_weather
50+
bar = foo.bar
51+
spam = bar.spam
52+
hello = spam.hello
53+
result = calculate_weather(hello)
54+
```
55+
56+
57+
**Why?** Because the code becomes more readable, and there is no need to execute several statements in your head while reading the code. Code broken down into simple atomic operations is perceived much better than complex one-liners. Try to simplify your code as much as possible — code is more often read than written.
58+
59+
60+
### Logical blocks
61+
62+
Try to divide the code into logical blocks — this way it will be much easier for the programmer to read and understand the essence.
63+
64+
Bad ❌:
65+
```python
66+
def register_model(self, app_label, model):
67+
model_name = model._meta.model_name
68+
app_models = self.all_models[app_label]
69+
if model_name in app_models:
70+
if (model.__name__ == app_models[model_name].__name__ and
71+
model.__module__ == app_models[model_name].__module__):
72+
warnings.warn(
73+
"Model '%s.%s' was already registered. "
74+
"Reloading models is not advised as it can lead to inconsistencies, "
75+
"most notably with related models." % (app_label, model_name),
76+
RuntimeWarning, stacklevel=2)
77+
else:
78+
raise RuntimeError(
79+
"Conflicting '%s' models in application '%s': %s and %s." %
80+
(model_name, app_label, app_models[model_name], model))
81+
app_models[model_name] = model
82+
self.do_pending_operations(model)
83+
self.clear_cache()
84+
```
85+
86+
Good ✅:
87+
```python
88+
def register_model(self, app_label, model):
89+
model_name = model._meta.model_name
90+
app_models = self.all_models[app_label]
91+
92+
if model_name in app_models:
93+
if (
94+
model.__name__ == app_models[model_name].__name__ and
95+
model.__module__ == app_models[model_name].__module__
96+
):
97+
warnings.warn(
98+
"Model '%s.%s' was already registered. "
99+
"Reloading models is not advised as it can lead to inconsistencies, "
100+
"most notably with related models." % (app_label, model_name),
101+
RuntimeWarning, stacklevel=2)
102+
103+
else:
104+
raise RuntimeError(
105+
"Conflicting '%s' models in application '%s': %s and %s." %
106+
(model_name, app_label, app_models[model_name], model))
107+
108+
app_models[model_name] = model
109+
110+
self.do_pending_operations(model)
111+
self.clear_cache()
112+
```
113+
114+
**Why?** In addition to improving readability, The Zen of Python teaches us how to write idiomatic Python code. One of the statements claims that "sparse is better than dense." Compressed code is harder to read than sparse code.
115+
116+
117+
### Sizes of methods, functions, and modules
118+
The size limit for a method or function is 50 lines. Reaching the size limit indicates that the function (method) is doing too much — so decompose the actions inside the function (method).
119+
The module size limit is 300 lines. Reaching the size limit indicates that the module has received too much logic — so decompose the module into several ones.
120+
The line length is 100 characters.
121+
Imports
122+
The recommended import method is absolute.
123+
Bad ❌:
124+
# spam.py
125+
from . import foo, bar
126+
Good ✅:
127+
# spam.py
128+
from some.absolute.path import foo, bar
129+
Why? Because absolute import explicitly defines the location (path) of the module that is being imported. With relative imports, you always need to remember the path and calculate in your mind the location of the modules foo.py, bar.py relative to spam.py
130+
Files __init__.py
131+
Only write imports in __init__.py files.
132+
Why? Because __init__.py is the last place a programmer will look when they read the code in the future.
133+
Docstrings
134+
We recommend adding docstrings to functions, methods, and classes.
135+
Why? Because the programmer who sees your code for the first time will be able to quickly understand what is happening in it. Code is read much more than it is written.
136+
About Pull Requests
137+
Creating Pull Requests
138+
1 Pull Request = 1 issue
139+
One Pull Request must solve exactly one issue.
140+
Why? Because it is more difficult for a reviewer to keep the context of several tasks in their head and switch between them. When a PR contains several issues, then the PR often increases and requires more time and effort for the review from the reviewer.
141+
Refactoring and Pull Requests
142+
Refactoring is best done in a separate Pull Request.
143+
Why? When refactoring goes along with resolving a specific issue, the refactoring blurs the context of the issue and introduces changes that are not related to that PR.
144+
Pull Request Size
145+
The resulting PR diff should not exceed +/- 600 changed lines.
146+
Bad ❌:
147+
148+
Diff 444 + 333 = 777
149+
150+
Good ✅:
151+
Diff 222 + 111 = 333
152+
153+
Why? Because the more PR involves, the more uncontrollable it becomes, and the merge is made "with eyes closed and ears shut." Also, most reviewers will find it difficult to accept a large volume of changes at once.
154+
About tooling
155+
Testing (pytest)
156+
pytest - code testing framework
157+
Recommended config in pytest.ini:
158+
[pytest]
159+
DJANGO_SETTINGS_MODULE = settings.local
160+
python_files = tests.py test_*.py *_tests.py
161+
162+
Package manager (poetry)
163+
poetry - dependency manager and package builder
164+
Code formatting (Black)
165+
Black - PEP8 code auto-formatter
166+
Recommended config in pyproject.toml:
167+
[tool.black]
168+
line-length = 100
169+
target-version = ['py38']
170+
exclude = '''
171+
(
172+
\.eggs
173+
|\.git
174+
|\.hg
175+
|\.mypy_cache
176+
|\.nox
177+
|\.tox
178+
|\.venv
179+
|_build
180+
|buck-out
181+
|build
182+
|dist
183+
)
184+
'''
185+
186+
Imports formatting (isort)
187+
isort - import block auto-formatter
188+
Recommended config in pyproject.toml:
189+
[tool.isort]
190+
line_length = 100
191+
sections = ["FUTURE", "STDLIB", "DJANGO", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
192+
multi_line_output = 3
193+
known_django = "django"
194+
profile = "django"
195+
src_paths = "app"
196+
lines_after_imports = 2
197+
198+
Linter (flake8)
199+
flake8 - PEP8 conformance validator
200+
Recommended config in .flake8:
201+
[flake8]
202+
max-line-length = 100
203+
max-complexity = 5
204+
exclude = .venv,venv,**/migrations/*,snapshots
205+
per-file-ignores =
206+
tests/**: S101
207+
**/tests/**: S101
208+
Type checker (mypy)
209+
mypy - checker for static typing
210+
Recommended config mypy.ini:
211+
[mypy]
212+
ignore_missing_imports = True
213+
allow_untyped_globals = True
214+
215+
[mypy-*.migrations.*]
216+
ignore_errors = True
217+
218+
Pre-commit hooks (pre-commit)
219+
pre-commit - framework for managing pre-commit hooks
220+
Recommended config .pre-commit-config.yaml:
221+
default_language_version:
222+
python: python3.8
223+
224+
repos:
225+
- repo: local
226+
hooks:
227+
- id: black
228+
name: black
229+
entry: black app
230+
language: python
231+
types: [python]
232+
233+
- id: isort
234+
name: isort
235+
entry: isort app
236+
language: python
237+
types: [python]
238+
239+
- id: flake8
240+
name: flake8
241+
entry: flake8 server
242+
language: python
243+
types: [python]
244+
Other
245+
REST API Documentation
246+
The recommended documentation format is OpenAPI. The schema for OpenAPI should be generated “on the fly” to provide API clients with fresh changes.
247+
Why? Because it's one of the common formats for documenting REST APIs that come out of Swagger. This documentation format is supported by a large number of clients (Swagger, Postman, Insomnia Designer, and many others). Also, handwritten documentation tends to quickly become outdated, and documentation that is generated directly from the code allows you to avoid constantly thinking about updating the documentation.
248+
249+

0 commit comments

Comments
 (0)