Python 3.10 match & улучшенная проверка типов

В Python 3.10 произошли большие изменения, из всего, что я точно буру себе это match и улучшенную проверку типов.

def get_color(color: str) -> str:
    match color:
        case "black":
            return "#fffff"
        case "white":
            return "#00000"
        case _:   #default
            return "#green"

Проверка типов в Python 3.10

#  Новый способ проверки типов
def some(value: int | float) -> int | float:
    return value

#  Старый способ
def some(value: Union[int, float]) -> Union[int, float]:
    return value

В productiuon использую только в одном проекте, так как на хостинге из коробки ещё нет доступного Python 3.10, пришлось собрать из исходников. Не везде есть такая возможность. Ещё на всех рабочих машинах пришлось поставить Python 3.10.

В общем пользоваться можно, это примерно то же, что и при переходе на Python 3.6 из-за async, начав использовать новые фичи теряешь обратную совместимость.

Django templates короткая запись for empty

Так вышло, что не знал о более короткой записи, если список пуст. Городил if else endif, а надо было делать так:

{% for article in articles %} 
    <h1>{{ article.name }}</h1> 
    {{ article.text|safe }}
{% empty %} 
    <h1>Добавьте статью</h1> 
    <p>Ещё не написано не одной статьи</p>
{% endfor %}

Старый вариант с if else.

{% if articles %}
    {% for article in articles %} 
        <h1>{{ article.name }}</h1> 
        {{ article.text|safe }}
    {% endfor %} 
{% else %}
    <h1>Добавьте статью</h1> 
    <p>Ещё не написано не одной статьи</p>
{% endif %}

For empty в Jinja2

Увы в Jinja2 я не нашел возможности использования похожей конструкции for. В документации написано про необходимость использовать комбинации if else.

Flask 2.0.X и axios — AJAX проще простого

Раньше я использовал активно связку jQuery для отправки Ajax запроса на сервер. Например так обрабатывал форму обратной связи или онлайн заказ товара.

Время не стоит на месте с использованием библиотеки VueJS я познакомился с библиотекой axios. И стал использовать не только в проектах VueJS но и просто с Flask и Django.

Flask + axios

Напишу подробно, с начало создаю виртуальное окружение, затем ставлю Flask.

mkdir flask-axios
cd flask-axios
python3 -m venv venv
source venv/bin/activate
pip install pip --upgrade
pip install Flask==2.0.1

Далее создаю директорию templates и шаблоны base.html, index.html, файл проекта app.py и дополнительные файлы app.js в директории static.

Создаю переменную окружения FLASK_ENV=development для того, что бы запускать проект в режиме отладки (DEBUG). В данном режиме приложение будет автоматический производить рестарт при изменении исходного кода.

Ещё одна переменная окружения FLASK_APP=app.py приложение Flask, запуск по команде flask run.

mkdir -p templates static/js
touch templates/{base,index}.html
touch static/js/callback.js
touch {app,forms}.py

export FLASK_ENV=development
export FLASK_APP=app.py

Сам проект на Flask

Файл app.py

from flask import Flask
from flask import render_template
import json
#  форма для обратной сзвязи
from forms import CallBackForm

app = Flask(__name__)
SECRET_KEY = '12345'
app.config['SECRET_KEY'] = SECRET_KEY


@app.route("/")
def index():
    form = CallBackForm()
    return render_template('index.html',
                           title="Пример отправки",
                           form=form)


@app.route("/callback", methods=['POST'])
def callback():
    form = CallBackForm()
    if form.validate_on_submit():
        #  функции отправить почту, записать в БД и т. д.
        return json.dumps({'success': 'true', 'msg': 'Ждите звонка!'})
    else:
        #  обработать ошибку
        return json.dumps({'success': 'false', 'msg': 'Ошибка на сервере!'})

В строке 20 происходит обработка нажатия на кнопку (index.html строка 7).

Благодаря использованию формы, происходит нормальная валидация и недоступные значения успешно обрабатываются.

Форма

Файл forms.py

from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired
from wtforms.validators import Length


class CallBackForm(FlaskForm):
    name = StringField('Имя', validators=[DataRequired()])
    phone = StringField('Телефон', validators=[DataRequired(), Length(min=4)])

В сроке 9 параметр Length(min=4) указывает, если количество знаков меньше 4, валидация не пройдёт.

Верстка

Файл base.html в директории templates

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Flask 2.0.X axios simple - {{ title }}</title>
        <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
        {% block scripts %}
        {% endblock scripts %}
    </head>
    <body>
        {% block content %}
        {% endblock content %}
    </body>
</html>

В сроке 6 и 7 подключаю через cnd — JQuery и Axios.

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

Верстка

Файл index.html в директории templates

{% extends "base.html" %}
{% block content %}
    <form action="/callback" id="form1" method="post">
        {{ form.csrf_token }}
        {{ form.name.label }} {{ form.name() }}
        {{ form.phone.label }} {{ form.phone() }}
        <input type="submit" value="Отправить"/>
    </form>
    <div id="msg"></div>
{% endblock %}

{% block scripts%}
    {{ super() }}
    <script src="{{ url_for('static', filename='js/callback.js') }}"></script>
{% endblock %}

В строке 4 csrf_token иначе не пройдёт валидацию.

JavaScript

Файл callback.html в директории static/js

/* переопределить поведение кнопки "Отправить" */
$(document).ready(function () {
    $("#form1").submit(function( event ) {
        CallBackForm("form1", "msg");
        event.preventDefault();
    });
});

/* отправка данных на сервер по AJAX */
function CallBackForm(form, msg){
    let from = new FormData();
    from.append('name', $( "#name" ).val());
    from.append('phone', $( "#phone" ).val());
    from.append('csrf_token', $( "#csrf_token" ).val());
    axios({
        url: '/callback',
        method: 'post',
        data: from,
    })
    .then(function (response) {
        $( '#' + msg ).html(response.data.msg);
        if (response.data.success == 'true') {
            $( "#" + form ).trigger('reset');
        }
        else
        {
            alert("Что-то пошло не так!");
            console.log("Ошибка");
        }
    })
    .catch(function (error) {
        console.log(error);
    });
};

В строках 12-14 добавляются данные из формы для отправки.  Строка 21 записывает ответ от сервера (файл app.py строка 25 или 28) в зависимости от правильности ввода.

Готовый код примера

git clone https://github.com/ivanov-s/flask-axios.git

Книга -Кафе на краю земли

В основном читаю техническую и бизнес литературу, иногда хочется прочитать, что-то для себя.

Книга Джон П. Стрелеки «Кафе на краю земли» – одна из тех которые я решил прочитать для развлечения. Не надеялся на профит, но получил его 😊.

Книга небольшая всего 150 страниц. В ней 3 вопроса, на которые не каждый может ответить просто и честно.

По ходу, чтения возникают другие вопросы, например «Как?».

Книга для меня запоздала на 4 месяца (события в жизни сложились так), за то я почерпнул правильность самостоятельно принятых решений.

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

Книга относится к мотивационным, много хвалебных отзывов. Но по факту всё зависит от человека, от поступков. Тут нужно ответить честно на вопросы, можно не вставая – глядишь и жизнь начнёт меняться.

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

VueJS конфликтует с Jinja2 и Django template

vuejs logo jinja2 logo django logo

В приложениях VueJS используется {{ varname }} для обработки шаблонов. В Jinja2 и Django шаблонах при рендеринге шаблона также используются фигурные скобки. Из-за этого возникает конфликт. Есть 3 способа решения проблемы.

VueJS — заменить фигурные скобки на что то другое

<!-- js файл -->
new Vue({
  delimiters: ['{*', '*}'],
  data: {
    VueVAR: 'test'
  }
})
<!-- html файл -->
<div id="app">
  {* VueVAR *} <!-- vuejs -->
  {{ name }}  <!-- переменная в Jinja2 или Django шаблоне переданная из views.py -->
</dev>

Использовать RAW в html для Jinja2

<!-- js файл -->
new Vue({
  data: {
    VueVAR: 'test'
  }
})
<!-- html файл -->
<div id="app">
  {{ VueVAR }}  <!-- vuejs -->
  {% raw %}
     {{ name }}  <!-- переменная в Jinja2 из views.py -->
  {% raw %}
</dev>

Для Django templates поменять фигурные скобки

# в файле setting.py
VARIABLE_TAG_START = '{*'
VARIABLE_TAG_END = '*}'
<!-- js файл -->
new Vue({
  data: {
    VueVAR: 'test'
  }
})
<!-- html файл -->
<div id="app">
{{ VueVAR }} <!-- vuejs -->
{* name *} <!-- переменная в Django template из views.py -->
</dev>

Ещё для Django использовать тег {% verbatim %}

<!-- html файл -->
<div id="app">
{% verbatim %}
{{ VueVAR }} <!-- vuejs -->
{% endverbatim %}
{{ name }} <!-- переменная в Django template из views.py -->
</dev>

Для Django последний способ рекомендуют использовать в большинстве случаев.

Посредник — посредственный

Это пост не про программирования. А про посредственных посредников.

Переезд №1

Ситуация дума всем очевидно, куда не обратись, везде агрегаторы и посредники.

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

Приехал водитель на 10 минут раньше времени, а грузчиков нет. Я спрашиваю где они? Водитель пояснил, что грузчики это отдельная организация, и они добираются своим ходом сами.

Грузчик приехал один вовремя, вполне адекватный человек, мужчина лет 40-45, одет по форме, комбинезон, удобная обувь, второй опоздал, мужчина лет 50 в резиновых сапогах, видно, что всю жизнь пьёт.

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

Долго, дорого и не качественно.

Переезд №2

Второй раз я переезжал уже зная, что кому попало звонить не нужно. И что оказалось? Везде операторы, или агрегаторы, нормальной организации так просто не найти, собственно как и раньше.

Позвонил оператору одной из фирм, сразу сказал, что если грузчики будут «алкоголиками», отменю заказ, мне нужны настоящая команда!

Уточнил у оператора насчёт машины, и заказал на 100 рублей дороже в час, за то большего объёма. (Легче и быстрей загружать).

Машина приехала на 20 минут

Грузчики пришли на 10 минут раньше, спросили меня где можно переодеться, одели спец одежду, удобную обувь, 2 молодых человека лет 25-30, спортивного телосложения.

И тут началось, быстро чётко, всё они сделали правильно, загрузили, выгрузили, подняли на этаж, переоделись и ушли.

Переезд планировал заранее, выбрал выходной день, что бы не стоять в пробках.

Зная, что машина и грузчики оплачиваются отдельная, машину удалось отпустить раньше (с начало выгрузили всё на улицу, потом заносили домой). 

Выводы

Тогда я не понял, в чём дело. Сильно мой разум был затуманен, теперь мне очевидно, есть 2 правила которые сэкономят силы, нервы, время и деньги:

  1. Свои действия — планируй и действую от плана.
  2. Уточняй все важные детали, и договаривайся сразу о увольнении при несоблюдение договорённостей.

В Vue-awesome-swiper не работают стрелки в слайдере

Столкнулся при использовании vue awesome swiper с неработающими стрелками в слайдере и пагинатором.

Оказалось проблема в версии новая версия swiper 6.1.1 не совместима с текущей версией плагина vue-awesome-swiper 4.1.1.

Делал как в документации, но только параметр loop подхватывался, остальные игнорировались:

        data: {
            swiperOption: {
                navigation: {
                    nextEl: '.swiper-button-next',
                    prevEl: '.swiper-button-prev'
                },
                loop: true,
                autoplay: {
                    delay: 3000,
                    stopOnLastSlide: false,
                },
            }
        }

Откатился на версию «swiper»: «^5.4.5» всё заработало.

Vim и Git показывать измененные строки

Очень удобно при работе с файлом видеть, какие строки были изменены но ещё не добавлены в Git.

Для этого прекрасно подходит плагин tpope/vim-fugitive.

Ставлю его через Vundle добавив в .vimrc строчку:

Plugin 'tpope/vim-fugitive'

Установка:

# перечитать файл
:source ~/.vim/vimrc
# установить плагин
:VundleInstall

Python3 установка pip пакетов offline

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

На машине с интернетом

python3 -m venv vevn
source venv/bin/active
pip install pip --upgrade
# директория для скачивания пакетов
mkdir pkg
cd pkg
# отдельно скачиваю последнею версию pip
pip download pip
# скачиваю необходимые пакеты с зависимостями
pip download -r ../requirements.txt

На машине без интернета

python3 -m venv vevn
source venv/bin/active
# устанавливаю ранее скаченный pip (версия может быть другая)
pip install pkg/pip-20.1-py2.py3-none-any.whl
# установка пакетов из списка requirements.txt, пакеты должны лежать в pkg (директория)
pip install --no-index --find-links pkg -r requirements.txt

Результат выполнения

# вывод консоли у меня
pip install --no-index --find-links pkg -r requirements.txt 
Looking in links: pkg
Processing ./pkg/Flask-1.1.2-py2.py3-none-any.whl
Processing ./pkg/Flask_WTF-0.14.3-py2.py3-none-any.whl
Processing ./pkg/et_xmlfile-1.0.1.tar.gz
Processing ./pkg/openpyxl-3.0.3.tar.gz
Processing ./pkg/jdcal-1.4.1-py2.py3-none-any.whl
Processing ./pkg/pylint-2.5.2-py3-none-any.whl
Processing ./pkg/itsdangerous-1.1.0-py2.py3-none-any.whl
Processing ./pkg/Jinja2-2.11.2-py2.py3-none-any.whl
Processing ./pkg/Werkzeug-1.0.1-py2.py3-none-any.whl
Processing ./pkg/click-7.1.2-py2.py3-none-any.whl
Processing ./pkg/WTForms-2.3.1-py2.py3-none-any.whl
Processing ./pkg/mccabe-0.6.1-py2.py3-none-any.whl
Processing ./pkg/isort-4.3.21-py2.py3-none-any.whl
Processing ./pkg/toml-0.10.1-py2.py3-none-any.whl
Processing ./pkg/astroid-2.4.1-py3-none-any.whl
Processing ./pkg/MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl
Processing ./pkg/typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl
Processing ./pkg/wrapt-1.12.1.tar.gz
Processing ./pkg/lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl
Processing ./pkg/six-1.14.0-py2.py3-none-any.whl
Could not build wheels for et-xmlfile, since package 'wheel' is not installed.
Could not build wheels for openpyxl, since package 'wheel' is not installed.
Could not build wheels for wrapt, since package 'wheel' is not installed.
Installing collected packages: itsdangerous, MarkupSafe, Jinja2, Werkzeug, click, Flask, WTForms, Flask-WTF, et-xmlfile, jdcal, openpyxl, mccabe, isort, toml, typed-ast, wrapt, lazy-object-proxy, six, astroid, pylint
    Running setup.py install for et-xmlfile ... done
    Running setup.py install for openpyxl ... done
    Running setup.py install for wrapt ... done
Successfully installed Flask-1.1.2 Flask-WTF-0.14.3 Jinja2-2.11.2 MarkupSafe-1.1.1 WTForms-2.3.1 Werkzeug-1.0.1 astroid-2.4.1 click-7.1.2 et-xmlfile-1.0.1 isort-4.3.21 itsdangerous-1.1.0 jdcal-1.4.1 lazy-object-proxy-1.4.3 mccabe-0.6.1 openpyxl-3.0.3 pylint-2.5.2 six-1.14.0 toml-0.10.1 typed-ast-1.4.1 wrapt-1.12.1

Мой файл requirements.txt для примера

Flask==1.1.2
Flask-WTF==0.14.3
et-xmlfile==1.0.1
openpyxl==3.0.3
jdcal==1.4.1
pylint==2.5.2

Установка из tar.gz или whl

# установка одного пакета из архива
pip install ./pkg-name.tar.gz

Python3 и MySQL что установить?

Для работы нужна библиотека mysqlclient. Установка библиотеки mysqlclient для работы с MySQL и Python3 происходит следующим образом (в Debian 9)

sudo apt install python3-dev libsqlclient-dev default-libmysqlclient-dev
python3 -m venv venv
source venv/bin/activate
pip install mysqlclient

В Debian 10 buster и Python3.7 с MySQL (MariaDB)

Я использую библиотеку PyMySQL. Если в проекте используется SQLAlchemy то в SQLALCHEMY_DATABASE_URI mysql://… нужно поменять на mysql+pymysql://…

pip install PyMySQL

Для Python2 использовать старый модуль MySQL-python.