# Python

# FastApi

# Общие команды

**Установка**

```bash
pip install fastapi uvicorn
```

**Ручной запуск** (api - имя файла, app - имя объекта FastApi)

```bash
uvicorn api:app --port 8000 --reload
```

**Запуск uvicorn из python скрипта**

Файл main.py

```python
from fastapi import FastAPI
from uvicorn import run
...
app = FastAPI()
...
if __name__ == '__main__':
    run(app="main:app", host='0.0.0.0', port=8000, workers=4, log_level='warning')
    #run(app="main:app", host='0.0.0.0', port=8000, reload=True)
```

**Запросы**

**curl запросы**

```bash
curl -X 'GET' 'http://127.0.0.1:8000/todo' -H 'accept: application/json' 
```

```bash
curl -X 'POST' \ 
'http://127.0.0.1:8000/todo' \ 
-H 'accept: application/json' \ 
-H 'Content-Type: application/json' \ 
-d '{ 
"id": 1, 
"item": "First Todo is to finish this book!" 
}'
```

**Requests**

```python
import requests
r = requests.get("http://localhost:8000/hi")
print(r.json())
```

Передача параметров

```
params = {"who": "Mom"}
r = requests.get("http://localhost:8000/hi", params=params)
```

**Httpx**

```
import httpx
r = httpx.get("http://localhost:8000/hi")
print(r.json())
```

**Автоматическая документация**

**Swagger**

```html
http://ip:port/docs
```

**Redoc**

```html
http://ip:port/redoc
```

**Шаблоны Jinja**

Поддерживает шаблоны Jinja при выводе данных (вплоть до циклов).

# Маршрутизация

**Параметризация запросов**

Передача параметров в запросе

```
@app.get("/hi/{who}")
def greet(who):
    return f"Hello? {who}?"
```

Передача параметров в параметре запроса

```
@app.get("/hi")
def greet(who):
    return f"Hello? {who}?"
```

Запрос типа localhost:8000/hi?who=Mom

Параметры передавать можно в параметрах запроса, в заголовках, в теле запроса, кукисах, ...

**Добавление маршрутов**

Основной файл:

```python
from fastapi import FastAPI 
from todo import todo_router 
 
app = FastAPI() 

@app.get("/") 
async def welcome() -> dict: 
    return { 
        "message": "Hello World" 
        } 

app.include_router(todo_router)
```

Файл дополнительных маршрутов

```python
from fastapi import APIRouter 
 
todo_router = APIRouter()

todo_list = [] 
 
@todo_router.post("/todo") 
async def add_todo(todo: dict) -> dict: 
    todo_list.append(todo) 
    return {"message": "Todo added successfully"} 

@todo_router.get("/todo") 
async def retrieve_todos() -> dict: 
    return {"todos": todo_list}
```

Автоматическое добавление маршрутов в основной файл *app* из файлов в директории data/plugins имеющих шаблон имени объекта APIRouter *modulename\_router*

```python
fpath = os.path.join('data', 'plugins')
flist = os.listdir(fpath)
sys.path.insert(0, fpath)
for fname in flist:
    if fname not in ['__pycache__', '__init__.py']:
        m = os.path.splitext(fname)[0]
        impmod = importlib.import_module(m)
        router_name = f'{m}_router'
        if router_name in dir(impmod):
            router_mod = getattr(impmod, router_name)
            app.include_router(router_mod)
```

**Возвращаемые данные**

По умолчанию возвращается JSON, добавляется заголовок Status Code и Content-type: application/json.

При помощи response\_model можно фильтровать отдаваемые данные. Т е можно в отдаваемой модели указать неполный набор.

```python
from typing import List 
 
class TodoItem(BaseModel): 
    item: str 
 
class TodoItems(BaseModel): 
    todos: List[TodoItem]
```

```python
@todo_router.get("/todo", response_model=TodoItems) 
async def retrieve_todo() -> dict: 
    return { 
        "todos": todo_list 
        }
```

У содержащихся в списке словарей будет оставлен только item

**Исключения**

Класс HTTPException принимает три аргумента:

- status\_code: Код состояния, который будет возвращен для этого сбоя
- detail: Сопроводительное сообщение для отправки клиенту
- headers: Необязательный параметр для ответов, требующих заголовков

```python
from fastapi import APIRouter, Path, HTTPException, status 

@todo_router.get("/todo/{todo_id}") 
async def get_single_todo(todo_id: int = Path(..., title="The ID of the todo to retrieve.")) -> dict: 
    for todo in todo_list:
      if todo.id == todo_id: 
        return { "todo": todo } 
    raise HTTPException( 
        status_code=status.HTTP_404_NOT_FOUND, 
        detail="Todo with supplied ID doesn't exist", 
        )
```

Можно переопределить код успешного возврата в декораторе

```python
@todo_router.post("/todo", status_code=201) 
async def add_todo(todo: Todo) -> dict: 
  todo_list.append(todo) 
  return { "message": "Todo added successfully." }
```

# Pydantic

```python
class Item(BaseModel): 
    item: str 
    status: str 
 
class Todo(BaseModel):
    id: int 
    item: Item
```

# Jinja2

<div class="align-center" id="bkmrk-%D0%A4%D0%BE%D1%80%D0%BC%D0%B0%D1%82-jinja2">**Формат Jinja2**</div><div id="bkmrk-%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5-%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D0%B0-j">Переменные шаблона Jinja могут относиться к любому типу или объекту Python, если их можно преобразовать в строки. Тип модели, списка или словаря можно передать шаблону и отобразить его атрибуты, поместив эти атрибуты во второй блок, указанный ранее. </div><div id="bkmrk-%D0%92%D0%B8%D0%B4%D1%8B-%D1%81%D0%B8%D0%BD%D1%82%D0%B0%D0%BA%D1%81%D0%B8%D1%81%D0%B0">**Виды синтаксиса**</div><div id="bkmrk-%7B%25-%E2%80%A6-%25%7D-%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D1%8F%D1%8E%D1%89%D0%B8%D0%B5-">{% … %} управляющие структуры</div><div id="bkmrk-%7B%7B-todo.item-%7D%7D-%D0%B2%D1%8B%D0%B2%D0%BE">{{ todo.item }} вывод значений переданных ему выражений  
{# This is a great API book! #} комментарии</div><div id="bkmrk-%D0%98%D0%B5%D1%80%D0%B0%D1%80%D1%85%D0%B8%D1%8F-%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D0%BE%D0%B2">**Иерархия шаблонов**</div><div id="bkmrk-%D0%A1%D0%BF%D0%BE%D1%81%D0%BE%D0%B1-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-%7B%25-i"><table border="1" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 24.5591%;"></col><col style="width: 75.4409%;"></col></colgroup><thead><tr><td class="align-center">Способ</td><td class="align-center">Описание</td></tr></thead><tbody><tr><td>{% include %}</td><td>Позволяет включить содержимое другого шаблона целиком. ```
<!-- основной шаблон -->
<h1>Главная страница</h1>
{% include 'partials/header.html' %}
<p>Основное содержимое...</p>
```

</td></tr><tr><td>{% extends %} + {% block %}</td><td>Наследование шаблонов и переопределение блоков.

Базовый шаблон

```

<html>
<head>
    <title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>
    {% block content %}{% endblock %}
</body>
</html>
```

Дочерний шаблон

```
{% extends "base.html" %}

{% block title %}Custom Title{% endblock %}

{% block content %}
    <h1>Привет, мир!</h1>
    {% include 'partials/footer.html' %}
{% endblock %}
```

</td></tr></tbody></table>

</div><div id="bkmrk-"></div><div class="align-left" id="bkmrk-%D0%A4%D0%B8%D0%BB%D1%8C%D1%82%D1%80%D1%8B%3A">**Фильтры**</div>```python
{{ variable | filter_name(*args) }}
```

<div id="bkmrk-hello%2C-%7B%7B-name%7Ccapit">**Виды фильтров**</div><div id="bkmrk-%D0%9D%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-%C2%A0d"><table border="1" style="border-collapse: collapse; width: 100%; height: 636.454px;"><colgroup><col style="width: 23.7133%;"></col><col style="width: 76.2867%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">Название</td><td class="align-center" style="height: 29.7969px;">Описание</td></tr></thead><tbody><tr style="height: 82.9844px;"><td style="height: 82.9844px;"> default(strdefault)</td><td style="height: 82.9844px;">Замена вывода переданного значения, если оно оказывается None

```python
{{ todo.item | default('This is a default todo item') }}
```

</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">escape</td><td style="height: 29.7969px;">Отображение необработанного вывода HTML</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">striptags</td><td style="height: 29.7969px;">Удаление HTML тетов перед отправкой</td></tr><tr style="height: 140.547px;"><td style="height: 140.547px;">int

float

</td><td style="height: 140.547px;">Преобразование типов перед ответом

```python
{{ 3.142 | int }} 
3 
{{ 31 | float }} 
31.0
```

</td></tr><tr style="height: 102.172px;"><td style="height: 102.172px;">join(whitespace)</td><td style="height: 102.172px;">Объединение элементов списка в строку ```python
{{ ['Packt', 'produces', 'great', 'books!'] | join(' ') }} 
Packt produces great books!
```

</td></tr><tr style="height: 102.172px;"><td style="height: 102.172px;">length</td><td style="height: 102.172px;">Длина переданного объекта ```python
Todo count: {{ todos | length }} 
Todo count: 4
```

</td></tr></tbody></table>

</div><div id="bkmrk-%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9-%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA-%D1%84%D0%B8%D0%BB%D1%8C%D1%82%D1%80">[Полный список фильтров](https://jinja.palletsprojects.com/en/3.0.x/%20%20templates/#builtin-filters)</div><div id="bkmrk-trim-removes-leading"></div><div id="bkmrk-%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D1%8F%3A">**Условия:**</div>```python
{% if user %}
    Hello, {{ user.name }}!
{% else %}
    Hello, Unknown!
{% endif %}
```

<div id="bkmrk-%7B%25-endif-%25%7D"></div><div id="bkmrk--1"></div><div id="bkmrk-%D0%A6%D0%B8%D0%BA%D0%BB%D1%8B%3A">**Циклы**</div>```python
{% for comment in comments %}
   <b>{{ comment }}</b>
{% endfor %}
```

<div id="bkmrk-%C2%A0-%C2%A0-%7B%25-endfor-%25%7D"></div><div id="bkmrk-%3C%2Ful%3E">Можно также обратиться к переменной loop.index для получения дополнительной информации</div><div id="bkmrk-%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-"><table border="1" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 36.131%;"></col><col style="width: 63.9882%;"></col></colgroup><thead><tr><td class="align-center">Переменная</td><td class="align-center">Описание</td></tr></thead><tbody><tr><td>loop.index</td><td>Текущее значение итерации (1 - первая итерация)</td></tr><tr><td>loop.index0</td><td>Текущее значение итерации (0 - первая итерация)</td></tr><tr><td>loop.revindex loop.revindex0</td><td>Кол-во оставшихся итераций</td></tr><tr><td>loop.first</td><td>True если первая итерация</td></tr><tr><td>loop.last</td><td>  
</td></tr><tr><td>loop.length</td><td>  
</td></tr><tr><td>loop.pervitem loop.nextitem</td><td>Значение предыдущей/следующей итерации (пусто если не существует)</td></tr></tbody></table>

</div><div id="bkmrk-%D0%9C%D0%B0%D0%BA%D1%80%D0%BE%D1%81%D1%8B%3A">Макросы:</div><div id="bkmrk-%7B%25-macro-render_comm">{% macro render_comment(comment) %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3E%7B%7B-comment-%7D"> &lt;li&gt;{{ comment }}&lt;/li&gt;</div><div id="bkmrk-%7B%25-endmacro-%25%7D">{% endmacro %}</div><div id="bkmrk-%3Cul%3E-1">&lt;ul&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-for-comment-i-1"> {% for comment in comments %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B%7B-render_co"> {{ render_comment(comment) }}</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-endfor-%25%7D-1"> {% endfor %}</div><div id="bkmrk-%3C%2Ful%3E-1">&lt;/ul&gt;</div><div id="bkmrk-%D0%9C%D0%B0%D0%BA%D1%80%D0%BE%D1%81%D1%8B-%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE-%D0%B8%D0%BC%D0%BF%D0%BE%D1%80%D1%82">Макросы можно импортировать из файлов</div><div id="bkmrk-%7B%25-import-%27macros.ht">{% import 'macros.html' as macros %}</div><div id="bkmrk--2">  
</div><div id="bkmrk--3">  
</div><div id="bkmrk-%D0%92-jinja-%D0%B4%D0%B2%D0%BE%D0%B9%D0%BD%D1%8B%D0%B5-%D1%84%D0%B8%D0%B3%D1%83">В Jinja двойные фигурные скобки {{ }} позволяют получить результат выражение, переменную или вызвать функцию и вывести значение в шаблоне.</div><div id="bkmrk-class-foo%3A">class Foo:</div><div id="bkmrk-%C2%A0def-__str__%28self%29%3A"> def __str__(self):</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0return-%22this-is"> return "This is an instance of Foo class"</div><div id="bkmrk-template%28%22%7B%7B-var-%7D%7D%22">Template("{{ var }}").render(var=Foo())</div><div id="bkmrk-%27this-is-an-instance">'This is an instance of Foo class'</div><div id="bkmrk-%D0%95%D1%81%D0%BB%D0%B8-%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%B8%D1%82%D1%81%D1%8F-%D0%BA-%D0%B8%D0%BD%D0%B4">Если обратится к индексу, который не существует, Jinja просто выведет пустую строку.</div><div id="bkmrk--4">  
</div><div id="bkmrk-%D0%92%D1%8B%D0%B7%D0%BE%D0%B2-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8">Вызов функции</div><div id="bkmrk-%D0%92-jinja-%D0%B4%D0%BB%D1%8F-%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5">В Jinja для определения функции ее нужно просто вызвать.</div><div id="bkmrk-%C2%A0def-foo%28%29%3A"> def foo():</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0return-%22foo%28%29-c"> return "foo() called"</div><div id="bkmrk-template%28%22%7B%7B-foo%28%29-%7D">Template("{{ foo() }}").render(foo=foo)</div><div id="bkmrk-%27foo%28%29-called%27">'foo() called'</div><div id="bkmrk--5">  
</div><div id="bkmrk-%D0%9E%D0%B1%D1%8A%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B">Объявление переменных</div><div id="bkmrk--6">  
</div><div id="bkmrk-%D0%92%D0%BD%D1%83%D1%82%D1%80%D0%B8-%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D0%B0-%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE">Внутри шаблона можно задать переменную с помощью инструкции set.</div><div id="bkmrk--7">  
</div><div id="bkmrk-%7B%25-set-fruit-%3D-%27appl">{% set fruit = 'apple' %}</div><div id="bkmrk--8">  
</div><div id="bkmrk-%7B%25-set-name%2C-age-%3D-%27">{% set name, age = 'Tom', 20 %}</div><div id="bkmrk--9">  
</div><div id="bkmrk-%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5-%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D1%8F%D1%8E">Переменные определяются для хранения результатов сложных операций, так чтобы их можно было использовать дальше в шаблоне. Переменные, определенные вне управляющих конструкций (о них дальше), ведут себя как глобальные переменные и доступны внутри любой структуры. Тем не менее переменные, созданные внутри конструкций, ведут себя как локальные переменные и видимы только внутри этих конкретных конструкций. Единственное исключение — инструкция if.</div><div id="bkmrk-%D0%A6%D0%B8%D0%BA%D0%BB-%D0%B8-%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BD%D1%8B%D0%B5-%D0%B2%D1%8B%D1%80%D0%B0">Цикл и условные выражения</div><div id="bkmrk-%D0%A0%D0%B5%D0%BA%D0%BB%D0%B0%D0%BC%D0%B0">Реклама</div><div id="bkmrk--10">  
</div><div id="bkmrk-%D0%A3%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D1%8F%D1%8E%D1%89%D0%B8%D0%B5-%D0%BA%D0%BE%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA">Управляющие конструкции позволяют добавлять в шаблоны элементы управления потоком и циклы. По умолчанию, управляющие конструкции используют разделитель {% … %} вместо двойных фигурных скобок {{ ... }}.</div><div id="bkmrk-%D0%98%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%86%D0%B8%D1%8F-if">Инструкция if</div><div id="bkmrk--11">  
</div><div id="bkmrk-%D0%98%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%86%D0%B8%D1%8F-if-%D0%B2-jinj">Инструкция if в Jinja имитирует выражение if в Python, а значение условия определяет набор инструкции. Например:</div><div id="bkmrk--12">  
</div><div id="bkmrk-%7B%25-if-bookmarks-%25%7D">{% if bookmarks %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Euser-has-some"> &lt;p&gt;User has some bookmarks&lt;/p&gt;</div><div id="bkmrk-%7B%25-endif-%25%7D-1">{% endif %}</div><div id="bkmrk--13">  
</div><div id="bkmrk-%D0%95%D1%81%D0%BB%D0%B8-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5">Если значение переменной bookmarks – True, тогда будет выведена строка &lt;p&gt;User has some bookmarks&lt;/p&gt;. Стоит запомнить, что в Jinja, если у переменной нет значения, она возвращает False.</div><div id="bkmrk--14">  
</div><div id="bkmrk-%D0%A2%D0%B0%D0%BA%D0%B6%D0%B5-%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE">Также можно использовать условия elif и else, как в обычном коде Python. Например:</div><div id="bkmrk--15">  
</div><div id="bkmrk-%7B%25-if-user.newbie-%25%7D">{% if user.newbie %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Edisplay-newbi"> &lt;p&gt;Display newbie stages&lt;/p&gt;</div><div id="bkmrk-%7B%25-elif-user.pro-%25%7D">{% elif user.pro %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Edisplay-pro-s"> &lt;p&gt;Display pro stages&lt;/p&gt;</div><div id="bkmrk-%7B%25-elif-user.ninja-%25">{% elif user.ninja %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Edisplay-ninja"> &lt;p&gt;Display ninja stages&lt;/p&gt;</div><div id="bkmrk-%7B%25-else-%25%7D-1">{% else %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Eyou-have-comp"> &lt;p&gt;You have completed all stages&lt;/p&gt;</div><div id="bkmrk-%7B%25-endif-%25%7D-2">{% endif %}</div><div id="bkmrk--16">  
</div><div id="bkmrk-%D0%A3%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D1%8F%D1%8E%D1%89%D0%B8%D0%B5-%D0%B8%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%86">Управляющие инструкции также могут быть вложенными. Например:</div><div id="bkmrk--17">  
</div><div id="bkmrk-%7B%25-if-user-%25%7D-1">{% if user %}</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-if-user.newbi"> {% if user.newbie %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cp%3Edisplay-n"> &lt;p&gt;Display newbie stages&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-elif-user.pro"> {% elif user.pro %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cp%3Edisplay-p"> &lt;p&gt;Display pro stages&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-elif-user.nin"> {% elif user.ninja %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cp%3Edisplay-n-1"> &lt;p&gt;Display ninja stages&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-else-%25%7D"> {% else %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cp%3Eyou-have-"> &lt;p&gt;You have completed all states&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-endif-%25%7D"> {% endif %}</div><div id="bkmrk-%7B%25-else-%25%7D-2">{% else %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Euser-is-not-d"> &lt;p&gt;User is not defined&lt;/p&gt;</div><div id="bkmrk-%7B%25-endif-%25%7D-3">{% endif %}</div><div id="bkmrk--18">  
</div><div id="bkmrk-%D0%A0%D0%B5%D0%BA%D0%BB%D0%B0%D0%BC%D0%B0-1">Реклама</div><div id="bkmrk--19">  
</div><div id="bkmrk-%D0%92-%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%BD%D1%8B%D1%85-%D1%81%D0%BB%D1%83%D1%87%D0%B0">В определенных случаях достаточно удобно записывать инструкцию if в одну строку. Jinja поддерживает такой тип записи, но называет это выражением if, потому что оно записывается с помощью двойных фигурных скобок {{ … }}, а не {% … %}. Например:</div><div id="bkmrk--20">  
</div><div id="bkmrk-%7B%7B-%22user-is-logged-i">{{ "User is logged in" if loggedin else "User is not logged in" }}</div><div id="bkmrk--21">  
</div><div id="bkmrk-%D0%97%D0%B4%D0%B5%D1%81%D1%8C-%D0%B5%D1%81%D0%BB%D0%B8-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0">Здесь если переменная loggedin вернет True, тогда будет выведена строка “User is logged in”. В противном случае — “User is not logged in”.</div><div id="bkmrk--22">  
</div><div id="bkmrk-%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5-else-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7">Условие else использовать необязательно. Если его нет, тогда блок else вернет объект undefined.</div><div id="bkmrk--23">  
</div><div id="bkmrk-%7B%7B-%22user-is-logged-i-1">{{ "User is logged in" if loggedin }}</div><div id="bkmrk--24">  
</div><div id="bkmrk-%D0%97%D0%B4%D0%B5%D1%81%D1%8C%2C-%D0%B5%D1%81%D0%BB%D0%B8-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD">Здесь, если переменная loggedin вернет True, будет выведена строка “User is logged in”. В противном случае — ничего.</div><div id="bkmrk--25">  
</div><div id="bkmrk-%D0%9A%D0%B0%D0%BA-%D0%B8-%D0%B2-python-%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE">Как и в Python можно использовать операторы сравнения, присваивания и логические операторы для управляющих конструкций, чтобы создавать более сложные условия. Вот несколько примеров:</div><div id="bkmrk--26">  
</div><div id="bkmrk-%7B%23-%D0%95%D1%81%D0%BB%D0%B8-user.count-%D1%80">{# Если user.count ревен 1000, код '&lt;p&gt;User count is 1000&lt;/p&gt;' отобразится #}</div><div id="bkmrk-%7B%25-if-users.count-%3D%3D">{% if users.count == 1000 %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Euser-count-is"> &lt;p&gt;User count is 1000&lt;/p&gt;</div><div id="bkmrk-%7B%25-endif-%25%7D-4">{% endif %}</div><div id="bkmrk--27">  
</div><div id="bkmrk-%7B%23-%D0%95%D1%81%D0%BB%D0%B8-%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5-10">{# Если выражение 10 &gt;= 2 верно, код '&lt;p&gt;10 &gt;= 2&lt;/p&gt;' отобразится #}</div><div id="bkmrk-%7B%25-if-10-%3E%3D-2-%25%7D">{% if 10 &gt;= 2 %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3E10-%3E%3D-2%3C%2Fp%3E"> &lt;p&gt;10 &gt;= 2&lt;/p&gt;</div><div id="bkmrk-%7B%25-endif-%25%7D-5">{% endif %}</div><div id="bkmrk--28">  
</div><div id="bkmrk-%7B%23-%D0%95%D1%81%D0%BB%D0%B8-%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5-%22c">{# Если выражение "car" &lt;= "train" верно, код '&lt;p&gt;car &lt;= train&lt;/p&gt;' отобразится #}</div><div id="bkmrk-%7B%25-if-%22car%22-%3C%3D-%22trai">{% if "car" &lt;= "train" %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Ecar-%3C%3D-train%3C"> &lt;p&gt;car &lt;= train&lt;/p&gt;</div><div id="bkmrk-%7B%25-endif-%25%7D-6">{% endif %}</div><div id="bkmrk--29">  
</div><div id="bkmrk-%7B%23">{#</div><div id="bkmrk-%C2%A0-%C2%A0-%D0%95%D1%81%D0%BB%D0%B8-user-%D0%B7%D0%B0%D0%BB%D0%BE%D0%B3%D0%B8"> Если user залогинен и superuser, код</div><div id="bkmrk-%C2%A0-%C2%A0-%27%3Cp%3Euser-is-logg"> '&lt;p&gt;User is logged in and is a superuser&lt;/p&gt;' отобразится</div><div id="bkmrk-%23%7D">\#}</div><div id="bkmrk-%7B%25-if-user.loggedin-">{% if user.loggedin and user.is_superuser %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Euser-is-logge"> &lt;p&gt;User is logged in and is a superuser&lt;/p&gt;</div><div id="bkmrk-%7B%25-endif-%25%7D-7">{% endif %}</div><div id="bkmrk--30">  
</div><div id="bkmrk-%7B%23-1">{#</div><div id="bkmrk-%C2%A0-%C2%A0-%D0%95%D1%81%D0%BB%D0%B8-user-%D1%8F%D0%B2%D0%BB%D1%8F%D0%B5%D1%82"> Если user является superuser, moderator или author, код</div><div id="bkmrk-%C2%A0-%C2%A0-%27%3Ca-href%3D%22%23%22%3Eedi"> '&lt;a href="#"&gt;Edit&lt;/a&gt;' отобразится</div><div id="bkmrk-%23%7D-1">\#}</div><div id="bkmrk-%7B%25-if-user.is_superu">{% if user.is_superuser or user.is_moderator or user.is_author %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Ca-href%3D%22%23%22%3Eedit"> &lt;a href="#"&gt;Edit&lt;/a&gt;</div><div id="bkmrk-%7B%25-endif-%25%7D-8">{% endif %}</div><div id="bkmrk--31">  
</div><div id="bkmrk-%7B%23-2">{#</div><div id="bkmrk-%C2%A0-%C2%A0-%D0%95%D1%81%D0%BB%D0%B8-user-%D0%B8-curr"> Если user и current_user один и тот же объект, код </div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Euser-and-curr"> &lt;p&gt;user and current_user are same&lt;/p&gt; отобразится</div><div id="bkmrk-%23%7D-2">\#}</div><div id="bkmrk-%7B%25-if-user-is-curren">{% if user is current_user %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Euser-and-curr-1"> &lt;p&gt;user and current_user are same&lt;/p&gt;</div><div id="bkmrk-%7B%25-endif-%25%7D-9">{% endif %}</div><div id="bkmrk--32">  
</div><div id="bkmrk-%7B%23-3">{#</div><div id="bkmrk-%C2%A0-%C2%A0-%D0%95%D1%81%D0%BB%D0%B8-%22flask%22-%D0%B5%D1%81%D1%82"> Если "Flask" есть в списке, код </div><div id="bkmrk-%C2%A0-%C2%A0-%27%3Cp%3Eflask-is-in-"> '&lt;p&gt;Flask is in the dictionary&lt;/p&gt;' отобразится</div><div id="bkmrk-%23%7D-3">\#}</div><div id="bkmrk-%7B%25-if-%5B%22flask%22%5D-in-%5B">{% if ["Flask"] in ["Django", "web2py", "Flask"] %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Eflask-is-in-t"> &lt;p&gt;Flask is in the dictionary&lt;/p&gt;</div><div id="bkmrk-%7B%25-endif-%25%7D-10">{% endif %}</div><div id="bkmrk--33">  
</div><div id="bkmrk-%D0%95%D1%81%D0%BB%D0%B8-%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D1%8F-%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D1%8F">Если условия становятся слишком сложными, или просто есть желание поменять приоритет оператора, можно обернуть выражения скобками ():</div><div id="bkmrk--34">  
</div><div id="bkmrk-%7B%25-if-%28user.marks-%3E-">{% if (user.marks &gt; 80) and (user.marks &lt; 90) %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Eyou-grade-is-"> &lt;p&gt;You grade is B&lt;/p&gt;</div><div id="bkmrk-%7B%25-endif-%25%7D-11">{% endif %}</div><div id="bkmrk--35">  
</div><div id="bkmrk-%D0%A6%D0%B8%D0%BA%D0%BB-for">Цикл for</div><div id="bkmrk--36">  
</div><div id="bkmrk-%D0%A6%D0%B8%D0%BA%D0%BB-for-%D0%BF%D0%BE%D0%B7%D0%B2%D0%BE%D0%BB%D1%8F%D0%B5%D1%82-%D0%BF">Цикл for позволяет перебирать последовательность. Например:</div><div id="bkmrk--37">  
</div><div id="bkmrk-%7B%25-set-user_list-%3D-%5B">{% set user_list = ['tom', 'jerry', 'spike'] %}</div><div id="bkmrk--38">  
</div><div id="bkmrk-%3Cul%3E-2">&lt;ul&gt;</div><div id="bkmrk-%7B%25-for-user-in-user_">{% for user in user_list %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3E%7B%7B-user-%7D%7D%3C%2F"> &lt;li&gt;{{ user }}&lt;/li&gt;</div><div id="bkmrk-%7B%25-endfor-%25%7D">{% endfor %}</div><div id="bkmrk-%3C%2Ful%3E-2">&lt;/ul&gt;</div><div id="bkmrk--39">  
</div><div id="bkmrk-%D0%92%D1%8B%D0%B2%D0%BE%D0%B4%3A">Вывод:</div><div id="bkmrk--40">  
</div><div id="bkmrk-%3Cul%3E-3">&lt;ul&gt;</div><div id="bkmrk--41">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3Etom%3C%2Fli%3E"> &lt;li&gt;tom&lt;/li&gt;</div><div id="bkmrk--42">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3Ejerry%3C%2Fli%3E"> &lt;li&gt;jerry&lt;/li&gt;</div><div id="bkmrk--43">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3Espike%3C%2Fli%3E"> &lt;li&gt;spike&lt;/li&gt;</div><div id="bkmrk--44">  
</div><div id="bkmrk-%3C%2Ful%3E-3">&lt;/ul&gt;</div><div id="bkmrk--45">  
</div><div id="bkmrk-%D0%92%D0%BE%D1%82-%D0%BA%D0%B0%D0%BA-%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE-%D0%BF%D0%B5%D1%80%D0%B5%D0%B1%D0%B8">Вот как можно перебирать значения словаря:</div><div id="bkmrk--46">  
</div><div id="bkmrk-%7B%25-set-employee-%3D-%7B-">{% set employee = { 'name': 'tom', 'age': 25, 'designation': 'Manager' } %}</div><div id="bkmrk--47">  
</div><div id="bkmrk-%3Cul%3E-4">&lt;ul&gt;</div><div id="bkmrk-%7B%25-for-key-in-employ">{% for key in employee.items() %}</div><div id="bkmrk-%3Cli%3E%7B%7B-key-%7D%7D-%3A-%7B%7B-e">&lt;li&gt;{{ key }} : {{ employee[key] }}&lt;/li&gt;</div><div id="bkmrk-%7B%25-endfor-%25%7D-1">{% endfor %}</div><div id="bkmrk-%3C%2Ful%3E-4">&lt;/ul&gt;</div><div id="bkmrk--48">  
</div><div id="bkmrk-%D0%92%D1%8B%D0%B2%D0%BE%D0%B4%3A-1">Вывод:</div><div id="bkmrk--49">  
</div><div id="bkmrk-%3Cul%3E-5">&lt;ul&gt;</div><div id="bkmrk--50">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3Edesignation-"> &lt;li&gt;designation : Manager&lt;/li&gt;</div><div id="bkmrk--51">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3Ename-%3A-tom%3C%2F"> &lt;li&gt;name : tom&lt;/li&gt;</div><div id="bkmrk--52">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3Eage-%3A-25%3C%2Fli"> &lt;li&gt;age : 25&lt;/li&gt;</div><div id="bkmrk--53">  
</div><div id="bkmrk-%3C%2Ful%3E-5">&lt;/ul&gt;</div><div id="bkmrk--54">  
</div><div id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%87%D0%B0%D0%BD%D0%B8%D0%B5%3A-%D0%B2-python">Примечание: в Python элементы словаря не хранятся в конкретном порядке, поэтому вывод может отличаться.</div><div id="bkmrk--55">  
</div><div id="bkmrk-%D0%95%D1%81%D0%BB%D0%B8-%D0%BD%D1%83%D0%B6%D0%BD%D0%BE-%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C-">Если нужно получить ключ и значение словаря вместе, используйте метод items().</div><div id="bkmrk--56">  
</div><div id="bkmrk-%7B%25-set-employee-%3D-%7B--1">{% set employee = { 'name': 'tom', 'age': 25, 'designation': 'Manager' } %}</div><div id="bkmrk--57">  
</div><div id="bkmrk-%3Cul%3E-6">&lt;ul&gt;</div><div id="bkmrk-%7B%25-for-key%2C-value-in">{% for key, value in employee.items() %}</div><div id="bkmrk-%3Cli%3E%7B%7B-key-%7D%7D-%3A-%7B%7B-v">&lt;li&gt;{{ key }} : {{ value }}&lt;/li&gt;</div><div id="bkmrk-%7B%25-endfor-%25%7D-2">{% endfor %}</div><div id="bkmrk-%3C%2Ful%3E-6">&lt;/ul&gt;</div><div id="bkmrk--58">  
</div><div id="bkmrk-%D0%92%D1%8B%D0%B2%D0%BE%D0%B4%3A-2">Вывод:</div><div id="bkmrk--59">  
</div><div id="bkmrk-%3Cul%3E-7">&lt;ul&gt;</div><div id="bkmrk--60">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3Edesignation--1"> &lt;li&gt;designation : Manager&lt;/li&gt;</div><div id="bkmrk--61">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3Ename-%3A-tom%3C%2F-1"> &lt;li&gt;name : tom&lt;/li&gt;</div><div id="bkmrk--62">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3Eage-%3A-25%3C%2Fli-1"> &lt;li&gt;age : 25&lt;/li&gt;</div><div id="bkmrk--63">  
</div><div id="bkmrk-%3C%2Ful%3E-7">&lt;/ul&gt;</div><div id="bkmrk--64">  
</div><div id="bkmrk-%D0%A6%D0%B8%D0%BA%D0%BB-for-%D1%82%D0%B0%D0%BA%D0%B6%D0%B5-%D0%BC%D0%BE%D0%B6%D0%B5%D1%82">Цикл for также может использовать дополнительное условие else, как в Python, но зачастую способ его применения отличается. Стоит вспомнить, что в Python, если else идет следом за циклом for, условие else выполняется только в том случае, если цикл завершается после перебора всей последовательности, или если она пуста. Оно не выполняется, если цикл остановить оператором break.</div><div id="bkmrk--65">  
</div><div id="bkmrk-%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0-%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5-else-%D0%B8">Когда условие else используется в цикле for в Jinja, оно исполняется только в том случае, если последовательность пустая или не определена. Например:</div><div id="bkmrk-%D0%A0%D0%B5%D0%BA%D0%BB%D0%B0%D0%BC%D0%B0-2">Реклама</div><div id="bkmrk--66">  
</div><div id="bkmrk-%7B%25-set-user_list-%3D-%5B-1">{% set user_list = [] %}</div><div id="bkmrk--67">  
</div><div id="bkmrk-%3Cul%3E-8">&lt;ul&gt;</div><div id="bkmrk-%7B%25-for-user-in-user_-1">{% for user in user_list %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3E%7B%7B-user-%7D%7D%3C%2F-1"> &lt;li&gt;{{ user }}&lt;/li&gt;</div><div id="bkmrk-%7B%25-else-%25%7D-3">{% else %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3Euser_list-is"> &lt;li&gt;user_list is empty&lt;/li&gt;</div><div id="bkmrk-%7B%25-endfor-%25%7D-3">{% endfor %}</div><div id="bkmrk-%3C%2Ful%3E-8">&lt;/ul&gt;</div><div id="bkmrk--68">  
</div><div id="bkmrk-%D0%92%D1%8B%D0%B2%D0%BE%D0%B4%3A-3">Вывод:</div><div id="bkmrk--69">  
</div><div id="bkmrk-%3Cul%3E-9">&lt;ul&gt;</div><div id="bkmrk--70">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3Euser_list-is-1"> &lt;li&gt;user_list is empty&lt;/li&gt;</div><div id="bkmrk--71">  
</div><div id="bkmrk-%3C%2Ful%3E-9">&lt;/ul&gt;</div><div id="bkmrk--72">  
</div><div id="bkmrk-%D0%9F%D0%BE-%D0%B0%D0%BD%D0%B0%D0%BB%D0%BE%D0%B3%D0%B8%D0%B8-%D1%81-%D0%B2%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD">По аналогии с вложенными инструкциями if, можно использовать вложенные циклы for. На самом деле, любые управляющие конструкции можно вкладывать одна в другую.</div><div id="bkmrk--73">  
</div><div id="bkmrk-%7B%25-for-user-in-user_-2">{% for user in user_list %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3E%7B%7B-user.full_"> &lt;p&gt;{{ user.full_name }}&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3E"> &lt;p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cul-class%3D%22f"> &lt;ul class="follower-list"&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B%25-for-f"> {% for follower in user.followers %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cli%3E%7B%7B-f"> &lt;li&gt;{{ follower }}&lt;/li&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B%25-endfo"> {% endfor %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3C%2Ful%3E"> &lt;/ul&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3C%2Fp%3E"> &lt;/p&gt;</div><div id="bkmrk-%7B%25-endfor-%25%7D-4">{% endfor %}</div><div id="bkmrk--74">  
</div><div id="bkmrk-%D0%A6%D0%B8%D0%BA%D0%BB-for-%D0%BF%D1%80%D0%B5%D0%B4%D0%BE%D1%81%D1%82%D0%B0%D0%B2%D0%BB%D1%8F">Цикл for предоставляет специальную переменную loop для отслеживания прогресса цикла. Например:</div><div id="bkmrk--75">  
</div><div id="bkmrk-%3Cul%3E-10">&lt;ul&gt;</div><div id="bkmrk-%7B%25-for-user-in-user_-3">{% for user in user_list %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3E%7B%7B-loop.inde"> &lt;li&gt;{{ loop.index }} - {{ user }}&lt;/li&gt;</div><div id="bkmrk-%7B%25-endfor-%25%7D-5">{% endfor %}</div><div id="bkmrk-%3C%2Ful%3E-10">&lt;/ul&gt;</div><div id="bkmrk--76">  
</div><div id="bkmrk-loop.index-%D0%B2%D0%BD%D1%83%D1%82%D1%80%D0%B8-%D1%86%D0%B8">loop.index внутри цикла for начинает отсчет с 1. В таблице упомянуты остальные широко используемые атрибуты переменной loop.</div><div id="bkmrk-%D0%9C%D0%B5%D1%82%D0%BE%D0%B4-%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5">Метод Значение</div><div id="bkmrk-loop.index0-%D1%82%D0%BE-%D0%B6%D0%B5-%D1%81%D0%B0">loop.index0 то же самое что и loop.index, но с индексом 0, то есть, начинает считать с 0, а не с 1.</div><div id="bkmrk-loop.revindex-%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0">loop.revindex возвращает номер итерации с конца цикла (считает с 1).</div><div id="bkmrk-loop.revindex0-%D0%B2%D0%BE%D0%B7%D0%B2%D1%80">loop.revindex0 возвращает номер итерации с конца цикла (считает с 0).</div><div id="bkmrk-loop.first-%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%89%D0%B0%D0%B5">loop.first возвращает True, если итерация первая. В противном случае — False.</div><div id="bkmrk-loop.last-%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%89%D0%B0%D0%B5%D1%82">loop.last возвращает True, если итерация последняя. В противном случае — False.</div><div id="bkmrk-loop.length-%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%89%D0%B0">loop.length возвращает длину цикла(количество итераций).</div><div id="bkmrk--77">  
</div><div id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%87%D0%B0%D0%BD%D0%B8%D0%B5%3A-%D0%BF%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9-%D1%81">Примечание: полный список есть в документации Flask.</div><div id="bkmrk-%D0%A4%D0%B8%D0%BB%D1%8C%D1%82%D1%80%D1%8B">Фильтры</div><div id="bkmrk--78">  
</div><div id="bkmrk-%D0%A4%D0%B8%D0%BB%D1%8C%D1%82%D1%80%D1%8B-%D0%B8%D0%B7%D0%BC%D0%B5%D0%BD%D1%8F%D1%8E%D1%82-%D0%BF%D0%B5%D1%80">Фильтры изменяют переменные до процесса рендеринга. Синтаксис использования фильтров следующий:</div><div id="bkmrk--79">  
</div><div id="bkmrk-variable_or_value%7Cfi">variable_or_value|filter_name</div><div id="bkmrk--80">  
</div><div id="bkmrk-%D0%92%D0%BE%D1%82-%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%3A">Вот пример:</div><div id="bkmrk--81">  
</div><div id="bkmrk-%7B%7B-comment%7Ctitle-%7D%7D">{{ comment|title }}</div><div id="bkmrk--82">  
</div><div id="bkmrk-%D0%A4%D0%B8%D0%BB%D1%8C%D1%82%D1%80-title-%D0%B4%D0%B5%D0%BB%D0%B0%D0%B5%D1%82-">Фильтр title делает заглавной первую букву в каждом слове. Если значение переменной comment — "dust in the wind", то вывод будет "Dust In The Wind".</div><div id="bkmrk--83">  
</div><div id="bkmrk-%D0%9C%D0%BE%D0%B6%D0%BD%D0%BE-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C-%D0%BD">Можно использовать несколько фильтров, чтобы точнее настраивать вывод. Например:</div><div id="bkmrk--84">  
</div><div id="bkmrk-%7B%7B-full_name%7Cstripta">{{ full_name|striptags|title }}</div><div id="bkmrk--85">  
</div><div id="bkmrk-%D0%A4%D0%B8%D0%BB%D1%8C%D1%82%D1%80-striptags-%D1%83%D0%B4%D0%B0">Фильтр striptags удалит из переменной все HTML-теги. В приведенном выше коде сначала будет применен фильтр striptags, а затем — title.</div><div id="bkmrk--86">  
</div><div id="bkmrk-%D0%A3-%D0%BD%D0%B5%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D1%8B%D1%85-%D1%84%D0%B8%D0%BB%D1%8C%D1%82%D1%80%D0%BE%D0%B2">У некоторых фильтров есть аргументы. Чтобы передать их фильтру, нужно вызвать фильтр как функцию. Например:</div><div id="bkmrk--87">  
</div><div id="bkmrk-%7B%7B-number%7Cround%282%29-%7D">{{ number|round(2) }}</div><div id="bkmrk--88">  
</div><div id="bkmrk-%D0%A4%D0%B8%D0%BB%D1%8C%D1%82%D1%80-round-%D0%BE%D0%BA%D1%80%D1%83%D0%B3%D0%BB%D1%8F">Фильтр round округляет число до конкретного количества символов.</div><div id="bkmrk--89">  
</div><div id="bkmrk-%D0%92-%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%B9-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B5-">В следующей таблице указаны широко используемые фильтры.</div><div id="bkmrk-%D0%9D%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5">Название Описание</div><div id="bkmrk-upper-%D0%B4%D0%B5%D0%BB%D0%B0%D0%B5%D1%82-%D0%B2%D1%81%D0%B5-%D1%81%D0%B8%D0%BC">upper делает все символы заглавными</div><div id="bkmrk-lower-%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D0%B8%D1%82-%D0%B2%D1%81%D0%B5-%D1%81">lower приводит все символы к нижнему регистру</div><div id="bkmrk-capitalize-%D0%B4%D0%B5%D0%BB%D0%B0%D0%B5%D1%82-%D0%B7%D0%B0">capitalize делает заглавной первую букву и приводит остальные к нижнему регистру</div><div id="bkmrk-escape-%D1%8D%D0%BA%D1%80%D0%B0%D0%BD%D0%B8%D1%80%D1%83%D0%B5%D1%82-%D0%B7%D0%BD">escape экранирует значение</div><div id="bkmrk-safe-%D0%BF%D1%80%D0%B5%D0%B4%D0%BE%D1%82%D0%B2%D1%80%D0%B0%D1%89%D0%B0%D0%B5%D1%82-%D1%8D">safe предотвращает экранирование</div><div id="bkmrk-length-%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%89%D0%B0%D0%B5%D1%82-%D0%BA%D0%BE">length возвращает количество элементов в последовательности</div><div id="bkmrk-trim-%D1%83%D0%B4%D0%B0%D0%BB%D1%8F%D0%B5%D1%82-%D0%BF%D1%83%D1%81%D1%82%D1%8B%D0%B5-">trim удаляет пустые символы в начале и в конце</div><div id="bkmrk-random-%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%89%D0%B0%D0%B5%D1%82-%D1%81%D0%BB">random возвращает случайный элемент последовательности</div><div id="bkmrk--90">  
</div><div id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%87%D0%B0%D0%BD%D0%B8%D0%B5%3A-%D0%BF%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9-%D1%81-1">Примечание: полный список фильтров доступен здесь.</div><div id="bkmrk-%D0%9C%D0%B0%D0%BA%D1%80%D0%BE%D1%81%D1%8B">Макросы</div><div id="bkmrk--91">  
</div><div id="bkmrk-%D0%9C%D0%B0%D0%BA%D1%80%D0%BE%D1%81%D1%8B-%D0%B2-jinja-%D0%BD%D0%B0%D0%BF%D0%BE">Макросы в Jinja напоминают функции в Python. Суть в том, чтобы сделать код, который можно использовать повторно, просто присвоив ему название. Например:</div><div id="bkmrk--92">  
</div><div id="bkmrk-%7B%25-macro-render_post">{% macro render_posts(post_list, sep=False) %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cdiv%3E"> &lt;div&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B%25-for-post-"> {% for post in post_list %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Ch2%3E%7B%7B-p"> &lt;h2&gt;{{ post.title }}&lt;/h2&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Carticle"> &lt;article&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B%7B-p"> {{ post.html|safe }}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3C%2Farticl"> &lt;/article&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B%25-endfor-%25%7D"> {% endfor %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B%25-if-sep-%25%7D"> {% if sep %}&lt;hr&gt;{% endif %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3C%2Fdiv%3E"> &lt;/div&gt;</div><div id="bkmrk-%7B%25-endmacro-%25%7D-1">{% endmacro %}</div><div id="bkmrk--93">  
</div><div id="bkmrk-%D0%92-%D1%8D%D1%82%D0%BE%D0%BC-%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D0%B5-%D1%81%D0%BE%D0%B7%D0%B4%D0%B0">В этом примере создан макрос render_posts, который принимает обязательный аргумент post_list и необязательный аргумент sep. Использовать его нужно следующим образом:</div><div id="bkmrk--94">  
</div><div id="bkmrk-%7B%7B-render_posts%28post">{{ render_posts(posts) }}</div><div id="bkmrk--95">  
</div><div id="bkmrk-%D0%9E%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BC%D0%B0%D0%BA%D1%80%D0%BE%D1%81%D0%B0-">Определение макроса должно идти до первого вызова, иначе выйдет ошибка.</div><div id="bkmrk-%D0%A0%D0%B5%D0%BA%D0%BB%D0%B0%D0%BC%D0%B0-3">Реклама</div><div id="bkmrk--96">  
</div><div id="bkmrk-%D0%92%D0%BC%D0%B5%D1%81%D1%82%D0%BE-%D1%82%D0%BE%D0%B3%D0%BE-%D1%87%D1%82%D0%BE%D0%B1%D1%8B-%D0%B8%D1%81">Вместо того чтобы использовать макросы прямо в шаблоне, лучше хранить их в отдельном файле и импортировать по надобности.</div><div id="bkmrk--97">  
</div><div id="bkmrk-%D0%9F%D1%80%D0%B5%D0%B4%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D0%BC%2C-%D0%B2%D1%81%D0%B5-%D0%BC%D0%B0%D0%BA">Предположим, все макросы хранятся в файле macros.html в папке templates. Чтобы импортировать их из файла, нужно использовать инструкцию import:</div><div id="bkmrk--98">  
</div><div id="bkmrk-%7B%25-import-%22macros.ht">{% import "macros.html" as macros %}</div><div id="bkmrk--99">  
</div><div id="bkmrk-%D0%A2%D0%B5%D0%BF%D0%B5%D1%80%D1%8C-%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE-%D1%81%D1%81%D1%8B%D0%BB%D0%B0%D1%82%D1%8C">Теперь можно ссылаться на макросы в файле macros.html с помощью переменной macros. Например:</div><div id="bkmrk--100">  
</div><div id="bkmrk-%7B%7B-macros.render_pos">{{ macros.render_posts(posts) }}</div><div id="bkmrk--101">  
</div><div id="bkmrk-%D0%98%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%86%D0%B8%D1%8F-%7B%25-import">Инструкция {% import “macros.html” as macros %} импортирует все макросы и переменные (определенные на высшем уровне) из файла macros.html в шаблон. Также можно импортировать определенные макросы с помощью from:</div><div id="bkmrk--102">  
</div><div id="bkmrk-%7B%25-from-%22macros.html">{% from "macros.html" import render_posts %}</div><div id="bkmrk--103">  
</div><div id="bkmrk-%D0%9F%D1%80%D0%B8-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B8-%D0%BC%D0%B0">При использовании макросов будут ситуации, когда потребуется передать им произвольное число аргументов.</div><div id="bkmrk--104">  
</div><div id="bkmrk-%D0%9F%D0%BE-%D0%B0%D0%BD%D0%B0%D0%BB%D0%BE%D0%B3%D0%B8%D0%B8-%D1%81-%2Aargs-">По аналогии с *args и **kwargs в Python внутри макросов можно получить доступ к varargs и kwargs.</div><div id="bkmrk--105">  
</div><div id="bkmrk-varags%3A-%D1%81%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D1%8F%D0%B5%D1%82-%D0%B4%D0%BE">varags: сохраняет дополнительные позиционные аргументы, переданные макросу, в виде кортежа.</div><div id="bkmrk--106">  
</div><div id="bkmrk-lwargs%3A-%D1%81%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D1%8F%D0%B5%D1%82-%D0%B4%D0%BE">lwargs: сохраняет дополнительные позиционные аргументы, переданные макросу, в виде словаря.</div><div id="bkmrk--107">  
</div><div id="bkmrk-%D0%A5%D0%BE%D1%82%D1%8F-%D0%BA-%D0%BD%D0%B8%D0%BC-%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE-%D0%BF%D0%BE%D0%BB">Хотя к ним можно получить доступ внутри макроса, объявлять их отдельно в заголовке макроса не нужно. Вот пример:</div><div id="bkmrk--108">  
</div><div id="bkmrk-%7B%25-macro-custom_rend">{% macro custom_renderer(para) %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3E%7B%7B-para-%7D%7D%3C%2Fp"> &lt;p&gt;{{ para }}&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Evarargs%3A-%7B%7B-v"> &lt;p&gt;varargs: {{ varargs }}&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Ekwargs%3A-%7B%7B-kw"> &lt;p&gt;kwargs: {{ kwargs }}&lt;/p&gt;</div><div id="bkmrk-%7B%25%C2%A0-endmacro%C2%A0-%25%7D">{% endmacro %}</div><div id="bkmrk--109">  
</div><div id="bkmrk-%7B%7B-custom_renderer%28%22">{{ custom_renderer("some content", "apple", name='spike', age=15) }}</div><div id="bkmrk--110">  
</div><div id="bkmrk-%D0%92-%D1%8D%D1%82%D0%BE%D0%BC-%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B5-%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD">В этом случае дополнительный позиционный аргумент, "apple", присваивается varargs, а дополнительные аргументы-ключевые слова (name=’spike’, age=15) — kwargs.</div><div id="bkmrk-%D0%AD%D0%BA%D1%80%D0%B0%D0%BD%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5">Экранирование</div><div id="bkmrk--111">  
</div><div id="bkmrk-jinja-%D0%BF%D0%BE-%D1%83%D0%BC%D0%BE%D0%BB%D1%87%D0%B0%D0%BD%D0%B8%D1%8E-%D0%B0">Jinja по умолчанию автоматически экранирует вывод переменной в целях безопасности. Поэтому если переменная содержит, например, такой HTML-код: "&lt;p&gt;Escaping in Jinja&lt;/p&gt;", он отрендерится в виде "&amp;lt;p&amp;gt;Escaping in Jinja&amp;lt;/p&amp;gt;". Благодаря этому HTML-коды будут отображаться в браузере, а не интерпретироваться. Если есть уверенность, что данные безопасны и их точно можно рендерить, стоит воспользоваться фильтром safe. Например:</div><div id="bkmrk--112">  
</div><div id="bkmrk-%7B%25-set-html-%3D-%22%3Cp%3Ees">{% set html = "&lt;p&gt;Escaping in Jinja&lt;/p&gt;" %}</div><div id="bkmrk-%7B%7B-html%7Csafe-%7D%7D">{{ html|safe }}</div><div id="bkmrk--113">  
</div><div id="bkmrk-%D0%92%D1%8B%D0%B2%D0%BE%D0%B4%3A-4">Вывод:</div><div id="bkmrk--114">  
</div><div id="bkmrk-%3Cp%3Eescaping-in-jinja">&lt;p&gt;Escaping in Jinja&lt;/p&gt;</div><div id="bkmrk--115">  
</div><div id="bkmrk-%D0%98%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C-%D1%84%D0%B8%D0%BB%D1%8C%D1%82%D1%80-">Использовать фильтр safe в большом блоке кода будет неудобно, поэтому в Jinja есть оператор autoescape, который используется, чтобы отключить экранирование для большого объема данных. Он может принимать аргументы true или false для включения и отключения экранирования, соответственно. Например:</div><div id="bkmrk--116">  
</div><div id="bkmrk-%7B%25-autoescape-true-%25">{% autoescape true %}</div><div id="bkmrk-%C2%A0-%C2%A0-escaping-enabled"> Escaping enabled</div><div id="bkmrk-%7B%25-endautoescape-%25%7D">{% endautoescape %}</div><div id="bkmrk--117">  
</div><div id="bkmrk-%7B%25-autoescape-false-">{% autoescape false %}</div><div id="bkmrk-%C2%A0-%C2%A0-escaping-disable"> Escaping disabled</div><div id="bkmrk-%7B%25-endautoescape-%25%7D-1">{% endautoescape %}</div><div id="bkmrk--118">  
</div><div id="bkmrk-%D0%92%D1%81%D0%B5-%D0%BC%D0%B5%D0%B6%D0%B4%D1%83-%7B%25-autoesc">Все между {% autoescape false %} и {% endautoescape %} отрендерится без экранирования символов. Если нужно экранировать отдельные символы при выключенном экранировании, стоит использовать фильтр escape. Например:</div><div id="bkmrk--119">  
</div><div id="bkmrk-%7B%25-autoescape-false--1">{% autoescape false %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cdiv-class%3D%22post"> &lt;div class="post"&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B%25-for-post--1"> {% for post in post_list %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Ch2%3E%7B%7B-p-1"> &lt;h2&gt;{{ post.title }}&lt;/h2&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Carticle-1"> &lt;article&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B%7B-p-1"> {{ post.html }}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3C%2Farticl-1"> &lt;/article&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B%25-endfor-%25%7D-1"> {% endfor %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3C%2Fdiv%3E-1"> &lt;/div&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cdiv%3E-1"> &lt;div&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B%25-for-comme"> {% for comment in comment_list %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cp%3E%7B%7B-co"> &lt;p&gt;{{ comment|escape }}&lt;/p&gt; # escaping is on for comments</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B%25-endfor-%25%7D-2"> {% endfor %}</div><div id="bkmrk-%C2%A0-%C2%A0-%3C%2Fdiv%3E-2"> &lt;/div&gt;</div><div id="bkmrk-%7B%25-endautoescape-%25%7D-2">{% endautoescape %}</div><div id="bkmrk--120">  
</div><div id="bkmrk-%D0%92%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5-%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D1%8B">Вложенные шаблоны</div><div id="bkmrk--121">  
</div><div id="bkmrk-%D0%98%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%86%D0%B8%D1%8F-include-%D1%80">Инструкция include рендерит шаблон внутри другого шаблона. Она широко используется, чтобы рендерить статический раздел, который повторяется в разных местах сайта. Вот синтаксис include:</div><div id="bkmrk--122">  
</div><div id="bkmrk-%D0%9F%D1%80%D0%B5%D0%B4%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D0%BC%2C-%D1%87%D1%82%D0%BE-%D0%BD%D0%B0%D0%B2">Предположим, что навигационное меню хранится в файле nav.html, сохраненном в папке templates:</div><div id="bkmrk--123">  
</div><div id="bkmrk-%3Cnav%3E">&lt;nav&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Ca-href%3D%22%2Fhome%22%3E"> &lt;a href="/home"&gt;Home&lt;/a&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Ca-href%3D%22%2Fblog%22%3E"> &lt;a href="/blog"&gt;Blog&lt;/a&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Ca-href%3D%22%2Fcontac"> &lt;a href="/contact"&gt;Contact&lt;/a&gt;</div><div id="bkmrk-%3C%2Fnav%3E">&lt;/nav&gt;</div><div id="bkmrk--124">  
</div><div id="bkmrk-%D0%A7%D1%82%D0%BE%D0%B1%D1%8B-%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C-%D1%8D%D1%82%D0%BE-%D0%BC">Чтобы добавить это меню в home.html, нужно использовать следующий код:</div><div id="bkmrk--125">  
</div><div id="bkmrk-%3C%21doctype-html%3E">&lt;!DOCTYPE html&gt;</div><div id="bkmrk-%3Chtml-lang%3D%22en%22%3E">&lt;html lang="en"&gt;</div><div id="bkmrk-%3Chead%3E">&lt;head&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cmeta-charset%3D%22u"> &lt;meta charset="UTF-8"&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Ctitle%3Etitle%3C%2Fti"> &lt;title&gt;Title&lt;/title&gt;</div><div id="bkmrk-%3C%2Fhead%3E">&lt;/head&gt;</div><div id="bkmrk-%3Cbody%3E">&lt;body&gt;</div><div id="bkmrk--126">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%23-%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%BB%D1%8F%D0%B5%D0%BC-%D0%BF%D0%B0%D0%BD"> {# добавляем панель навигации из nav.html #}</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-include-%27nav."> {% include 'nav.html' %}</div><div id="bkmrk--127">  
</div><div id="bkmrk-%3C%2Fbody%3E">&lt;/body&gt;</div><div id="bkmrk-%3C%2Fhtml%3E">&lt;/html&gt;</div><div id="bkmrk--128">  
</div><div id="bkmrk-%D0%92%D1%8B%D0%B2%D0%BE%D0%B4%3A-5">Вывод:</div><div id="bkmrk--129">  
</div><div id="bkmrk-%3C%21doctype-html%3E-1">&lt;!DOCTYPE html&gt;</div><div id="bkmrk-%3Chtml-lang%3D%22en%22%3E-1">&lt;html lang="en"&gt;</div><div id="bkmrk-%3Chead%3E-1">&lt;head&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cmeta-charset%3D%22u-1"> &lt;meta charset="UTF-8"&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Ctitle%3Etitle%3C%2Fti-1"> &lt;title&gt;Title&lt;/title&gt;</div><div id="bkmrk-%3C%2Fhead%3E-1">&lt;/head&gt;</div><div id="bkmrk-%3Cbody%3E-1">&lt;body&gt;</div><div id="bkmrk--130">  
</div><div id="bkmrk-%3Cnav%3E-1">&lt;nav&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Ca-href%3D%22%2Fhome%22%3E-1"> &lt;a href="/home"&gt;Home&lt;/a&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Ca-href%3D%22%2Fblog%22%3E-1"> &lt;a href="/blog"&gt;Blog&lt;/a&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Ca-href%3D%22%2Fcontac-1"> &lt;a href="/contact"&gt;Contact&lt;/a&gt;</div><div id="bkmrk-%3C%2Fnav%3E-1">&lt;/nav&gt;</div><div id="bkmrk--131">  
</div><div id="bkmrk-%3C%2Fbody%3E-1">&lt;/body&gt;</div><div id="bkmrk-%3C%2Fhtml%3E-1">&lt;/html&gt;</div><div id="bkmrk--132">  
</div><div id="bkmrk-%D0%9D%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D0%BE">Наследование шаблонов</div><div id="bkmrk--133">  
</div><div id="bkmrk-%D0%9D%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D0%BE-1">Наследование шаблонов — один из самых мощных элементов шаблонизатора Jinja. Его принцип похож на ООП (объектно-ориентированное программирование). Все начинается с создания базового шаблона, который содержит в себе скелет HTML и отдельные маркеры, которые дочерние шаблоны смогут переопределять. Маркеры создаются с помощью инструкции block. Дочерние шаблоны используют инструкцию extends для наследования или расширения основного шаблона. Вот пример:</div><div id="bkmrk--134">  
</div><div id="bkmrk-%7B%23-%D0%AD%D1%82%D0%BE-%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD-templa">{# Это шаблон templates/base.html #}</div><div id="bkmrk-%3C%21doctype-html%3E-2">&lt;!DOCTYPE html&gt;</div><div id="bkmrk-%3Chtml-lang%3D%22en%22%3E-2">&lt;html lang="en"&gt;</div><div id="bkmrk-%3Chead%3E-2">&lt;head&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cmeta-charset%3D%22u-2"> &lt;meta charset="UTF-8"&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Ctitle%3E%7B%25-block-"> &lt;title&gt;{% block title %}Default Title{% endblock %}&lt;/title&gt;</div><div id="bkmrk-%3C%2Fhead%3E-2">&lt;/head&gt;</div><div id="bkmrk-%3Cbody%3E-2">&lt;body&gt;</div><div id="bkmrk--135">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-block-nav-%25%7D"> {% block nav %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cul%3E"> &lt;ul&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cli%3E%3Ca-h"> &lt;li&gt;&lt;a href="/home"&gt;Home&lt;/a&gt;&lt;/li&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cli%3E%3Ca-h-1"> &lt;li&gt;&lt;a href="/api"&gt;API&lt;/a&gt;&lt;/li&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3C%2Ful%3E-1"> &lt;/ul&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-endblock-%25%7D"> {% endblock %}</div><div id="bkmrk-%C2%A0-%C2%A0%C2%A0"> </div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-block-content"> {% block content %}</div><div id="bkmrk-%C2%A0-%C2%A0%C2%A0-1"> </div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-endblock-%25%7D-1"> {% endblock %}</div><div id="bkmrk-%3C%2Fbody%3E-2">&lt;/body&gt;</div><div id="bkmrk-%3C%2Fhtml%3E-2">&lt;/html&gt;</div><div id="bkmrk--136">  
</div><div id="bkmrk-%D0%AD%D1%82%D0%BE-%D0%B1%D0%B0%D0%B7%D0%BE%D0%B2%D1%8B%D0%B9-%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD-b">Это базовый шаблон base.html. Он создает три блока с помощью block, которые впоследствии будут заполнены дочерними шаблонами. Инструкция block принимает один аргумент — название блока. Внутри шаблона это название должно быть уникальным, иначе возникнет ошибка.</div><div id="bkmrk--137">  
</div><div id="bkmrk-%D0%94%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9-%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD-%E2%80%94-%D1%8D%D1%82">Дочерний шаблон — это шаблон, который растягивает базовый шаблон. Он может добавлять, перезаписывать или оставлять элементы родительского блока. Вот как можно создать дочерний шаблон.</div><div id="bkmrk--138">  
</div><div id="bkmrk-%7B%23-%D0%AD%D1%82%D0%BE-%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD-templa-1">{# Это шаблон templates/child.html #}</div><div id="bkmrk-%7B%25-extends-%27base.htm">{% extends 'base.html' %}</div><div id="bkmrk--139">  
</div><div id="bkmrk--140">  
</div><div id="bkmrk-%7B%25-block-content-%25%7D">{% block content %}</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-for-bookmark-"> {% for bookmark in bookmarks %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cp%3E%7B%7B-bookma"> &lt;p&gt;{{ bookmark.title }}&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-endfor-%25%7D-2"> {% endfor %}</div><div id="bkmrk-%7B%25-endblock-%25%7D">{% endblock %}</div><div id="bkmrk--141">  
</div><div id="bkmrk-%D0%98%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%86%D0%B8%D1%8F-extends-%D1%81">Инструкция extends сообщает Jinja, что child.html — это дочерний элемент, наследник base.html. Когда Jinja обнаруживает инструкцию extends, он загружает базовый шаблон, то есть base.html, а затем заменяет блоки контента внутри родительского шаблона блоками с теми же именами из дочерних шаблонов. Если блок с соответствующим названием не найден, используется блок родительского шаблона.</div><div id="bkmrk--142">  
</div><div id="bkmrk-%D0%A1%D1%82%D0%BE%D0%B8%D1%82-%D0%BE%D1%82%D0%BC%D0%B5%D1%82%D0%B8%D1%82%D1%8C%2C-%D1%87%D1%82%D0%BE-">Стоит отметить, что в дочернем шаблоне перезаписывается только блок content, так что содержимое по умолчанию из title и nav будет использоваться при рендеринге дочернего шаблона. Вывод должен выглядеть следующим образом:</div><div id="bkmrk-%D0%A0%D0%B5%D0%BA%D0%BB%D0%B0%D0%BC%D0%B0-4">Реклама</div><div id="bkmrk--143">  
</div><div id="bkmrk-%3C%21doctype-html%3E-3">&lt;!DOCTYPE html&gt;</div><div id="bkmrk-%3Chead%3E-3">&lt;head&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cmeta-charset%3D%22u-3"> &lt;meta charset="UTF-8"&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Ctitle%3Edefault-t"> &lt;title&gt;Default Title&lt;/title&gt;</div><div id="bkmrk-%3C%2Fhead%3E-3">&lt;/head&gt;</div><div id="bkmrk-%3Cbody%3E-3">&lt;body&gt;</div><div id="bkmrk--144">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cul%3E"> &lt;ul&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cli%3E%3Ca-href%3D"> &lt;li&gt;&lt;a href="/home"&gt;Home&lt;/a&gt;&lt;/li&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cli%3E%3Ca-href%3D-1"> &lt;li&gt;&lt;a href="/api"&gt;API&lt;/a&gt;&lt;/li&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3C%2Ful%3E"> &lt;/ul&gt;</div><div id="bkmrk--145">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Ebookmark-titl"> &lt;p&gt;Bookmark title 1&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Ebookmark-titl-1"> &lt;p&gt;Bookmark title 2&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Ebookmark-titl-2"> &lt;p&gt;Bookmark title 3&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Ebookmark-titl-3"> &lt;p&gt;Bookmark title 4&lt;/p&gt;</div><div id="bkmrk--146">  
</div><div id="bkmrk--147">  
</div><div id="bkmrk-%3C%2Fbody%3E-3">&lt;/body&gt;</div><div id="bkmrk-%3C%2Fhtml%3E-3">&lt;/html&gt;</div><div id="bkmrk--148">  
</div><div id="bkmrk-%D0%95%D1%81%D0%BB%D0%B8-%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%2C-%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE-%D0%BF%D0%BE">Если нужно, можно поменять заголовок по умолчанию, переписав блок title в child.html:</div><div id="bkmrk--149">  
</div><div id="bkmrk-%7B%23-%D0%AD%D1%82%D0%BE-%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD-templa-2">{# Это шаблон templates/child.html #}</div><div id="bkmrk-%7B%25-extends-%27base.htm-1">{% extends 'base.html' %}</div><div id="bkmrk--150">  
</div><div id="bkmrk-%7B%25-block-title-%25%7D">{% block title %}</div><div id="bkmrk-%C2%A0-%C2%A0-child-title"> Child Title</div><div id="bkmrk-%7B%25-endblock-%25%7D-1">{% endblock %}</div><div id="bkmrk--151">  
</div><div id="bkmrk-%7B%25-block-content-%25%7D-1">{% block content %}</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-for-bookmark--1"> {% for bookmark in bookmarks %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cp%3E%7B%7B-bookma-1"> &lt;p&gt;{{ bookmark.title }}&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-endfor-%25%7D-3"> {% endfor %}</div><div id="bkmrk-%7B%25-endblock-%25%7D-2">{% endblock %}</div><div id="bkmrk--152">  
</div><div id="bkmrk-%D0%9F%D0%BE%D1%81%D0%BB%D0%B5-%D0%BF%D0%B5%D1%80%D0%B5%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8-%D0%B1%D0%BB%D0%BE">После перезаписи блока на контент из родительского шаблона все еще можно ссылаться с помощью функции super(). Обычно она используется, когда в дополнение к контенту дочернего шаблона нужно добавить содержимое из родительского. Например:</div><div id="bkmrk--153">  
</div><div id="bkmrk-%7B%23-%D0%AD%D1%82%D0%BE-%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD-templa-3">{# Это шаблон templates/child.html #}</div><div id="bkmrk-%7B%25-extends-%27base.htm-2">{% extends 'base.html' %}</div><div id="bkmrk--154">  
</div><div id="bkmrk-%7B%25-block-title-%25%7D-1">{% block title %}</div><div id="bkmrk-%C2%A0-%C2%A0-child-title-1"> Child Title</div><div id="bkmrk-%7B%25-endblock-%25%7D-3">{% endblock %}</div><div id="bkmrk--155">  
</div><div id="bkmrk-%7B%25-block-nav-%25%7D">{% block nav %}</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%7B-super%28%29-%7D%7D-%7B%23"> {{ super() }} {# referring to the content in the parent templates #}</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3E%3Ca-href%3D%22%2Fco"> &lt;li&gt;&lt;a href="/contact"&gt;Contact&lt;/a&gt;&lt;/li&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cli%3E%3Ca-href%3D%22%2Fca"> &lt;li&gt;&lt;a href="/career"&gt;Career&lt;/a&gt;&lt;/li&gt;</div><div id="bkmrk-%7B%25-endblock-%25%7D-4">{% endblock %}</div><div id="bkmrk--156">  
</div><div id="bkmrk-%7B%25-block-content-%25%7D-2">{% block content %}</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-for-bookmark--2"> {% for bookmark in bookmarks %}</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cp%3E%7B%7B-bookma-2"> &lt;p&gt;{{ bookmark.title }}&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%7B%25-endfor-%25%7D-4"> {% endfor %}</div><div id="bkmrk-%7B%25-endblock-%25%7D-5">{% endblock %}</div><div id="bkmrk--157">  
</div><div id="bkmrk-%D0%92%D1%8B%D0%B2%D0%BE%D0%B4%3A-6">Вывод:</div><div id="bkmrk--158">  
</div><div id="bkmrk-%3C%21doctype-html%3E-4">&lt;!DOCTYPE html&gt;</div><div id="bkmrk-%3Chead%3E-4">&lt;head&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cmeta-charset%3D%22u-4"> &lt;meta charset="UTF-8"&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Ctitle%3Echild-tit"> &lt;title&gt;Child Title&lt;/title&gt;</div><div id="bkmrk-%3C%2Fhead%3E-4">&lt;/head&gt;</div><div id="bkmrk-%3Cbody%3E-4">&lt;body&gt;</div><div id="bkmrk--159">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cul%3E-1"> &lt;ul&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cli%3E%3Ca-href%3D-2"> &lt;li&gt;&lt;a href="/home"&gt;Home&lt;/a&gt;&lt;/li&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cli%3E%3Ca-href%3D-3"> &lt;li&gt;&lt;a href="/api"&gt;API&lt;/a&gt;&lt;/li&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cli%3E%3Ca-href%3D-4"> &lt;li&gt;&lt;a href="/contact"&gt;Contact&lt;/a&gt;&lt;/li&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3Cli%3E%3Ca-href%3D-5"> &lt;li&gt;&lt;a href="/career"&gt;Career&lt;/a&gt;&lt;/li&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3C%2Ful%3E-1"> &lt;/ul&gt;</div><div id="bkmrk--160">  
</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Ebookmark-titl-4"> &lt;p&gt;Bookmark title 1&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Ebookmark-titl-5"> &lt;p&gt;Bookmark title 2&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Ebookmark-titl-6"> &lt;p&gt;Bookmark title 3&lt;/p&gt;</div><div id="bkmrk-%C2%A0-%C2%A0-%3Cp%3Ebookmark-titl-7"> &lt;p&gt;Bookmark title 4&lt;/p&gt;</div><div id="bkmrk--161">  
</div><div id="bkmrk--162">  
</div><div id="bkmrk-%3C%2Fbody%3E-4">&lt;/body&gt;</div><div id="bkmrk-%3C%2Fhtml%3E-4">&lt;/html&gt;</div></body></html>

# Авторизация и аутентификация

**Ссылки:**

[Fastapi users документация](https://fastapi-users.github.io/fastapi-users/latest/)

[Role-based authentification](https://app-generator.dev/docs/technologies/fastapi/rbac.html)

**Подготовка проекта**

```
python -m venv --system-site-packages env
python -m pip install fastapi uvicorn
```

# SQLAlchemy

# Sqlalchemy

<div class="align-center" id="bkmrk-%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0">**Установка**</div><div id="bkmrk-%D0%AF%D0%B4%D1%80%D0%BE%C2%A0">Ядро </div>```bash
pip install sqlalchemy
```

<div id="bkmrk-%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-%D1%8F%D0%B4%D1%80%D0%B0%3A-pip-"></div><div id="bkmrk-%D0%94%D1%80%D0%B0%D0%B9%D0%B2%D0%B5%D1%80-%D0%B4%D0%BB%D1%8F-postgres">Драйвер для postgres, mysql</div>```bash
pip install psycopg2
```

```bash
pip install psycopg2-binary
```

```bash
pip  install  pymysql
```

<div id="bkmrk-%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-%D1%80%D0%B0%D1%81%D1%88%D0%B8%D1%80%D0%B5%D0%BD%D0%B8%D0%B9"></div><div class="align-center" id="bkmrk-">**Подключение**</div>```python
from sqlalchemy import create_engine
engine = create_engine('postgresql+psycopg2://username:password@localhost:5432/mydb')
engine = create_engine('mysql+pymysql://cookiem:chip@mysql01.com/cookies', pool_recycle=3600)
engine = create_engine('sqlite:///cookies.db')
engine2 = create_engine('sqlite:///:memory:')
```

<div id="bkmrk-engine-%3D-create_engi"></div><div id="bkmrk-%D0%94%D0%BE%D0%BF.-%D0%BA%D0%BB%D1%8E%D1%87%D0%B8-%D1%87%D0%B5%D1%80%D0%B5%D0%B7-%D0%B7%D0%B0%D0%BF">Доп. ключи через запятую:</div><div id="bkmrk-echo-%D0%B1%D1%83%D0%BB%D0%B5%D0%B2%D0%BE.-%D0%9B%D0%BE%D0%B3-%D0%B7%D0%B0%D0%BF"><table border="1" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 17.7977%;"></col><col style="width: 82.3215%;"></col></colgroup><tbody><tr><td>echo</td><td>булево. Лог запросов. По-умолчанию False.</td></tr><tr><td>encoding</td><td>строка. По-умолчанию utf-8</td></tr><tr><td>isolation\_level</td><td>уровень изоляции</td></tr><tr><td>pool\_recycle</td><td>число секунд для переподключения, желательно выставлять 3600. При работе с mysql соединение может быть активным до 4 часов.</td></tr></tbody></table>

</div>Создание engine не создает фактического соединения с БД.

```python
connection = engine.connect()
```

Сырой запрос:

```python
result = connection.execute("select * from orders").fetchall()
```

<div id="bkmrk-2.-%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C-%D0%BF%D0%BE%D0%B4%D0%BA%D0%BB%D1%8E"></div><div class="align-center" id="bkmrk-%D0%A1%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0-%D1%84%D1%80%D0%B5%D0%B9%D0%BC%D0%B2%D0%BE%D1%80%D0%BA%D0%B0">**Структура фреймворка**</div><div id="bkmrk-%D0%A2%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B--%3E-metadata-">Таблицы -&gt; MetaData -&gt; Engine -&gt; Dialect -&gt; DB</div><div id="bkmrk-metadata%3A-%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%2C-%D0%B2-"><div>**MetaData:** объект, в котором таблицы, индексы,...</div>- Может получать информацию о существующих сейчас сущностях в БД
- Может хранить шаблоны именования индексов и ограничений (constraint). Т е перед началом проекта добавляется настройка именования, затем при создании/удалении все ок. Это словарь ограничение:шаблон

</div><table border="1" id="bkmrk-%D0%9E%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B8%D0%BB%D0%B8-%D0%B8%D0%BD%D0%B4%D0%B5" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 50%;"></col><col style="width: 50%;"></col></colgroup><thead><tr><td class="align-center">Ограничение или индекс</td><td class="align-center">Описание</td></tr></thead><tbody><tr><td class="align-center">ix</td><td>обычный индекс</td></tr><tr><td class="align-center">uq</td><td>уникальный индекс</td></tr><tr><td class="align-center">ck</td><td>ограничение проверки</td></tr><tr><td class="align-center">fk</td><td>foreign-ключ</td></tr><tr><td class="align-center">pk</td><td>primary-ключ</td></tr></tbody></table>

```python
from sqlalchemy import MetaData
convention = {
'all_column_names': lambda constraint, table: '_'.join([
column.name for column in constraint.columns.values()
]),
'ix': 'ix__%(table_name)s__%(all_column_names)s',
'uq': 'uq__%(table_name)s__%(all_column_names)s',
'ck': 'ck__%(table_name)s__%(all_column_names)s',
'fk': ('fk__%(table_name)s__%(all_column_names)s' '%(referred_table_name)s'),
'pk': 'pk__%(table_name)s'
}
metadata_obj = MetaData(naming_convention=convention)
```

<div id="bkmrk-%D0%94%D0%BE%D0%BB%D0%B6%D0%B5%D0%BD-%D0%B1%D1%8B%D1%82%D1%8C-%D0%B8%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8">- Должен быть инициализирован до обращения к нему в таблицах
- Изначально пустой объект, можно или вручную занести данные, или получить из базы.
- Получение информации об одной таблице по имени 
    - Нельзя получить одновременно две таблицы. Либо одна, либо вся база
    - Нельзя (и одна таблица, и база) получить ограничения (CONSTRAINT), комментарии, триггеры, значения по умолчанию. Но можно вручную добавить данные. Но похоже проще импортировать описание.

</div>```python
from sqlalchemy import ForeignKeyConstraint
album.append_constraint(ForeignKeyConstraint(['ArtistId'], ['artist.ArtistId']))
```

<div id="bkmrk-%D0%BF%D1%80%D0%B8-%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B8-%D0%B1%D0%B0%D0%B7%D1%8B%2C-"><div>- - при получении базы, имена таблиц с большой и маленькой буквы. Т е удваивается количество объектов.

</div></div>```python
engine = create_engine(...)
metadata = MetaData()
cookie_tbl = Table('cookies', metadata, autoload_with=engine)
s = select(cookie_tbl)
with engine.connect() as conn:
    m = conn.execute(s)
    print(m.keys())
```

<div id="bkmrk-%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%86%D0%B8%D0%B8"><div>- Получение информации обо всей базе

</div></div>```python
from sqlalchemy import create_engine, MetaData

engine = create_engine(...)
metadata = MetaData()
metadata.reflect(bind=engine)
for table in metadata.sorted_tables:
    print(table.name)
#Потом получить объект таблицы: 
mytable = metadata.tables['mytable']
```

<div id="bkmrk-%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%86%D0%B8%D0%B8-1"><div>- Получение информации обо всей базе через ORM + Automap

</div><div>  
</div></div>```python
from sqlalchemy.ext.automap import automap_base
from sqlalchemy import create_engine

Base = automap_base()
engine = create_engine('sqlite:///Chinook_Sqlite.sqlite')
Base.prepare(engine, reflect=True)
# данные о классах загружены. 
#Например для получения списка классов:
Base.classes.keys()
Artist = Base.classes.Artist # создание классов
#Внешние связи - в свойстве <related_object>_collection
artist = session.query(Artist).first()
for album in artist.album_collection:
    print('{} - {}'.format(artist.Name, album.Title))
```

<div id="bkmrk-engine%3A-%D0%A1%D0%BA%D1%80%D1%8B%D0%B2%D0%B0%D0%B5%D1%82-%D0%BF%D1%83%D0%BB"><div>  
</div><div>**Engine:** Скрывает пул подключений и диалект.   
**Dialect:** Скрывает детали реализации в конкретной базе  
**Core:** SQL в чистом виде  
**ORM:** абстракции</div></div><div id="bkmrk-%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D-%D0%9D%D0%B5%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B8"></div><div id="bkmrk-foreignkeyconstraint"></div><div class="align-center" id="bkmrk-%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D-1">**Работа с данными**</div><div id="bkmrk-insert">**INSERT**</div><div id="bkmrk-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-1%3A">Вариант 1:</div>```python
ins = cookies.insert().values(
    cookie_name="chocolate chip",
    cookie_recipe_url="http://some.aweso.me/cookie/recipe.html",
    cookie_sku="CC01",
    quantity="12",
    unit_cost="0.50"
    )
    result = connection.execute(ins)
    print(result.inserted_primary_key)
```

<div id="bkmrk-%C2%A0-%C2%A0-print%28result.ins"></div><div id="bkmrk-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2%3A">Вариант 2:</div>```python
from sqlalchemy import insert
ins = insert(cookies).values( 
    cookie_name="chocolate chip",
    cookie_recipe_url="http://some.aweso.me/cookie/recipe.html",
    cookie_sku="CC01",
    quantity="12",
    unit_cost="0.50"
)
```

<div id="bkmrk-%29-3"></div><div id="bkmrk-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-3">Вариант 3</div>```python
ins = cookies.insert()
result = connection.execute(
    ins, 
    cookie_name='dark chocolate chip', 
    cookie_recipe_url='http://some.aweso.me/cookie/recipe_dark.html',
    cookie_sku='CC02',
    quantity='1',
    unit_cost='0.75'
)
result.inserted_primary_key
```

<div id="bkmrk-result.inserted_prim"></div><div id="bkmrk-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-4">Вариант 4</div>```python
inventory_list = [ 
    {
        'cookie_name': 'peanut butter',
        'cookie_recipe_url': 'http://some.aweso.me/cookie/peanut.html',
        'cookie_sku': 'PB01',
        'quantity': '24',
        'unit_cost': '0.25'
    },
    {
        'cookie_name': 'oatmeal raisin',
        'cookie_recipe_url': 'http://some.okay.me/cookie/raisin.html',
        'cookie_sku': 'EWW01',
        'quantity': '100',
        'unit_cost': '1.00'
    }
]
result = connection.execute(ins, inventory_list)
```

<div id="bkmrk-result-%3D-connection.-2"></div><div id="bkmrk-select%C2%A0">**SELECT** </div><div id="bkmrk-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-1">Вариант 1</div>```python
from sqlalchemy.sql import select
s = select([cookies]) 
rp = connection.execute(s)
results = rp.fetchall()
```

<div id="bkmrk-results-%3D-rp.fetchal"></div><div id="bkmrk-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2">Вариант 2</div>```python
s = cookies.select()
rp = connection.execute(s)
results = rp.fetchall()
```

<div id="bkmrk-results-%3D-rp.fetchal-1"></div><div id="bkmrk-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-3-1">Вариант 3</div>```python
s = cookies.select()
rp = connection.execute(s) 
for record in rp:
    print(record.cookie_name)
```

<div id="bkmrk-%C2%A0-%C2%A0-print%28record.coo"></div><div id="bkmrk-select-%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%BD%D1%8B%D1%85-">**SELECT определенных столбцов**</div>```python
s = select([cookies.c.cookie_name, cookies.c.quantity])
```

<div id="bkmrk-ordering">**ORDERING**</div>```python
s = select([cookies.c.cookie_name, cookies.c.quantity])
```

<div id="bkmrk-%D0%9F%D1%80%D1%8F%D0%BC%D0%B0%D1%8F-%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0">Прямая сортировка  
</div>```python
s = s.order_by(cookies.c.quantity)
```

<div id="bkmrk-%D0%9E%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D0%B0%D1%8F-%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0">Обратная сортировка  
</div>```python
s = s.order_by(desc(cookies.c.quantity))
```

```python
rp = connection.execute(s)
```

<div id="bkmrk-%D0%B8%D0%BB%D0%B8-s-%3D-s.order_by%28d"></div><div id="bkmrk-limiting">**LIMITING**</div>```python
s = select([cookies.c.cookie_name, cookies.c.quantity])
s = s.order_by(cookies.c.quantity)
s = s.limit(2)
rp = connection.execute(s)
```

<div id="bkmrk-rp-%3D-connection.exec-4"></div><div id="bkmrk-%D0%92%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8-s">**Встроенные функции SQL**</div><div id="bkmrk-%D0%A1%D1%83%D0%BC%D0%BC%D0%B0%3A-func.sum">Сумма: func.sum</div>```python
from sqlalchemy.sql import func
s = select([func.sum(cookies.c.quantity)])
rp = connection.execute(s)
print(rp.scalar())
```

<div id="bkmrk-print%28rp.scalar%28%29%29%C2%A0">Количество: func.count</div>```python
s = select([func.count(cookies.c.cookie_name)])
rp = connection.execute(s)
record = rp.first()
print(record.keys()) # ключи могут быть разные.
print(record.count_1) 
```

<div id="bkmrk-print%28record.count_1"></div><div id="bkmrk-%D0%9F%D1%80%D0%B8-%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D0%B8-label-%D0%BC%D0%BE%D0%B6">Название свойства: label </div>```python
s = select([func.count(cookies.c.cookie_name).label('inventory_count')]) 
rp = connection.execute(s)
record = rp.first()
print(record.keys())
print(record.inventory_count)
```

<div id="bkmrk-print%28record.invento"></div><div id="bkmrk-where">**WHERE**</div><div id="bkmrk-%D0%98%D1%85-%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE-%D0%BA%D0%BE%D0%BC%D0%B1%D0%B8%D0%BD%D0%B8%D1%80%D0%BE%D0%B2%D0%B0">Их можно комбинировать, используется AND</div>```python
s = select([cookies]).where(cookies.c.cookie_name == 'chocolate chip')
rp = connection.execute(s)
```

<div id="bkmrk-rp-%3D-connection.exec-8"></div><div id="bkmrk-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D1%8B-%D0%BC%D0%BE%D0%B4%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%82%D0%BE%D1%80">Варианты модификаторов:</div>- between(cleft, cright) Значение столбца между cleft и cright
- concat(column\_two) Объединение column и column\_two
- distinct() Находит только уникальные значения в столбце
- in\_(\[list\]) Только если значения столбца в списке
- is\_(None) Проверка на пустые значения
- contains(string) Значение столбца содержит строку
- endswith(string) Оканчивается строкой, зависит от больших букв
- like(string) зависит от больших букв
- startswith(string) зависит от больших букв
- ilike(string) не зависит от больших букв
- Есть отрицательные модификаторы not&lt;method&gt;, исключение - метод isnot()

<div id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%B4%D0%BB%D1%8F-like">**Пример для LIKE**</div>```python
s = select([cookies]).where(cookies.c.cookie_name.like('%chocolate%'))
rp = connection.execute(s)
for record in rp.fetchall():
    print(record.cookie_name)
```

<div id="bkmrk-%C2%A0-%C2%A0-print%28record.coo-1"></div><div id="bkmrk-%D0%9C%D0%BE%D0%B6%D0%BD%D0%BE-%D0%BC%D0%BE%D0%B4%D0%B8%D1%84%D0%B8%D1%86%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C">Модификация полученных данных по шаблону</div><div id="bkmrk-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-1-1">Вариант 1: к каждому значению столбца</div>```python
s = select([cookies.c.cookie_name, 'SKU-' + cookies.c.cookie_sku]) - добавит строку 'SKU-'
```

<div id="bkmrk-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2%3A-%D1%87%D0%B5%D1%80%D0%B5%D0%B7-%D1%84%D1%83%D0%BD">Вариант 2: через функцию cast</div>```python
from sqlalchemy import cast 
s = select([cookies.c.cookie_name,
          cast((cookies.c.quantity * cookies.c.unit_cost),
               Numeric(12,2)).label('inv_cost')]) 
for row in connection.execute(s):
    print('{} - {}'.format(row.cookie_name, row.inv_cost))
```

<div id="bkmrk-%C2%A0-%C2%A0-print%28%27%7B%7D---%7B%7D%27."></div><div id="bkmrk-%D0%9B%D0%BE%D0%B3%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B5-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8%3A">**Логические функции**</div>```python
from sqlalchemy import and_, or_, not_
s = select([cookies]).where(
    and_(
        cookies.c.quantity > 23,
        cookies.c.unit_cost < 0.40
   )
)
```

<div id="bkmrk-%29-5"></div><div id="bkmrk-%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85">**Извлечение данных**</div>```python
first_row = results[0] 
first_row[1] 
first_row.cookie_name 
first_row[cookies.c.cookie_name]
```

<div id="bkmrk-first_row%5Bcookies.c."></div><div id="bkmrk-%D0%94%D0%BB%D1%8F-rp-%D0%B5%D1%81%D1%82%D1%8C-%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8">Для rp есть следующие варианты, однако без limit() извлекаются все данные, затем одна строка:</div>- first() - Первая запись и закрытие соединения
- fetchone() - Одна запись и оставляет открытый курсор для последующих запросов
- scalar() - Одно значение если запрос возвращает одно значение в одной строке
- rp.keys() - список столбцов

<div id="bkmrk-%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85">**Обновление данных**</div>```python
from sqlalchemy import update
u = update(cookies).where(cookies.c.cookie_name == "chocolate chip")
u = u.values(quantity=(cookies.c.quantity + 120)) 
result = connection.execute(u)
```

<div id="bkmrk-result-%3D-connection.-3"></div><div id="bkmrk-%D0%A3%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85">**Удаление данных**</div>```python
from sqlalchemy import delete
u = delete(cookies).where(cookies.c.cookie_name == "dark chocolate chip")
result = connection.execute(u)
```

<div id="bkmrk-result-%3D-connection.-4"></div><div id="bkmrk-join">**JOIN**</div>```python
columns = [orders.c.order_id, users.c.username, users.c.phone,
           cookies.c.cookie_name, line_items.c.quantity,
           line_items.c.extended_cost]
cookiemon_orders = select(columns)
cookiemon_orders = cookiemon_orders.select_from(orders.join(users).join( 
                        line_items).join(cookies)).where(users.c.username ==
                            'cookiemon')
result = connection.execute(cookiemon_orders).fetchall()
```

<div id="bkmrk-result-%3D-connection.-5"></div><div id="bkmrk-outer-join%3A">**OUTER JOIN**</div><div id="bkmrk-%D0%94%D0%BB%D1%8F-%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D1%8F-%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%BD">Для получения обратной статистики</div>```python
columns = [users.c.username, func.count(orders.c.order_id)]
all_orders = select(columns)
all_orders = all_orders.select_from(users.outerjoin(orders)) 
all_orders = all_orders.group_by(users.c.username)
result = connection.execute(all_orders).fetchall()
```

<div id="bkmrk-result-%3D-connection.-6"></div><div id="bkmrk-%D0%95%D1%81%D1%82%D1%8C-%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%BA%D0%B0-alias">Есть поддержка ALIAS</div><div id="bkmrk-%D0%93%D1%80%D1%83%D0%BF%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0-%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%3A">**Группировка данных**</div>```python
columns = [users.c.username, func.count(orders.c.order_id)] 
all_orders = select(columns)
all_orders = all_orders.select_from(users.outerjoin(orders))
all_orders = all_orders.group_by(users.c.username) 
result = connection.execute(all_orders).fetchall()
```

<div id="bkmrk-result-%3D-connection.-7"></div><div id="bkmrk-%D0%9E%D0%B1%D1%8A%D0%B5%D0%B4%D0%B8%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%BE%D0%B2">**Объединение запросов по условию**</div>```python
def get_orders_by_customer(cust_name, shipped=None, details=False):
    columns = [orders.c.order_id, users.c.username, users.c.phone]
    joins = users.join(orders)
    if details:
        columns.extend([cookies.c.cookie_name, line_items.c.quantity,
                       line_items.c.extended_cost])
        joins = joins.join(line_items).join(cookies)
    cust_orders = select(columns)
    cust_orders = cust_orders.select_from(joins)


    cust_orders = cust_orders.where(users.c.username == cust_name)
    if shipped is not None:
        cust_orders = cust_orders.where(orders.c.shipped == shipped)
    result = connection.execute(cust_orders).fetchall()
    return result
get_orders_by_customer('cakeeater') 
get_orders_by_customer('cakeeater', details=True) 
get_orders_by_customer('cakeeater', shipped=True) 
get_orders_by_customer('cakeeater', shipped=False) 
get_orders_by_customer('cakeeater', shipped=False, details=True) 
```

<div id="bkmrk-get_orders_by_custom-4"></div><div id="bkmrk-print%28result%29">  
</div>

# ORM режим

Таблица это класс с требованиями:

- Потомок объекта, возвращаемого функцией declarative\_base
- Включает \_\_tablename\_\_ с именем таблицы
- Включает 1+ атрибутов, являющихся объектом Column
- При определении не включает имя столбца в конструкторе Column, имя столбца = имя атрибута
- 1+ атрибутов определяют первичный ключ
- \_\_table\_args\_\_ свойства таблицы (ограничения,...)

```python
from sqlalchemy import Table, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
			
	Base = declarative_base() 
	
	class Cookie(Base): 
		__tablename__ = 'cookies' 
		__table_args__ = (CheckConstraint('quantity >= 0', name='quantity_positive'),)
		cookie_id = Column(Integer(), primary_key=True) 
		cookie_name = Column(String(50), index=True)
		quantity = Column(Integer())
```

**Создание таблиц**

```python
from sqlalchemy import create_engine
from dataclasses import Base

engine = create_engine(...)
Base.metadata.create_all(engine)
```

**Ограничения**

```python
__table_args__ = (ForeignKeyConstraint(['id'], ['other_table.id']), CheckConstraint(unit_cost >= 0.00', name='unit_cost_positive'))
```

**Внешние связи**

- Определяется столбец с ForeignKey
- Определяется дополнительный атрибут с relationship и необязательным backref
- При определении backref, relationship будет определен в указанном классе с указанным именем.

Один к одному:

```python
cookie = relationship("Cookie", uselist=False)
```

Один ко многим:

```python
user = relationship("User", backref=backref('orders'))
```

На себя (дерево) - неоднозначное решение.

```python
class Employee(Base):
	__tablename__ = 'employees'
	id = Column(Integer(), primary_key=True)
	manager_id = Column(Integer(), ForeignKey('employees.id'))
	name = Column(String(255), nullable=False)
	manager = relationship("Employee", backref=backref('reports'), remote_side=[id])
```

**Сессии**

В ORM режиме, сессия упаковывает

- соединение с БД через engine и предоставляет словарь объектов, загруженных через сессию или ассоциированных с сессией
- транзакции, которые открыты до коммита сессии

Это похожая на хэш-систему, состоящую из списка объектов, таблиц и ключей. Сессия создается через sessionmaker, один раз. Соединяется с базой в момент необходимости

```python
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker 

engine = create_engine('sqlite:///:memory:')
Session = sessionmaker(bind=engine) 
session = Session()
```

Состояния объекта в сессии:

<table border="1" id="bkmrk-transient-%D0%9E%D0%B1%D1%8A%D0%B5%D0%BA%D1%82-%D0%BD%D0%B5-" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 32.9167%;"></col><col style="width: 67.2025%;"></col></colgroup><tbody><tr><td>Transient</td><td>Объект не в сессии и не в БД</td></tr><tr><td>Pending</td><td>Объект добавлен в сессию add(), но не flush или commit</td></tr><tr><td>Persistent</td><td>Объект в сессии имеет связанную запись в БД</td></tr><tr><td>Detached</td><td>Объект больше не в сессии, но в БД есть соответствующая запись</td></tr><tr><td>Modified</td><td>Объект изменен</td></tr></tbody></table>

Просмотр состояния:

```python
from sqlalchemy import inspect
insp = inspect(cc_cookie)
```

Отключение объекта от сессии:

```python
session.expunge(cc_cookie)
```

Просмотр списка атрибутов и выяснение, что было модифицировано

```python
for attr, attr_state in insp.attrs.items():
	if attr_state.history.has_changes(): 
		print('{}: {}'.format(attr, attr_state.value))
		print('History: {}\n'.format(attr_state.history))
```

**Добавление данных**

Создаем объект класса, добавляем в сессию и коммитим. Множественное добавление данных:

```python
dcc = Cookie(...)
mcc = Cookie(...)
session.add(dcc)
session.add(mcc)
session.flush()
```

flush: не выполняет коммит и не завершает транзакцию, но получает id. Потом нужно в пределах сессии сделать commit. Commit в пределах чужой сессии не влияет. Дальше можно использовать объект.

Если дальше не нужно выполнять операции с объектами:

```
session.bulk_save_objects([dcc,mcc])
session.commit()
```

**Получение данных**

<table border="1" id="bkmrk-all%28%29-%D0%B2%D1%81%D0%B5-first%28%29-%D0%B2%D0%BE" style="border-collapse: collapse; width: 100%; height: 119.188px;"><colgroup><col style="width: 22.4025%;"></col><col style="width: 77.5975%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">all()</td><td style="height: 29.7969px;">все</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">first()</td><td style="height: 29.7969px;">возвращает одну запись если она единственная и закрывает соединение</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">one()</td><td style="height: 29.7969px;">возвращает одну запись и оставляет соединение. Аккуратно!</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">scalar()</td><td style="height: 29.7969px;">возвращает одно значение если результат запроса одна строка с одним столбцом</td></tr></tbody></table>

```
cookies = session.query(Cookie).all() 
print(cookies)
```

Если использовать через итератор, то без all:

```python
for cookie in session.query(Cookie): 
    print(cookie)
```

Получение определенных столбцов:

```
cookies = session.query(Cookie.cookie_id).all()
```

Сортировка:

```python
session.query(Cookie).order_by(Cookie.quantity).all()
.order_by(desc(Cookie.quantity))
```

Ограничения через срезы или .limit(2)

Встроенные функции:

```python
from sqlalchemy import func
inv_count = session.query(func.sum(Cookie.quantity)).scalar() 
print(inv_count)
rec_count = session.query(func.count(Cookie.cookie_name)).first()
```

Метки: можно добавить для дальнейшего обращения

```python
rec_count = session.query(func.count(Cookie.cookie_name).label('inventory_count')).first()
print(rec_count.inventory_count)
```

Фильтрация

```
record = session.query(Cookie).filter(cookie_name=='chocolate chip').first() 
record = session.query(Cookie).filter_by(cookie_name='chocolate chip').first()
query = session.query(Cookie).filter(Cookie.cookie_name.like('%chocolate%'))
query = session.query(Cookie).filter(Cookie.quantity > 23, Cookie.unit_cost < 0.40)
```

Изменение строк при выводе

```python
results = session.query(Cookie.cookie_name, 'SKU-' + Cookie.cookie_sku).all()
query = session.query(Cookie.cookie_name,
      cast((Cookie.quantity * Cookie.unit_cost),
           Numeric(12,2)).label('inv_cost'))
```

Join:

```python
query = session.query(Order.order_id, User.username)
results = query.join(User).all()

query = session.query(User.username, func.count(Order.order_id))
query = query.outerjoin(Order).group_by(User.username)
```

Group:

```python
query = session.query(User.username, func.count(Order.order_id))
query = query.outerjoin(Order).group_by(User.username)
```

Сырые запросы

```
from sqlalchemy import text
query = session.query(User).filter(text("username='cookiemon'"))
```

**Обновление данных**

Через объект

```python
query = session.query(Cookie) 
cc_cookie = query.filter(Cookie.cookie_name == "chocolate chip").first() 
cc_cookie.quantity = cc_cookie.quantity + 120
session.commit()
```

Через метод update

```python
query = session.query(Cookie)
query = query.filter(Cookie.cookie_name == "chocolate chip")
query.update({Cookie.quantity: Cookie.quantity - 20})
```

**Удаление**

```
session.delete(dcc_cookie)
session.commit()
```

**Исключения**

```python
from sqlalchemy.orm.exc import MultipleResultsFound 
try:
	results = session.query(Cookie).one()
except MultipleResultsFound as error: 
	print('We found too many cookies... is that even possible?')
```

**Транзакции**

В ORM транзакция создается автоматически до очередного коммита.

```python
session.add(order)
try:
	session.commit()
except IntegrityError as error: 
	session.rollback() 
```

**Пример структурирования**

db.py

```python
from datetime import datetime
from sqlalchemy import (MetaData, Table, Column, Integer, Numeric, String,
                        DateTime, ForeignKey, Boolean, create_engine)
class DataAccessLayer:
    connection = None
    engine = None
    conn_string = None
    metadata = MetaData()
    cookies = Table('cookies',
                    metadata,
                    Column('cookie_id', Integer(), primary_key=True),
                    Column('cookie_name', String(50), index=True),
                    Column('cookie_recipe_url', String(255)),
                    Column('cookie_sku', String(55)),
                    Column('quantity', Integer()),
                    Column('unit_cost', Numeric(12, 2))
              ) 
			  
    def db_init(self, conn_string): 
        self.engine = create_engine(conn_string or self.conn_string)
        self.metadata.create_all(self.engine)
        self.connection = self.engine.connect()
dal = DataAccessLayer()
```

app.py

```python
from db import dal 
from sqlalchemy.sql import select

def get_orders_by_customer(cust_name, shipped=None, details=False):
    columns = [dal.orders.c.order_id, dal.users.c.username, dal.users.c.phone]
    joins = dal.users.join(dal.orders) 
    if details:
        columns.extend([dal.cookies.c.cookie_name,
                        dal.line_items.c.quantity,
                        dal.line_items.c.extended_cost])
        joins = joins.join(dal.line_items).join(dal.cookies)
    cust_orders = select(columns)
    cust_orders = cust_orders.select_from(joins).where(
        dal.users.c.username == cust_name)
    if shipped is not None:
        cust_orders = cust_orders.where(dal.orders.c.shipped == shipped)
    return dal.connection.execute(cust_orders).fetchall()
```

test.py

```python
import unittest
class TestApp(unittest.TestCase): 
    
	@classmethod
    def setUpClass(cls): 
        dal.db_init('sqlite:///:memory:')
	
	def test_one(self):
		res = get_orders_by_customer('', False)
		self.assert_equal(res, [])
```

# Core режим

Сначала необходимо определить, как данные хранятся в таблице. Варианты определения:

- Объект Table
- Декларативный класс
- Получение структуры из базы данных

**Сопоставление типов**

<table border="1" id="bkmrk-sqlalchemy-python-sq" style="border-collapse: collapse; width: 100%; height: 417.157px;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">SQLAlchemy</td><td class="align-center" style="height: 29.7969px;">Python</td><td class="align-center" style="height: 29.7969px;">SQL</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">BigInteger</td><td style="height: 29.7969px;">int</td><td style="height: 29.7969px;">BIGINT</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Boolean</td><td style="height: 29.7969px;">bool</td><td style="height: 29.7969px;">BOOLEAN or SMALLINT</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Date</td><td style="height: 29.7969px;">datetime.date</td><td style="height: 29.7969px;">DATE (SQLite: STRING)</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">DateTime</td><td style="height: 29.7969px;">datetime.datetime</td><td style="height: 29.7969px;">DATETIME (SQLite: STRING)</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Time</td><td style="height: 29.7969px;">datetime.time</td><td style="height: 29.7969px;">DATETIME</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Enum</td><td style="height: 29.7969px;">str</td><td style="height: 29.7969px;">ENUM or VARCHAR</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Float</td><td style="height: 29.7969px;">float or Decimal</td><td style="height: 29.7969px;">FLOAT or REAL</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Integer</td><td style="height: 29.7969px;">int</td><td style="height: 29.7969px;">INTEGER</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Interval</td><td style="height: 29.7969px;">datetime.timedelta</td><td style="height: 29.7969px;">INTERVAL or DATE from epoch</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">LargeBinary</td><td style="height: 29.7969px;">byte</td><td style="height: 29.7969px;">BLOB or BYTEA</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Numeric</td><td style="height: 29.7969px;">decimal.Decimal</td><td style="height: 29.7969px;">NUMERIC or DECIMAL</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Unicode</td><td style="height: 29.7969px;">unicode</td><td style="height: 29.7969px;">UNICODE or VARCHAR</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Text</td><td style="height: 29.7969px;">str</td><td style="height: 29.7969px;">CLOB or TEXT</td></tr></tbody></table>

**Metadata**

Каталог объектов Table с опциональной информацией о engine и соединении.

```python
from sqlalchemy import MetaData
metadata = MetaData()
```

**Создание таблицы**

```python
metadata.create_all(engine)
```

Метод ...create\_all не пересоздает таблицы.

Объект таблицы состоит из названия, переменной метаданных и столбцов.

```python
from sqlalchemy import Table, Column, Integer, Numeric, String, ForeignKey
from datetime import datetime
from sqlalchemy import DateTime

cookies = Table('cookies', metadata,
    Column('cookie_id', Integer(), primary_key=True), 
    Column('cookie_name', String(50), index=True), 
    Column('cookie_recipe_url', String(255)),
    Column('cookie_sku', String(55)),
    Column('quantity', Integer()),
    Column('unit_cost', Numeric(12, 2)) 
)

users = Table('users', metadata,
    Column('user_id', Integer(), primary_key=True),
    Column('username', String(15), nullable=False, unique=True), 
    Column('email_address', String(255), nullable=False),
    Column('phone', String(20), nullable=False),
    Column('password', String(25), nullable=False),
    Column('created_on', DateTime(), default=datetime.now), 
    Column('updated_on', DateTime(), default=datetime.now, onupdate=datetime.now) 
)
```

**Класс Column**

- название столбца 
    - тип данных 
        - в String обязательно указывать длину
        - Numeric(11,2) означает 11
    - доп. параметры

```python
primary_key=True
index=True
nullable=False
unique=True
default=datetime.now
onupdate=datetime.now
```

**Ключи, ограничения и индексы**

Могут быть определены в конструкторе столбца (primary\_key=True) или позже в конструкторе таблицы.

```python
from sqlalchemy import PrimaryKeyConstraint, UniqueConstraint, CheckConstraint

users = Table(...
             PrimaryKeyConstraint('user_id', name='user_pk'),
             UniqueConstraint('username', name='uix_username'),
             CheckConstraint('unit_cost >= 0.00', name='unit_cost_positive'),
             ...)
```

Множественные ключи перечисляются через запятую.

```python
from sqlalchemy import Index
 
Index('ix_cookies_cookie_name', 'cookie_name')
Index('ix_test', mytable.c.cookie_sku, mytable.c.cookie_name)
```

Внешние связи

```python
from sqlalchemy import ForeignKey
orders = Table('orders', metadata,
    Column('order_id', Integer(), primary_key=True),
    Column('user_id', ForeignKey('users.user_id')),
    Column('shipped', Boolean(), default=False)
)
line_items = Table('line_items', metadata,
    Column('line_items_id', Integer(), primary_key=True),
    Column('order_id', ForeignKey('orders.order_id')),
    Column('cookie_id', ForeignKey('cookies.cookie_id')),
    Column('quantity', Integer()),
    Column('extended_cost', Numeric(12, 2))
)
```

Связь для поля order\_id:

```python
Column('user_id', ForeignKey('users.user_id'))
#При желании - ограничение
ForeignKeyConstraint(['order_id'], ['orders.order_id'])
```

**Добавление данных**

```python
from sqlalchemy import insert
перем = таблица.insert().values()
```

Лучше (?) вариант

```python
from sqlalchemy import insert
перем = insert(таблица).values()
```

Строковое представление запроса

```python
str(перем)
```

Компиляция запроса

```
перем.compile()
```

```
перем.compile().params
```

**Примеры**

```python
with engine.connect() as connection:
	metadata = ...
	cookies = Table...
	ins = insert(cookies).values(...)
	res = connection.execute(ins)
	#res.inserted_primary_key - какой в будущем будет ключ (сейчас фактически в БД нет данных)
	connection.commit()
```

```python
ins = cookies.insert()
inventory_list = [ 
	{
		'cookie_name': 'peanut butter',
		'cookie_recipe_url': 'http://some.aweso.me/cookie/peanut.html',
	},
	{
		'cookie_name': 'oatmeal raisin',
		'cookie_recipe_url': 'http://some.okay.me/cookie/raisin.html',
	}
]
result = connection.execute(ins, inventory_list)
```

```python
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import Table, Column, Integer, Numeric, String, ForeignKey

metadata = MetaData()
cookies = Table('cookies', metadata,
	Column('cookie_id', Integer(), primary_key=True), 
	Column('cookie_name', String(50), index=True), 
	Column('cookie_recipe_url', String(255))
)
engine = create_engine('sqlite:///:memory:')
connection = engine.connect()
metadata.create_all(engine)

from sqlalchemy import insert

ins = cookies.insert().values(
	cookie_name="chocolate chip",
	cookie_recipe_url="http://some.aweso.me/cookie/recipe.html"
	)
print(str(ins))
```

**Получение данных**

```python
from sqlalchemy.sql import select
s = select(cookies) 
rp = connection.execute(s)
```

Список столбцов

```
rp.keys()
```

Получение результата

```
results = rp.fetchall()
```

<table border="1" id="bkmrk-fetchall%28%29-%D0%92%D1%81%D0%B5-%D0%B7%D0%B0%D0%BF%D0%B8%D1%81" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 12.7977%;"></col><col style="width: 87.3215%;"></col></colgroup><tbody><tr><td>fetchall()</td><td>Все записи</td></tr><tr><td>first()</td><td>Возвращает одну запись если она единственная и закрывает соединение</td></tr><tr><td>fetchone()</td><td>Возвращает одну запись и оставляет соединение. Аккуратно!</td></tr><tr><td>scalar()</td><td>Возвращает одно значение если результат запроса одна строка с одним столбцом</td></tr></tbody></table>

Доступ возможен по:

<table border="1" id="bkmrk-first_row-%3D-results%5B" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 32.7679%;"></col><col style="width: 67.2321%;"></col></colgroup><tbody><tr><td>first\_row = results\[0\]</td><td>по индексу результата</td></tr><tr><td>first\_row\[1\]</td><td>по номеру столбца в результате</td></tr><tr><td>first\_row.cookie\_name</td><td>по имени столбца</td></tr><tr><td>first\_row\[cookies.c.cookie\_name\]</td><td>через объект таблицы</td></tr></tbody></table>

Сортировка

```python
s = select(cookies.c.cookie_name, cookies.c.quantity)
s = s.order_by(cookies.c.quantity)
s = s.order_by(desc(cookies.c.quantity))
```

Ограничения количества

```python
s = s.limit(2)
```

Встроенные функции

```python
from sqlalchemy.sql import func
s = select([func.sum(cookies.c.quantity)])
rp = connection.execute(s)
print(rp.scalar())
```

Фильтрация

```python
s = select([cookies]).where(cookies.c.cookie_name == 'chocolate chip')
```

<table border="1" id="bkmrk-%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-%3D%3D" style="border-collapse: collapse; width: 100%; height: 297.969px;"><colgroup><col style="width: 33.0358%;"></col><col style="width: 67.0834%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">Оператор</td><td class="align-center" style="height: 29.7969px;">Описание</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">==</td><td style="height: 29.7969px;">Точное равенство</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">like('%chocolate%')</td><td style="height: 29.7969px;">Вхождение элемента (регистрозависимый)</td></tr><tr><td>ilike(string)</td><td>Вхождение элемента</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">between(cleft, cright)</td><td style="height: 29.7969px;">Элемент между значениями</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">concat(column\_two)</td><td style="height: 29.7969px;">Объединение столбцов</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">distinct()</td><td style="height: 29.7969px;">Только уникальные значения столбца</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">in\_(\[list\])</td><td style="height: 29.7969px;">Значения столбца в списке</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">is\_(None)</td><td style="height: 29.7969px;">Значение в столбце None</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">contains(string)</td><td style="height: 29.7969px;">Содержит в себе строку (регистрозависимый)</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">endswith(string)</td><td style="height: 29.7969px;">Заканчивается строкой (регистрозависимый)</td></tr><tr><td>startswith(string)</td><td>Начинается строкой (регистрозависимый)</td></tr><tr><td>notin\_()</td><td>Отрицание</td></tr><tr><td>isnot()</td><td>Исключение</td></tr></tbody></table>

Внутри where можно использовать and\_, or\_, not\_

```python
from sqlalchemy import and_, or_, not_
s = select([cookies]).where(
	and_(
		cookies.c.quantity > 23,
		cookies.c.unit_cost < 0.40
	)
)
```

Join

```python
columns = [orders.c.order_id, users.c.username, users.c.phone,
		cookies.c.cookie_name, line_items.c.quantity, line_items.c.extended_cost]
cookiemon_orders = select(*columns)
cookiemon_orders = cookiemon_orders.select_from(orders.join(users).join(line_items).join(cookies)).where(users.c.username == 'cookiemon')
result = connection.execute(cookiemon_orders).fetchall()
for row in result:
	print(row)
```

Для outerjoin: join -&gt; outerjoin

Алиасы

```python
manager = employee_table.alias('mgr')
```

Grouping:

```python
columns = [users.c.username, func.count(orders.c.order_id)] 
all_orders = select(columns)
all_orders = all_orders.select_from(users.outerjoin(orders))
all_orders = all_orders.group_by(users.c.username)
```

Обновление данных:

```python
from sqlalchemy import update
u = update(cookies).where(cookies.c.cookie_name == "chocolate chip")
u = u.values(quantity=(cookies.c.quantity + 120)) 
result = connection.execute(u)
```

Удаление данных:

```python
from sqlalchemy import delete
u = delete(cookies).where(cookies.c.cookie_name == "dark chocolate chip")
result = connection.execute(u)
```

Сырые запросы (raw)

```python
result = connection.execute("select * from orders").fetchall()
```

**Обработка исключений**

AttributeError - ошибка набора данных  
IntegrityError - ошибка ограничений  
Стандартная обработка подходит если исполняется один независимый запрос.

```python
try:
	result = connection.execute(ins)
except IntegrityError as error: 
	print(error.orig.message, error.params)
```

В случае нескольких взаимозависимых запросов необходимо использовать транзакции.

```python
transaction = connection.begin() 
try:
	...
	transaction.commit() 
except IntegrityError as error:
	transaction.rollback()
```

Пример:

```python
transaction = connection.begin() 
cookies_to_ship = connection.execute(s).fetchall() 
try:
	for cookie in cookies_to_ship:
		u = update(cookies).where(cookies.c.cookie_id == cookie.cookie_id)
		u = u.values(quantity = cookies.c.quantity-cookie.quantity)
		result = connection.execute(u)
	u = update(orders).where(orders.c.order_id == order_id)
	u = u.values(shipped=True)
	result = connection.execute(u)
	print("Shipped order ID: {}".format(order_id))
	transaction.commit() 
except IntegrityError as error:
	transaction.rollback() 
```

# Пример проекта

**Структура проекта**

<table border="1" id="bkmrk-%D0%94%D0%B8%D1%80%D0%B5%D0%BA%D1%82%D0%BE%D1%80%D0%B8%D1%8F-%2F-%D1%84%D0%B0%D0%B9%D0%BB-%D0%9E%D0%BF" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 35.272%;"></col><col style="width: 64.728%;"></col></colgroup><thead><tr><td class="align-center">Директория / файл</td><td class="align-center">Описание</td></tr></thead><tbody><tr><td>alembic/  
</td><td>Настройки alembic  
</td></tr><tr><td>conf/</td><td>Настройки окружений. </td></tr><tr><td>conf/settings  
</td><td>Файлы основных настроек.

</td></tr><tr><td>db/</td><td>Описание структуры базы данных.

initializer.py - Инициализация базы данных, метаданных

</td></tr><tr><td>db/tablesdefinition  
</td><td>Файлы описания структур таблиц и методов взаимодействия с данными.

</td></tr><tr><td>docker/</td><td>Настройки контейнера</td></tr><tr><td>docker/data</td><td>Данные БД</td></tr><tr><td>docker/docker-entrypoint-initdb.d</td><td>Скрипты инициализации БД

main.sql - Файл скрипта иницализации

</td></tr><tr><td>docker/docker-compose.yml</td><td>Compose файл</td></tr><tr><td>src/</td><td>Дополнительные модули  
</td></tr><tr><td>main.py</td><td>Точка входа</td></tr><tr><td>error.log  
</td><td>Файл лога.  
</td></tr></tbody></table>

**Предварительная настройка**

Для работы примера необходимо установить docker.

Клонировать проекта с репозитория

```bash
git clone https://gitverse.ru/bobrobot/alembictemplate.git
```

Перейти в директорию проекта, создать виртуальное окружение и активировать

```
cd alembictemplate
python3 -m venv env
source env/bin/activate
```

Установить дополнительные модули

```bash
pip install -r requirements.txt
```

Перейти в директорию docker и в файле docker-compose.yml настроить пути, имя БД, логин и пароль к новой базе данных.

```yaml
services:
  postgres:
    image: postgres:latest
    environment:
      POSTGRES_DB: "learnsqlalchemy"
      POSTGRES_USER: "learner"
      POSTGRES_PASSWORD: "StrongPassword123"
      PGDATA: "/home/sergey/projects/alembictemplate/docker/data/pgdata"
    volumes:
      - .:/docker-entrypoint-initdb.d
      - mydata:/home/sergey/projects/alembictemplate/docker/data
    ports:
      - "5430:5432"
volumes:
  mydata:
```

В файле docker-entrypoint-initdb.d/main.sql изменить имя БД, логин и пароль.

```sql
CREATE DATABASE learnsqlalchemy;
CREATE USER learner WITH PASSWORD 'StrongPassword123';
ALTER ROLE learner WITH PASSWORD 'StrongPassword123';
GRANT ALL PRIVILEGES ON DATABASE learnsqlalchemy to learner;
```

В директории docker запустить контейнер БД в фоновом режиме.

```bash
docker compose up -d
```

Для остановки контейнера:

```bash
docker compose stop
```

Сейчас, запустив контейнер, при помощи консольного клиента psql можно проверить соединение с базой данных для пользователя.

```
psql -d learnsqlalchemy -U learner -W -h 127.0.0.1 -p 5430
```

Для работы с настройками в формате json используется библиотека src/libsettings.py [Описание библиотеки](http://bobrobotirk.ru/books/python/page/pip-opisanie-modulei "Описание модулей") Создать папку src, скопировать из проекта библиотеку libsettings.py

**Настройки системы**

Файлы основных настроек расположены в conf/settings/ Файл base.py

```python
'''Loading settings to project'''
import os
from src.libsettings import Jsettings

settingspath = os.path.join('conf', 'settings', 'settings.json')
schemapath = os.path.join('conf', 'settings', 'schema.json')
# dev settings
mysettings = Jsettings(settingsfname= settingspath,
                       schemafname=schemapath)
mysettings.load_settings()
```

Файл schema.json

```json
{
    "type": "object",
    "properties": {
        "db_username": {"type": "string"},
        "db_password": {"type": "string"},
        "db_host": {"type": "string"},
        "db_port": {"type": "string"},
        "db_name": {"type": "string"}
    }, 
    "required": ["db_username", "db_password", "db_host",
                "db_port", "db_name"]
}
```

Файл settings.json

```json
{
    "db_username": "learner",
    "db_password": "StrongPassword123",
    "db_host": "127.0.0.1",
    "db_port": "5430",
    "db_name": "learnsqlalchemy"
}
```

Файл base.py проверяет схему и создает объект настроек mysettings из файла settings.json. Для получения объекта настроек нужно импортировать объект mysettings в нужном модуле. В данный момент присутствуют настройки базы данных с префиксом db\_\*

Если такая усложненная система управления настройками покажется излишней, возможно использовать экспорт настроек напрямую из файла.

**Инициализация базы данных**

Создаем папку db, в ней создаем файл initializer.py

```python
'''Db classes and  initialization'''
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base
from conf.settings.base import mysettings

#load engine settings
engine = create_engine(
    "postgresql+psycopg2://{db_username}:{db_password}@{db_host}:{db_port}/{db_name}".format(
        db_username=mysettings.db_username,
        db_password=mysettings.db_password,
        db_host=mysettings.db_host,
        db_port=mysettings.db_port,
        db_name=mysettings.db_name
    ),
    echo=True)
Base = declarative_base()
```

Здесь только создается engine для подключения к БД и класс Base. При настройке структуры таблиц данный файл будет обновлен, сейчас нужен только класс Base.

**Настройка системы версионирования базы данных alembic**

Инициализируем alembic в корне проекта.

```bash
alembic init alembic
```

В корне проекта будет создан файл alembic.ini, будет создана папка alembic с файлами инициализации. В большинстве инструкций параметры подключения задаются в файле alembic.ini однако, для доступа к настройкам из единой точки будет изпользоваться способ установки параметров в файле env.py Поэтому в файле alembic.ini переменная sqlalchemy.url должна быть закомментирована. Часть файла alembic.ini

```python
#sqlalchemy.url = driver://user:pass@localhost/dbname
```

В файле env.py

- импортируем путь

```python
import os
import sys

sys.path.append(os.getcwd())
```

Импортируем настройки, создаем строку соединения и создаем закоментированный ранее в файле alembic.ini параметр sqlalchemy.url

```python
from conf.settings.base import mysettings

connstring = "postgresql+psycopg2://{db_username}:{db_password}@{db_host}:{db_port}/{db_name}".format(
        db_username=mysettings.db_username,
        db_password=mysettings.db_password,
        db_host=mysettings.db_host,
        db_port=mysettings.db_port,
        db_name=mysettings.db_name
    )
config.set_main_option(name="sqlalchemy.url", value=connstring)

```

Импортируем db.initializer и создаем метаданные

```python
import db.initializer

target_metadata = db.initializer.Base.metadata
```

Остальные параметры оставляем неизменными. Результирующий файл настроек окружения alembic env.py:

```python
from logging.config import fileConfig
import os
import sys

from sqlalchemy import engine_from_config
from sqlalchemy import pool

from alembic import context

from conf.settings.base import mysettings

sys.path.append(os.getcwd())

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
    fileConfig(config.config_file_name)

connstring = "postgresql+psycopg2://{db_username}:{db_password}@{db_host}:{db_port}/{db_name}".format(
        db_username=mysettings.db_username,
        db_password=mysettings.db_password,
        db_host=mysettings.db_host,
        db_port=mysettings.db_port,
        db_name=mysettings.db_name
    )
config.set_main_option(name="sqlalchemy.url", value=connstring)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
import db.initializer

target_metadata = db.initializer.Base.metadata

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.


def run_migrations_offline() -> None:
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    url = config.get_main_option("sqlalchemy.url")
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )

    with context.begin_transaction():
        context.run_migrations()


def run_migrations_online() -> None:
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    connectable = engine_from_config(
        config.get_section(config.config_ini_section, {}),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(
            connection=connection, target_metadata=target_metadata
        )

        with context.begin_transaction():
            context.run_migrations()


if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

```

**Обновление конфигурации таблиц**

Для проверки создаем первую пустую миграцию. После ее выполнения создастся таблица alembic\_version

```bash
alembic revision -m "Empty Init"
```

В данный момент фактического соединения с БД не было. В папке versions сформируется файл вида &lt;id&gt;\_empty\_init.py

После выполнения команды

```bash
alembic upgrade head
```

в таблице alembic\_version появится одна запись - идентификатор текущей версии базы данных.

Сейчас в папке db создаем папку tablesdefinition. В ней будем хранить файлы описаний таблиц и методы для работы с таблицами. Создадим файл userprofile.py

```python
'''Definition tables of userprofile'''
import logging
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
import db.initializer
from db.initializer import engine

def create_userprofile_class(Curbase):
    class Userprofile(Curbase):
        '''Class Userprofile definition'''
        __tablename__ = 'userprofile'

        user_id = Column(Integer(), primary_key=True)
        username = Column(String(15), nullable=False, unique=True)
        password = Column(String(255), nullable=False)
        email = Column(String(255))
        name = Column(String(100))
        second_name = Column(String(100))
        photo = Column(String(255))
        balance = Column(Integer())
    return Userprofile

def create_one_userprofile(username, password, email='', name='',
                           second_name='', photo='', balance=0):
    ''' Create one userprofile '''
    try:
        with engine.connect():
            Session = sessionmaker(bind=engine)
            with Session() as sess:
                upelem = db.initializer.userprofile(username=username, password=password,
                                     email=email, name=name, second_name=second_name,
                                     photo=photo, balance=balance)
                sess.add(upelem)
                sess.commit()
    except Exception as e:
        logging.error(e)
```

И в файле initializer.py после инициализации переменной Base добавим раздел инициализации описания таблицы

```python
#============================ Creation classes definitions =========================
# === Import Userprofile class ===
from db.tablesdefinition.userprofile import create_userprofile_class
userprofile = create_userprofile_class(Base)
# ==================================================================================
```

Теперь после выполнения команды

```bash
alembic revision --autogenerate -m "Added userprofile model"
```

будет автоматически сгененрирован файл миграции, и после

```bash
alembic upgrade head
```

создастся таблица userprofile.

P.s. В точке входа необходимо полностью импортировать initializer иначе будет ошибка, пример:

```python
'''Main learning module'''
import logging

from db import initializer
from db.tablesdefinition.userprofile import create_one_userprofile

logging.basicConfig(level=logging.INFO,
                    filename='error.log',
                    format="%(levelname)s %(message)s")

if __name__ == '__main__':
    create_one_userprofile(username = 'first6',
                           password = 'first',
                           balance = 1)
```

# Alembic

# Установка и настройка

Стандартная установка:

```bash
pip install alembic
```

Первая инициализация: alembic init folder\_for\_dbdata

```bash
alembic init alembic
```

Будет создан файл alembic.ini и директория в соответствии с названием.

В alembic.ini обновляем параметр sqlalchemy.url

В файл env.py добавляем код

```python
import os
import sys
sys.path.append(os.getcwd())
from app.db import Base 
target_metadata = Base.metadata 
```

# Миграции

**Создание первой (пустой) миграции.**

После создания пустой миграции, в БД создастся таблица alembic\_version в которой хранится идентификатор текущей версии.

```bash
alembic revision -m "Empty Init"
```

**Обновление базы данных после создания миграции**

```bash
alembic upgrade head
```

**Хэш текущей миграции**

```bash
alembic current
```

**История миграций.**

Считываются все файлы миграций. Т.е. в случае загрузки предыдущего состояния, последующие миграции будут отображаться до тех пор, пока ненужные файлы миграций не будут удалены.

```bash
alembic history
```

**Возврат к предыдущему состоянию**

```bash
alembic downgrade migration_id
```

Для возврата в стартовое состояние, нужно выполнить

```bash
alembic downgrade base
```

Если что-то пошло не так, для возврата в нулевое состояние нужно удалить из таблицы alembic\_version текущий номер.

```sql
DELETE FROM public.alembic_version;
```

**Пропуск состояния**

```bash
alembic stamp migration_id
```

**Экспорт в формате sql**

```bash
alembic upgrade migration_id_start:migration_id_stop --sql > migration.sql
```

**Автогенерация миграции**

```bash
alembic revision --autogenerate -m "Added Cookie model"
```

Поддерживаемые и неподдерживаемые действия при автоматической миграции

<table border="1" id="bkmrk-%D0%A2%D0%B8%D0%BF-%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0-%D0%9F%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 14.1836%;"></col><col style="width: 45.0624%;"></col><col style="width: 40.754%;"></col></colgroup><thead><tr><td>Тип элемента</td><td>Поддерживаемые</td><td>Неподдерживаемые</td></tr></thead><tbody><tr><td>Таблицы</td><td>Добавление и удаление</td><td>Изменение имени</td></tr><tr><td>Столбец</td><td>Добавление, удаление, изменение нулевого статуса на столбце</td><td>Изменение имени</td></tr><tr><td>Индекс</td><td>Основные изменения в индексах и явно обозначенных уникальных ограничениях, поддержка автоматической генерации индексов  
и уникальных ограничений</td><td>  
</td></tr><tr><td>Ограничения</td><td>  
</td><td>Ограничения без явного имени</td></tr><tr><td>Ключи</td><td>Переименование</td><td>  
</td></tr><tr><td>Типы</td><td>  
</td><td>Типы, которые явно не поддерживаются базой данных</td></tr></tbody></table>

Чтобы alembic увидел класс данных, необходимо его непосредственно импортировать. Импорт всей директории не работает.

**Ручное создание миграций**

На примере изменения имени таблицы

- Изменить имя.
- Выполнить генерацию миграции

```bash
alembic revision -m "Renaming table"
```

- В созданном файле изменить upgrade/downgrade

```python
def upgrade():
    op.rename_table('old_name', 'new_name')
def downgrade():
    op.rename_table('new_name', 'old_name')
```

- Выполнить миграцию

```bash
alembic upgrade head
```

**Команды alembic**

<table border="1" id="bkmrk-add_column-%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C-" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 23.512%;"></col><col style="width: 76.6072%;"></col></colgroup><tbody><tr><td>add\_column</td><td>Добавить столбец</td></tr><tr><td>alter\_column</td><td>Изменить тип столбца, имя или значение по-умолчанию</td></tr><tr><td>create\_check\_constraint</td><td>Добавить ограничение</td></tr><tr><td>create\_foreign\_key</td><td>Добавить внешний ключ</td></tr><tr><td>create\_index</td><td>Создать индекс</td></tr><tr><td>create\_primary\_key</td><td>Создать основной ключ</td></tr><tr><td>create\_table</td><td>Создать таблицу</td></tr><tr><td>create\_unique\_constraint</td><td>Создать ограничение уникальности</td></tr><tr><td>drop\_column</td><td>Удалить столбец</td></tr><tr><td>drop\_constraint</td><td>Удалить ограничение</td></tr><tr><td>drop\_index</td><td>Удалить индекс</td></tr><tr><td>drop\_table</td><td>Удалить таблицу</td></tr><tr><td>execute</td><td>Выполнить сырую SQL команду</td></tr><tr><td>rename\_table</td><td>Переименовать таблицу</td></tr></tbody></table>

# Модули

# Pip, описание модулей

**Альтернативные зеркала**

Возможны случаи, когда это потребуется.

```
pip install <package> -i <hostname>
Например
pip install fastapi -i https://pypi.tuna.tsinghua.edu.cn/simple
```

<table border="1" id="bkmrk-%D0%90%D0%B4%D1%80%D0%B5%D1%81-%D1%80%D0%B5%D0%BF%D0%BE%D0%B7%D0%B8%D1%82%D0%BE%D1%80%D0%B8%D1%8F-%D0%A2%D0%B8" style="border-collapse: collapse; width: 100%; height: 119.188px;"><colgroup><col style="width: 35.988%;"></col><col style="width: 64.012%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">Адрес репозитория</td><td class="align-center" style="height: 29.7969px;">Тип</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;"><div>https://pypi.org/simple</div></td><td style="height: 29.7969px;">Основной репозиторий</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">[https://pypi.tuna.tsinghua.edu.cn/simple](https://pypi.tuna.tsinghua.edu.cn/simple)</td><td style="height: 29.7969px;">Вроде очень стабильный репозиторий</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">[https://mirrors.aliyun.com/pypi/simple](https://mirrors.aliyun.com/pypi/simple)</td><td style="height: 29.7969px;">Европейский вариант</td></tr></tbody></table>

<div id="bkmrk-"></div><div id="bkmrk-pip-install-fastapi--1">Настройка pip без необходимости указания сервера</div><div id="bkmrk-%25appdata%25%5Cpip%5Cpip.in">Файл %APPDATA%\pip\pip.ini Если директории нет - создать.</div>```
[global]
index-url = https://pip.ya.ru/simple
trusted-host = pip.ya.ru
```

**Хранение конфигурации**

**Configparser** стандартная библиотека для чтения и записи .ini файлов. [Инструкция 1](https://sky.pro/media/kak-rabotat-s-modulem-configparser-v-python/)

**Jsonschema** модуль для проверки соответствия json существующей схеме. [Документация](https://python-jsonschema.readthedocs.io/en/stable/index.html)

**Libsettings** модуль на основе [jsonschema](http://bobrobotirk.ru/books/python/page/jsonschema "jsonschema") для чтения конфигурации из json файла и проверки конфигурации на соответствие схеме. [Gitverse проекта](https://gitverse.ru/bobrobot/libsettings)

```python
import logging
from libsettings import Jsettings

logging.basicConfig(level=logging.ERROR,
                    filename='error.log',
                    format="%(levelname)s %(message)s")
mysettings = Jsettings(settingsfname='mysettings.json',
                          schemafname='myschema.json')
mysettings.load_settings()
```

# jsonschema

Используется для валидации json схемы. По умолчанию дополнительно указанные ключи (не существующие в схеме, но присутствующие в документе) не проверяются.

**Установка**

```bash
pip install jsonschema
```

**Базовое использование**

```python
from jsonschema import validate

validate(instance=json_to_check, schema=schema)
```

**Исключения**

jsonschema.exceptions.ValidationError - если документ не соответствует структуре

jsonschema.exceptions.SchemaError - если сама схема некорректна

**Пример схемы:**

```json
schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "number"},
    },
    "required": ["name"],
}
```

Данная схема определяет json объект с 2 свойствами: name и age. Обязательное свойство name.

**Ключевые слова**

Для некоторых типов используются дополнительные ключевые слова.

<table border="1" id="bkmrk-%D0%9A%D0%BB%D1%8E%D1%87%D0%B5%D0%B2%D0%BE%D0%B5-%D1%81%D0%BB%D0%BE%D0%B2%D0%BE-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 16.9211%;"></col><col style="width: 83.0789%;"></col></colgroup><thead><tr><td class="align-center">Ключевое слово</td><td class="align-center">Описание</td></tr></thead><tbody><tr><td>type</td><td>Тип. Для корня часто object.

string - строка

number - число

object - объект

array - список

</td></tr><tr><td>$defs</td><td>Вложенный шаблон для случая, когда шаблон элемента встречается в нескольких местах.</td></tr><tr><td>$ref</td><td>Подстановка вложенного шаблона.</td></tr><tr><td>$schema</td><td>Ссылка на шаблон шаблона. При обновлении версии библиотеки будет использоваться новый шаблон шаблона, что может привести к проблемам. Желательно указывать.</td></tr></tbody></table>

**Дополнительные ключевые слова для типов.**

 **Тип array**

<table border="1" id="bkmrk-%D0%9A%D0%BB%D1%8E%D1%87%D0%B5%D0%B2%D0%BE%D0%B5-%D1%81%D0%BB%D0%BE%D0%B2%D0%BE-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0-1" style="border-collapse: collapse; width: 100%; height: 89.3907px;"><colgroup><col style="width: 17.4405%;"></col><col style="width: 82.6786%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">Ключевое слово</td><td class="align-center" style="height: 29.7969px;">Описание</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">items</td><td style="height: 29.7969px;">Тип элементов списка.

```json
"scores": {
            "type": "array",
            "items": {"type": "number"},
        }
```

</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">minItems</td><td style="height: 29.7969px;">Минимальное количество элементов</td></tr><tr><td>  
</td><td>  
</td></tr></tbody></table>

**Тип object**

<table border="1" id="bkmrk-%D0%9A%D0%BB%D1%8E%D1%87%D0%B5%D0%B2%D0%BE%D0%B5-%D1%81%D0%BB%D0%BE%D0%B2%D0%BE-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0-2" style="border-collapse: collapse; width: 100%; height: 89.3907px;"><colgroup><col style="width: 17.2025%;"></col><col style="width: 82.9167%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">Ключевое слово</td><td class="align-center" style="height: 29.7969px;">Описание</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">required</td><td style="height: 29.7969px;">Список обязательных ключей.

```json
"required": ["name"]
```

</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">properties</td><td style="height: 29.7969px;">Определяет ключи объекта и их тип.

```json
"properties": {
        "name": {"type": "string"}
    },
```

</td></tr><tr><td>additionalProperties</td><td>Если True, то дополнительно указанные ключи приводят к исключению.

</td></tr></tbody></table>

**Локальные вложенные шаблоны.**

Для определения используется переменная $defs, для использования - $ref.

```json
schema = {
    "type": "object",
    "properties": {
        "address": {"$ref": "#/$defs/address"},
    },
    "$defs": {
        "address": {
            "type": "object",
            "properties": {
                "street": {"type": "string"},
            }
        },
    },
}
```

# Pydantic 2

**Описание**

Библиотека валидации (проверка на соответствие типов) и трансформации (автоматическое приведение к нужным типам и форматам) данных.

Модели наследуются от класса BaseModel. Модель описывает набор полей, представляющих структуру данных и условия валидации.

**Установка**

```bash
pip install pydantic
```

**Типизация:**

- Простая: указание типа, например, name: str.
- Объект Field(): дополнительные параметры, например, значения по умолчанию, ограничения и т.д.

Внутри класса можно комбинировать способы типизации.

```python
from pydantic import BaseModel, Field

class User(BaseModel):
    name: str
    email: str = Field(..., alias='email_address')
```

**Валидация:**

- Минимальная валидация: встроенные типы Python (например, str, int).
- Валидаторы: например EmailStr для проверки email-адресов. Требуется установка дополнительных зависимостей: pydantic\[email\] или pydantic\[all\].
- @field\_validator — добавляет логику валидации поля. Вызывается при создании или изменении модели.

```python
from pydantic import BaseModel, field_validator

class User(BaseModel):
    age: int

    @field_validator('age')
    def check_age(cls, value):
        if value < 18:
            raise ValueError('Возраст должен быть больше 18 лет')
        return value
```

- @computed\_field — вычисляемое поле на основе данных в модели. Его можно использовать для автоматической генерации значений, а также для валидации.

```python
from pydantic import BaseModel, computed_field

class User(BaseModel):
    name: str
    surname: str

    @computed_field
    def full_name(self) -> str:
        return f"{self.name} {self.surname}"
```

- @model\_validator - валидация всей модели.

Работает со всей моделью (а не с отдельными полями), может изменять данные перед валидацией (mode='before') или после (mode='after'), полезен для комплексных проверок, где одно поле зависит от другого, может возвращать новую версию модели (если нужно модифицировать данные).

```python
@model_validator(mode='before')
def validate_before(cls, data: dict):
    if 'username' not in data:
        data['username'] = "guest_" + str(data.get('id', 0))
    return data
```

```python
@model_validator(mode='after')
def validate_after(self):
    if self.age < 18 and self.is_premium:
        raise ValueError("Minors cannot have premium accounts!")
    return self
```

При проверке before передается класс, при after - объект. Можно делать два валидатора: before для подстановки вычисляемых значений и after для финальной проверки

**Интеграция с SQLAlchemy:**

Для настройки используется параметр ConfigDict с флагом from\_attributes=True.

```python
from datetime import date
from pydantic import BaseModel, ConfigDict

class User(BaseModel):
    id: int
    name: str = 'John Doe'
    birthday_date: date

    config = ConfigDict(from_attributes=True)
```

Для создания модели Pydantic из объекта ORM используется метод from\_orm.

```python
user = User.from_orm(orm_instance)
```

**Ссылки:**

[Основа для текста](https://habr.com/ru/companies/amvera/articles/851642/)

# Pyinstaller

Установка:

```
python -m pip install pyinstaller
```

Использование

```
pyinstaller [параметры] script.py
```

Параметры

<table border="1" id="bkmrk-%D0%9F%D0%B0%D1%80%D0%B0%D0%BC%D0%B5%D1%82%D1%80-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5---" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 25.6232%;"></col><col style="width: 74.3768%;"></col></colgroup><thead><tr><td class="align-center">Параметр</td><td class="align-center">Описание</td></tr></thead><tbody><tr><td>--onefile</td><td>собирает всё в один .exe файл</td></tr><tr><td>--windowed</td><td>скрывает консоль (если у вас GUI-приложение). Если нужна консоль, уберите этот флаг.</td></tr><tr><td>--icon=ваша\_иконка.ico</td><td>иконка</td></tr><tr><td>--name "МояПрограмма"</td><td>имя программы</td></tr><tr><td>--dest &lt;путь\_к\_директории&gt;</td><td>директория, в которую будет собираться exe файл</td></tr></tbody></table>

# Telegram

При взаимодействии с ботом нужен идентификатор.

**Свой идентификатор**

- В Telegram напиши боту <a class="decorated-link cursor-pointer" data-end="565" data-start="525" rel="noopener" target="_new">@userinfobot</a>
- Он ответит твоим `user_id` (число). Это и есть твой `chat_id`. <a class="decorated-link cursor-pointer" data-end="565" data-start="525" rel="noopener" target="_new"><span aria-hidden="true" class="ms-0.5 inline-block align-middle leading-none"><svg class="block h-[0.75em] w-[0.75em] stroke-current stroke-[0.75]" data-rtl-flip="" fill="currentColor" height="20" viewbox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"></svg></span></a>

**Для групп или каналов**

- Добавь бота в группу/канал.
- Напиши любое сообщение.
- В браузере открой: https://api.telegram.org/bot&lt;YOUR\_BOT\_TOKEN&gt;/getUpdates
- В JSON-ответе найди `chat":{"id": ... }` — это и есть `CHAT_ID`. Для супергрупп он будет вида `-1001234567890`.

# Bitcoinlib

Библиотека для работы с кошельками. Операции, связанные с кошельком:

- + Создание нового кошелька
- Экспорт данных о созданном кошельке
- Импорт существующего кошелька
- + Информация о кошельке
- Транзакция

**Создание кошелька.** Создается хранилище данных в ~/.bitcoinlib Затем можно проводить операции.

```python
def create_wallet():
    # Создаем новый testnet кошелек
    wallet = Wallet.create(
        name='my_testnet_wallet',
        network='testnet'
    )

    print(f"Адрес: {wallet.get_key().address}")
    print(f"Приватный ключ (WIF): {wallet.get_key().wif}")
    print(f"Баланс: {wallet.balance()} satoshi")

    # Получить информацию об адресе
    print(f"Это testnet адрес? {wallet.get_key().address.startswith(('m', 'n', '2', 'tb1'))}")
```

Для получения стартовых btc в сети testnet использовал [https://coinfaucet.eu/en/btc-testnet/](https://coinfaucet.eu/en/btc-testnet/)

Информация о кошельке. Кошелек с данным названием уже установлен в системе.

```python
def wallet_info():
    """Полная информация о кошельке (исправленная)"""
    
    wallet = Wallet('my_testnet_wallet')
    wallet.scan()  # Важно: синхронизируем с сетью
    
    print("=" * 60)
    print(f"КОШЕЛЁК: {wallet.name}")
    print(f"СЕТЬ: {wallet.network.name}")
    print("=" * 60)
    
    # Баланс
    balance = wallet.balance()
    print(f"\n💰 БАЛАНС: {balance:,} satoshi")
    print(f"         ≈ {balance / 100000000:.8f} BTC")
    
    # UTXOs
    utxos = wallet.utxos()
    print(f"\n UTXOs: {len(utxos)}")
    
    if utxos:
        utxo_total = 0
        for i, utxo in enumerate(utxos, 1):
            print(f"\n  UTXO #{i}:")
            print(f"    Транзакция: {utxo['txid'][:20]}...:{utxo['output_n']}")
            print(f"    Адрес: {utxo['address']}")
            print(f"    Сумма: {utxo['value']:,} sat")
            
            if 'confirmations' in utxo:
                confs = utxo['confirmations']
                status = "✓ Подтверждено" if confs > 0 else " Ожидание"
                print(f"    Статус: {status} ({confs} подтверждений)")
            
            utxo_total += utxo['value']
        
        print(f"\n  Сумма всех UTXOs: {utxo_total:,} sat")
    
    # Транзакции
    transactions = wallet.transactions()
    print(f"\n ТРАНЗАКЦИИ: {len(transactions)}")
    
    if transactions:
        for tx in transactions:
            print(f"\n  Транзакция: {tx.txid[:20]}...")
            print(f"    Дата: {tx.date}")
            
            if tx.confirmations:
                print(f"    Подтверждений: {tx.confirmations}")
            else:
                print(f"    Статус: Неподтверждена")
            
            print(f"    Комиссия: {tx.fee} sat")
            
            # Анализируем сумму
            our_addresses = wallet.addresslist()
            received = 0
            sent = 0
            
            # Выходы (получение)
            for output in tx.outputs:
                if output.address in our_addresses:
                    received += output.value
            
            # Входы (отправка)
            for input_tx in tx.inputs:
                if input_tx.address in our_addresses:
                    sent += input_tx.value
            
            if received > 0 and sent > 0:
                print(f"    Тип: Перевод")
                print(f"    Изменение баланса: {received - sent:,} sat")
            elif received > 0:
                print(f"    Тип: Получение")
                print(f"    Сумма: +{received:,} sat")
            elif sent > 0:
                net_sent = sent - tx.fee
                print(f"    Тип: Отправка")
                print(f"    Сумма: -{net_sent:,} sat (включая комиссию)")
    
    # Ключи и адреса
    print(f"\n КЛЮЧИ И АДРЕСА:")
    keys = wallet.keys()
    print(f"  Всего ключей: {len(keys)}")
    
    used_addresses = [key.address for key in keys if key.used]
    print(f"  Использованных адресов: {len(used_addresses)}")
    
    # Показываем первые 5 адресов
    for i, key in enumerate(keys[:5], 1):
        status = " Использован" if key.used else " Не использован"
        print(f"  {i}. {key.address} - {status} ({key.balance} sat)")
    
    if len(keys) > 5:
        print(f"  ... и ещё {len(keys) - 5} адресов")
    
    # Сетевая информация
    print(f"\n СЕТЕВАЯ ИНФОРМАЦИЯ:")
    #print(f"  Последний блок: {wallet.last_block}")
    print(f"  ID кошелька: {wallet.wallet_id}")
    
    return wallet
```

# Логгирование

Встроенный модуль logging. Нужно настроить логгер и использовать его.

Настройка:

---

```python
import logging

if __name__ == '__main__':
    logging.basicConfig(level=logging.ERROR, filename="error.log",filemode="w",
                        format="%(asctime)s %(levelname)s %(message)s")
```

**Использование:**

Внутри модуля, где настраивался логгер:

```
logging.error("Критическая ошибка в основном модуле")
```

В вызываемом модуле не проводим настройку, только:

```python
logger = logging.getLogger(__name__)
logger.error('Wow')
```

# Docsvision

Существует webapi

Узнать realm можно через ipconfig на win машине в домене, строка "DNS-суффикс подключения"

Список контроллеров домена: nslookup -type=SRV \_ldap.\_tcp.&lt;realm&gt;

Для работы требуется

```
sudo apt install krb5-user
sudo apt install libkrb5-dev
```

Файл /etc/krb5.conf

```
[libdefaults]
    default_realm = DOMAIN.LOCAL
    dns_lookup_realm = true
    dns_lookup_kdc = true

[realms]
    DOMAIN.LOCAL = {
        kdc = dc01.domain.local
        admin_server = dc01.domain.local
    }
```

Для ручного получения тикета <span style="color: rgb(224, 62, 45);">(REALM обязательно прописными!)</span>:

```
kinit username@REALM
```

Проверка полученного тикета:

```
klist
```

Модули python

b

# Rabbitmq

[Базовая информация](https://yourtodo.ru/posts/ispolzovanie-rabbitmq-v-python-ot-docker-do-raspredelyonnyih-sistem/)

# Тестирование Playwright

# Начало

[Официальный сайт проекта](https://playwright.dev/)

**Установка**

```
python -m pip install playwright
```

Проверка установки

```
playwright --version
```

Установка драйверов для браузеров

```
playwright install #Все браузеры
playwright install name #Только name браузеры
playwright install chromium #Chrome
```

Установка pytest

```
python -m pip install pytest
```

Установка плагина pytest-playwright

```
python -m pip install pytest-playwright
```

**Описание**

Два режима работы: синхронный и асинхронный. Для синхронного:

```python
from playwright.sync_api import sync_playwright
```

Для асинхронного режима:

```python
import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as playwright:
        browser = await playwright.chromium.launch()
        page = await browser.new_page()
        await page.goto("https://playwright.dev")
        print(await page.title())
        await browser.close()

asyncio.run(main())
```

Чаще используется синхронный режим.

Запуск и закрытие браузера:

```python
from playwright.sync_api import sync_playwright

with sync_playwright() as playwright:
    browser = playwright.chromium.launch(headless=False, slow_mo=500)
    page = browser.new_page()
    page.goto("https://playwright.dev/python")

    docs_button = page.get_by_role('link', name="Docs")
    docs_button.click()

    browser.close()

```

headless=False обозначает визуальное открытие, slow\_mo задержка

**Использование интерактивной консоли**

Иногда для удобства можно использовать консоль python для ручного тестирования покомандного ввода.

```python
python
Python 3.13.1 (tags/v3.13.1:0671451, Dec  3 2024, 19:06:28) [MSC v.1942 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from playwright.sync_api import sync_playwright
>>> playwright = sync_playwright().start()
>>> browser = playwright.chromium.launch(headless=False, slow_mo=100)
>>> page = browser.new_page()
>>> browser.close()
>>> playwright.stop()
```

Если создать docs\_button, у нее будет метод highlight() для визуальной подсветки найденного элемента.

# Локаторы

Локаторы: способ поиска элементов на странице. Поэтому они являются методами page

В VSC Ctrl+Click по методу выводит код метода.

<table border="1" id="bkmrk-%D0%9B%D0%BE%D0%BA%D0%B0%D1%82%D0%BE%D1%80-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-pag" style="border-collapse: collapse; width: 100%; height: 447.5px;"><colgroup><col style="width: 34.5834%;"></col><col style="width: 65.5358%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">Локатор</td><td class="align-center" style="height: 29.7969px;">Описание</td></tr></thead><tbody><tr style="height: 80.1875px;"><td style="height: 80.1875px;">page.get\_by\_role('link', name="Docs")</td><td style="height: 80.1875px;">Поиск элемента по роли name - текст

link &lt;a&gt;

heading &lt;h&gt;

radio, checkbox, button

</td></tr><tr style="height: 191.531px;"><td style="height: 191.531px;">page.get\_by\_label("Email address")</td><td style="height: 191.531px;">Для выделения элементов, у которых есть привязанная метка. Например ```html
<div>
  <label for="exampleInputEmail1" class="form-label mt-4">Email address</label>
  <input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email">
  <small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
```

</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">page.get\_by\_placeholder("Enter email")</td><td style="height: 29.7969px;">Поиск элементов по placeholder</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">page.get\_by\_text("Something", exact=False)</td><td style="height: 29.7969px;">Поиск по тексту. Exact=False ищет вхождение.</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">page.get\_by\_alt\_text(text)</td><td style="height: 29.7969px;">Поиск по атрибуту alt у изображений</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">page.get\_by\_title(text)</td><td style="height: 29.7969px;">Атрибут title</td></tr><tr style="height: 26.7969px;"><td style="height: 26.7969px;">page.locator(text)</td><td style="height: 26.7969px;">Поиск по CSS. Можно использовать tagname, classname, id, attribute/value

Примеры^

css=h1

footer

&lt;tagname&gt;.&lt;classname&gt; button.btn-outline-sucess

&lt;tagname&gt;#&lt;idname&gt; button#BtnGroupDrop1

&lt;tagname&gt;\[attribute\] input\[readonly\]

&lt;tagname&gt;\[attribute=somevalue\] input\[value='correct value'\]

</td></tr><tr><td>  
</td><td>Поиск по иерархии элементов.

Если через пробелы-то вложенные элементы. Если через точку - то у элемента несколько классов. Но они не обязательно непосредственно вложенные.

nav.bg-dark a.nav-link.active

Для непосредственного вложения:

nav.bg-dark &gt; a.nav-link.active

</td></tr><tr><td>  
</td><td>Называются sudo классами,

Класс и текст в теге. Для вхождения:

h1:text('Navbars')

Для полного соответствия:

h1:text-is('Navbars')

div.dropdown-menu:visible

Для определения по номеру вхождения, когда их много

 :nth-match(button.btn-primary, 4)

</td></tr><tr><td>  
</td><td>XPath

Абсолютный путь: xpath=/html/head/title

С любого начала: xpath=//h1/h2

С указанием атрибута xpath=//h1\[ @id='navbars' \]

</td></tr><tr><td>  
</td><td>Функции XPath

Для поиска по тексту, точно: //h1\[text()='Headling1'\]

Для поиска по тексту, содержит: //h1\[contains(text(), 'Headling1')\]

Для поиска по тексту, содержит: //h1\[contains(@class, 'btn')\]

</td></tr><tr><td>Множественные условия</td><td>Поиск родительского элемента

page.get\_by\_label("Email address").locator("..")

Фильтрация

page.get\_by\_role("heading").filter(has\_text="First")

По дочернему элементу

page.locator("div.form-group").filter(has=page.get\_by\_label("Password"))

</td></tr></tbody></table>

**Доступ к iframe**

```python
'''Test for auth module'''
import pytest
from playwright.sync_api import Browser, Page, expect

AUTH_URL = "https://wood.bobrobotirk.ru/auth"

@pytest.fixture
def page_and_auth(browser: Browser):
    context = browser.new_context(
        storage_state="playwright/.auth/vk.json"
        )
    page = context.new_page()
    yield page
    context.close()

def test_first(page_and_auth: Page):
    page_and_auth.goto(AUTH_URL)
    vkframe = page_and_auth.frame(url=lambda url: "id.vk.com" in url)

    if vkframe:
        authbutton = vkframe.get_by_role("button", name="Продолжить как")
        expect(authbutton, "Кнопка Продолжить как... отсутствует").to_be_visible(timeout=20000)
        authbutton.click()
        back_button = page_and_auth.get_by_role("button", name="Авторизация успешна")
        expect(back_button, "Сервис не произвел авторизацию").to_be_visible()
        back_button.click()
    else:
        assert False, "VK фрейм не найден."
```

# Actions

<table border="1" id="bkmrk-%D0%94%D0%B5%D0%B9%D1%81%D1%82%D0%B2%D0%B8%D0%B5-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-cl" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 30.2977%;"></col><col style="width: 69.8215%;"></col></colgroup><thead><tr><td class="align-center">Действие</td><td class="align-center">Описание</td></tr></thead><tbody><tr><td>click()</td><td>Однократное нажатие. Опции:

button="left"

modifiers=\["Shift", "Alt"\] с зажатой кнопкой Shift

timeout=2\_000 Задержка перед ошибкой. Обычно 30 сек.

force=True Ошибка сразу же если не найден.

</td></tr><tr><td>dblclick()</td><td>Двойной щелчок. Опции как у click +:

delay=100 - задержка в миллисекундах

</td></tr><tr><td>hover()</td><td>Навести мышь на выбранный элемент</td></tr><tr><td>fill("my text")</td><td>Заполнить поле ввода текстом my text. Аналогично Ctrl-V</td></tr><tr><td>clear()</td><td>Очистить поле ввода</td></tr><tr><td>type("my text", delay=100)</td><td>Имитация побуквенного ввода</td></tr><tr><td>check()

Еще: set\_checked(True)

</td><td>Выбор radiobutton, checkbox, switch

is\_checked() для checkbox проверяет, выбран ли checkbox

</td></tr><tr><td>uncheck()</td><td>убрать выбор

</td></tr><tr><td>select\_option("text")</td><td>Выбор опции из раскрывающегося списка. Но если отсутствует - будет Timeout Error.

Если передать список - будет множественный выбор.

</td></tr><tr><td>  
</td><td>Для раскрытия Dropdown элемента: нажатие на него, выбор элемента и нажатие

</td></tr><tr><td>set\_input\_files("")</td><td>Для элемента позволяющего загружать файлы, имя файла из директории, из которой запускается скрипт. Можно передать список.

</td></tr><tr><td>  
</td><td>Если по кнопке открывается меню выбора файла, то

```python
with page.expect_file_chooser() as fc_info:
    file_input.click() #до этого через локатор найден file_input
file_chooser = fc_info.value
file_chooser.set_files("first.txt")
```

</td></tr><tr><td>  
</td><td>press("KeyW")

press("Shift+KeyW")

press("Control+ArrowLeft")

</td></tr></tbody></table>

# События (Events)

**События в page.goto**

В переменной wait\_until.

- load: загрузка всего контента
- domcontentloaded: загрузка dom
- commit: при получении ответа от сервера
- networkidle: до завершения всех событий сети. Для динамического контента не меньше чем load.

Можно считать время загрузки.

```python
from playwright.sync_api import sync_playwright
from time import perf_counter

with sync_playwright() as playwright:
    browser = playwright.chromium.launch(headless=False, slow_mo=500)
    page = browser.new_page()
    print('Page loading...')
    start = perf_counter()
    page.goto("https://playwright.dev/python", wait_until='load')
    delta = perf_counter() - start
    print(f'Page loaded in {delta} s.')
    
    browser.close()
```

**События динамического контента (React, ...)**

Находим динамический элемент, кликаем по нему и при помощи wait\_for() ждем.

```python
from playwright.sync_api import sync_playwright
from time import perf_counter

with sync_playwright() as playwright:
    browser = playwright.chromium.launch(headless=False, slow_mo=500)
    page = browser.new_page()
    page.goto("https://www.scrapethissite.com/pages/ajax-javascript/", timeout=60_000)
    mylink = page.get_by_role("link", name="2014")
    mylink.click()
    print('Loading movie...')
    start = perf_counter()
    loadedcont = page.locator("td.film-title").first
    loadedcont.wait_for()
    delta = perf_counter() - start
    print(f'Movie loaded in {delta} s.')
    
    browser.close()

```

**Ожидание события.**

Указываем тип события и функцию, выполняемую при наступлении события.

```python
from playwright.sync_api import sync_playwright

def onload(page):
    print("Page loaded", page)

with sync_playwright() as playwright:
    browser = playwright.chromium.launch(headless=False, slow_mo=500)
    page = browser.new_page()
    page.on("load", onload)
    page.goto("https://bootswatch.com/default")
    browser.close()
```

Пример просмотра событий запросов

```python
from playwright.sync_api import sync_playwright

def onrequest(request):
    print("Request send: ", request)

with sync_playwright() as playwright:
    browser = playwright.chromium.launch(headless=False, slow_mo=500)
    page = browser.new_page()
    page.on("request", onrequest)
    page.goto("https://bootswatch.com/default")
    browser.close()

```

Вывод:

```
Request send:  <Request url='https://bootswatch.com/default' method='GET'>
Request send:  <Request url='http://bootswatch.com/default/' method='GET'>
Request send:  <Request url='https://bootswatch.com/default/' method='GET'>
Request send:  <Request url='https://bootswatch.com/_vendor/bootstrap/dist/css/bootstrap.css' method='GET'>
Request send:  <Request url='https://bootswatch.com/_vendor/bootstrap-icons/font/bootstrap-icons.min.css' method='GET'>
Request send:  <Request url='https://bootswatch.com/_vendor/prismjs/themes/prism-okaidia.css' method='GET'>
Request send:  <Request url='https://bootswatch.com/_assets/css/custom.min.css' method='GET'>
Request send:  <Request url='https://www.googletagmanager.com/gtag/js?id=G-KGDJBEFF3W' method='GET'>
Request send:  <Request url='https://cdn.carbonads.com/carbon.js?serve=CKYIE23N&placement=bootswatchcom' method='GET'>
Request send:  <Request url='https://bootswatch.com/_vendor/bootstrap/dist/js/bootstrap.bundle.min.js' method='GET'>
Request send:  <Request url='https://bootswatch.com/_vendor/prismjs/prism.js' method='GET'>
Request send:  <Request url='https://bootswatch.com/_assets/js/custom.js' method='GET'>
Request send:  <Request url='https://bootswatch.com/_vendor/bootstrap-icons/font/fonts/bootstrap-icons.woff2?1fa40e8900654d2863d011707b9fb6f2' method='GET'>
Request send:  <Request url='https://www.google-analytics.com/g/collect?v=2&tid=G-KGDJBEFF3W&gtm=45je54g0v9135688085za200&_p=1744912703288&gcd=13l3l3l3l1l1&npa=0&dma=0&tag_exp=102509682~102803279~102813109~102887800~102926062~103027016~103051953~103055465~103077950~103106314~103106316~103130495~103130497&cid=128797986.1744912704&ul=ru-ru&sr=1280x720&uaa=x86&uab=64&uafvl=Not%253AA-Brand%3B24.0.0.0%7CChromium%3B134.0.6998.35&uamb=0&uam=&uap=Windows&uapv=10.0.0&uaw=0&are=1&frm=0&pscdl=noapi&_s=1&sid=1744912703&sct=1&seg=0&dl=https%3A%2F%2Fbootswatch.com%2Fdefault%2F&dt=Bootswatch%3A%20Default&en=page_view&_fv=1&_nsi=1&_ss=1&_ee=1&tfd=2018' method='POST'>
Request send:  <Request url='https://srv.carbonads.net/ads/CKYIE23N.json?segment=placement:bootswatchcom&v=true' method='GET'>
```

Есть событие при выборе файла.

Удаление прослушивания события: page.remove\_listener("name\_event", func)

**События всплывающих окон (alert, confirm, prompt)**

Событие page.on("dialog", func), устанавливаем ожидание до возможного появления окна.

Функция:

```
def on_dialog(dialog)
    dialog.accept()
    dialog.dismiss()
```

Если диалог prompt, то для заполнения поля ввода в функции accept нужно добавить строковую переменную.

```
def on_dialog(dialog)
    dialog.accept("Text for enter")
```

**Событие скачивания файла**

```python
from playwright.sync_api import sync_playwright

with sync_playwright() as playwright:
    browser = playwright.chromium.launch(headless=False, slow_mo=500)
    page = browser.new_page()
    page.goto("https://bootswatch.com/default")
    btn = page.get_by_role("link", name="Download me")
    with page.expect_download() as download_info:
        btn.click()
    download = download_info.value
    download.save_as(fname)
    browser.close()
```

Второй вариант: добавить listener

```python
from playwright.sync_api import sync_playwright

def saving_func(download):
    fname = "first.jpg"
    download.save_as(fname)

with sync_playwright() as playwright:
    browser = playwright.chromium.launch(headless=False, slow_mo=500)
    page = browser.new_page()
    page.goto("https://bootswatch.com/default")
    page.once("download", saving_func)
    btn = page.get_by_role("link", name="Download me")
    with page.expect_download() as download_info:
        btn.click()
    browser.close()
```

# Аутентификация

При 2FA аутентификации возникают проблемы при повторном исполнении скрипта. Для обхода этого используют контекст браузера.

Шаг 1. Сохранение контекста.

```python
from playwright.sync_api import sync_playwright

with sync_playwright() as playwright:
    browser = playwright.firefox.launch(headless=False, slow_mo=500)
    context = browser.new_context()
    page = context.new_page()
    page.goto("https://vk.ru")
    page.pause() # Откроется доп. окно. Проходим авторизацию, в доп.окне play
    context.storage_state(path="playwright/.auth/vk.json")
    context.close()
```

Шаг 2. Использование контекста

```python
from playwright.sync_api import sync_playwright

with sync_playwright() as playwright:
    browser = playwright.firefox.launch(headless=False, slow_mo=500)
    context = browser.new_context(storage_state="playwright/.auth/vk.json")
    page = context.new_page()
    page.goto("https://vk.ru")
    page.pause() #в реальных скриптах это убирается)
    context.close()
```

# Pytest & Playwright

**Pytest**

Имена файлов тестов должны иметь префикс test\_ или постфикс \_test. Имена тестов должны иметь префикс test\_

В модуле utils функция root, отнимающая 1 от входного параметра. Пример теста:

```python
import utils

def test_first():
    num24 = utils.root(25)
    assert num24 == 24
```

**Запуск теста:**

```
pytest second_test.py
```

Ключ -v отображает расширенную информацию.  
Ключ -s разрешает вывод данных из тестируемых функций.

Запуск без указания имени файла исполняет все тесты.

Желательно определение типов в функциях.

**Виды проверок**

<table border="1" id="bkmrk-%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-as" style="border-collapse: collapse; width: 100%; height: 178.781px;"><colgroup><col style="width: 50%;"></col><col style="width: 50%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">Проверка</td><td class="align-center" style="height: 29.7969px;">Описание</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">assert mynum == 32</td><td style="height: 29.7969px;">Проверка на значение</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">assert type(dt) == dict</td><td style="height: 29.7969px;">Проверка типа</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">assert "timestamp" in dt\_list</td><td style="height: 29.7969px;">Проверка присутствия в списке</td></tr></tbody></table>

Для вывода доп. информации:

```python
assert mynum == 32, "Число mynum должно быть равно 32"
```

**Фикстуры:**

Для упаковки повторяющихся действий.

```python
import json
import report
import pytest

@pytest.fixtures
def report_json():
    report.generate_report()
    with open("report.json") as file:
        return json.load(file)

def test_report_json(report_json):
    assert type(report_json) == dict
```

Фикстура исполняется каждый раз при вызове. Для однократного исполнения фикстуры нужно добавить scope="session"

```python
import json
import report
import pytest

@pytest.fixtures(scope="session")
def report_json():
    report.generate_report()
    with open("report.json") as file:
        return json.load(file)

def test_report_json(report_json):
    assert type(report_json) == dict
```

Виды scope

<table border="1" id="bkmrk-session-%D0%9E%D0%B4%D0%B8%D0%BD-%D1%80%D0%B0%D0%B7-%D0%B2-%D0%BF" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 50%;"></col><col style="width: 50%;"></col></colgroup><tbody><tr><td>session</td><td>Один раз в пределах сессии</td></tr><tr><td>function</td><td>Каждый раз</td></tr><tr><td>module</td><td>Если несколько тестовых файлов, то один раз в пределе запуска.</td></tr></tbody></table>

**Pytest-playwright**

Данный плагин упрощает работу с pytest. Встроенные фикстуры. Задачу создания и удаления playwright, browser берет на себя.

```python
from playwright.sync_api import Page

def test_first(page: Page):
    page.goto("https://playwright.dev/python")
    link = page.get_by_role("link", name="GET STARTED")
    link.click()
    assert page.url == "https://playwright.dev/python/docs/intro"
```

**Настройка pytest**

Через консоль:

- --headed Отображать окно
- --slowmo=500 Замедление работы
- --browser=firefox Настройка браузера

Через конфигурационный файл pytest.ini

```ini
[pytest]
addopts = --headed --slowmo=500 --browser=firefox
```

**Фикстуры**

Добавить фикстуру

```python
import pytest
from playwright.sync_api import Page

@pytest.fixture
def load_testpage(page: Page):
    page.goto("https://playwright.dev/python")
    return page

def test_first(load_testpage: Page):
    link = load_testpage.get_by_role("link", name="GET STARTED")
    link.click()
    assert load_testpage.url == "https://playwright.dev/python/docs/intro"
```

Автоматическое исполнение фикстуры перед каждой функцией:

```python
import pytest
from playwright.sync_api import Page

@pytest.fixture(autouse=True)
def load_testpage(page: Page):
    page.goto("https://playwright.dev/python")
    return page

def test_first(page: Page):
    link = page.get_by_role("link", name="GET STARTED")
    link.click()
    assert page.url == "https://playwright.dev/python/docs/intro"
```

Пред и пост исполнение кода:

```python
import pytest
from playwright.sync_api import Page

@pytest.fixture(autouse=True)
def load_testpage(page: Page):
    page.goto("https://playwright.dev/python")
    yield page
    page.goto("https://yandex.ru")

def test_first(page: Page):
    link = page.get_by_role("link", name="GET STARTED")
    link.click()
    assert page.url == "https://playwright.dev/python/docs/intro"
```

**Пример проверки авторизации ВК**

```python
'''Test for auth module'''
import pytest
from playwright.sync_api import Browser, Page, expect

AUTH_URL = "https://wood.bobrobotirk.ru/auth"

@pytest.fixture
def page_and_auth(browser: Browser):
    context = browser.new_context(
        storage_state="playwright/.auth/vk.json"
        )
    #page = context.new_page()
    yield context
    context.close()

def get_cookie(all_cookies, cname):
    val = next((cookie['value'] for cookie in all_cookies if cookie.get('name') == cname), None)
    return val

def test_first(page_and_auth: Browser):
    page = page_and_auth.new_page()
    page.goto(AUTH_URL)
    vkframe = page.frame(url=lambda url: "id.vk.com" in url)

    if vkframe:
        authbutton = vkframe.get_by_role("button", name="Продолжить как")
        expect(authbutton, "Кнопка Продолжить как... отсутствует").to_be_visible(timeout=20000)
        authbutton.click()
        back_button = page.get_by_role("button", name="Авторизация успешна")
        expect(back_button, "Сервис не произвел авторизацию").to_be_visible()
        back_button.click()
        all_cookies = page_and_auth.cookies()
        session_id_value = get_cookie(all_cookies, 'session_id')
        assert session_id_value, "Cookie session_id не установлен."
    else:
        assert False, "VK фрейм не найден."
```

# Дополнительные возможности

**Скриншоты**

Скрин страницы

page.screenshot(path="", full\_page=True)

Скрин элемента тоже работает.

link.screenshot(path="")

**Запись видео**

```python
from playwright.sync_api import Browser

def test_first(browser: Browser):
    context = browser.new_context(
        storage_state="playwright/.auth/vk.json",
        record_video_dir="video/"
        )
    page = context.new_page()
    page.goto("https://playwright.dev/python")
    link = page.get_by_role("link", name="GET STARTED")
    link.click()
    assert page.url == "https://playwright.dev/python/docs/intro"
    page.goto("https://vk.ru")
```

Вариант с текстурой:

```python
import pytest
from playwright.sync_api import Browser, Page

@pytest.fixture
def recordable(browser: Browser):
    context = browser.new_context(
        storage_state="playwright/.auth/vk.json",
        record_video_dir="video/"
        )
    page = context.new_page()
    yield page
    context.close()

def test_first(recordable: Page):
    recordable.goto("https://playwright.dev/python")
    link = recordable.get_by_role("link", name="GET STARTED")
    link.click()
    assert recordable.url == "https://playwright.dev/python/docs/intro"
    recordable.goto("https://vk.ru")
```

**Трассировка данных**

Создание трассировки:

```python
import pytest
from playwright.sync_api import Page, BrowserContext

@pytest.fixture(autouse=True)
def trace_test(context: BrowserContext):
    context.tracing.start(
        name="playwrite",
        screenshots=True,
        snapshots=True,
        sources=True
    )
    yield 
    context.tracing.stop(path="trace.zip")

def test_first(page: Page):
    page.goto("https://playwright.dev/python")
    link = page.get_by_role("link", name="GET STARTED")
    link.click()
    assert page.url == "https://playwright.dev/python/docs/intro"
```

Просмотр трассировки

```
playwright show-trace trace.zip
```

**Запись действий**

Старт записи

```
playwright codegen playwright.dev
```

В окне кода есть поле Target, позволяет переключать тип библиотек (playwright sync/async, pytest). Потом сохраняем полученную последовательность и устанавливаем нужные условия для проверки.

# Ожидание

```python
from playwright.sync_api import Page, expect

DOCS_URL = "https://playwright.dev/python/docs/intro"

def test_first(page: Page):
    page.goto("https://playwright.dev/python")
    link = page.get_by_role("link", name="GET STARTED")
    link.click()
    #assert page.url == DOCS_URL
    expect(page).to_have_url(DOCS_URL)
```

<table border="1" id="bkmrk-expect%28page%29.to_have" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 50.0596%;"></col><col style="width: 50.0596%;"></col></colgroup><tbody><tr><td>expect(page).to\_have\_url</td><td>наличие url</td></tr><tr><td>expect(page).to\_have\_title</td><td>наличие title</td></tr><tr><td><div><div>link = page.get_by_role("link", name="GET STARTEDer")</div><div>expect(link).to_be_visible()</div></div></td><td>Видимость элемента в переменной link</td></tr><tr><td><div><div>expect(link).to_be_enabled()</div></div></td><td>Доступный элемент</td></tr><tr><td><div><div>expect(heading).to_contain_text()</div></div></td><td>Присутствие текста (часть)</td></tr><tr><td><div><div>expect(heading).to_have_text()</div></div></td><td>Присутствие текста (полное совпадение)</td></tr><tr><td><div><div>expect(mylink).to_have_class()</div></div></td><td>Наличие класса у элемента Несколько классов: "class1 class2"

Должно быть полное соответствие. Но можно использовать регулярки.

```python
expect(mylink).to_have_class(
    re.compile(r"navbar__link")
    )
```

</td></tr><tr><td><div><div>expect(mylink).to_have_id()</div></div></td><td>Наличие id

</td></tr><tr><td><div><div>expect(mylink).to_have_attribute(attr_name, attr_value)</div></div></td><td>Наличие атрибута. При необходимости можно указать значение атрибута.

</td></tr><tr><td><div><div>expect(mylink).to_be_editable()</div></div></td><td></td></tr><tr><td><div><div>expect(mylink).to_be_empty()</div></div></td><td></td></tr><tr><td><div><div>expect(mycheckbox).to_be_checked()</div></div></td><td></td></tr><tr><td><div><div>expect(mymenu).to_have_value()</div></div></td><td>Элемент в меню выбора

</td></tr><tr><td><div><div>expect(mymultimenu).to_have_values([])</div></div></td><td>Несколько выбранных

</td></tr></tbody></table>

not\_ - префикс отрицания

# Pytest

**Теория**

<div id="bkmrk-%D0%92%D0%B8%D0%B4%D1%8B-%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F">**Виды тестирования**</div>- Модульное: небольшой элемент/модуль
- Компонентное: проверка подсистем по отдельности
- Альфа/бета: в реальных условиях на настоящих данных в прод версии
- Комплексное: проверка связей интерфейсов между парами и группами компонентов
- Системное: поведение как в целом, так и в частности
- ПСИ: проверка удовлетворения системы требованиям
- Пилотное: опытная эксплуатация под тщательным контролем

<div id="bkmrk-%D0%9F%D0%BB%D0%B0%D0%BD%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE">**Планирование тестирования**</div>1. Планирование тестирования - описывает все процессы. Понимание места тестирования 
    1. операционный и организационный контекст
    2. Риски качества системы + ранжирование, понимание каждого возможностей тестирования для смягчения рисков
    3. Потребности временнЫх ресурсах
    4. План мероприятий по тестированию. Задачи, состав участников. ```
        Важно Ожидание качества (численные критерии, предьявляемые перед началом) и 
        Опыт качества (численное значение критериев после выполнения работ). 
        Жизненный цикл системы = Жизненный цикл разработки + эксплуатации
        ```
2. Подготовка к тестированию 
    1. Обучение тестировщиков до нужного уровня
    2. Спроектировать систему тестирования (окружение, процедура, распределение задач, )
3. Проведение тестирования 
    1. Получение версии для тестирования
    2. Проведение тестов ```
        Предпочтительнее алгоритм Дано-Ожидаемо-Проверка
        Лучше пофункциональное тестирование. Интегральные тесты хороши но не дают нужной детализации.
        Каждый тест должен возвращать состояние в начальное, они должны быть последовательно-независимыми
        ```
4. Совершенствование 
    1. Документирование тестирования
    2. Информирование о результатах
    3. При изменении контекста изменение процесса

<div id="bkmrk-%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%B8%D0%B5-%D1%83%D1%81%D0%BF%D0%B5%D1%88%D0%BD%D0%BE%D0%B3%D0%BE">**Построение успешного процесса**</div>- Что делает группа тестирования, когда применяет успешный процесс, и какие преимущества она получает.
- Нереалистичные ожидания со стороны руководства
- Получить согласие на изменение процесса сложнее самого изменения
- Процесс усовершенствования требует наличия плана

<div id="bkmrk-%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B-%D0%B4%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%86%D0%B8">**Элементы документации**</div><div id="bkmrk-%D0%A2%D0%BE%D1%87%D0%BA%D0%B0-%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%3A-">- Точка тестирования:
- Итоговая цель тестирования:
- Краткосрочная цель и параметры тестирования:
- Документирование:
- Фиксирование версионности:
- Приоретизация тестирования:
- Параметры тестируемой подсистемы: 
    - Блоки:
    - Функции в каждом блоке:
    - Переменные каждой функции:
    - Комбинированные ожидаемые результаты каждой функции от переменной:

</div><div class="align-center" id="bkmrk-pytest">**Pytest**</div><div id="bkmrk-%D0%A1%D0%BE%D0%B3%D0%BB%D0%B0%D1%88%D0%B5%D0%BD%D0%B8%D1%8F-%D0%BE%D0%B1-%D0%B8%D0%BC%D0%B5%D0%BD%D0%BE%D0%B2"><div>**Соглашения об именованиях**</div><div>Имя файла должна начинаться с test_</div><div>Функция тестирования должна начинаться с test_</div><div>Классы: Test&lt;Something&gt;</div><div>**Запуск тестов**</div><div><div>pytest </div><div>по-умолчанию без параметров - все файлы test_ в текущей папке и поддиректориях</div>pytest test_classes.py::TestEquality Запуск конкретного класса </div><div>pytest test_classes.py::TestEquality::test_equality Запуск конкретного метода в конкретном тестклассе</div><div>@pytest.mark.skip() или @pytest.mark.skipif() - декораторы для пропуска теста</div><div><div>test_file.py использование конкретного файла</div><div>dir конкретная директория</div><div>-v расширенный вывод</div><div>--tb=no включение/отключение traceback, по-умолчанию включено</div><div>-k Маркер</div><div>-k equality все классы/тесты с именем включающим слово equality</div><div>-k "equality and not equality_fail" все классы/тесты с именем включающим слово equality и без equality_fail</div><div>-k "(dict or ids) and not TestEquality"</div><div><div>--setup-show показывает последовательность применения fixture и самого теста</div><div>--fixtures -v расположение файла fixtures</div><div>--fixtures-per-test отдельно использовать fixtures для каждого теста</div></div></div><div>**Возможные статусы:**</div><div>PASSED (.), FAILED (F), SKIPPED (s), </div><div>XFAIL (x), Тест ожидался провальным (был обернут декоратором @pytest.mark.xfail()), и провалился</div><div>XPASS (X), Тест ожидался провальным (был обернут декоратором @pytest.mark.xfail()), но успешно</div><div>ERROR (E) При выполнении теста исключение</div><div>  
</div><div>**Пример** полного файла (test_one.py): </div><div>  
</div></div>```python
def test_passing():
    assert (1, 2, 3) == (1, 2, 3)
```

<div id="bkmrk-%D0%B7%D0%B0%D0%BF%D1%83%D1%81%D0%BA%3A-pytest-test_"><div>запуск: pytest test_one.py</div></div><div id="bkmrk-">  
</div><div id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D1%8B-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B9-%D1%82%D0%B5%D1%81%D1%82">**Примеры функций тестов**</div><div id="bkmrk-%D0%98%D0%B7%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8-%D0%BF%D1%80"><div>Изменение функции проверки</div></div>```python
def assert_identical(c1: Card, c2: Card):
    __tracebackhide__ = True
    assert c1 == c2
    if c1.id != c2.id:
        pytest.fail(f'id\'s don\'t match. {c1.id} != {c2.id}')
```

<div id="bkmrk-%D0%A2%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BE%D0%B6%D0%B8%D0%B4%D0%B0%D0%B5%D0%BC"><div>Тестирование ожидаемого исключения</div><div>  
</div></div>```python
def test_no_path_raises():
    with pytest.raises(TypeError):
        cards.CardsDB()
```

<div id="bkmrk-%D0%A2%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BE%D0%B6%D0%B8%D0%B4%D0%B0%D0%B5%D0%BC-1"><div>Тестирование ожидаемого исключения с конкретным текстом через regex</div><div>  
</div></div>```python
def test_raises_with_info():
    match_regex = "missing 1 .* positional argument"
    with pytest.raises(TypeError, match=match_regex):
         cards.CardsDB()
```

<div id="bkmrk-%D0%A2%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BE%D0%B6%D0%B8%D0%B4%D0%B0%D0%B5%D0%BC-2"><div>  
</div><div>Тестирование ожидаемого исключения с конкретным текстом через проверку наличия текста</div><div>  
</div></div>```python
def test_raises_with_info_alt():
    with pytest.raises(TypeError) as exc_info:
        cards.CardsDB()
    expected = "missing 1 required positional argument"
    assert expected in str(exc_info.value)
```

<div id="bkmrk-fixtures-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8%2C-%D0%B7%D0%B0"><div>  
</div><div>**Fixtures**</div><div>функции, запускающиеся до выполнения тестов (и/или после), для перевода системы в нужный контекст.</div><div>При ошибке в fixture генерится Error</div><div>@pytest.fixture() - запуск при каждом обращении</div><div>@pytest.fixture(scope="module") - один запуск на уровне модуля</div><div>scope='function' по умолчанию</div><div>scope='class' один запуск на уровне класса</div><div>scope='module'</div><div>scope='package' - в случае определения fixture в файле conftest.py</div><div>scope='session' - в случае определения fixture в файле conftest.py</div><div>  
</div><div>Запуск до исполнения</div><div>  
</div></div>```python
import pytest

@pytest.fixture()
def some_data():
    """Return answer to ultimate question."""
    return 42

def test_some_data(some_data):
    """Use fixture return value in a test."""
    assert some_data == 42
```

<div id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%B7%D0%B0%D0%BF%D1%83%D1%81%D0%BA%D0%B0-%D1%81-%D0%B8%D1%82%D0%B5"><div>  
</div><div>Пример запуска с итераторами:</div><div>  
</div></div>```python
@pytest.fixture()
def cards_db():
	# setup part
	with TemporaryDirectory() as db_dir:
		db_path = Path(db_dir)
		db = cards.CardsDB(db_path)
		# end setup part, return db object
		yield db
		# closing db after testing
		db.close()

def test_empty(cards_db):
	# in cards_db - db object, we can use it
	assert cards_db.count() == 0
```

<div id="bkmrk-fixtures-%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE-%D0%B2%D1%8B%D0%BD%D0%B5%D1%81"><div>  
</div><div>Fixtures можно вынести в отдельный файл conftest.py В директории теста или в родительской директории</div><div>  
</div></div>```bash
pytest --fixtures -v расположение файла
```

```python
#ch3/a/conftest.py
from pathlib import Path
from tempfile import TemporaryDirectory
import cards
import pytest

@pytest.fixture(scope="session")
def cards_db():
	"""CardsDB object connected to a temporary database"""
	with TemporaryDirectory() as db_dir:
		db_path = Path(db_dir)
		db = cards.CardsDB(db_path)
		yield db
		db.close()

#ch3/a/test_count.py
import cards

def test_empty(cards_db):
	assert cards_db.count() == 0
def test_two(cards_db):
	cards_db.add_card(cards.Card("first"))
	cards_db.add_card(cards.Card("second"))
	assert cards_db.count() == 2
```

<div id="bkmrk-%D0%92%D0%B7%D0%B0%D0%B8%D0%BC%D0%BE%D0%B7%D0%B0%D0%BF%D1%83%D1%81%D0%BA-fixture"><div>Взаимозапуск fixtures</div></div>```python
    @pytest.fixture(scope="session")
	def db():
		"""CardsDB object connected to a temporary database"""
		with TemporaryDirectory() as db_dir:
			db_path = Path(db_dir)
			db_ = cards.CardsDB(db_path)
			yield db_
			db_.close()
			
	@pytest.fixture(scope="function")
	def cards_db(db):
		"""CardsDB object that's empty"""
		db.delete_all()
		return db
```

<div id="bkmrk-%D0%9C%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C"><div>Множественное использование fixtures</div><div></div></div>```python
#ch3/c/conftest.py
@pytest.fixture(scope="function")
def non_empty_db(cards_db, some_cards):
...
```

<div id="bkmrk--1"></div>

# Asyncio

# Сопрограммы

<span style="color: rgb(224, 62, 45);">ЭТО ТЕХНОЛОГИЯ УСКОРЕНИЯ РАБОТЫ В ОДНОМ ПОТОКЕ. ДЛЯ ПАРАЛЛЕЛЬНЫХ ВЫЧИСЛЕНИЙ THREADING,</span>

Сопрограммы - функции с возможностью приостановки при длительной внешней операции. Async определяет сопрограмму, await приостанавливает сопрограмму на время выполнения внешней задачи. При вызове сопрограммы она напрямую не выполняется. Для старта нужна точка входа в асинхронные вычисления.

Пример: правильное использование.

```python
import asyncio

async def corutine_add_one(a: int) -> int:
    return b + 1

corutine_res = asyncio.run(corutine_add_one(5))
print(type(corutine_res), '  ', corutine_res)
```

Однако если просто запустить корутину, то результат будет интересным:

```python
import asyncio

async def corutine_add_one(a: int) -> int:
    return b + 1

corutine_res = corutine_add_one(5)
print(type(corutine_res), '  ', corutine_res)
```

Вывод:

```
<class 'coroutine'>    <coroutine object corutine_add_one at 0x0000026850FD4270>
```

Существует цикл событий. При постановке корутины на паузу задача передается следующей корутине. Однако такой код выполняется в виде простого синхронного кода:

```python
import asyncio

async def sleep_10(x: int) -> int:
    await asyncio.sleep(10)
    return x+10

async def main():
    x1 = await sleep_10(1)
    x2 = await sleep_10(1)
    print(x1, '  ', x2)

asyncio.run(main())
```

**Задачи**

Для приближения к параллельным вычислениям необходимо использовать задачи.

```python
import asyncio
from util import delay

async def main():
    sleep1 = asyncio.create_task(delay(10))
    sleep2 = asyncio.create_task(delay(10))
    await sleep1
    await sleep2

asyncio.run(main())
```

К тому же, если убрать await sleep2, то вторая задача все равно выполнится, т.к. вроде все задачи из очереди запускаются. Это так, вот только не говорится о том, завершаются они или нет. Вот пример кода:

```python
async def delay(delay_seconds: int) -> int:
    print(f'Засыпаю на {delay_seconds} секунд.')
    await asyncio.sleep(delay_seconds)
    print(f'сон в течение {delay_seconds} закончился.')
    return delay_seconds

async def delay_with_inner(delay_seconds: int) -> int:
    print(f'Засыпаю на {delay_seconds} секунд.')
    t1 = asyncio.create_task(delay(15))
    await asyncio.sleep(delay_seconds)
    #await t1 
    print(f'сон в течение {delay_seconds} закончился.')
    return delay_seconds

async def main():
    sleep1 = asyncio.create_task(delay_with_inner(10))
    sleep2 = asyncio.create_task(delay_with_inner(10))
    sleep3 = asyncio.create_task(delay_with_inner(10))
    await sleep1
    await sleep2
    await sleep3

asyncio.run(main())
```

Если внешнюю мы запускаем на 10 секунд, то внутренняя (t1) тоже запускается - т.к. находится в очереди задач. Но вот только программа завершается до завершения t1.

```
Засыпаю на 10 секунд.
Засыпаю на 10 секунд.
Засыпаю на 10 секунд.
Засыпаю на 15 секунд.
Засыпаю на 15 секунд.
Засыпаю на 15 секунд.
сон в течение 10 закончился.
сон в течение 10 закончился.
сон в течение 10 закончился.
```

И все! А если раскомментировать строку с ожиданием t1, то тогда сначала завершится t1

```
Засыпаю на 10 секунд.
Засыпаю на 10 секунд.
Засыпаю на 10 секунд.
Засыпаю на 15 секунд.
Засыпаю на 15 секунд.
Засыпаю на 15 секунд.
сон в течение 15 закончился.
сон в течение 15 закончился.
сон в течение 15 закончился.
сон в течение 10 закончился.
сон в течение 10 закончился.
сон в течение 10 закончился.
```

А для такого кода будет создано 3 файла. Но если вместо sleep3 установить sleep1, то все задачи стартуют, но будет создан только файл 5.txt.

```python
import asyncio
from util import delay_with_inner, delay, delay_and_writefile
from time import sleep

async def main():
    sleep1 = asyncio.create_task(delay_and_writefile(5))
    sleep2 = asyncio.create_task(delay_and_writefile(10))
    sleep3 = asyncio.create_task(delay_and_writefile(11))
    await sleep3

asyncio.run(main())
```

Исходя из этого, важна не последовательность помещения в задачи, а последовательность при обращении (момент await).

**Снятие задач и тайм-ауты**

```python
import asyncio
from asyncio import CancelledError
from util import delay
async def main():
    long_task = asyncio.create_task(delay(10))
    seconds_elapsed = 0
    while not long_task.done():
        print('Задача не закончилась, следующая проверка через секунду.')
        await asyncio.sleep(1)
        seconds_elapsed = seconds_elapsed + 1
        if seconds_elapsed == 5:
            long_task.cancel()
    try:
        await long_task
    except CancelledError:
        print('Наша задача была снята')
asyncio.run(main())

```

 Тайм-аут:

```python
import asyncio
from util import delay
async def main():
    delay_task = asyncio.create_task(delay(2))
    try:
        result = await asyncio.wait_for(delay_task, timeout=1)
        print(result)
    except asyncio.exceptions.TimeoutError:
        print('Тайм-аут!')
        print(f'Задача была снята? {delay_task.cancelled()}')
asyncio.run(main())

```

При помощи shield можно игнорировать запросы на снятие:

```python
import asyncio
from util import delay
async def main():
    task = asyncio.create_task(delay(10))
    try:
    result = await asyncio.wait_for(asyncio.shield(task), 5)
        print(result)
    except TimeoutError:
        print("Задача заняла более 5 с, скоро она закончится!")
        result = await task
        print(result)
asyncio.run(main())

```

Список задач:

```
for task in asyncio.tasks.all_tasks():
    print(task)
```

**Множественные task в цикле.**

Проблема: при создании в цикле при возникновении исключения в одной задаче, остальные задачи могут не завершить выполнение.

**Функция gather**

```python
import asyncio
import aiohttp
from aiohttp import ClientSession
from chapter_04 import fetch_status

async def main():
    async with aiohttp.ClientSession() as session:
        urls = ['https://example.com' for _ in range(1000)]
        requests = [fetch_status(session, url) for url in urls]
        status_codes = await asyncio.gather(*requests)
        print(status_codes)
asyncio.run(main())

```

Gather возвращает статусы в порядке передаче объектов, независимо от времени исполнения. Возвращает все данные одновременно.

Обработка исключений - необязательный bool параметр return\_exceptions.

- return\_exceptions=False по умолчанию. Если хотя бы одна сопрограмма возбуждает исключение, то gather возбуждает то же исключение в точке await. Но, даже если какая-то сопрограмма откажет, остальные не снимаются и продолжат работать при условии, что мы обработаем исключение и оно не приведет к остановке цикла событий и снятию задач;
- return\_exceptions=True – в этом случае исключения возвращаются в том же списке, что результаты. Сам по себе вызов gather не возбуждает исключений, и мы можем обработать исключения, как нам удобно.

```python
async def main():
    async with aiohttp.ClientSession() as session:
        urls = ['https://example.com', 'python://example.com']
        tasks = [fetch_status_code(session, url) for url in urls]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        exceptions = [res for res in results if isinstance(res, Exception)]
        successful_results = [res for res in results if not isinstance(res, Exception)]
        print(f'Все результаты: {results}')
        print(f'Завершились успешно: {successful_results}')
        print(f'Завершились с исключением: {exceptions}')

```

**Функция as\_completed**

Начинает возвращать данные по мере поступления. Однако порядок не определен.

```python
import asyncio
import aiohttp
from aiohttp import ClientSession
from chapter_04 import fetch_status

async def main():
    async with aiohttp.ClientSession() as session:
        fetchers = [fetch_status(session, 'https://www.example.com', 1),
                    fetch_status(session, 'https://www.example.com', 1),
                    fetch_status(session, 'https://www.example.com', 10)]
        for finished_task in asyncio.as_completed(fetchers):
            print(await finished_task)
asyncio.run(main())

```

asyncio.as\_completed(fetchers, timeout=2) задает тайм-аут. <span style="background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);">Но созданные задачи продолжают работать в фоновом режиме. </span>

**<span style="background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);">Точный контроль над функциями</span>**

Используется wait:

```python
async def main():
    async with aiohttp.ClientSession() as session:
    fetchers = \
        [asyncio.create_task(fetch_status(session, 'https://example.com')),
        asyncio.create_task(fetch_status(session, 'https://example.com'))]
    done, pending = await asyncio.wait(fetchers)
    print(f'Число завершившихся задач: {len(done)}')
    print(f'Число ожидающих задач: {len(pending)}')
    for done_task in done:
        result = await done_task
        print(result)
```

Сигнатура wait – список допускающих ожидание объектов, за которым следует факультативный тайм-аут и факультативный   
параметр return\_when, который может принимать значения ALL\_COMPLETED, FIRST\_EXCEPTION и FIRST\_COMPLETED, а по умолчанию равен ALL\_COMPLETED.

wait не возбуждает исключений, исключения содержатся в методе done\_task.exception()

Пример обработки исключений

```python
import asyncio
import logging

async def main():
    async with aiohttp.ClientSession() as session:
        good_request = fetch_status(session, 'https://www.example.com')
        bad_request = fetch_status(session, 'python://bad')
        fetchers = [asyncio.create_task(good_request),
                    asyncio.create_task(bad_request)]
        done, pending = await asyncio.wait(fetchers)
        print(f'Число завершившихся задач: {len(done)}')
        print(f'Число ожидающих задач: {len(pending)}')
        for done_task in done:
            # result = await done_task возбудит исключение
            if done_task.exception() is None:
                print(done_task.result())
            else:
                logging.error("При выполнении запроса возникло исключение",
                              exc_info=done_task.exception())
asyncio.run(main())

```

# Типы future и awaitable

Практически редко применяются, но нужны для понимания

Будущие объекты можно использовать в выражениях await. Это означает «я посплю, пока в будущем объекте не будет установлено значение, с которым я могу работать, а когда оно появится, разбуди меня и дай возможность его обработать».

```python
from asyncio import Future
my_future = Future()
print(f'my_future готов? {my_future.done()}')
my_future.set_result(42)
print(f'my_future готов? {my_future.done()}')
print(f'Какой результат хранится в my_future? {my_future.result()}')

```

Вариант для использования не до конца определенной переменной:

```python
from asyncio import Future
import asyncio
def make_request() -> Future:
    future = Future()
    asyncio.create_task(set_future_value(future))
    return future
async def set_future_value(future) -> None:
    await asyncio.sleep(1)
    future.set_result(42)
async def main():
    future = make_request()
    print(f'Будущий объект готов? {future.done()}')
    value = await future
    print(f'Будущий объект готов? {future.done()}')
    print(value)
asyncio.run(main())

```

Любой объект, который реализует метод \_\_await\_\_, можно использовать в выражении await. Это объекты, допускающие ожидание (Awaitaible).

# Асинхронный контекстный менеджер

**Асинхронный контекстный менеджер.**

Это класс, реализующий два специальных метода-сопрограммы: \_\_aenter\_\_, который асинхронно захватывает ресурс, и \_\_aexit\_\_, который закрывает ресурс. Сопрограмма \_\_aexit\_\_ принимает несколько аргументов, относящихся к обработке исключений. Пример для сокетов:

```python
import asyncio
import socket
from types import TracebackType
from typing import Optional, Type
class ConnectedSocket:
    def __init__(self, server_socket):
        self._connection = None
        self._server_socket = server_socket
    async def __aenter__(self):
        print('Вход в контекстный менеджер, ожидание подключения')
        loop = asyncio.get_event_loop()
        connection, address = await loop.sock_accept(self._server_socket)
        self._connection = connection
        print('Подключение подтверждено')
        return self._connection
    async def __aexit__(self,
                        exc_type: Optional[Type[BaseException]],
                        exc_val: Optional[BaseException],
                        exc_tb: Optional[TracebackType]):
        print('Выход из контекстного менеджера')
        self._connection.close()
        print('Подключение закрыто')
async def main():
    loop = asyncio.get_event_loop()
    server_socket = socket.socket()
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_address = ('127.0.0.1', 8000)
    server_socket.setblocking(False)
    server_socket.bind(server_address)
    server_socket.listen()
    async with ConnectedSocket(server_socket) as connection:
        data = await loop.sock_recv(connection, 1024)
        print(data)
asyncio.run(main())


```

# Aiohttp

Сеансовый асинхронный http(s) клиент с автоматической поддержкой cookies. Пул подключений использует один сеанс.

**Установка:**

```
pip install aiohttp
```

Использование:

```python
import asyncio
import aiohttp
from aiohttp import ClientSession
from util import async_timed

async def fetch_status(session: ClientSession, url: str) -> int:
    async with session.get(url) as result:
        return result.status

async def main():
    async with aiohttp.ClientSession() as session:
        url = 'https://www.example.com'
        status = await fetch_status(session, url)
        print(f'Состояние для {url} было равно {status}')
asyncio.run(main())

```

По умолчанию в сеансе не более 100 подключений. Для увеличения можно создать экземпляр класса TCPConnector, входящего в состав aiohttp, указав максимальное число подключений, и передать его конструктору ClientSession. Подробнее в документации aiohttp.

**Тайм-аут:**

По умолчанию тайм-аут запроса 5 минут. Можно устанавливать на уровне сеанса или запроса.

```python
import asyncio
import aiohttp
from aiohttp import ClientSession
async def fetch_status(session: ClientSession, url: str) -> int:
    ten_millis = aiohttp.ClientTimeout(total=.01)
    async with session.get(url, timeout=ten_millis) as result:
        return result.status

async def main():
    session_timeout = aiohttp.ClientTimeout(total=1, connect=.1)
    async with aiohttp.ClientSession(timeout=session_timeout) as session:
        await fetch_status(session, 'https://example.com')
asyncio.run(main())

```

В этом случае полный тайм-аут 1 секунда, для установки соединения - 100мс. В функции fetch\_status переопределяется в 10мс.

**Множественные запросы**

```python
import asyncio
import aiohttp
from aiohttp import ClientSession
from chapter_04 import fetch_status

async def main():
    async with aiohttp.ClientSession() as session:
        urls = ['https://example.com' for _ in range(1000)]
        requests = [fetch_status(session, url) for url in urls]
        status_codes = await asyncio.gather(*requests)
        print(status_codes)
asyncio.run(main())
```

**Обработка ошибок**

```python
async def main():
    async with aiohttp.ClientSession() as session:
        urls = ['https://example.com', 'python://example.com']
        tasks = [fetch_status_code(session, url) for url in urls]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        exceptions = [res for res in results if isinstance(res, Exception)]
        successful_results = [res for res in results if not isinstance(res, Exception)]
        print(f'Все результаты: {results}')
        print(f'Завершились успешно: {successful_results}')
        print(f'Завершились с исключением: {exceptions}')
```

# GUI

# Tkinter

Встроенный модуль.

**Импорт:**

```python
import tkinter
from tkinter import *
import tkinter as tk
```

Современный подход сразу использовать ttk

```python
import tkinter as tk
from tkinter import ttk

root = tk.Tk()
# Используйте ttk.Button, ttk.Entry, ttk.Combobox и т.д.
button = ttk.Button(root, text="Современная кнопка")
button.pack()
```

**Иерархия виджетов**

[![изображение.png](http://bobrobotirk.ru/uploads/images/gallery/2026-01/scaled-1680-/izobrazenie.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-01/izobrazenie.png)

**Общая последовательность действий:**

- Создать главное окно.
- Создать виджеты и выполнить конфигурацию их свойств (опций).
- Определить события, то есть то, на что будет реагировать программа.
- Описать обработчики событий, то есть то, как будет реагировать программа.
- Расположить виджеты в главном окне.
- Запустить цикл обработки событий.

Создается главное окно от класса Tk модуля tkinter.

```python
root = Tk()
```

Пример окна:

```python
from tkinter import *

root = Tk()

ent = Entry(root,width=20)                          #поле ввода
but = Button(root, text="Преобразовать")            #кнопка
lab = Label(root, width=20, bg='black', fg='white') #метка

def str_to_sort_list(event):
    s = ent.get()
    lab['text'] = s

but.bind('<Button-1>', str_to_sort_list)           #событие левой кнопки мыши
ent.pack()                                         #размещение элементов при помощи менеджера геометрии
but.pack()
lab.pack()
root.mainloop()                                    #основной цикл

```

Объектно-ориентированный подход

```python
from tkinter import *

class Block:
    def __init__(self, master, func):
        self.ent = Entry(master, width=20)
        self.but = Button(master, text="Преобразовать")
        self.lab = Label(master, width=20, bg='black', fg='white')
        self.but['command'] = self.str_to_sort
        #self.but['command'] = getattr(self, func) #вариант с передачей имени функции
        self.ent.pack()
        self.but.pack()
        self.lab.pack()
    def str_to_sort(self):
        s = self.ent.get()
        self.lab['text'] = s

    def str_reverse(self):
        s = self.ent.get()
        self.lab['text'] = s + '1'

root = Tk()
first_block = Block(root)
#first_block = Block(root, 'str_to_sort')
#second_block = Block(root, 'str_reverse')
root.mainloop()
```

События в этом случае определяются по-другому (через свойство).

Есть стилизация через ttk, типа удобнее, узнать.

Если потом работать с элементами не нужно, то без присвоения переменной

```
Label(text="Пункт выдачи").pack()
```

**Менеджеры геометрии**

Три менеджера геометрии – упаковщик (pack), сетка (grid) и размещение по координатам (place). В одном окне (или родительском виджете) нельзя комбинировать разные менеджеры.

**Pack**

Объекты применяются последовательно, относительно заданной точки в противоположную сторону. Сторона задается параметром side. Окно создается максимальным размером элементов.

[![image.png](http://bobrobotirk.ru/uploads/images/gallery/2025-06/scaled-1680-/image.png)](http://bobrobotirk.ru/uploads/images/gallery/2025-06/image.png)

По умолчанию side = TOP.

Внутренние (ipadx, ipady) отступы и внешние (padx и pady) отступы.

Изменение размеров окна.

expand (расширение, по умолчанию 0) - равномерное размещение по вертикали.

fill (заполнение) - какое направление экрана заполняем. Может быть NONE, BOTH, X, Y. Без expand не работает.

```
l1.pack(expand=1, fill=Y)
```

anchor (якорь) – может принимать значения N (north – север), S (south – юг), W (west – запад), E (east – восток) и их комбинации.

Вложения блоков

Для вложения используются классы Frame и LabelFrame (с подписью)

```python
f_top = LabelFrame(text="Верх")
l1 = Label(f_top, width=7, height=4, bg='yellow', text="1")
```

Т е размещаем Frame, затем размещаем элементы.

**Виджеты**

**Button** кнопка

<table border="1" id="bkmrk-%D0%A1%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%BE-%D0%9D%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-" style="border-collapse: collapse; width: 100%; height: 182.578px;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">Свойство</td><td class="align-center" style="height: 29.7969px;">Назначение</td><td class="align-center" style="height: 29.7969px;">Пример</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">text</td><td style="height: 29.7969px;">Надпись на кнопке</td><td style="height: 29.7969px;">b1\['text'\] = "Изменено"</td></tr><tr style="height: 63.3906px;"><td style="height: 63.3906px;">command</td><td style="height: 63.3906px;">Настройка действия</td><td style="height: 63.3906px;">b1.config(command=change)

self.but\['command'\] = self.str\_to\_sort

(change - настроенная ранее функция)

</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">width и height</td><td style="height: 29.7969px;">Ширина и высота.</td><td style="height: 29.7969px;">По умолчанию ширина и высота текста</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">bg , fg</td><td style="height: 29.7969px;">Цвет фона и текста</td><td style="height: 29.7969px;">b1\['bg'\] = '#000000'</td></tr><tr><td>activebackground, activeforeground</td><td>Цвет фона и текста во время нажатия</td><td>  
</td></tr><tr><td>font</td><td>Шрифт</td><td>b1\["font"\] = ("Comic Sans MS", 24, "bold")</td></tr></tbody></table>

**Label** метка. Похожа на кнопку. Нет опции command, связь с событием с помощью bind.

<table border="1" id="bkmrk-%D0%A1%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%BE-%D0%9D%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5--1" style="border-collapse: collapse; width: 100%; height: 182.578px;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">Свойство</td><td class="align-center" style="height: 29.7969px;">Назначение</td><td class="align-center" style="height: 29.7969px;">Пример</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">bd</td><td style="height: 29.7969px;">Ширина границы вокруг метки</td><td style="height: 29.7969px;">  
</td></tr></tbody></table>

**Entry, text** однострочное и многострочное поле ввода

<table border="1" id="bkmrk-%D0%A1%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%BE-%2F-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4-%D0%9D%D0%B0%D0%B7" style="border-collapse: collapse; width: 100%; height: 182.578px;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">Свойство / метод</td><td class="align-center" style="height: 29.7969px;">Назначение</td><td class="align-center" style="height: 29.7969px;">Пример</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">get</td><td style="height: 29.7969px;">Получить текст</td><td style="height: 29.7969px;">s = ent.get()</td></tr><tr><td>insert(position, text)</td><td>Вставить текст. Позиция: 0, END</td><td>e1.insert(0, t.strftime('%H:%M:%S '))

</td></tr><tr><td>delete</td><td>Удалить текст</td><td>  
</td></tr><tr><td>justify</td><td>Выравнивание строки

CENTER - по центру

</td><td>  
</td></tr></tbody></table>

Text:

<table border="1" id="bkmrk-%D0%A1%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%BE-%2F-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4-%D0%9D%D0%B0%D0%B7-1" style="border-collapse: collapse; width: 100%; height: 182.578px;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">Свойство / метод</td><td class="align-center" style="height: 29.7969px;">Назначение</td><td class="align-center" style="height: 29.7969px;">Пример</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">wrap</td><td style="height: 29.7969px;">Правило переноса.

WORD - по словам.

</td><td style="height: 29.7969px;">text = Text(width=25, height=5, bg="darkgreen", fg='white', wrap=WORD)</td></tr><tr><td>  
</td><td>Скролл для текста (и не только для текста) сначала нужно создать

</td><td>text = Text(width=20, height=7)  
text.pack(side=LEFT)  
scroll = Scrollbar(command=text.yview)  
scroll.pack(side=LEFT, fill=Y)  
text.config(yscrollcommand=scroll.set)</td></tr><tr><td>insert</td><td>номер строки и номер столбца Нумерация строк с единицы, а столбцов – с нуля

</td><td>text.insert(1.0, s)</td></tr><tr><td>  
</td><td>Разное форматирование в текстовом поле.

</td><td>text.tag\_add('title', 1.0, '1.end')  
text.tag\_config('title', justify=CENTER,  
 font=("Verdana", 24, 'bold'))</td></tr></tbody></table>

**Radiobutton** радиокнопки, **Checkbutton** флажки

**Listbox** списки

<table border="1" id="bkmrk-%D0%A1%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%BE-%2F-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4-%D0%9D%D0%B0%D0%B7-2" style="border-collapse: collapse; width: 100%; height: 182.578px;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">Свойство / метод</td><td class="align-center" style="height: 29.7969px;">Назначение</td><td class="align-center" style="height: 29.7969px;">Пример</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">insert</td><td style="height: 29.7969px;">Добавить элемент Индекс (0-начало, END-конец)

</td><td style="height: 29.7969px;">for i in \["1", "2"\]:

 lbox.insert(0,i)

</td></tr></tbody></table>

**Окно выбора файла**

- **askopenfilename**. Открывает диалоговое окно для выбора файла и возвращает путь к выбранному файлу. Если файл не выбран, возвращается пустая строка.
- **askopenfilenames**. Открывает диалоговое окно для выбора файлов и возвращает список путей к выбранным файлам.
- **askdirectory**. Открывает диалоговое окно для выбора каталога (нельзя выбрать несколько каталогов).
- **asksaveasfilename**. Открывает диалоговое окно для сохранения файла, вместо открытия его.

```
from tkinter import filedialog  
filename = filedialog.askopenfilename()  # Открываем диалоговое окно для выбора файла  
print(filename)
```

**PythonMegaWidgets**

Сборник виджетов.

# GUI QT6

# QT6 + оглавление

[Платная лицензия](https://www.riverbankcomputing.com/commercial/buy) Похоже надо углубиться в лицензирование opensource.

Страницы компонентов

<table border="1" id="bkmrk-label-push-button-ra" style="border-collapse: collapse; width: 100%; height: 106.172px;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><tbody><tr style="height: 35.3906px;"><td class="align-center" style="height: 35.3906px;">[Label](http://bobrobotirk.ru/books/python/page/qt6-qlabel-lcd "QT6 QLabel")</td><td class="align-center" style="height: 35.3906px;">[Push button](http://bobrobotirk.ru/books/python/page/qt6-buttons)</td><td class="align-center" style="height: 35.3906px;">[Radio button](http://bobrobotirk.ru/books/python/page/qt6-buttons)</td></tr><tr style="height: 35.3906px;"><td class="align-center" style="height: 35.3906px;">[Line edit](http://bobrobotirk.ru/books/python/page/qt6-qlineedit)</td><td class="align-center" style="height: 35.3906px;">[Check box](http://bobrobotirk.ru/books/python/page/qt6-checkbox-spinbox-combobox)</td><td class="align-center" style="height: 35.3906px;">[SpinBox](http://bobrobotirk.ru/books/python/page/qt6-checkbox-spinbox-combobox)</td></tr><tr style="height: 35.3906px;"><td class="align-center" style="height: 35.3906px;">[QLCD](http://bobrobotirk.ru/books/python/page/qt6-qlabel-lcd)</td><td class="align-center" style="height: 35.3906px;">[ComboBox](http://bobrobotirk.ru/books/python/page/qt6-checkbox-spinbox-combobox)</td><td class="align-center" style="height: 35.3906px;">[Slider](http://bobrobotirk.ru/books/python/page/qt6-qslider-qlistwidget)</td></tr><tr><td class="align-center">[ListWidget](http://bobrobotirk.ru/books/python/page/qt6-qslider-qlistwidget)</td><td class="align-center">[Table](http://bobrobotirk.ru/books/python/page/qt6-qtable-qmessagebox-dialogs)</td><td class="align-center">Calendar</td></tr><tr><td class="align-center">ColorDialog</td><td class="align-center">FontDialog</td><td class="align-center">[MessageBox](http://bobrobotirk.ru/books/python/page/qt6-qtable-qmessagebox-dialogs)</td></tr><tr><td class="align-center">[Dialogs (save)](http://bobrobotirk.ru/books/python/page/qt6-qtable-qmessagebox-dialogs)</td><td>  
</td><td>  
</td></tr></tbody></table>

Установка:

```bash
pip install pyqt6
pip install pyqt6-tools
```

Минимальное приложение:

```python
from PyQt6.QtWidgets import QApplication, QWidget
import sys

app = QApplication(sys.argv)
window = QWidget()
window.show()
sys.exit(app.exec())
```

Архитектура QT прячется под стандартную, но это не так. QT основывается на цикле событий, внутри реализованы используемые системные процедуры (таймер, ...), и приходится использовать соответствующие QT-модули, а не системные модули. Поэтому в составе QT много модулей.

<table border="1" id="bkmrk-%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C-%D0%9D%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-qt" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 16.341%;"></col><col style="width: 83.659%;"></col></colgroup><thead><tr><td class="align-center">**Модуль**</td><td class="align-center">**Назначение**</td></tr></thead><tbody><tr><td>QtWidgets</td><td>Основной и шаблонные виджеты (окно, метка, ...)

</td></tr><tr><td>QtGui</td><td>Классы для интеграции с оконной системой, обработки событий, 2D-графики, базовых изображений, шрифтов, иконок и текста.

QIcon класс работы с иконками

</td></tr><tr><td>QtCore</td><td>Системные модули.

</td></tr></tbody></table>

**Типы окон**

**QMainWindow** Главное окно приложения и связанные с ним классы для управления главным окном.

```python
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys

app = QApplication(sys.argv)
window = QMainWindow()
window.statusBar().showMessage("Welcome to pyqt6 coding")
window.show()
sys.exit(app.exec())
```

QMainWindow имеет свой собственный макет, содержащий QToolBars, QDockWidgets, QMenuBar и QStatusBar.

**QDialog** Базовый класс окна верхнего уровня, используемое для краткосрочных задач и краткого общения с пользователем.   
QDialogs может быть модальным или немодальным.

**QWidget** Базовый класс всех объектов пользовательского интерфейса, получает мышь, клавиатуру и другие события  
из оконной системы и отображает свое изображение на экране.

**Объектно-ориентированный подход настройки окна**

Создаем класс-потомок например от QWidget или QMainWindow, настраиваем свойства и

```python
from PyQt6.QtWidgets import QApplication, QWidget 
from PyQt6.QtGui import QIcon 
import sys 
 
class Window(QWidget): 
    def __init__(self): 
        super().__init__() 
        self.setGeometry(200,200, 700, 400) 
        self.setWindowTitle("Python GUI Development") 
        self.setWindowIcon(QIcon('pyqt6lessons\images\python.png')) 
        self.setStyleSheet('background-color:green') 
        self.setWindowOpacity(0.5) 
 
app = QApplication(sys.argv) 
window = Window() 
window.show() 
sys.exit(app.exec()) 
```

**Управление событиями**

Основной элемент всех приложений в Qt — класс QApplication. Каждому приложению нужен только один объект QApplication, который содержит цикл событий приложения. Это основной цикл, управляющий всем взаимодействием пользователя с графическим интерфейсом.

[![изображение.png](http://bobrobotirk.ru/uploads/images/gallery/2026-01/scaled-1680-/kJEizobrazenie.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-01/kJEizobrazenie.png)

При каждом взаимодействии с приложением генерируется событие, которое помещается в очередь событий. В цикле событий очередь проверяется на каждой итерации: если найдено ожидающее событие, оно вместе с управлением передаётся определённому обработчику этого события. Последний обрабатывает его, затем возвращает управление в цикл событий и ждёт новых событий. Для каждого приложения выполняется только один цикл событий.

Класс QApplication содержит цикл событий Qt (нужен один экземпляр QApplication). Приложение ждёт в цикле событий новое событие, которое будет сгенерировано при выполнении действия. Всегда выполняется только один цикл событий.

Сигналы — уведомления, отправляемые виджетами, когда что-то происходит. Это может быть нажатие кнопки, изменение текста в поле ввода, изменение текста в окне, ... Многие сигналы инициируются в ответ на действия пользователя, но не только: в сигналах могут отправляться данные с дополнительным контекстом произошедшего.

Слоты — приёмники сигналов. Слотом можно сделать любую функцию (или метод), просто подключив к нему сигнал. Принимающая функция получает данные, отправляемые ей в сигнале. У многих виджетов Qt есть встроенные слоты, эти виджеты можно подключать друг к другу напрямую.

```python
class Window(QWidget): 
    def __init__(self): 
        super().__init__() 
        self.setGeometry(200,200, 700, 400) 
        self.setWindowTitle("Python GUI Development") 
        self.create_button()

    def create_button(self):
        btn = QPushButton("Click", self)
        btn.clicked.connect(self.the_button_was_clicked)

    def the_button_was_clicked(self):
        print("Clicked")
```

Соединение сигнала и слота происходит в функции btn.clicked.connect(self.the\_button\_was\_clicked) Таблицы событий:

Мышь:

<table id="bkmrk-%D0%A2%D0%B8%D0%BF-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-mousebu" style="width: 97.2619%;"><thead><tr><th style="width: 47.9042%;">Тип</th><th style="width: 52.0958%;">Описание</th></tr></thead><tbody><tr><td style="width: 47.9042%;">`MouseButtonPress`</td><td style="width: 52.0958%;">Нажата кнопка мыши</td></tr><tr><td style="width: 47.9042%;">`MouseButtonRelease`</td><td style="width: 52.0958%;">Отпущена кнопка мыши</td></tr><tr><td style="width: 47.9042%;">`MouseButtonDblClick`</td><td style="width: 52.0958%;">Двойной клик</td></tr><tr><td style="width: 47.9042%;">`MouseMove`</td><td style="width: 52.0958%;">Движение мыши</td></tr><tr><td style="width: 47.9042%;">`Wheel`</td><td style="width: 52.0958%;">Колёсико мыши</td></tr><tr><td style="width: 47.9042%;">`Enter`</td><td style="width: 52.0958%;">Курсор вошёл в виджет</td></tr><tr><td style="width: 47.9042%;">`Leave`</td><td style="width: 52.0958%;">Курсор покинул виджет</td></tr><tr><td style="width: 47.9042%;">`HoverEnter`</td><td style="width: 52.0958%;">Hover вошёл</td></tr><tr><td style="width: 47.9042%;">`HoverMove`</td><td style="width: 52.0958%;">Hover движение</td></tr><tr><td style="width: 47.9042%;">`HoverLeave`</td><td style="width: 52.0958%;">Hover вышел</td></tr></tbody></table>

Клавиатура:

<table id="bkmrk-%D0%A2%D0%B8%D0%BF-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-keypres" style="width: 97.7381%;"><thead><tr><th style="width: 41.4925%;">Тип</th><th style="width: 58.806%;">Описание</th></tr></thead><tbody><tr><td style="width: 41.4925%;">`KeyPress`</td><td style="width: 58.806%;">Нажата клавиша</td></tr><tr><td style="width: 41.4925%;">`KeyRelease`</td><td style="width: 58.806%;">Отпущена клавиша</td></tr><tr><td style="width: 41.4925%;">`Shortcut`</td><td style="width: 58.806%;">Сработал shortcut</td></tr><tr><td style="width: 41.4925%;">`ShortcutOverride`</td><td style="width: 58.806%;">Попытка перехвата shortcut</td></tr><tr><td style="width: 41.4925%;">`InputMethod`</td><td style="width: 58.806%;">IME ввод</td></tr><tr><td style="width: 41.4925%;">`InputMethodQuery`</td><td style="width: 58.806%;">Запрос IME</td></tr></tbody></table>

Фокус и активация

<table id="bkmrk-%D0%A2%D0%B8%D0%BF-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-focusin" style="width: 97.8571%;"><thead><tr><th style="width: 40.8284%;">Тип</th><th style="width: 59.1716%;">Описание</th></tr></thead><tbody><tr><td style="width: 40.8284%;">`FocusIn`</td><td style="width: 59.1716%;">Получен фокус</td></tr><tr><td style="width: 40.8284%;">`FocusOut`</td><td style="width: 59.1716%;">Потерян фокус</td></tr><tr><td style="width: 40.8284%;">`ActivationChange`</td><td style="width: 59.1716%;">Изменение активности окна</td></tr></tbody></table>

Окна и виджеты

<table id="bkmrk-%D0%A2%D0%B8%D0%BF-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-show-%D0%92%D0%B8" style="width: 98.2143%;"><thead><tr><th style="width: 45.3258%;">Тип</th><th style="width: 54.6742%;">Описание</th></tr></thead><tbody><tr><td style="width: 45.3258%;">`Show`</td><td style="width: 54.6742%;">Виджет показан</td></tr><tr><td style="width: 45.3258%;">`Hide`</td><td style="width: 54.6742%;">Виджет скрыт</td></tr><tr><td style="width: 45.3258%;">`Close`</td><td style="width: 54.6742%;">Закрытие</td></tr><tr><td style="width: 45.3258%;">`Resize`</td><td style="width: 54.6742%;">Изменение размера</td></tr><tr><td style="width: 45.3258%;">`Move`</td><td style="width: 54.6742%;">Перемещение</td></tr><tr><td style="width: 45.3258%;">`Paint`</td><td style="width: 54.6742%;">Перерисовка</td></tr><tr><td style="width: 45.3258%;">`LayoutRequest`</td><td style="width: 54.6742%;">Запрос layout</td></tr><tr><td style="width: 45.3258%;">`UpdateRequest`</td><td style="width: 54.6742%;">Запрос обновления</td></tr><tr><td style="width: 45.3258%;">`Polish`</td><td style="width: 54.6742%;">Финальная инициализация</td></tr><tr><td style="width: 45.3258%;">`PolishRequest`</td><td style="width: 54.6742%;">Запрос polish</td></tr><tr><td style="width: 45.3258%;">`ParentChange`</td><td style="width: 54.6742%;">Изменился родитель</td></tr><tr><td style="width: 45.3258%;">`ParentAboutToChange`</td><td style="width: 54.6742%;">Родитель изменится</td></tr><tr><td style="width: 45.3258%;">`WindowStateChange`</td><td style="width: 54.6742%;">Изменение состояния окна</td></tr><tr><td style="width: 45.3258%;">`WindowActivate`</td><td style="width: 54.6742%;">Окно активировано</td></tr><tr><td style="width: 45.3258%;">`WindowDeactivate`</td><td style="width: 54.6742%;">Окно деактивировано</td></tr><tr><td style="width: 45.3258%;">`WindowTitleChange`</td><td style="width: 54.6742%;">Заголовок окна</td></tr><tr><td style="width: 45.3258%;">`WindowIconChange`</td><td style="width: 54.6742%;">Иконка окна</td></tr><tr><td style="width: 45.3258%;">`WindowBlocked`</td><td style="width: 54.6742%;">Окно заблокировано</td></tr><tr><td style="width: 45.3258%;">`WindowUnblocked`</td><td style="width: 54.6742%;">Окно разблокировано</td></tr></tbody></table>

Геометрия и экран

<table id="bkmrk-%D0%A2%D0%B8%D0%BF-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-screenc" style="width: 98.2143%;"><thead><tr><th style="width: 53.3923%;">Тип</th><th style="width: 46.6077%;">Описание</th></tr></thead><tbody><tr><td style="width: 53.3923%;">`ScreenChangeInternal`</td><td style="width: 46.6077%;">Изменился экран</td></tr><tr><td style="width: 53.3923%;">`ScreenChangeInternal`</td><td style="width: 46.6077%;">DPI/Screen изменился</td></tr><tr><td style="width: 53.3923%;">`OrientationChange`</td><td style="width: 46.6077%;">Смена ориентации</td></tr><tr><td style="width: 53.3923%;">`DevicePixelRatioChange`</td><td style="width: 46.6077%;">Изменение DPR</td></tr></tbody></table>

Drag &amp; Drop

<table id="bkmrk-%D0%A2%D0%B8%D0%BF-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-dragent" style="width: 98.0952%;"><thead><tr><th style="width: 38.6957%;">Тип</th><th style="width: 61.3043%;">Описание</th></tr></thead><tbody><tr><td style="width: 38.6957%;">`DragEnter`</td><td style="width: 61.3043%;">Drag вошёл</td></tr><tr><td style="width: 38.6957%;">`DragMove`</td><td style="width: 61.3043%;">Drag перемещение</td></tr><tr><td style="width: 38.6957%;">`DragLeave`</td><td style="width: 61.3043%;">Drag покинул</td></tr><tr><td style="width: 38.6957%;">`Drop`</td><td style="width: 61.3043%;">Drop</td></tr></tbody></table>

Буфер обмена

<table id="bkmrk-%D0%A2%D0%B8%D0%BF-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-clipboa" style="width: 97.9762%;"><thead><tr><th style="width: 32.6007%;">Тип</th><th style="width: 67.3993%;">Описание</th></tr></thead><tbody><tr><td style="width: 32.6007%;">`Clipboard`</td><td style="width: 67.3993%;">Изменился буфер обмена</td></tr></tbody></table>

Таймеры

<table id="bkmrk-%D0%A2%D0%B8%D0%BF-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-timer-%D0%A1" style="width: 97.8571%;"><thead><tr><th style="width: 37.4622%;">Тип</th><th style="width: 62.5378%;">Описание</th></tr></thead><tbody><tr><td style="width: 37.4622%;">`Timer`</td><td style="width: 62.5378%;">Сработал таймер</td></tr><tr><td style="width: 37.4622%;">`ZeroTimerEvent`</td><td style="width: 62.5378%;">Таймер с нулевой задержкой</td></tr></tbody></table>

Touch / Tablet / Gesture

<table id="bkmrk-%D0%A2%D0%B8%D0%BF-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-touchbe" style="width: 97.8571%;"><thead><tr><th style="width: 49.0637%;">Тип</th><th style="width: 50.9363%;">Описание</th></tr></thead><tbody><tr><td style="width: 49.0637%;">`TouchBegin`</td><td style="width: 50.9363%;">Touch начало</td></tr><tr><td style="width: 49.0637%;">`TouchUpdate`</td><td style="width: 50.9363%;">Touch обновление</td></tr><tr><td style="width: 49.0637%;">`TouchEnd`</td><td style="width: 50.9363%;">Touch конец</td></tr><tr><td style="width: 49.0637%;">`TabletPress`</td><td style="width: 50.9363%;">Перо нажато</td></tr><tr><td style="width: 49.0637%;">`TabletMove`</td><td style="width: 50.9363%;">Перо движение</td></tr><tr><td style="width: 49.0637%;">`TabletRelease`</td><td style="width: 50.9363%;">Перо отпущено</td></tr><tr><td style="width: 49.0637%;">`Gesture`</td><td style="width: 50.9363%;">Жест</td></tr><tr><td style="width: 49.0637%;">`GestureOverride`</td><td style="width: 50.9363%;">Перехват жеста</td></tr></tbody></table>

Состояние

<table id="bkmrk-%D0%A2%D0%B8%D0%BF-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-enabled" style="width: 98.0952%;"><thead><tr><th style="width: 51.567%;">Тип</th><th style="width: 48.433%;">Описание</th></tr></thead><tbody><tr><td style="width: 51.567%;">`EnabledChange`</td><td style="width: 48.433%;">Изменение enabled</td></tr><tr><td style="width: 51.567%;">`FontChange`</td><td style="width: 48.433%;">Изменение шрифта</td></tr><tr><td style="width: 51.567%;">`StyleChange`</td><td style="width: 48.433%;">Изменение стиля</td></tr><tr><td style="width: 51.567%;">`PaletteChange`</td><td style="width: 48.433%;">Изменение палитры</td></tr><tr><td style="width: 51.567%;">`LanguageChange`</td><td style="width: 48.433%;">Смена языка</td></tr><tr><td style="width: 51.567%;">`LocaleChange`</td><td style="width: 48.433%;">Смена локали</td></tr><tr><td style="width: 51.567%;">`ThemeChange`</td><td style="width: 48.433%;">Смена темы</td></tr><tr><td style="width: 51.567%;">`ApplicationStateChange`</td><td style="width: 48.433%;">Состояние приложения</td></tr></tbody></table>

Продвинутые опции

<table id="bkmrk-%D0%A2%D0%B8%D0%BF-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-dynamic" style="width: 97.7381%;"><thead><tr><th style="width: 41.8269%;">Тип</th><th style="width: 58.1731%;">Описание</th></tr></thead><tbody><tr><td style="width: 41.8269%;">`DynamicPropertyChange`</td><td style="width: 58.1731%;">Изменение свойства</td></tr><tr><td style="width: 41.8269%;">`ChildAdded`</td><td style="width: 58.1731%;">Добавлен ребёнок</td></tr><tr><td style="width: 41.8269%;">`ChildRemoved`</td><td style="width: 58.1731%;">Удалён ребёнок</td></tr><tr><td style="width: 41.8269%;">`ChildPolished`</td><td style="width: 58.1731%;">Ребёнок отполирован</td></tr><tr><td style="width: 41.8269%;">`MetaCall`</td><td style="width: 58.1731%;">Вызов meta-object</td></tr><tr><td style="width: 41.8269%;">`ThreadChange`</td><td style="width: 58.1731%;">Смена потока</td></tr><tr><td style="width: 41.8269%;">`DeferredDelete`</td><td style="width: 58.1731%;">Отложенное удаление</td></tr><tr><td style="width: 41.8269%;">`Quit`</td><td style="width: 58.1731%;">Завершение приложения</td></tr><tr><td style="width: 41.8269%;">`PlatformSurface`</td><td style="width: 58.1731%;">Изменение поверхности</td></tr><tr><td style="width: 41.8269%;">`PlatformPanel`</td><td style="width: 58.1731%;">Platform panel</td></tr><tr><td style="width: 41.8269%;">`User`</td><td style="width: 58.1731%;">Начало пользовательских событий</td></tr></tbody></table>

Пользовательские события

<table id="bkmrk-%D0%A2%D0%B8%D0%BF-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-user-%D0%91%D0%B0" style="width: 97.1429%;"><thead><tr><th style="width: 24.5914%;">Тип</th><th style="width: 75.4137%;">Описание</th></tr></thead><tbody><tr><td style="width: 24.5914%;">`User`</td><td style="width: 75.4137%;">Базовый пользовательский event</td></tr><tr><td style="width: 24.5914%;">`MaxUser`</td><td style="width: 75.4137%;">Максимальный ID</td></tr></tbody></table>

```python
event = QEvent(QEvent.Type.User)
QCoreApplication.postEvent(obj, event)
```

Поиск событий:

```python
def event(self, event):
    print(event.type())
    return super().event(event)

#или
widget.installEventFilter(self)

def eventFilter(self, obj, event):
    print(obj, event.type())
    return False

```

# QT6Core

QTime класс управления / работы со временем.

```python
time = QTime.currentTime()
text = time.toString('hh:mm')
```

QTimer класс тайминга. Настраивается при инициализации класса.

```python
class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setGeometry(200,200, 700, 400)
        self.setWindowTitle("Python GUI Development")

        self.timer = QTimer()
        self.timer.timeout.connect(self.update_lcd)
        self.timer.start(1000)

        self.create_button()
```

# QT6 настройка окна

**Компоновщики (Layouts).**

Нужны для автоматического упорядочивания и изменения размеров виджетов при изменении размера окна. Без Layouts виджеты имеют фиксированные позиции и размеры. Импортируются все типы компоновщиков через

```python
from PyQt6.QtWidgets import QHBoxLayout
```

Типы Layouts в Qt Designer

<table border="1" id="bkmrk-vertical-layout-%28%D0%92%D0%B5%D1%80" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 28.2739%;"></col><col style="width: 71.8453%;"></col></colgroup><tbody><tr><td>Vertical Layout (Вертикальный компоновщик)</td><td>Располагает виджеты сверху вниз в столбик. ```python
layout = QVBoxLayout()
layout.addWidget(button1)
layout.addWidget(button2)
```

</td></tr><tr><td>Horizontal Layout (Горизонтальный компоновщик)</td><td>Располагает виджеты слева направо в строку. ```python
QHBoxLayout()
```

</td></tr><tr><td>Grid Layout (Сеточный компоновщик)</td><td>Располагает виджеты в таблице (строках и столбцах). ```python
layout = QGridLayout()
layout.addWidget(button1, 0, 0)  # строка 0, столбец 0
layout.addWidget(button2, 0, 1)  # строка 0, столбец 1
```

</td></tr><tr><td>Form Layout (Формовый компоновщик)</td><td>Идеален для форм (метка + поле ввода).  
Располагает виджеты в две колонки: labels слева, поля справа. ```
QFormLayout()
```

</td></tr></tbody></table>

```python
class Window(QWidget): 
    def __init__(self): 
        super().__init__() 
        self.setGeometry(200,200, 700, 400) 
        self.mainlayout = QVBoxLayout()

        bt1 = QPushButton("one")
        bt2 = QPushButton("two")
        bt3 = QPushButton("three")
        bt4 = QPushButton("four")

        self.mainlayout.addWidget(bt1)
        self.mainlayout.addWidget(bt2)
        self.mainlayout.addWidget(bt3)
        self.mainlayout.addWidget(bt4)

        self.setLayout(self.mainlayout)
```

**Базовый подход:**

- Перетащите виджеты на форму
- Выделите несколько виджетов (зажав Ctrl)
- Нажмите правой кнопкой → "Lay out" → Выберите нужный тип
- Или используйте панель инструментов сверху (кнопки с иконками Layouts)

**Вложенные Layouts:**

```
Main Vertical Layout
├── Horizontal Layout (для кнопок)
│   ├── Кнопка "Открыть"
│   ├── Кнопка "Сохранить"
│   └── Кнопка "Выход"
└── Text Edit (занимает оставшееся пространство)
```

Еще класс, просто для примера. Соотношение 1:2

```python
class Window(QWidget): 
    '''
    Вложенные Layout
    '''
    def __init__(self): 
        super().__init__() 
        self.setGeometry(200,200, 700, 400) 
        self.setWindowTitle("Python GUI Development") 
        self.mainlayout = QVBoxLayout()

        lbl1 = QLabel("one label")
        lbl2 = QLabel("two label")

        bt1 = QPushButton("one")
        bt2 = QPushButton("two")

        self.innerlayout1 = QHBoxLayout()
        self.innerlayout1.addWidget(lbl1, stretch=1)
        self.innerlayout1.addWidget(bt1,stretch=2)
        self.mainlayout.addLayout(self.innerlayout1)

        self.innerlayout2 = QHBoxLayout()
        self.innerlayout2.addWidget(lbl2, stretch=1)
        self.innerlayout2.addWidget(bt2,stretch=2)
        self.mainlayout.addLayout(self.innerlayout2)

        self.setLayout(self.mainlayout)
```

**Растяжения (Stretch):**

```
В коде: layout.addStretch()
В Designer: есть специальный виджет "Horizontal Spacer" / "Vertical Spacer"
```

**Выравнивание:** В Property Editor настраивается

- layoutStretch — пропорции растяжения
- alignment — выравнивание содержимого
- spacing — расстояние между виджетами, пустое место

```
self.mainlayout.addSpacing(100)
```

- margin — отступ от краев

Пожелания при использовании Layouts

- Всегда используйте центральный Layout для главного окна
- Не смешивайте абсолютное позиционирование и Layouts
- Тестируйте ресайз окна
- Используйте Spacers для гибких промежутков
- Настройте stretch factors для управления пропорциями

**Stretch Factors**

Stretch factor — числовое значение, определяющее пропорцию, в которой виджеты делят доступное пространство при растяжении окна.

**Базовые принципы:**

- По умолчанию stretch factor = 0
- Виджет сохраняет свой минимальный размер
- Не растягивается при увеличении окна
- Чем больше значение, тем больше пространства получает виджет
- Важны относительные пропорции

**Настройка при создании**

Способ 1: Панель свойств (Property Editor)

- Выберите Layout в иерархии объектов
- Найдите свойство layoutStretch или layoutRowStretch / layoutColumnStretch
- Введите значения через запятую

[![изображение.png](http://bobrobotirk.ru/uploads/images/gallery/2026-01/scaled-1680-/3vzizobrazenie.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-01/3vzizobrazenie.png)

В данном примере первая кнопка не будет менять размеры, вторая будет занимать 20% от оставшегося свободного места, третья 80%.

Способ 2:Через свойства виджетов внутри Layout

У каждого виджета есть свойство sizePolicy → horizontalStretch / verticalStretch

Способ 3: Через код

```python
# при добавлении нового виджета
self.mainlayout.addWidget(bt1, stretch=1)
self.mainlayout.addWidget(bt2, stretch=3)
self.mainlayout.addWidget(bt3, stretch=0)
self.mainlayout.addWidget(bt4, stretch=5)
```

**Настройка после создания**

Способ 1: Использовать setStretch(index, stretch)

```python
# Предположим, у вас уже есть Layout с виджетами
self.mainlayout.addWidget(bt1, stretch=1)
self.mainlayout.addWidget(bt2, stretch=3) 
self.mainlayout.addWidget(bt3, stretch=0)
self.mainlayout.addWidget(bt4, stretch=5)

# Позже меняем stretch для bt4 (индекс 3, так как индексы начинаются с 0)
self.mainlayout.setStretch(3, 2)  # Меняем с 5 на 2
```

Способ 2: Получить индекс виджета динамически

```python
# Находим индекс виджета bt4 в Layout
index = self.mainlayout.indexOf(bt4)
if index != -1:  # -1 означает "не найден"
    self.mainlayout.setStretch(index, 2)
```

Способ 3: Пересоздать Layout (более кардинальный)

```python
# Удаляем все виджеты из Layout
while self.mainlayout.count():
    item = self.mainlayout.takeAt(0)
    if item.widget():
        item.widget().hide()

# Добавляем заново с новыми stretch factors
self.mainlayout.addWidget(bt1, stretch=1)
self.mainlayout.addWidget(bt2, stretch=3)
self.mainlayout.addWidget(bt3, stretch=0)
self.mainlayout.addWidget(bt4, stretch=2)  # Новое значение!
```

Способ 4: Изменить через setSizePolicy виджета.

```python
# Получаем текущую политику размеров
policy = bt4.sizePolicy()

# Устанавливаем горизонтальный/вертикальный stretch
policy.setHorizontalStretch(2)  # Для Horizontal Layout
policy.setVerticalStretch(2)    # Для Vertical Layout

bt4.setSizePolicy(policy)
bt4.update()  # Обновляем виджет
```

Важно: Этот метод влияет на поведение виджета во всех Layout, где он находится!

Способ 5: Временное отключение обновления для избежания мерцания при изменении:

```python
# Блокируем обновление
self.setUpdatesEnabled(False)

# Меняем stretch
index = self.mainlayout.indexOf(bt4)
self.mainlayout.setStretch(index, 2)

# Включаем обновление и форсируем перерасчет
self.setUpdatesEnabled(True)
self.mainlayout.invalidate()  # Помечаем Layout как невалидный
self.mainlayout.activate()    # Принудительно пересчитываем
```

Особенности работы:

- Изменения применяются немедленно — Layout пересчитается при следующем обновлении интерфейса
- Индексы начинаются с 0 и соответствуют порядку добавления
- Spacer'ы тоже имеют индексы — учитывайте это при поиске
- setStretch() работает только для QBoxLayout (QVBoxLayout, QHBoxLayout)
- Для QGridLayout используйте setRowStretch() и setColumnStretch()
- Для QFormLayout сложнее — лучше удалить и добавить заново

**Проверка текущих значений:**

```python
# Получить текущий stretch factor
current_stretch = self.mainlayout.stretch(3)  # Для индекса 3
print(f"Текущий stretch: {current_stretch}")

# Получить список всех stretch factors
for i in range(self.mainlayout.count()):
    widget = self.mainlayout.itemAt(i).widget()
    stretch = self.mainlayout.stretch(i)
    if widget:
        print(f"Индекс {i}: {widget.text()} - stretch={stretch}")
```

**Класс окна**

Для управления классом окна, класс создается, затем настраиваются нужные свойства

```python
from PyQt6.QtWidgets import QApplication, QWidget 
from PyQt6.QtGui import QIcon 
import sys 
 
class Window(QWidget): 
    def __init__(self): 
        super().__init__() 
        self.setGeometry(200,200, 700, 400) 
 
app = QApplication(sys.argv) 
window = Window() 
window.show() 
sys.exit(app.exec())
```

Свойства:

   
 self.setWindowIcon(QIcon('pyqt6lessons\\images\\python.png'))   
 self.setStyleSheet('background-color:green')   
 self.setWindowOpacity(0.5)

<table border="1" id="bkmrk-%D0%A1%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%BE-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 30.8929%;"></col><col style="width: 69.2263%;"></col></colgroup><thead><tr><td class="align-center">**Свойство**</td><td class="align-center">**Применение**</td></tr></thead><tbody><tr><td>Размеры окна</td><td>self.setGeometry(x,y, height, width)

self.setGeometry(200,200, 700, 400)

</td></tr><tr><td>Заголовок окна</td><td>self.setWindowTitle("Python GUI Development")</td></tr><tr><td>  
</td><td>  
</td></tr><tr><td>  
</td><td>  
</td></tr><tr><td>  
</td><td>  
</td></tr></tbody></table>

# QT6 desiner

Устанавливается при установке pyqt6-tools

У меня вызвался обычной командой

```bash
(myenv) D:\projects\calclulator_long>pyside6-designer.exe
```

**Типы создаваемых окон:**

- 3 типа диалоговых окон,
- Основное окно
- 10 виджетов.

Различаются родительским классом и наличием дополнительных виджетов (кнопки, ...)

**Предпросмотр результата**

Блок меню Form - Preview... очень занимательный.

[![изображение.png](http://bobrobotirk.ru/uploads/images/gallery/2026-01/scaled-1680-/34Yizobrazenie.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-01/34Yizobrazenie.png)

QT использует стили операционных систем, поэтому вид будет отличаться на разных ОС.

**Слои (Layouts)**

Настраивается отдельно тип для всей формы и для группы элементов.

Настройка для группы элементов: выделяются виджеты, ПКМ - Lay Out - Нужный тип.

[![image.png](http://bobrobotirk.ru/uploads/images/gallery/2026-02/scaled-1680-/image.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-02/image.png)

Для всей формы: ПКМ на пустом месте формы - Lay Out - Нужный тип.

[![image.png](http://bobrobotirk.ru/uploads/images/gallery/2026-02/scaled-1680-/gVYimage.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-02/gVYimage.png)

**Использование .ui файла**

**Использование в скрипте**

Два варианта: преобразование в py файл и загрузка в py скрипт ui-файла во время выполнения.

Преобразование в python файл:

```bash
(myenv) D:\projects\calclulator_long>pyuic6 -x testui.ui -o testuicreated.py
```

После этого запуск \*,py файла откроет пользовательский интерфейс. При изменении \*.ui файла необходимо обновить файл исходного кода и связанных процедур.

Загрузка ui файла во время выполнения

```python
from PyQt6.QtWidgets import QApplication, QWidget 
import sys 
from PyQt6 import uic 
 
class UI(QWidget): 
    def __init__(self): 
        super().__init__() 
        uic.loadUi("WindowUI.ui", self) 
 
app = QApplication(sys.argv) 
window = UI() 
window.show() 
app.exec()
```

**Доступ к виджетам внутри .ui файла**

Для дальнейшего доступа к виджетам из py скрипта необходимо знать тип виджета и имя объекта. Например, есть виджет типа QLineEdit, имя объекта lineEdit\_price.

[![image.png](http://bobrobotirk.ru/uploads/images/gallery/2026-02/scaled-1680-/t3Rimage.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-02/t3Rimage.png)

Создадим свойство объекта через FindChild

```python
class UI(QWidget): 
    def __init__(self): 
        super().__init__() 
        uic.loadUi("double_spin.ui", self)

        self.linePrice = self.findChild(QLineEdit, "lineEdit_price")
```

Дальше с этим свойством работать также как с созданным объектом. Пример:

```python
lass UI(QWidget): 
    def __init__(self): 
        super().__init__() 
        uic.loadUi("double_spin.ui", self)

        self.linePrice = self.findChild(QLineEdit, "lineEdit_price")
        self.doublespin = self.findChild(QDoubleSpinBox, "doubleSpinBox")
        self.doublespin.valueChanged.connect(self.spin_selected)
        self.lineresult = self.findChild(QLineEdit, "lineEdit_total")

    def spin_selected(self):
        if self.linePrice.text() != 0:
            price = int(self.linePrice.text())
            totalPrice = self.doublespin.value() * price
            self.lineresult.setText(str(totalPrice))
```

**Соединение действий виджетов**

Функционал урезан. Нажать кнопку Edit signals/slots

[![image.png](http://bobrobotirk.ru/uploads/images/gallery/2026-02/scaled-1680-/7hAimage.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-02/7hAimage.png)

При наведении мыши виджет становится красным. Если нажать виджет, с которого сигнал будет исходить и, удерживая мышь, переместить указатель на виджет, на который будет влиять сигнал, появится стрелка и элемент выбора сигналов/слотов.

[![image.png](http://bobrobotirk.ru/uploads/images/gallery/2026-02/scaled-1680-/WFrimage.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-02/WFrimage.png)

Нужно установить флажок внизу. Выбирается сигнал, слот и все работает даже на превью.

[![image.png](http://bobrobotirk.ru/uploads/images/gallery/2026-02/scaled-1680-/mFhimage.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-02/mFhimage.png)

# QT6 QLabel, LCD

**QLabel**

Класс QLabel используется для отображения сообщений и изображений,

- Импорт QLabel
- Создание объекта класса QLabel
- Применение методов класса к переменной

```python
from PyQt6.QtWidgets import QApplication, QWidget, QLabel
import sys 
 
class Window(QWidget): 
    def __init__(self): 
        super().__init__() 
        self.setGeometry(200,200, 700, 400) 
        self.setWindowTitle("Python GUI Development") 
        label = QLabel("", self)
        label.setText('first text in label')
 
app = QApplication(sys.argv) 
window = Window() 
window.show() 
sys.exit(app.exec())
```

Для создания метки с изображением текст не передается

```python
label = QLabel(self)
```

Таблица методов:

<table border="1" id="bkmrk-%D0%9C%D0%B5%D1%82%D0%BE%D0%B4-%D0%9D%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-set" style="border-collapse: collapse; width: 100%; height: 331.6px;"><colgroup><col style="width: 25.3874%;"></col><col style="width: 74.6126%;"></col></colgroup><thead><tr style="height: 29.8px;"><td class="align-center" style="height: 29.8px;">**Метод**</td><td class="align-center" style="height: 29.8px;">**Назначение**</td></tr></thead><tbody><tr style="height: 29.8px;"><td style="height: 29.8px;">setText()</td><td style="height: 29.8px;">Устанавливается новый текст метки</td></tr><tr style="height: 29.8px;"><td style="height: 29.8px;">setNum()</td><td style="height: 29.8px;">добавляет целое или двойное значение</td></tr><tr style="height: 29.8px;"><td style="height: 29.8px;">clear()</td><td style="height: 29.8px;">удаляет текст</td></tr><tr style="height: 29.8px;"><td style="height: 29.8px;">setMovie()</td><td style="height: 29.8px;">установки изображения gif

movie = QMovie('images/sky.gif')   
movie.setSpeed(500)   
label.setMovie(movie)   
movie.start()

</td></tr><tr style="height: 46.6px;"><td style="height: 46.6px;">setFont()</td><td style="height: 46.6px;">Изменения шрифта, setFont() ожидает класс QFont, (потомок QtGui)   
label.setFont(QFont("Sanserif", 15))</td></tr><tr style="height: 46.6px;"><td style="height: 46.6px;">label.setStyleSheet()</td><td style="height: 46.6px;">Изменение цвета шрифта

label.setStyleSheet('color:red')

</td></tr><tr style="height: 29.8px;"><td style="height: 29.8px;">Добавление изображения</td><td style="height: 29.8px;"></td></tr><tr style="height: 29.8px;"><td style="height: 29.8px;">setPixamp()</td><td style="height: 29.8px;">pixmap = QPixmap('images/python.png')   
label.setPixmap(pixmap) </td></tr><tr><td>QImage, QBitmap QPicture</td><td>  
</td></tr></tbody></table>

**QLCDNumber**

Класс для отображения 7-сегментного дисплея, отображает 5 (пять) 8- 10- 16- ричных элементов.

```python
class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setGeometry(200,200, 700, 400)
        self.setWindowTitle("Python GUI Development")

        self.timer = QTimer()
        self.timer.timeout.connect(self.update_lcd)
        self.timer.start(1000)

        self.create_button()

    def create_button(self):
        vbox = QVBoxLayout()

        self.lcd = QLCDNumber()
        self.lcd.setStyleSheet('background:red')

        vbox.addWidget(self.lcd)

        time = QTime.currentTime()
        text = time.toString('hh:mm')

        self.lcd.display(text)
        self.setLayout(vbox)

    def update_lcd(self):
        time = QTime.currentTime()
        text = time.toString('hh:mm')
        self.lcd.display(text)
```

# QT6 Buttons

**QPushButtons**

Командная кнопка является наиболее часто используемым виджетом в любом графическом интерфейсе пользователя. Нажатие (click) кнопки является командой компьютеру выполнить какое-либо действие. Типичными кнопками являются "ОК", "Применить", "Отмена", "Закрыть"., Да, Нет и Справка.   
Командная кнопка имеет прямоугольную форму и обычно отображает текстовую метку, описывающую ее действие. Можно указать комбинацию клавиш, указав перед нужным символом амперсанд в тексте.   
чтобы отобразить кнопку в приложении, вам необходимо создать экземпляр класса QPushButton.

```python
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton
import sys 
 
class Window(QWidget): 
    def __init__(self): 
        super().__init__() 
        self.setGeometry(200,200, 700, 400) 
        self.setWindowTitle("Python GUI Development") 
        self.create_button()

    def create_button(self):
        btn = QPushButton("Click", self)
 
app = QApplication(sys.argv) 
window = Window() 
window.show() 
sys.exit(app.exec())
```

Методы класса:

<table border="1" id="bkmrk-%D0%9C%D0%B5%D1%82%D0%BE%D0%B4-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-sette" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 16.2501%;"></col><col style="width: 83.8691%;"></col></colgroup><thead><tr><td class="align-center">**Метод**</td><td class="align-center">**Описание**</td></tr></thead><tbody><tr><td>setText()</td><td>Изменение текста</td></tr><tr><td>setIcon()</td><td>Добавление иконки на кнопку

btn.setIcon(QIcon("images/python.png"))

</td></tr><tr><td>  
</td><td>Изменение размера иконки

```python
from PyQt6.QtCore import QSize
from PyQt6.QtGui import QIcon 

    def create_button(self):
        btn = QPushButton("Click", self)
        btn.setGeometry(100, 100, 130, 50)
        btn.setIcon(QIcon('pyqt6lessons\images\python.png'))
        btn.setIconSize(QSize(36,36))
```

</td></tr><tr><td>setGeometry()</td><td>Настройка положения кнопки, </td></tr><tr><td>setMenu()</td><td>Всплывающее меню над кнопкой. Сначала создать объект QMenu, класс QMenu связан с модулем QtWidgets, класс QMenu предоставляет виджет меню для использования в строках меню, контекстных меню и других всплывающих меню.   
Виджет меню - это меню выбора. Это может быть как выпадающее меню в строке меню, так и отдельное контекстное меню. Выпадающие меню отображаются в строке меню, когда пользователь щелкает на соответствующем элементе или нажимает указанную комбинацию клавиш.

```python
menu = QMenu() 
menu.setFont(QFont("Times", 14, 
QFont.Weight.ExtraBold)) 
menu.setStyleSheet('background-color:green') 
menu.addAction("Copy") 
menu.addAction("Cut") 
menu.addAction("Paste") 
btn.setMenu(menu) 
```

 [![изображение.png](http://bobrobotirk.ru/uploads/images/gallery/2026-01/scaled-1680-/UnYizobrazenie.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-01/UnYizobrazenie.png)

</td></tr><tr><td>setFont()</td><td>Настройка шрифта

btn.setFont(QFont("Times", 14, QFont.Weight.ExtraBold))

</td></tr><tr><td>setCheckable()</td><td>Вид кнопки при нажатии меняется. Выделяется и снимается выделение.

</td></tr></tbody></table>

**QRadioButtons**

Кнопка, которую можно включить (установить флажок) или выключить (снять флажок). Переключатели обычно предоставляют  
пользователю возможность выбора "из многих". В группе переключателей одновременно может быть установлен только один переключатель, если пользователь выбирает другую кнопку, ранее выбранная кнопка отключается. Существуют различные методы, которые вы можете использовать, например, у нас есть IsChecked(), и он возвращает логическое значение true, если кнопка находится в выбранном состоянии, или у нас есть метод setIcon(), с помощью которого мы можем добавить значок для радиокнопки, а также setText(), который задает текст выбранной кнопки. Также существуют различные сигналы, которые вы можете использовать, например, у нас есть переключаемый сигнал, который используется всякий раз, когда переключатель меняет свое состояние с установленного на снятое и наоборот.

RadioButtons объединенные в одном hbox рассматриваются как зависимые.

```python
from PyQt6.QtWidgets import QApplication, QWidget, QHBoxLayout, QRadioButton, QLabel
from PyQt6.QtWidgets import QVBoxLayout
import sys 
 
class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setGeometry(200,200, 700, 400)
        self.setWindowTitle("Python GUI Development")
        self.create_button()

    def create_button(self):
        hbox = QHBoxLayout()

        self.label = QLabel("", self)
        vbox = QVBoxLayout()
        vbox.addWidget(self.label)
        vbox.addLayout(hbox)

        rad1 = QRadioButton("Python")
        rad1.toggled.connect(self.radio_selected)
        hbox.addWidget(rad1)
        rad2 = QRadioButton("Java")
        rad2.toggled.connect(self.radio_selected)
        hbox.addWidget(rad2)
        rad3 = QRadioButton("JavaScript")
        rad3.toggled.connect(self.radio_selected)
        hbox.addWidget(rad3)

        self.setLayout(vbox)

    def radio_selected(self):
        radio_btn = self.sender()
        if radio_btn.isChecked():
            self.label.setText(f'Selected: {radio_btn.text()}')

app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())
```

Методы класса:

<table border="1" id="bkmrk-%D0%9C%D0%B5%D1%82%D0%BE%D0%B4-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-ische" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 16.2501%;"></col><col style="width: 83.8691%;"></col></colgroup><thead><tr><td class="align-center">**Метод**</td><td class="align-center">**Описание**</td></tr></thead><tbody><tr><td>isChecked()</td><td>Возвращает True если кнопка выбрана</td></tr><tr><td>setChecked()</td><td>Переводит кнопку в выбранное состояние</td></tr><tr><td>setIcon()</td><td>Устанавливает иконку кнопки</td></tr><tr><td>setText()</td><td>Текст</td></tr></tbody></table>

# QT6 QLineEdit

Виджет позволяет вводить и редактировать одну строку обычного текста с помощью полезного набора функций редактирования, включая отмену и повтор, вырезание и вставку, а также перетаскивание.

  
Методы:

<table border="1" id="bkmrk-%D0%9C%D0%B5%D1%82%D0%BE%D0%B4-%D0%9D%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-set" style="border-collapse: collapse; width: 100%; height: 238.4px;"><colgroup><col style="width: 31.4882%;"></col><col style="width: 68.631%;"></col></colgroup><thead><tr style="height: 29.8px;"><td class="align-center" style="height: 29.8px;">**Метод**</td><td class="align-center" style="height: 29.8px;">**Назначение**</td></tr></thead><tbody><tr style="height: 29.8px;"><td style="height: 29.8px;">setEchoMode()</td><td style="height: 29.8px;">Режим эхо. Варианты:

- Normal: все введенные символы отображаются
- NoEcho: ни один введенный символ не отображается
- Password: вместо символов выводится звездочка
- PasswordEchoOnEdit: При редактировании полей пароля отображается фактический текст, в противном случае текст будет помечен звездочками

</td></tr><tr style="height: 29.8px;"><td style="height: 29.8px;">setFont()</td><td style="height: 29.8px;">Настройка шрифта</td></tr><tr style="height: 29.8px;"><td style="height: 29.8px;">maxLength()</td><td style="height: 29.8px;">Максимальная длина текста</td></tr><tr style="height: 29.8px;"><td style="height: 29.8px;">setText()</td><td style="height: 29.8px;">Устанавливает текст</td></tr><tr style="height: 29.8px;"><td style="height: 29.8px;">text()</td><td style="height: 29.8px;">Получает текст</td></tr><tr style="height: 29.8px;"><td style="height: 29.8px;">clear()</td><td style="height: 29.8px;">Очищает строку ввода</td></tr><tr style="height: 29.8px;"><td style="height: 29.8px;">setReadOnly()</td><td style="height: 29.8px;">Установка режима только для чтения</td></tr><tr><td>setEnabled()</td><td>Доступность компонента пользователю</td></tr><tr><td>setFocus()</td><td>Установить фокус</td></tr><tr><td>setPlaceholderText()</td><td>Текст когда поле пустое</td></tr></tbody></table>

# QT6 CheckBox, SpinBox, ComboBox

**QCheckbox**

Это кнопка выбора, которую можно включить (установить флажок) или выключить (снять флажок). Флажки обычно используются для обозначения функций в приложении, которые можно включать или отключать, не затрагивая другие. При изменении состояния флажка выдается сигнал StateChanged(). Метод IsChecked() используется для запроса, установлен ли флажок.

Методы:

<table border="1" id="bkmrk-%D0%9C%D0%B5%D1%82%D0%BE%D0%B4-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-ische" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 13.0358%;"></col><col style="width: 87.0834%;"></col></colgroup><thead><tr><td>Метод</td><td>Описание</td></tr></thead><tbody><tr><td>isChecked()</td><td>  
</td></tr><tr><td>setIcon()</td><td>  
</td></tr><tr><td>setText()</td><td>  
</td></tr><tr><td>setChecked()</td><td>  
</td></tr></tbody></table>

Сигналы:

stateChanged

**QSpinbox**

QSpinBox предназначен для обработки целых чисел и дискретных наборов значений, позволяет выбирать значение, нажимая кнопки вверх / вниз или нажимая клавиши вверх / вниз на клавиатуре, чтобы увеличить / уменьшить отображаемое в данный момент значение. Также возможно ввести значение вручную.

Методы:

<table border="1" id="bkmrk-%D0%9C%D0%B5%D1%82%D0%BE%D0%B4-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-value" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 13.0358%;"></col><col style="width: 87.0834%;"></col></colgroup><thead><tr><td>Метод</td><td>Описание</td></tr></thead><tbody><tr><td>value()</td><td>текущее выбранное целое значение</td></tr><tr><td>text()</td><td>отображения текста в окне прокрутки</td></tr><tr><td>setMinimum()

</td><td>  
</td></tr><tr><td>setMaximum()</td><td>  
</td></tr><tr><td>setPrefix()</td><td>текстовый префикс, добавляемый перед значением, возвращаемым полем прокрутки. </td></tr><tr><td>setSuffix()</td><td>текст суффикса, добавляемый к значению, возвращаемому блоком spin.</td></tr></tbody></table>

Сигналы:

valueChanged()

editingFinished() выдается при потере фокуса на spinbox. Предполагаю, актуально для приложений с web backend при передаче финальных данных.

```python
from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QHBoxLayout, QLineEdit
from PyQt6.QtWidgets import QSpinBox
import sys 
 
class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setGeometry(200,200, 700, 400)
        self.setWindowTitle("Python GUI Development")
        self.create_button()

    def create_button(self):
        hbox = QHBoxLayout()
        label = QLabel("Laptop price: ")
        self.lineedit = QLineEdit()
        self.spinbox = QSpinBox()
        self.spinbox.valueChanged.connect(self.spin_selected)

        self.total_result = QLineEdit()

        hbox.addWidget(label)
        hbox.addWidget(self.lineedit)
        hbox.addWidget(self.spinbox)
        hbox.addWidget(self.total_result)
        self.setLayout(hbox)

    def spin_selected(self):
        if self.lineedit.text() != 0:
            price = int(self.lineedit.text())
            totalPrice = self.spinbox.value() * price
            self.total_result.setText(str(totalPrice))
 
app = QApplication(sys.argv) 
window = Window() 
window.show() 
sys.exit(app.exec())
```

**QComboBox**

Виджет выбора, отображающий текущий элемент. Также отображает список выбираемых элементов. Может быть редактируемым.

Также есть специализированный ComboBox: для выбора шрифтов (fontComboBox).

Методы:

<table border="1" id="bkmrk-%D0%9C%D0%B5%D1%82%D0%BE%D0%B4-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-setit" style="border-collapse: collapse; width: 100%; height: 331.563px;"><colgroup><col style="width: 15.1345%;"></col><col style="width: 84.8655%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Метод</td><td style="height: 29.7969px;">Описание</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">setItemText()</td><td style="height: 29.7969px;">Устанавливает или изменяет текст элемента в поле со списком. </td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">removeItem()</td><td style="height: 29.7969px;">Удаляет определенный элемент из поля со списком. </td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">clear()</td><td style="height: 29.7969px;">Удаляет все элементы из поля со списком.</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">currentText()</td><td style="height: 29.7969px;">Возвращает текст текущего элемента, то есть элемента, который выбран в данный момент. </td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;">setCurrentIndex()</td><td style="height: 46.5938px;">Устанавливает текущий индекс поля со списком, то есть задает желаемый элемент в поле со списком в качестве выбранного в данный момент элемента. </td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">count()</td><td style="height: 29.7969px;">Возвращает количество элементов в поле со списком. </td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;">setEditable()</td><td style="height: 46.5938px;">Сделайте поле со списком доступным для редактирования, то есть пользователь можно редактировать элементы в поле со списком. </td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">addItem()</td><td style="height: 29.7969px;">Добавляет указанное содержимое в поле со списком. </td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">itemText()</td><td style="height: 29.7969px;">Возвращает текст в указанное расположение индекса в поле со списком.</td></tr><tr><td>currentIndex()</td><td>Возвращает индексное местоположение текущего выбранного элемента в поле со списком. Если поле со списком пусто или в поле со списком в данный момент не выбран ни один элемент, метод вернет значение -1 в качестве индекса.</td></tr></tbody></table>

Сигналы

currentIndexChanged() выбор нового элемента

editTextChanged() изменение текста в редактируемом комбобоксе

```python
from PyQt6.QtWidgets import QApplication, QWidget, QComboBox, QLabel, QHBoxLayout, QVBoxLayout
import sys 
 
class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setGeometry(200,200, 700, 400)
        self.setWindowTitle("Python GUI Development")
        self.create_button()

    def create_button(self):
        hbox = QHBoxLayout()
        label = QLabel('Select type: ')
        self.combo = QComboBox()
        self.combo.addItem('')
        self.combo.addItem('Current account')
        self.combo.addItem('Deposite account')
        self.combo.addItem('Saving account')
        self.combo.currentTextChanged.connect(self.updresult)
        hbox.addWidget(label)
        hbox.addWidget(self.combo)
        
        vbox = QVBoxLayout()
        vbox.addLayout(hbox)

        self.label_result = QLabel('')
        vbox.addWidget(self.label_result)
        
        self.setLayout(vbox)

    def updresult(self):
        self.label_result.setText('Your type: ' + self.combo.currentText())
 
app = QApplication(sys.argv) 
window = Window() 
window.show() 
sys.exit(app.exec())
```

# QT6 QSlider, QListWidget

**QSlider**

Ползунок - виджет управления ограниченным значением. Позволяет перемещать ручку ползунка и преобразовывать положение ручки в целое значение в допустимом диапазоне.

Слайдер бывает горизонтальным и вертикальным

```python
self.slider.setOrientation(Qt.Orientation.Horizontal)
```

Настройка положения галочки ползунка

```python
self.slider.setTickPosition(QSlider.TickPosition.TicksAbove)
```

Интервал шага

```python
self.slider.setTickInterval(5)
```

Границы диапазона

```python
self.slider.setMinimum(0) 
self.slider.setMaximum(100) 
```

Методы

<table border="1" id="bkmrk-%D0%9C%D0%B5%D1%82%D0%BE%D0%B4-%D0%9D%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-set" style="width: 103.214%;"><thead><tr><td class="align-center" style="width: 21.5873%;">**Метод**</td><td class="align-center" style="width: 78.4127%;">**Назначение**</td></tr></thead><tbody><tr><td style="width: 21.5873%;">minimum()</td><td style="width: 78.4127%;">возвращает минимальное значение ползунка</td></tr><tr><td style="width: 21.5873%;">maximum()</td><td style="width: 78.4127%;">возвращает максимальное значение ползунка</td></tr><tr><td style="width: 21.5873%;">setValue()</td><td style="width: 78.4127%;">используется для установки значения ползунка</td></tr></tbody></table>

Сигналы

<table border="1" id="bkmrk-%D0%A1%D0%B8%D0%B3%D0%BD%D0%B0%D0%BB-%D0%9D%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-va" style="width: 103.214%; height: 89.3907px;"><thead><tr style="height: 29.7969px;"><td class="align-center" style="width: 21.5873%; height: 29.7969px;">**Сигнал**</td><td class="align-center" style="width: 78.4127%; height: 29.7969px;">**Назначение**</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="width: 21.5873%; height: 29.7969px;">valueChanged()</td><td style="width: 78.4127%; height: 29.7969px;">подается при перемещении ручки ползунка</td></tr><tr style="height: 29.7969px;"><td style="width: 21.5873%; height: 29.7969px;">sliderPressed()</td><td style="width: 78.4127%; height: 29.7969px;">подается, когда пользователь начинает перетаскивать ручку ползунка. </td></tr><tr><td style="width: 21.5873%;">sliderMoved()</td><td style="width: 78.4127%;">подается, когда пользователь перемещает ручку ползунка.</td></tr><tr><td style="width: 21.5873%;">sliderReleased()</td><td style="width: 78.4127%;">подается, когда пользователь отпускает ручку ползунка</td></tr></tbody></table>

**QListWidget**

QListWidget - представление списка, аналогичное QListView, но с классическим интерфейсом на основе элементов для добавления и удаления элементов. QListWidget использует внутреннюю модель для управления удалением элементов. QListWidget использует внутреннюю модель для управления каждым QListWidgetItem в списке.

Методы

<table border="1" id="bkmrk-%D0%9C%D0%B5%D1%82%D0%BE%D0%B4-%D0%9D%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-ins" style="width: 103.214%;"><thead><tr><td class="align-center" style="width: 21.5873%;">**Метод**</td><td class="align-center" style="width: 78.4127%;">**Назначение**</td></tr></thead><tbody><tr><td style="width: 21.5873%;">insertItem()</td><td style="width: 78.4127%;">вставляет новый элемент в виджет списка в указанном месте. </td></tr><tr><td style="width: 21.5873%;">insertItems()</td><td style="width: 78.4127%;">вставляет несколько элементов из предоставленного списка, начиная с указанного места </td></tr><tr><td style="width: 21.5873%;">count()</td><td style="width: 78.4127%;">возвращает количество элементов в списке. </td></tr><tr><td style="width: 21.5873%;">takeItem()</td><td style="width: 78.4127%;">удаляет и возвращает элементы из указанной строки</td></tr><tr><td style="width: 21.5873%;">CurrentItem()</td><td style="width: 78.4127%;">возвращает текущий элемент в списке</td></tr><tr><td style="width: 21.5873%;">addItem()</td><td style="width: 78.4127%;">добавляет элемент с указанным текстом в конец</td></tr><tr><td style="width: 21.5873%;">currentRow()</td><td style="width: 78.4127%;">возвращает номер строки выбранного элемента. Если ни один элемент не выбран, возвращает -1</td></tr></tbody></table>

Сигналы

<table border="1" id="bkmrk-%D0%A1%D0%B8%D0%B3%D0%BD%D0%B0%D0%BB-%D0%9D%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-cl" style="width: 103.214%; height: 148.984px;"><thead><tr style="height: 29.7969px;"><td class="align-center" style="width: 21.5873%; height: 29.7969px;">**Сигнал**</td><td class="align-center" style="width: 78.4127%; height: 29.7969px;">**Назначение**</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="width: 21.5873%; height: 29.7969px;">clicked()</td><td style="width: 78.4127%; height: 29.7969px;">подается при щелчке по элементу в виджете списка </td></tr><tr style="height: 29.7969px;"><td style="width: 21.5873%; height: 29.7969px;">currentRowChanged()</td><td style="width: 78.4127%; height: 29.7969px;">подается при изменении строки текущего элемента списка</td></tr><tr style="height: 29.7969px;"><td style="width: 21.5873%; height: 29.7969px;">currentTextChanged()</td><td style="width: 78.4127%; height: 29.7969px;">подается при каждом изменении текста в текущем элементе списка</td></tr><tr style="height: 29.7969px;"><td style="width: 21.5873%; height: 29.7969px;">currentItemChanged()</td><td style="width: 78.4127%; height: 29.7969px;">подается при изменении фокуса текущего элемента списка</td></tr></tbody></table>

# QT6 QTable, QMessageBox, Dialogs

**QTable**

Отображение таблиц. Элементы в QTableWidget предоставляются с помощью QTableWidgetItem.

Методы:

<table border="1" id="bkmrk-%D0%9C%D0%B5%D1%82%D0%BE%D0%B4-%D0%9D%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-set" style="width: 102.143%;"><colgroup><col style="width: 17.8821%;"></col><col style="width: 82.1179%;"></col></colgroup><thead><tr><td class="align-center">**Метод**</td><td class="align-center">**Назначение**</td></tr></thead><tbody><tr><td>setRowCount()</td><td>определения количества строк</td></tr><tr><td>setColumnCount()</td><td>определения количества столбцов</td></tr><tr><td>rowCount()</td><td>возвращает количество строк</td></tr><tr><td>columnCount()</td><td>возвращает количество столбцов</td></tr></tbody></table>

```python
from PyQt6.QtWidgets import QApplication, QWidget, QTableWidget, QTableWidgetItem, QVBoxLayout
import sys 
 
class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setGeometry(200,200, 700, 400)
        self.setWindowTitle("Python GUI Development")
        self.create_button()

    def create_button(self):
        vbox = QVBoxLayout()
        curtable = QTableWidget()
        curtable.setRowCount(3)
        curtable.setColumnCount(3)

        curtable.setItem(0, 0, QTableWidgetItem('Заголовок столбца 1'))
        curtable.setItem(0, 1, QTableWidgetItem('Заголовок столбца 2'))
        curtable.setItem(0, 2, QTableWidgetItem('Заголовок столбца 3'))

        vbox.addWidget(curtable)

        self.setLayout(vbox)
 
app = QApplication(sys.argv) 
window = Window() 
window.show() 
sys.exit(app.exec())
```

**QMessageBox**

QMessageBox - модальный диалог информирования пользователя и получения ответа. В окне сообщения отображается текст, есть необязательный подробный текст в случае необходимости. Также может отображаться значок и стандартные кнопки для принятия ответа пользователя. Существуют различные типы диалогов (about messagebox, information messagebox, warning messagebox, multichoice messagebox).

```python
from PyQt6.QtWidgets import QApplication, QDialog, QPushButton
from PyQt6.QtWidgets import QMessageBox

from PyQt6 import uic
import sys 
 
class Window(QDialog):
    def __init__(self):
        super().__init__()
        self.setGeometry(200,200, 700, 400)
        self.setWindowTitle("Python GUI Development")
        uic.loadUi("messagedemo.ui", self)
        
        self.loadguiobjects()

    def loadguiobjects(self):
        self.butt_warn = self.findChild(QPushButton, "pushButton_warn")
        self.butt_warn.clicked.connect(self.show_warn)
        self.butt_info = self.findChild(QPushButton, "pushButton_info")
        self.butt_info.clicked.connect(self.show_info)
        self.butt_abt = self.findChild(QPushButton, "pushButton_abt")
        self.butt_abt.clicked.connect(self.show_about)

    def show_warn(self):
        QMessageBox.warning(self, 'Warning', 'This is a warning message')
    
    def show_info(self):
        #кастомный messagebox + стандартные кнопки
        msg_box = QMessageBox(self)
        msg_box.setWindowTitle('Information')
        msg_box.setText('This is a information message')
        msg_box.setIcon(QMessageBox.Icon.Information)

        # Добавляем кнопки
        ok_button = msg_box.addButton('OK', QMessageBox.ButtonRole.AcceptRole)
        cancel_button = msg_box.addButton('Отменить задание', QMessageBox.ButtonRole.RejectRole)

        # Показываем сообщение и ждем нажатия кнопки
        msg_box.exec()

        # Проверяем какая кнопка была нажата
        if msg_box.clickedButton() == cancel_button:
            print("Действие отменено")

        elif msg_box.clickedButton() == ok_button:
            print("OK нажата")

    def show_about(self):
        # другой способ с разными кнопкам
        msg_box = QMessageBox(
            QMessageBox.Icon.NoIcon,
            'About',
            'This is a about message',
            QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel
        )

        # Изменяем текст стандартных кнопок
        msg_box.button(QMessageBox.StandardButton.Ok).setText('Продолжить')
        msg_box.button(QMessageBox.StandardButton.Cancel).setText('Отменить задание')

        result = msg_box.exec()

        if result == QMessageBox.StandardButton.Ok:
            print("Продолжаем выполнение")
        else:
            print("Задание отменено")
 
app = QApplication(sys.argv) 
window = Window() 
window.show() 
sys.exit(app.exec())
```

**SaveFile**

Сама кнопка меню в сформированном из QtDesiner, класс Ui\_MainWindow

```python
from PyQt6.QtWidgets import QMainWindow, QApplication, QFileDialog, QMessageBox
import sys
from notepadapp import Ui_MainWindow

class NotePadWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.show()

        self.actionSave.triggered.connect(self.save_file)

    def save_file(self):
        filename = QFileDialog.getSaveFileName(self, 'Save file')
        if filename[0]:
            f = open(filename[0], 'w')
            with f:
                text = self.textEdit.toPlainText()
                f.write(text)
                QMessageBox.about(self, 'Save file', 'File saved successfully!')

app = QApplication(sys.argv)
Note = NotePadWindow()
sys.exit(app.exec())
```

# Пример: notepad

**Начальная информация**

Внешний вид приложения:

[![image.png](http://bobrobotirk.ru/uploads/images/gallery/2026-02/scaled-1680-/XJIimage.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-02/XJIimage.png)

Элементы интерфейса: меню, быстрые кнопки и многострочное поле ввода. Элементы меню:

<table border="1" id="bkmrk--1" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 33.3731%;"></col><col style="width: 33.3731%;"></col><col style="width: 33.3731%;"></col></colgroup><tbody><tr><td>[![image.png](http://bobrobotirk.ru/uploads/images/gallery/2026-02/scaled-1680-/37qimage.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-02/37qimage.png)

</td><td>[![image.png](http://bobrobotirk.ru/uploads/images/gallery/2026-02/scaled-1680-/69timage.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-02/69timage.png)

</td><td>[![image.png](http://bobrobotirk.ru/uploads/images/gallery/2026-02/scaled-1680-/YRWimage.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-02/YRWimage.png)

</td></tr></tbody></table>

**Установленные модули:**

```bash
pip install pyqt6
```

Параметры интерфейса:

Окно - MainWindow

```python
from PyQt6.QtWidgets import QMainWindow, QApplication, QFileDialog, QMessageBox
from PyQt6.QtPrintSupport import QPrinter, QPrintDialog, QPrintPreviewDialog
from PyQt6.QtCore import QFileInfo
from PyQt6.QtGui import QFont
import sys
from notepadapp import Ui_MainWindow

class NotePadWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.show()

        self.actionSave.triggered.connect(self.file_save)
        self.actionNew.triggered.connect(self.file_new)
        self.actionOpen.triggered.connect(self.file_open)
        self.actionPrint.triggered.connect(self.file_print)
        self.actionPrint_preview.triggered.connect(self.preview_dialog)
        self.actionExport_PDF.triggered.connect(self.exporting_pdf)
        self.actionQuit.triggered.connect(self.exit_app)

        self.actionUndo.triggered.connect(self.textEdit.undo)
        self.actionRedo.triggered.connect(self.textEdit.redo)

        self.actionCut.triggered.connect(self.textEdit.cut)
        self.actionCopy.triggered.connect(self.textEdit.copy)
        self.actionPaste.triggered.connect(self.textEdit.paste)

        self.actionBold.triggered.connect(self.text_bold)

    def maybe_save(self) -> bool:
        if not self.textEdit.document().isModified():
            return True
        
        ret = QMessageBox.warning(self, "Application", 
                                  "The document changed \n Save working?",
                                  QMessageBox.StandardButton.Save | 
                                  QMessageBox.StandardButton.Discard | 
                                  QMessageBox.StandardButton.Cancel)
        if ret == QMessageBox.StandardButton.Save:
            self.file_save()
            return True
        elif ret == QMessageBox.StandardButton.Cancel:
            return False
        
        return True
    
    def file_new(self):
        if self.maybe_save():
            self.textEdit.clear()

    def file_save(self):
        filename = QFileDialog.getSaveFileName(self, 'Save file')
        if filename[0]:
            f = open(filename[0], 'w')
            with f:
                text = self.textEdit.toPlainText()
                f.write(text)
                QMessageBox.about(self, 'Save file', 'File saved successfully!')

    def file_open(self):
        self.maybe_save()
        filename = QFileDialog.getOpenFileName(self, 'Open file')
        if filename[0]:
            f = open(filename[0], 'r')
            with f:
                data = f.read()
                self.textEdit.setText(data)

    def file_print(self):
        printer = QPrinter(QPrinter.PrinterMode.HighResolution)
        dialog = QPrintDialog(printer)
        if dialog.exec() == QPrintDialog.DialogCode.Accepted:
            self.textEdit.print(printer)

    def print_preview(self, printer):
        self.textEdit.print(printer)

    def preview_dialog(self):
        printer = QPrinter(QPrinter.PrinterMode.HighResolution)
        preview_dialog = QPrintPreviewDialog(printer, self)
        preview_dialog.paintRequested.connect(self.print_preview)
        preview_dialog.exec()

    def exporting_pdf(self):
        #fn, _ = QFileDialog.getSaveFileName(self, 'Export PDF', "PDF Files (.pdf) ;; AllFiles()")
        fn, _ = QFileDialog.getSaveFileName(self, 'Export PDF','',"PDF Files (.pdf) ;; All Files (*)")
        if QFileInfo(fn).suffix() == "":
            fn += '.pdf'
        printer = QPrinter(QPrinter.PrinterMode.HighResolution)
        printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat)
        printer.setOutputFileName(fn)
        self.textEdit.document().print(printer)

    def exit_app(self):
        self.close()

    def text_bold(self):
        font = QFont()
        font.setBold(True)
        self.textEdit.setFont(font)


app = QApplication(sys.argv)
Note = NotePadWindow()
sys.exit(app.exec())
```

Это неполный код, операции однотипные.

# QT6: База данных

Пример для работы с Mysql

```bash
pip install mysql-connector-python
```

Пример кода для подключения к базе и создания БД:

```python
from PyQt6 import QtCore, QtGui, QtWidgets
import mysql.connector as mc


class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(456, 300)
        self.verticalLayout = QtWidgets.QVBoxLayout(Form)
        self.verticalLayout.setObjectName("verticalLayout")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label_dbname = QtWidgets.QLabel(parent=Form)
        font = QtGui.QFont()
        font.setPointSize(14)
        self.label_dbname.setFont(font)
        self.label_dbname.setObjectName("label_dbname")
        self.horizontalLayout.addWidget(self.label_dbname)
        self.lineEdit_dbname = QtWidgets.QLineEdit(parent=Form)
        font = QtGui.QFont()
        font.setPointSize(14)
        self.lineEdit_dbname.setFont(font)
        self.lineEdit_dbname.setText("")
        self.lineEdit_dbname.setObjectName("lineEdit_dbname")
        self.horizontalLayout.addWidget(self.lineEdit_dbname)
        self.verticalLayout.addLayout(self.horizontalLayout)
        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
        self.verticalLayout.addItem(spacerItem)
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.pushButton_dbcreate = QtWidgets.QPushButton(parent=Form)
        self.pushButton_dbcreate.setObjectName("pushButton_dbcreate")
        self.pushButton_dbcreate.clicked.connect(self.create_db)
        self.horizontalLayout_2.addWidget(self.pushButton_dbcreate)
        self.pushButton_dbconn = QtWidgets.QPushButton(parent=Form)
        self.pushButton_dbconn.setObjectName("pushButton_dbconn")
        self.pushButton_dbconn.clicked.connect(self.check_connect)
        self.horizontalLayout_2.addWidget(self.pushButton_dbconn)
        self.verticalLayout.addLayout(self.horizontalLayout_2)
        self.label_result = QtWidgets.QLabel(parent=Form)
        font = QtGui.QFont()
        font.setFamily("PMingLiU-ExtB")
        font.setPointSize(14)
        self.label_result.setFont(font)
        self.label_result.setText("")
        self.label_result.setObjectName("label_result")
        self.verticalLayout.addWidget(self.label_result)

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def create_db(self):
        try:
            mydb = mc.connect(
                host="192.168.1.193",
                user="root",
                password="rootpassword"
            )
            cursor = mydb.cursor()
            dbname = self.lineEdit_dbname.text()
            cursor.execute("CREATE DATABASE {} ".format(dbname))
            self.label_result.setText('Database {} created!'.format(dbname))
        except mc.Error as e:
            self.label_result.setText(str(e))

    def check_connect(self):
        try:
            mydb = mc.connect(
                host="192.168.1.193",
                user="root",
                password="rootpassword",
                database="pyqtdb"
            )
            self.label_result.setText("Connected!")
        except mc.Error as e:
            self.label_result.setText(str(e))

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.label_dbname.setText(_translate("Form", "Database name:"))
        self.pushButton_dbcreate.setText(_translate("Form", "Create Database"))
        self.pushButton_dbconn.setText(_translate("Form", "Database connection"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Form = QtWidgets.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec())
```

# Авторизация

# Авторизация через ВК

Перейти на [VK для разработчиков](https://dev.vk.com/ru)

Создать новое приложение, страницы настроек:

[![python-vk-1.JPG](http://bobrobotirk.ru/uploads/images/gallery/2025-04/scaled-1680-/python-vk-1.JPG) ](http://bobrobotirk.ru/uploads/images/gallery/2025-04/python-vk-1.JPG)

[![python-vk-2.JPG](http://bobrobotirk.ru/uploads/images/gallery/2025-04/scaled-1680-/python-vk-2.JPG)](http://bobrobotirk.ru/uploads/images/gallery/2025-04/python-vk-2.JPG)[![python-vk-3.JPG](http://bobrobotirk.ru/uploads/images/gallery/2025-04/scaled-1680-/python-vk-3.JPG)](http://bobrobotirk.ru/uploads/images/gallery/2025-04/python-vk-3.JPG)В настройках приложения будут защищенный ключ и сервисный ключ.

# RBAC+ACL модель

Это гибридная модель, где: пользователь имеет общую роль доступа (например, can\_manage\_company или viewer\_of\_sphere) и у этой роли есть контекст, т.е. привязка к объекту: компании или сфере

**Таблицы**

SQL запросы для PostgreSQL

**Пользователи:**

```sql
CREATE TABLE users (
    user_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username TEXT UNIQUE NOT NULL,
    fio TEXT NOT NULL DEFAULT '',
    phone TEXT NOT NULL,
    password_hash TEXT NOT NULL
);
```

**Роли:**

```sql
CREATE TABLE roles (
    role_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    role_name TEXT NOT NULL UNIQUE,
    role_description TEXT
);
```

**Сферы:**

```sql
CREATE TABLE fields (
    field_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    field_name TEXT NOT NULL
);
```

**Компании:**

```postgresql
CREATE TABLE companies (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    field_id INTEGER REFERENCES fields(id)
);
```

Связь между всеми параметрами

```sql
CREATE TABLE user_role_assignments (
    user_id INTEGER REFERENCES users(id),
    role_id INTEGER REFERENCES roles(id),
    field_id INTEGER REFERENCES fields(id),      -- либо это...
    company_id INTEGER REFERENCES companies(id), -- ...либо это
    PRIMARY KEY (user_id, role_id, field_id, company_id),
    CHECK (
        (field_id IS NOT NULL AND company_id IS NULL) OR
        (field_id IS NULL AND company_id IS NOT NULL)
    )
);
```

# Тестирование

# Locust Введение

[Официальная документация](https://docs.locust.io/en/stable/what-is-locust.html)

Locust - open-source python framework для проведения нагрузочного тестирования. Поддерживает распределенные серверы. Используется для тестирования web серверов.

Идея: настраивается поведение пользователей, период теста и имитируется данный процесс. Вроде просто.

Проведение теста, близкого к реальной нагрузке - задача не совсем простая. Проблема в формировании корректного профиля поведения пользователя и в изменении профиля в зависимости от времени (например, профиль пользователя меняется во время распродаж для сайта online магазина). Тестирование это всегда аппроксимация, и близость к реальному поведению сегодня совсем не означает аналогичного через месяц. К тому же продукт обновляется, что приводит к изменениям структуры теста.

Поэтому тестирование подразумевает следующие этапы:

- Сборка логов
- Анализ логов в разрезе клиентов и времени, выявление усредненных шаблонов
- Формирование профилей пользователей
- Формирование тестовых сценариев
- Тестирование
- Сопоставление результатов с последующими реальными значениями

Установка стандартная

```
pip install locust
```

Есть web интерфейс.

# Общая теория

Термин "производительность" очень часто основан на субъективном восприятии конечного пользователя.

KPI можно разделить на две группы: сервис-ориентированные и эффект-ориентированные. Сервис-ориентированные включают в себя доступность и время отклика. Эффект-ориентированные включают пропускную способность и емкость в пределах существующей инфраструктуры.

# Black hat python

# Затраты времени

**Текущие задачи**

Общая идея системы

**Задачи в очереди**

**Следующий релиз**

**Решенные задачи**

<table border="1" id="bkmrk-%D0%94%D0%B0%D1%82%D0%B0-%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0-%D0%A2%D0%B8%D0%BF-%D0%B7%D0%B0%D0%B4%D0%B0" style="width: 102.381%;"><colgroup><col style="width: 12.63%;"></col><col style="width: 12.8766%;"></col><col style="width: 58.4029%;"></col><col style="width: 16.0906%;"></col></colgroup><thead><tr><td class="align-center">Дата</td><td class="align-center">Время</td><td class="align-center">Задача</td><td class="align-center">Тип задачи</td></tr></thead><tbody><tr><td></td><td></td><td></td><td class="align-right">  
</td></tr></tbody></table>

# POC системы управления ботнетом

Архитектура системы

<div drawio-diagram="160"><img src="http://bobrobotirk.ru/uploads/images/drawio/2025-08/drawing-1-1756654810.png" alt=""/></div>

Общие требования

- Модульность
- Управление обновлениями
- Резервирование основного сервера
- Собственное шифрование данных

Основной сервер

- Docker, FastAPI, PostgreSQL
- Простое добавление резервных серверов
- Разграничение по endpoint для клиентов
- Блокировка клиентов при несанкционированной активности
- Динамическое управление фаерволом
- Индивидуальные сборки для каждого клиента
- Настройки и payload интегрированы в исполняемый файл

Admin console

- параметризованная и интерактивная консоли
- многоуровневая консоль в стиле juniper

Протокол взаимодействия сервера и консоли

Клиент

Перспективные опции

- web интерфейс
- Сервис модулей типа apt (например bmm)
- Магазин ботов
- TOR
- Продажа своих серверных мощностей
- Платные модули, оплата за подписку
- Интеграция с криптой
- Межботнет взаимодействие

Модули

<div drawio-diagram="161"><img src="http://bobrobotirk.ru/uploads/images/drawio/2025-08/drawing-1-1756666548.png" alt=""/></div>

# VK

# Стартовая информация

[API](https://dev.vk.com/ru/api/api-requests)

Сервисный ключ предоставляет доступ к группам. Если лимиты на количество запросов (3 в секунду)

Доступ к группам:

В комментариях поста отдается все, включая комментарии комментариев.

# Авторизация

**Общая схема взаимодействия**

Участники:

- VK Mini App (frontend)
- VK (как источник авторизации)
- backend (FastAPI)

Поток:

```
[Frontend (VK Mini App)] → получает launch params от VK, отправляет их на backend
        ↓
[Backend] → проверяет подпись VK, верно - переданный от frontend vk_user_id корректен
        ↓
[Backend] → сохраняет / читает данные из БД
```

Параметры, передаваемые в запросе:

vk\_app\_id, vk\_are\_notifications\_enabled, vk\_is\_app\_user, vk\_is\_favorite, vk\_language, vk\_platform, vk\_ref, vk\_ts, vk\_user\_id

JS:

```
async function auth() {
  try {
    const launchParams = await vkBridge.send('VKWebAppGetLaunchParams');
    console.log('LAUNCH PARAMS:', launchParams);
  } catch (e) {
    console.error('VK AUTH ERROR:', e);
  }
}

async function startApp() {
  await vkBridge.send('VKWebAppInit');
  await auth();
  requestAnimationFrame(loop);
}

startApp();
```

В переменной LaunchParams будут содержаться параметры для запроса на back

# Streamlit

# Основа

Интересная вещь для быстрого создания ненагруженных web приложений

# Общее

# Магические методы

Все объекты. Тип данных - по сути правило операций (функций) для одного типа. Для определения используемых при написании конструкций (например +, \*, ...) используются магические методы. Очень интересная возможность.

**Магические методы**

**1. Инициализация и управление объектом**

<table border="1" id="bkmrk-__new__%28cls%2C-...%29-%D0%A1%D0%BE" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 26.6072%;"></col><col style="width: 73.512%;"></col></colgroup><tbody><tr><td>\_\_new\_\_(cls, ...)</td><td>Создание экземпляра (до \_\_init\_\_)</td></tr><tr><td>\_\_init\_\_(self, ...)</td><td>Инициализация объекта</td></tr><tr><td>\_\_del\_\_(self)</td><td>При удалении объекта (деструктор, вызывается редко)</td></tr><tr><td>\_\_repr\_\_(self)</td><td>Для разработчиков (repr()) — однозначное представление</td></tr><tr><td>\_\_str\_\_(self)</td><td>Для пользователей (str(), print()) — читаемое представление</td></tr><tr><td>\_\_bytes\_\_(self)</td><td>bytes()</td></tr><tr><td>\_\_format\_\_(self, spec)</td><td>format() и f-строки</td></tr><tr><td>\_\_hash\_\_(self)</td><td>hash() (если None — объект нехэшируемый)</td></tr><tr><td>\_\_eq\_\_(self, other)</td><td>==</td></tr><tr><td>\_\_lt\_\_(self, other)</td><td>&lt;</td></tr><tr><td>\_\_le\_\_(self, other)</td><td>&lt;=</td></tr><tr><td>\_\_gt\_\_(self, other)</td><td>&gt;</td></tr><tr><td>\_\_ge\_\_(self, other)</td><td>&gt;=</td></tr><tr><td>\_\_bool\_\_(self)</td><td>bool(), if obj: (если нет — проверяет \_\_len\_\_)</td></tr><tr><td>\_\_len\_\_(self)</td><td>len()</td></tr><tr><td>\_\_contains\_\_(self, item)</td><td>in (если нет — перебирает через \_\_iter\_\_)</td></tr><tr><td>\_\_getitem\_\_(self, key)</td><td>self\[key\], for i in obj:</td></tr><tr><td>\_\_setitem\_\_(self, key, value)</td><td>self\[key\] = value</td></tr><tr><td>\_\_delitem\_\_(self, key)</td><td>del self\[key\]</td></tr><tr><td>\_\_iter\_\_(self)</td><td>Итератор (for x in obj:)</td></tr><tr><td>\_\_next\_\_(self)</td><td>next()</td></tr><tr><td>\_\_reversed\_\_(self)</td><td>reversed()</td></tr></tbody></table>

**2. Арифметические операторы**

Возвращается всегда новая переменная. Существует три варианта в случае арифметических операций:

- Базовый. Вызывается первым, для объекта слева. Объект справа может быть любого типа. Внутри нужно проверять принадлежность к классу для второго аргумента. В случае ошибки / несоответствия типов / ... вызывается NotImplemented. ```python
    class MyClass:
        def __add__(self, other):
            if isinstance(other, MyClass):
                return MyClass(self.value + other.value)
            elif isinstance(other, (int, float)):
                return MyClass(self.value + other)
            else:
                # Не знаю, что делать
                return NotImplemented
    ```
- Если был вызван NotImplemented, право сделать что-то передается объекту справа (правые версии). Если правая версия не определена, то будет TypeError. Но если порядок не важен, можно сделать так: ```python
    class MyClass:
    ...
        def __radd__(self, other):
            return self.__add__(other)
    ```
- Существует способ записи например += Для него может быть отдельный (расширенный) метод. Если не определять, то вызывается соответствующий базовый метод. Можно менять сам объект. Обязательно возвращать объект.

<table border="1" id="bkmrk-__add__%28self%2C-other%29" style="border-collapse: collapse; width: 100%; height: 387.36px;"><colgroup><col style="width: 11.4422%;"></col><col style="width: 24.0763%;"></col><col style="width: 24.7914%;"></col><col style="width: 12.7563%;"></col><col style="width: 21.5703%;"></col><col style="width: 5.24434%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Операция</td><td style="height: 29.7969px;">Базовые методы</td><td style="height: 29.7969px;">Правые версии</td><td>  
</td><td style="height: 29.7969px;">Расширенные версии</td><td style="height: 29.7969px;">  
</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">+</td><td style="height: 29.7969px;">\_\_add\_\_(self, other)</td><td style="height: 29.7969px;">\_\_radd\_\_(self, other)</td><td>other + self</td><td style="height: 29.7969px;">\_\_iadd\_\_(self, other)</td><td style="height: 29.7969px;">+=</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">-</td><td style="height: 29.7969px;">\_\_sub\_\_(self, other)</td><td style="height: 29.7969px;">\_\_rsub\_\_(self, other)</td><td>other - self</td><td style="height: 29.7969px;">\_\_isub\_\_(self, other)</td><td style="height: 29.7969px;">-=</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\*</td><td style="height: 29.7969px;">\_\_mul\_\_(self, other)</td><td style="height: 29.7969px;">\_\_rmul\_\_(self, other)</td><td>other \* self</td><td style="height: 29.7969px;">\_\_imul\_\_(self, other)</td><td style="height: 29.7969px;">\*=</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">/</td><td style="height: 29.7969px;">\_\_truediv\_\_(self, other)</td><td style="height: 29.7969px;">\_\_rtruediv\_\_(self, other)</td><td>other / self</td><td style="height: 29.7969px;">\_\_itruediv\_\_(self, other)</td><td style="height: 29.7969px;">/=</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">//</td><td style="height: 29.7969px;">\_\_floordiv\_\_(self, other)</td><td style="height: 29.7969px;">\_\_rfloordiv\_\_(self, other)</td><td>other // self</td><td style="height: 29.7969px;">\_\_ifloordiv\_\_(self, other)</td><td style="height: 29.7969px;">//=</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">%</td><td style="height: 29.7969px;">\_\_mod\_\_(self, other)</td><td style="height: 29.7969px;">\_\_rmod\_\_(self, other)</td><td>other % self</td><td style="height: 29.7969px;">\_\_imod\_\_(self, other)</td><td style="height: 29.7969px;">%=</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\*\*, pow()</td><td style="height: 29.7969px;">\_\_pow\_\_(self, other\[, modulo\])</td><td style="height: 29.7969px;">\_\_rpow\_\_(self, other)</td><td>other \*\* self</td><td style="height: 29.7969px;">\_\_ipow\_\_(self, other)</td><td style="height: 29.7969px;">\*\*=</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">-obj</td><td style="height: 29.7969px;">\_\_neg\_\_(self)</td><td style="height: 29.7969px;"> </td><td>  
</td><td style="height: 29.7969px;">  
</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">+obj</td><td style="height: 29.7969px;">\_\_pos\_\_(self)</td><td style="height: 29.7969px;"> </td><td>  
</td><td style="height: 29.7969px;">  
</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">abs()</td><td style="height: 29.7969px;">\_\_abs\_\_(self)</td><td style="height: 29.7969px;"> </td><td>  
</td><td style="height: 29.7969px;">  
</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">round()</td><td style="height: 29.7969px;">\_\_round\_\_(self\[, n\])</td><td style="height: 29.7969px;"> </td><td>  
</td><td style="height: 29.7969px;">  
</td><td style="height: 29.7969px;">  
</td></tr></tbody></table>

**3. Битовые операторы**

<table border="1" id="bkmrk-__and__%28self%2C-other%29" style="border-collapse: collapse; width: 100%; height: 208.578px;"><colgroup><col style="width: 12.8725%;"></col><col style="width: 23.1228%;"></col><col style="width: 25.7449%;"></col><col style="width: 27.6418%;"></col><col style="width: 10.6181%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Операция</td><td style="height: 29.7969px;">Базовые методы</td><td style="height: 29.7969px;">Правые версии</td><td style="height: 29.7969px;">Расширенные версии</td><td>  
</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">&amp;</td><td style="height: 29.7969px;">\_\_and\_\_(self, other)</td><td style="height: 29.7969px;">\_\_rand\_\_(self, other)</td><td style="height: 29.7969px;">\_\_iand\_\_(self, other)</td><td>&amp;=</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">|</td><td style="height: 29.7969px;">\_\_or\_\_(self, other)</td><td style="height: 29.7969px;">\_\_ror\_\_(self, other)</td><td style="height: 29.7969px;">\_\_ior\_\_(self, other)</td><td>|=</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">^</td><td style="height: 29.7969px;">\_\_xor\_\_(self, other)</td><td style="height: 29.7969px;">\_\_rxor\_\_(self, other)</td><td style="height: 29.7969px;">\_\_ixor\_\_(self, other)</td><td>^=</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">&lt;&lt;</td><td style="height: 29.7969px;">\_\_lshift\_\_(self, other)</td><td style="height: 29.7969px;">\_\_rlshift\_\_(self, other)</td><td style="height: 29.7969px;">\_\_ilshift\_\_(self, other)</td><td>&lt;&lt;=</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">&gt;&gt;</td><td style="height: 29.7969px;">\_\_rshift\_\_(self, other)</td><td style="height: 29.7969px;">\_\_rrshift\_\_(self, other)</td><td style="height: 29.7969px;">\_\_irshift\_\_(self, other)</td><td>&gt;&gt;=</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">~</td><td style="height: 29.7969px;">\_\_invert\_\_(self)</td><td style="height: 29.7969px;"> </td><td style="height: 29.7969px;">  
</td><td>  
</td></tr></tbody></table>

**4. Методы для контекстного менеджера (``with``)**

<table border="1" id="bkmrk-__enter__%28self%29-%D0%92%D1%85%D0%BE%D0%B4" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 32.0597%;"></col><col style="width: 67.9403%;"></col></colgroup><tbody><tr><td>\_\_enter\_\_(self)</td><td>Вход в контекст</td></tr><tr><td>\_\_exit\_\_(self, exc\_type, exc\_val, exc\_tb)</td><td>Выход из контекста (с обработкой исключений)</td></tr></tbody></table>

**5. Работа с атрибутами**

<table border="1" id="bkmrk-__getattr__%28self%2C-na" style="border-collapse: collapse; width: 100%; height: 178.781px;"><colgroup><col style="width: 32.4405%;"></col><col style="width: 67.6786%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_getattr\_\_(self, name)</td><td style="height: 29.7969px;">при обращении к несуществующему атрибуту</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_setattr\_\_(self, name, value)</td><td style="height: 29.7969px;">при установке любого атрибута</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_delattr\_\_(self, name)</td><td style="height: 29.7969px;">del obj.name</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_getattribute\_\_(self, name)</td><td style="height: 29.7969px;">при обращении к ЛЮБОМУ атрибуту (осторожно, рекурсия!), редко используется</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_dir\_\_(self)</td><td style="height: 29.7969px;">dir()</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_hasattr\_\_(self, name)</td><td style="height: 29.7969px;">hasattr() (не нужен — \_\_getattr\_\_ обработает)</td></tr></tbody></table>

**6. Вызов объекта как функции**

\_\_call\_\_(self, \*args, \*\*kwargs) # obj()

**7. Работа с классами и метаклассами**

<table border="1" id="bkmrk-__init_subclass__%28cl" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 33.2739%;"></col><col style="width: 66.8453%;"></col></colgroup><tbody><tr><td>\_\_init\_subclass\_\_(cls, \*\*kwargs)</td><td>при создании подкласса</td></tr><tr><td>\_\_set\_name\_\_(self, owner, name)</td><td>при создании дескриптора в классе</td></tr><tr><td>\_\_prepare\_\_(name, bases, \*\*kwargs)</td><td>метакласс: подготовка пространства имён</td></tr><tr><td>\_\_instancecheck\_\_(self, instance)</td><td>isinstance() (для метаклассов)</td></tr><tr><td>\_\_subclasscheck\_\_(self, subclass)</td><td>issubclass() (для метаклассов)</td></tr></tbody></table>

 **8. Дескрипторы (управление атрибутами другого класса)**

\_\_get\_\_(self, instance, owner) # получить атрибут  
\_\_set\_\_(self, instance, value) # установить атрибут  
\_\_delete\_\_(self, instance) # удалить атрибут

**9. Сериализация**

<table border="1" id="bkmrk-__reduce__%28self%29-pic" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 34.4644%;"></col><col style="width: 65.6548%;"></col></colgroup><tbody><tr><td>\_\_reduce\_\_(self)</td><td>pickle (возвращает (callable, args\[, state\]))</td></tr><tr><td>\_\_reduce\_ex\_\_(self, protocol)</td><td>расширенная версия для pickle</td></tr><tr><td>\_\_getstate\_\_(self)</td><td>что сохранять в pickle</td></tr><tr><td>\_\_setstate\_\_(self, state)</td><td>восстановление из pickle</td></tr></tbody></table>

**10. Математические и другие**

<table border="1" id="bkmrk-__complex__%28self%29-co" style="border-collapse: collapse; width: 100%; height: 297.969px;"><colgroup><col style="width: 34.7025%;"></col><col style="width: 65.4167%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_complex\_\_(self)</td><td style="height: 29.7969px;">complex()</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_int\_\_(self)</td><td style="height: 29.7969px;">int()</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_float\_\_(self)</td><td style="height: 29.7969px;">float()</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_index\_\_(self)</td><td style="height: 29.7969px;">для преобразования в int (для срезов, bin(), hex())</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_trunc\_\_(self)</td><td style="height: 29.7969px;">math.trunc()</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_floor\_\_(self)</td><td style="height: 29.7969px;">math.floor()</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_ceil\_\_(self)</td><td style="height: 29.7969px;">math.ceil()</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_matmul\_\_(self, other)</td><td style="height: 29.7969px;">@ (матричное умножение в Python 3.5+)</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_rmatmul\_\_(self, other)</td><td style="height: 29.7969px;">right @</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">\_\_imatmul\_\_(self, other)</td><td style="height: 29.7969px;">@=</td></tr></tbody></table>

**11. Асинхронные методы (async/await)**

<table border="1" id="bkmrk-__await__%28self%29-awai" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 34.9405%;"></col><col style="width: 65.1786%;"></col></colgroup><tbody><tr><td>\_\_await\_\_(self)</td><td>await obj</td></tr><tr><td>\_\_aiter\_\_(self)</td><td>async for </td></tr><tr><td>\_\_anext\_\_(self)</td><td>async next()</td></tr><tr><td>\_\_aenter\_\_(self)</td><td>async with</td></tr><tr><td>\_\_aexit\_\_(self, exc\_type, exc\_val, exc\_tb)</td><td>async with exit</td></tr></tbody></table>

**Самые частые методы:**  
`\_\_init\_\_`, `\_\_str\_\_`, `\_\_repr\_\_` — 90% обычных классов  
`\_\_add\_\_`, `\_\_radd\_\_`, `\_\_iadd\_\_` — для своего класса с `+`  
`\_\_getitem\_\_`, `\_\_setitem\_\_`, `\_\_len\_\_` — чтобы объект вёл себя как коллекция  
`\_\_enter\_\_`, `\_\_exit\_\_` — для `with`  
`\_\_call\_\_` — сделать объект вызываемым (как функция)  
`\_\_eq\_\_`, `\_\_lt\_\_`, `\_\_hash\_\_` — для сравнения и словарей