Штош. В этой статье я научу вас делать кроссплатформенное приложение генератор паролей с графическим интерфейсом. Мы будем использовать язык Python и библиотеку PySide6 - привязку к инструментарию фреймворка Qt.

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

В статье я постарался затронуть все моменты создания и сборки приложения. Ознакомиться с проектом можно на GitHub.

Устанавливаем все необходимое

Скачайте и установите Python последней версии (желательно). Приложение должно работать с Python версии 3.7+

Создадим директорию проекта password-generator и виртуальное окружение, в моем случае venvPasswordGenerator

Установим библиотеку PySide6.

pip install pyside6

Документация Qt for Python.

Создаем интерфейс

Переходим к созданию интерфейса. Для этого нам понадобится программа Qt Designer. Её можно найти в папке установленной ранее библиотеки.

venv*/Lib/site-packages/PySide6/designer

Окно новой формы в программе Qt Designer
Окно новой формы в программе Qt Designer

Создаем MainWindow. Убираем menubar и statusbar

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

Сначала закинем 4 Horizontal Layout для компоновки элементов. Скопировать элемент можно перетаскиванием с зажатой клавишей Ctrl.

Выберем вертикальное расположение для центрального виджета. centralwidget -> Lay Out Vertically

Компоновка выбора символов

Закинем одну кнопку выбора символов в нижнюю горизонтальную компоновку.

Чтобы кнопка имела 2 состояния, нужно поставить свойство checkable

Скопируем кнопку 3 раза. Поставим текст. Я думаю, так должно быть понятно, за какой набор символов отвечает каждая отдельная кнопка.

Дадим элементам осмысленные имена.

Проставим свойство checked на всех символах, кроме специальных.

Компоновка длины пароля

Здесь бу��ет указываться длина пароля с помощью слайдера и счетчика. Компоновку я назову layout_length, слайдер - slider_length, счетчик - spinbox_length

Компоновка энтропии и сложности пароля

Закидываем лейбл.

Ставим горизонтальное выравнивание по центру.

Копируем элемент и вводим примерный текст.

Компоновка пароля

Перед нами встает интересная задача. Как поместить кнопку видимости пароля в элемент Line Edit? К сожалению, встроенных в Qt Designer методов для такого действия я не нашел. Лучшим решением я посчитал поместить поле и кнопку рядом во фрейме. Если вы знаете способ лучше, поделитесь в комментариях.

Widget Box -> Containers -> Frame

Поставим горизонтальное выравнивание для фрейма.

Добавим 2 кнопки в горизонтальную компоновку, а не во фрейм. Перетаскивать элемент нужно в самый правый край компоновки.

Еще я захотел поставить сверху изображение замка. На удивление, в Qt Designer это тоже реализовано через одно место, в котором никто не хочет побывать. Есть способ с помощью лейбла и его свойства pixmap, но в таком случае нельзя изменять размер изображения. Поэтому я решил сделать через иконку заблокированной кнопки.

Иконки Material Icons

Возьмем иконки для приложения. Я буду использовать бесплатные Material Icons.

Скачаем белую векторную иконку замка. Я выберу первый вид - Outlined. Вы можете взять другой, как вам приятнее.

Для повторной генерации возьмем Refresh. Копирование в буфер обмена - Content Copy. Видимость пароля - Visibility и Visibility Off. Для иконки приложения возьмем черную иконку ключа в формате png и сконвертируем её в формат для иконок ico

Иконки в Windows File Explorer
Иконки в Windows File Explorer

Создаем файл ресурсов

Создадим файл ресурсов, который будет хранить иконки. Позже мы сконвертируем его в Python код. Resource Browser -> Edit Resources -> New Resource File -> resources.qrc

Добавляем префикс icons

Добавляем все файлы иконок.

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

В заблокированную кнопку ставим иконку замка с помощью свойства icon -> Disabled On. Выбираем иконку с помощью Choose Resource

Для кнопки видимости выбираем свойство checkable, так как она тоже имеет 2 состояния. Сразу проставим checked по умолчанию.

В Normal Off берем иконку невидимости, в Normal On - видимости.

Остальные иконки проставляем в Normal On

Стилизуем интерфейс

Для стилизации приложения я буду использовать урезанный язык CSS. Писать советую в каком-нибудь редакторе. Я пишу в Visual Studio Code, конечно же.

Главное окно

Создадим файл QMainWindow.css. Для главного виджета поставим цвет фона #121212, белый цвет текста, шрифт Verdana с размером 16 поинтов и внешний отступ 10 пикселей.

QWidget {
    background-color: #121212;
    color: white;
    font-family: Verdana;
    font-size: 16pt;
    margin: 10px;
}

Для кнопок ставим сплошную серую границу 2 пикселя с радиусом границы 5 пикселей.

QPushButton {
    border: 2px solid gray;
    border-radius: 5px;
}

Вставим код в элемент MainWindow с помощью опции Change styleSheet

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

QPushButton#btn_lower,
#btn_upper,
#btn_digits,
#btn_special {
    padding: 10px;
}

При наведении на кнопку цвет границ будет меняться на зеленый #090.

QPushButton:hover {
    border-color: #090;
}

При нажатии граница будет увеличиваться до 4 пикселей.

QPushButton:pressed {
    border: 4px solid #090;
    border-radius: 5px;
}

Для отмеченного состояния кнопки поставим на фон темно-зеленый цвет #006300.

QPushButton:checked {
    background-color: #006300;
    border-color: #090;
}
QMainWindow.css
QWidget {
    background-color: #121212;
    color: white;
    font-family: Verdana;
    font-size: 16pt;
    margin: 10px;
}

QPushButton {
    border: 2px solid gray;
    border-radius: 5px;
}

QPushButton#btn_lower,
#btn_upper,
#btn_digits,
#btn_special {
    padding: 10px;
}

QPushButton:hover {
    border-color: #090;
}

QPushButton:pressed {
    border: 4px solid #090;
    border-radius: 5px;
}

QPushButton:checked {
    background-color: #006300;
    border-color: #090;
}

Чтобы посмотреть превью интерфейса, нужно нажать сочетание клавиш Ctrl + R

Иконка замка

Уберем границы с помощью стиля border: none;

Поставим размер 100 на 100 пикселей.

Фрейм пароля

Поставим для фрейма пароля такую же границу, как и для кнопок. Уберем отступ до правого элемента.

QFrame {
    border: 2px solid gray;
    border-radius: 5px;
    margin-right: 0;
}

QFrame:hover {
    border-color: #090;
}

Поставим вертикальную политику Maximum

Поле пароля

Для поля пароля уберем границы и внешние отступы. Поставим размер шрифта 20 поинтов.

QLineEdit {
    border: none;
    margin: 0;
    font-size: 20pt;
}

Кнопка видимости

Для кнопки видимости пароля так же уберем границы и внешние отступы. Еще поставим прозрачный фон и размер 30 на 30 пикселей.

QPushButton {
    border: none;
    margin: 0;
    background-color: transparent;
}

Кнопка генерации пароля

Для кнопки генерации пароля поставим размер иконки 52 на 52 пикселя. Так границы кнопки будут идти ровно по границам фрейма. Уберем правый и левый внешний отступ.

QPushButton {
    margin-right: 0;
    margin-left: 0;
}

Кнопка копирования пароля в буфер обмена

Почему-то иконка копирования очень плотно прилегает к границам. Я нашел размер 42 на 42 пикселя и внутренний отступ 5 пикселей. Уберем отступ до левого элемента.

QPushButton {
    padding: 5px;
    margin-left: 0;
}

Слайдер

Для псевдоэлемента groove уберем цвет и поставим высоту 5 пикселей. Грув - это линия слайдера, или "желобок", "канавка", "борозда", если верить гугл переводчику.

QSlider::groove:horizontal {
    background-color: transparent;
    height: 5px;
}

Слева от ручки слайдера будет зеленый цвет. Для этого используется селектор псевдоэлемента sub-page

QSlider::sub-page:horizontal {
    background-color: #090;
}

Справа от ручки будет серый цвет. Псевдоэлемент add-page

QSlider::add-page:horizontal {
    background-color: gray;
}

Ручку слайдера я сделаю белой с шириной 22 пикселя, радиусом 10 пикселей и внешними отступами снизу и сверху по -8 пикселей.

QSlider::handle:horizontal {
    background-color: white;
    width: 22px;
    border-radius: 10px;
    margin-top: -8px;
    margin-bottom: -8px;
}

Поставим максимум 100 пикселей и значение по умолчанию 12.

Счетчик

Счетчик похож на кнопки.

QSpinBox {
    border: 2px solid gray;
    border-radius: 5px;
    background: transparent;
    padding: 2px;
}

QSpinBox:hover {
    border-color: #009900;
}

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

Лейблы информации

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

Делаем последние штрихи

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

Напишем название приложения в свойстве windowTitle. Поставим иконку в windowIcon

Поставим курсор Pointing Hand для кнопок и слайдера.

Уберем демонстрационный текст из лейблов. Он будет генерироваться программой.

Штош. Интерфейс готов, переходим к следующему этапу.

Конвертируем файл ресурсов и интерфейса

Для того, чтобы сконвертировать файл ресурсов, нужно написать в терминал pyside6-rcc, название файла ресурсов, флаг -o и название файла на выходе.

pyside6-rcc resources.qrc -o resources.py

Файл интерфейса конвертируется таким же образом, только с помощью приложения pyside6-uic

pyside6-uic main.ui -o ui_main.py

Этот файл хочет, чтобы я добавлял в конце _rc к файлу ресурсов, а я не хочу. Поменяем import resources_rc на import resources

Пишем код

Рекомендую писать код в среде разработки PyCharm или Visual Studio Code. Vim-еры, не бейте.

Для начала создадим модуль приложения - app.py. Я вставлю готовый сниппет для запуска приложения с файлом дизайна.

Сниппет
import sys

from PySide6.QtWidgets import QApplication, QMainWindow

from ui_main import Ui_MainWindow


class App(QMainWindow):
    def __init__(self):
        super(App, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)


if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = App()
    window.show()

    sys.exit(app.exec())

Поменяю название класса App на PasswordGenerator

Модуль кнопок

Давайте сделаем отдельный модуль с привязкой сущностей к кнопкам - buttons.py. В стандартной библиотеке string есть строковые переменные для пула символов. Возьмем ascii_lowercase, ascii_uppercase, digits и punctuation.

from string import ascii_lowercase, ascii_uppercase, digits, punctuation

Вот так они выглядят.

ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
digits = '0123456789'
punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""

Давайте сделаем перечисление, которое привязывает кнопки выбора символов к этим строковым переменным.

from enum import Enum

class Characters(Enum):
    btn_upper = ascii_uppercase
    btn_lower = ascii_lowercase
    btn_digits = digits
    btn_special = punctuation

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

CHARACTER_NUMBER = {
    'btn_lower': len(Characters.btn_lower.value),
    'btn_upper': len(Characters.btn_upper.value),
    'btn_digits': len(Characters.btn_digits.value),
    'btn_special': len(Characters.btn_special.value)
}

Модуль генерации пароля

Давайте сделаем модуль генерации паролей без привязки к интерфейсу - password.py. Напишем функцию создания нового пароля. Аргументами будут длина пароля и все используемые символы в виде строки.

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

import secrets

def create_new(length: int, characters: str) -> str:
    return "".join(secrets.choice(characters) for _ in range(length))

Получаем энтропию пароля

Теперь сделаем функцию для получения энтропии. В нее нужно будет передавать длину пароля и количество символов. Формула энтропии пароля выглядит следующим образом:

H=log_2N^L=L \times log_2N

где N — это количество возможных символов, а L — количество символов в пароле. H измеряется в битах.

Давайте импортируем логарифм по основанию 2 из библиотеки math.

from math import log2

Возвращаем значение, округленное до 2 знаков после запятой.

def get_entropy(length: int, character_number: int) -> float:
    entropy = length * log2(character_number)
    return round(entropy, 2)

Перечисление для сложности пароля

Создадим целочисленное перечисление сложности пароля к его энтропии по нижней границе. Я не нашел единого стандарта, в разных генераторах сила пароля считается по-разному, поэтому сделаю так: ничтожный пароль от 0 до 30 бит, слабый после 30, хороший после 50, сильный после 70 и замечательный после 120.

from enum import IntEnum

class StrengthToEntropy(IntEnum):
    Pathetic = 0
    Weak = 30
    Good = 50
    Strong = 70
    Excellent = 120

Связываем значения слайдера и счетчика

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

  def connect_slider_to_spinbox(self) -> None:
      self.ui.slider_length.valueChanged.connect(self.ui.spinbox_length.setValue)
      self.ui.spinbox_length.valueChanged.connect(self.ui.slider_length.setValue)

Не забудьте прописать метод в конструкторе класса.

class PasswordGenerator(QMainWindow):
    def __init__(self):
        ...
        self.connect_slider_to_spinbox()

Получаем символы для пароля

Создадим новый метод для получения символов отмеченных кнопок. Объявим пустую строку chars. Для каждой кнопки в перечислении добавляем её символы в строку, если она отмечена.

def get_characters(self) -> str:
    chars = ""

    for btn in buttons.Characters:
        if getattr(self.ui, btn.name).isChecked():
            chars += btn.value

    return chars

Ставим пароль

Ну и наконец-то, метод установки пароля. Ставим в элемент line_password новый пароль с помощью метода setText. Длину берем из слайдера или счетчика, не имеет значения.

def set_password(self) -> None:
    self.ui.line_password.setText(
        password.create_new(
            length=self.ui.slider_length.value(),
            characters=self.get_characters())
    )

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

Соединим изменение слайдера или счетчика с установкой пароля.

def connect_slider_to_spinbox(self) -> None:
    ...
    self.ui.spinbox_length.valueChanged.connect(self.set_password)

Позалипаем на быстрое изменение паролей :3

Когда не нажата ни одна кнопка символов, происходит IndexError. Обработаем этот случай, просто очищая поле пароля.

def set_password(self) -> None:
    try:
        self.ui.line_password.setText(
            password.create_new(
                length=self.ui.slider_length.value(),
                characters=self.get_characters())
        )
    except IndexError:
        self.ui.line_password.clear()

Получаем количество символов

Принцип тот же, что и у получения строки символов, только в этот раз мы работаем со словарем, а не с перечислением.

def get_character_number(self) -> int:
    num = 0

    for btn in buttons.CHARACTER_NUMBER.items():
        if getattr(self.ui, btn[0]).isChecked():
            num += btn[1]

    return num

Ставим энтропию пароля

Делаем метод для лейбла энтропии. Берем длину из текста пароля, а не из слайдера или счетчика, так нужно. Ставим в лейбл выражение с помощью f-строки.

def set_entropy(self) -> None:
    length = len(self.ui.line_password.text())
    char_num = self.get_character_number()

    self.ui.label_entropy.setText(
        f"Entropy: {password.get_entropy(length, char_num)} bit"
    )

Добавим установку энтропии в конце метода установки пароля.

def set_password(self) -> None:
    ...

    self.set_entropy()

Без отмеченных кнопок символов получается ValueError. Обработаем эту ошибку в функции get_entropy модуля password. В таком случае она будет возвращать 0.0.

def get_entropy(length: int, character_number: int) -> float:
    try:
        entropy = length * log2(character_number)
    except ValueError:
        return 0.0

    return round(entropy, 2)

Ставим сообщение о сложности пароля

Теперь сделаем метод для лейбла сложности пароля. Для каждой сложности из перечисления сравниваем энтропию со значением.

def set_strength(self) -> None:
    length = len(self.ui.line_password.text())
    char_num = self.get_character_number()

    for strength in password.StrengthToEntropy:
        if password.get_entropy(length, char_num) >= strength.value:
            self.ui.label_strength.setText(f"Strength: {strength.name}")

Добавим в конец метода установки пароля.

Ставим генерацию пароля на кнопки

Запишу в модуль buttons кортеж с именами кнопок, при нажатии на которые будет генерироваться новый пароль.

GENERATE_PASSWORD = (
    'btn_refresh', 'btn_lower', 'btn_upper', 'btn_digits', 'btn_special'
)

Теперь соединим кнопки с методом в конструкторе класса.

for btn in buttons.GENERATE_PASSWORD:
    getattr(self.ui, btn).clicked.connect(self.set_password)

Изменяем видимость пароля

Напишем метод для изменения видимости пароля. Если кнопка отмечена, ставим нормальный эхо мод, иначе ставим эхо мод пароля.

from PySide6.QtWidgets import QApplication, QMainWindow, QLineEdit

...

def change_password_visibility(self) -> None:
    if self.ui.btn_visibility.isChecked():
        self.ui.line_password.setEchoMode(QLineEdit.Normal)
    else:
        self.ui.line_password.setEchoMode(QLineEdit.Password)

Соединим нажатие кнопки с методом в конструкторе класса.

class PasswordGenerator(QMainWindow):
    def __init__(self):
        ...
        self.ui.btn_visibility.clicked.connect(self.change_password_visibility)

Копируем в буфер обмена

Чтобы скопировать текст в буфер обмена, нужно вызвать метод clipboard класса QApplication и поставить в него текст.

def copy_to_clipboard(self) -> None:
    QApplication.clipboard().setText(self.ui.line_password.text())

Так же соединим нажатие кнопки с методом в конструкторе класса. Я думаю, вы справитесь.

Очищаем буфер обмена при закрытии приложения

Для того, чтобы делать что-либо при закрытии приложения, нам нужна функция этого события. В Qt это событие называется QCloseEvent. В PySide6 пишем closeEvent

Событие не нужно ни с чем соединять в конструкторе. Оно работает сразу при объявлении соответствующего метода в классе приложения.

from PySide6.QtGui import QCloseEvent

def closeEvent(self, event: QCloseEvent) -> None:
    QApplication.clipboard().clear()

Меняем лейблы при ручном изменении пароля

Остался последний штрих. При ручном изменении пароля энтропия не меняется. Решить проблему можно с помощью сигнала редактирования текста textEdited

def do_when_password_edit(self) -> None:
    self.ui.line_password.textEdited.connect(self.set_entropy)
    self.ui.line_password.textEdited.connect(self.set_strength)

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

buttons.py
from string import ascii_lowercase, ascii_uppercase, digits, punctuation
from enum import Enum


class Characters(Enum):
    btn_lower = ascii_lowercase
    btn_upper = ascii_uppercase
    btn_digits = digits
    btn_special = punctuation


CHARACTER_NUMBER = {
    'btn_lower': len(Characters.btn_lower.value),
    'btn_upper': len(Characters.btn_upper.value),
    'btn_digits': len(Characters.btn_digits.value),
    'btn_special': len(Characters.btn_special.value)
}

GENERATE_PASSWORD = (
    'btn_refresh', 'btn_lower', 'btn_upper', 'btn_digits', 'btn_special'
)

password.py
from enum import IntEnum
from math import log2
import secrets


class StrengthToEntropy(IntEnum):
    Pathetic = 0
    Weak = 30
    Good = 50
    Strong = 70
    Excellent = 120


def create_new(length: int, characters: str) -> str:
    return ''.join(secrets.choice(characters) for _ in range(length))


def get_entropy(length: int, character_number: int) -> float:
    try:
        entropy = length * log2(character_number)
    except ValueError:
        return 0.0

    return round(entropy, 2)

Меняем структуру проекта

Штош. Давайте поменяем структуру проекта. Поместим все файлы в папку password-generator. Здесь создадим папку ui, в которую поместим все файлы, связанные с интерфейсом. Поменяем импортирование модулей, чтобы все работало. Из ui_main.py я убрал импортирование ресурсов и добавил его в главный модуль приложения.

from ui.ui_main import Ui_MainWindow
import ui.resources
app.py
import sys

from PySide6.QtWidgets import QApplication, QMainWindow, QLineEdit
from PySide6.QtGui import QCloseEvent

import buttons
import password
from ui.ui_main import Ui_MainWindow
import ui.resources


class PasswordGenerator(QMainWindow):
    def __init__(self):
        super(PasswordGenerator, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.connect_slider_to_spinbox()
        self.set_password()
        self.do_when_password_edit()

        for btn in buttons.GENERATE_PASSWORD:
            getattr(self.ui, btn).clicked.connect(self.set_password)

        self.ui.btn_visibility.clicked.connect(self.change_password_visibility)
        self.ui.btn_copy.clicked.connect(self.copy_to_clipboard)

    def connect_slider_to_spinbox(self) -> None:
        self.ui.slider_length.valueChanged.connect(self.ui.spinbox_length.setValue)
        self.ui.spinbox_length.valueChanged.connect(self.ui.slider_length.setValue)
        self.ui.spinbox_length.valueChanged.connect(self.set_password)

    def do_when_password_edit(self) -> None:
        self.ui.line_password.textEdited.connect(self.set_entropy)
        self.ui.line_password.textEdited.connect(self.set_strength)

    def get_characters(self) -> str:
        chars = ''

        for btn in buttons.Characters:
            if getattr(self.ui, btn.name).isChecked():
                chars += btn.value

        return chars

    def set_password(self) -> None:
        try:
            self.ui.line_password.setText(
                password.create_new(
                    length=self.ui.slider_length.value(),
                    characters=self.get_characters())
            )
        except IndexError:
            self.ui.line_password.clear()

        self.set_entropy()
        self.set_strength()

    def get_character_number(self) -> int:
        num = 0

        for btn in buttons.CHARACTER_NUMBER.items():
            if getattr(self.ui, btn[0]).isChecked():
                num += btn[1]

        return num

    def set_entropy(self) -> None:
        length = len(self.ui.line_password.text())
        char_num = self.get_character_number()

        self.ui.label_entropy.setText(
            f'Entropy: {password.get_entropy(length, char_num)} bit'
        )

    def set_strength(self) -> None:
        length = len(self.ui.line_password.text())
        char_num = self.get_character_number()

        for strength in password.StrengthToEntropy:
            if password.get_entropy(length, char_num) >= strength.value:
                self.ui.label_strength.setText(f'Strength: {strength.name}')

    def change_password_visibility(self) -> None:
        if self.ui.btn_visibility.isChecked():
            self.ui.line_password.setEchoMode(QLineEdit.Normal)
        else:
            self.ui.line_password.setEchoMode(QLineEdit.Password)

    def copy_to_clipboard(self) -> None:
        QApplication.clipboard().setText(self.ui.line_password.text())

    def closeEvent(self, event: QCloseEvent) -> None:
        QApplication.clipboard().clear()


if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = PasswordGenerator()
    window.show()

    sys.exit(app.exec())

Создаем файл зависимостей

Создадим файл зависимостей с помощью команды pip freeze > requirements.txt

PySide6==6.3.1
PySide6-Addons==6.3.1
PySide6-Essentials==6.3.1
shiboken6==6.3.1

Система контроля версий

Инициализируем систему контроля версий, конечно же Git. Установите систему, если вы еще этого не сделали. Пропишите в терминал git init из корневого каталога или используйте удобный графический интерфейс в PyCharm.

Создадим файл .gitignore. Будем игнорировать любую папку виртуального окружения, папку PyCharm в моем случае и питонячий кэш. Для VS Code можно прописать .vscode

venv*
.idea
.vscode
__pycache__

Теперь можно сделать первый коммит. Опять же, в PyCharm это делается очень удобно, но если вы любите работать в терминале, то пропишите команды:

git add .

git commit -m "Initial commit"

Собираем приложение для Windows

Переходим к созданию исполняемых файлов. У Qt есть понятная документация, в которой собраны разные способы дистрибуции приложения. Самый простой способ - это PyInstaller. Нет, вру, самый простой способ - это PyInstaller с интерфейсом, auto-py-to-exe. Все эти библиотеки хороши своей понятностью и легкостью, но обычно на выходе получаются небезопасные и тяжелые бинарники.

Я решил показать вам инструмент получше - компилятор Nuitka. Он доступен для всех версий Qt, работает на всех платформах и распространяется по свободной лицензии MIT.

Установим библиотеку с помощью команды pip install nuitka. Для сборки приложения в один файл понадобится библиотека zstandard, добавим ее в команду через пробел.

Пишем команду компиляции. Собираем приложение в один файл с помощью флага --onefile. Флаг --follow-imports нужен для соблюдения всех импортов. Добавляем плагин PySide6 с помощью флага --enable-plugin=pyside6. Чтобы убрать консольное окно в системе Windows добавим --windows-disable-console. Добавим иконку с помощью флага --windows-icon-from-ico

Чтобы убрать все генерируемые нуиткой папки сборки добавим флаг --remove-output

Можно указать название файла через флаг -o, но это вообще необязательно, всегда можно просто переименовать файл. В конце напишем путь к файлу скрипта, который мы собираемся собирать.

nuitka --onefile --follow-imports --enable-plugin=pyside6 --windows-disable-console --windows-icon-from-ico=ui\icons\app-icon.ico --remove-output -o password-generator.exe app.py

На выходе получилось приложение с размером 16.7 мегабайт.

Собираем приложение для Linux

Я использовал дистрибутив Ubuntu версии 22.04. С нуиткой вышло громадное приложение в 145 мегабайт. Если вы знаете, как сделать меньше - пишите в комментарии.

python -m nuitka --onefile --follow-imports --enable-plugin=pyside6 --remove-output app.py

Я попробовал собрать приложение с PyInstaller, получилось уже получше, 57,6 мегабайт. С иконкой в докбаре и проводнике вообще беда, она просто не хотела ставиться.

pyinstaller --name="Password Generator" --windowed app.py

Собираем приложение для macOS

Я использовал версию 10.15 Catalina. Для сборки приложения под macOS нужно прописать специальный параметр --macos-create-app-bundle

python3 -m nuitka --onefile --follow-imports --enable-plugin=pyside6 --macos-create-app-bundle --remove-output -o password-generator.app app.py

Вышло почти так же компактно, как и на винде - 18 мегабайт. С иконкой тоже возникли проблемы, поэтому собирал без нее.

Штош

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


GitHub репозиторий