mirror of
https://github.com/liberatedsystems/openCom-Companion.git
synced 2024-11-29 16:50:37 +01:00
1066 lines
33 KiB
Python
1066 lines
33 KiB
Python
"""
|
||
Script creates a project with the MVC pattern
|
||
=============================================
|
||
|
||
.. versionadded:: 1.0.0
|
||
|
||
.. seealso::
|
||
|
||
`MVC pattern <https://en.wikipedia.org/wiki/Model–view–controller>`_
|
||
|
||
.. rubric:: Use a clean architecture for your applications.
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/preview-mvc.png
|
||
:align: center
|
||
|
||
Use a clean architecture for your applications. KivyMD allows you to quickly
|
||
create a project template with the MVC pattern. So far, this is the only
|
||
pattern that this utility offers. You can also include database support in
|
||
your project. At the moment, support for the Firebase database
|
||
(the basic implementation of the real time database) and RestDB
|
||
(the full implementation) is available.
|
||
|
||
Project creation
|
||
----------------
|
||
|
||
Template command::
|
||
|
||
python -m kivymd.tools.patterns.create_project \\
|
||
name_pattern \\
|
||
path_to_project \\
|
||
name_project \\
|
||
python_version \\
|
||
kivy_version
|
||
|
||
Example command::
|
||
|
||
python -m kivymd.tools.patterns.create_project \\
|
||
MVC \\
|
||
/Users/macbookair/Projects \\
|
||
MyMVCProject \\
|
||
python3.10 \\
|
||
2.1.0
|
||
|
||
This command will by default create a project with an MVC pattern.
|
||
Also, the project will create a virtual environment with Python 3.10,
|
||
Kivy version 2.1.0 and KivyMD master version.
|
||
|
||
.. note::
|
||
Please note that the Python version you specified must be installed on your
|
||
computer.
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mvc-base.png
|
||
:align: center
|
||
|
||
Creating a project using a database
|
||
-----------------------------------
|
||
|
||
.. note::
|
||
Note that in the following command, you can use one of two database names:
|
||
'firebase' or 'restdb'.
|
||
|
||
Template command::
|
||
|
||
python -m kivymd.tools.patterns.create_project \\
|
||
name_pattern \\
|
||
path_to_project \\
|
||
name_project \\
|
||
python_version \\
|
||
kivy_version \\
|
||
--name_database
|
||
|
||
Example command::
|
||
|
||
python -m kivymd.tools.patterns.create_project \\
|
||
MVC \\
|
||
/Users/macbookair/Projects \\
|
||
MyMVCProject \\
|
||
python3.10 \\
|
||
2.1.0 \\
|
||
--name_database restdb
|
||
|
||
This command will create a project with an MVC template by default.
|
||
The project will also create a virtual environment with Python 3.10,
|
||
Kivy version 2.1.0, KivyMD master version and a wrapper for working with
|
||
the database restdb.io.
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mvc-database.png
|
||
:align: center
|
||
|
||
.. code-block:: python
|
||
|
||
class DataBase:
|
||
def __init__(self):
|
||
database_url = "https://restdbio-5498.restdb.io"
|
||
api_key = "7ce258d66f919d3a891d1166558765f0b4dbd"
|
||
|
||
.. note::
|
||
Please note that `database.py` the shell in the `DataBase` class uses the
|
||
`database_url` and `api_key` parameters on the test database (works only in read mode),
|
||
so you should use your data for the database.
|
||
|
||
Preview of the basic MVC template using the RestDB database
|
||
-----------------------------------------------------------
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mvc-restbd-preview.png
|
||
:align: center
|
||
|
||
Create project with hot reload
|
||
------------------------------
|
||
|
||
Template command::
|
||
|
||
python -m kivymd.tools.patterns.create_project \\
|
||
name_pattern \\
|
||
path_to_project \\
|
||
name_project \\
|
||
python_version \\
|
||
kivy_version \\
|
||
--use_hotreload
|
||
|
||
Example command::
|
||
|
||
python -m kivymd.tools.patterns.create_project \\
|
||
MVC \\
|
||
/Users/macbookair/Projects \\
|
||
MyMVCProject \\
|
||
python3.10 \\
|
||
2.1.0 \\
|
||
--use_hotreload yes
|
||
|
||
After creating the project, open the file `main.py`, there is a lot of useful
|
||
information. Also, the necessary information is in other modules of the project
|
||
in the form of comments. So do not forget to look at the source files of the
|
||
created project.
|
||
|
||
Others command line arguments
|
||
======================
|
||
|
||
Required Arguments
|
||
------------------
|
||
|
||
- pattern
|
||
- the name of the pattern with which the project will be created
|
||
|
||
- directory
|
||
- directory in which the project will be created
|
||
|
||
- name
|
||
- project name
|
||
|
||
- python_version
|
||
- the version of Python (specify as `python3.9` or `python3.8`) with
|
||
- which the virtual environment will be created
|
||
|
||
- kivy_version
|
||
- version of Kivy (specify as `2.1.0` or `master`) that will be used in the project
|
||
|
||
Optional arguments
|
||
------------------
|
||
|
||
- name_screen
|
||
- the name of the class which be used when creating the project pattern
|
||
|
||
- name_database
|
||
- provides a basic template for working with the 'firebase' library
|
||
- or a complete implementation for working with a database 'restdb.io'
|
||
|
||
- use_hotreload
|
||
- creates a hot reload entry point to the application
|
||
|
||
-use_localization
|
||
- creates application localization files
|
||
|
||
.. warning:: On Windows, hot reloading of Python files may not work.
|
||
But, for example, there is no such problem in macOS. If you fix this,
|
||
please report it to the KivyMD community.
|
||
"""
|
||
|
||
import os
|
||
import re
|
||
import shutil
|
||
from typing import Union
|
||
|
||
from kivy import Logger, platform
|
||
|
||
from kivymd import path as kivymd_path
|
||
from kivymd.tools.argument_parser import ArgumentParserWithHelp
|
||
|
||
_database_model = '''import multitasking
|
||
|
||
from Model.base_model import BaseScreenModel
|
||
|
||
multitasking.set_max_threads(10)
|
||
|
||
|
||
class {name_screen}Model(BaseScreenModel):
|
||
"""
|
||
Implements the logic of the
|
||
:class:`~View.{name_screen}.{module_name}.{name_screen}View` class.
|
||
"""
|
||
|
||
def __init__(self, database):
|
||
self.database = database
|
||
# Dict:
|
||
# 'login': 'KivyMD'
|
||
# 'password': '1111'
|
||
self.user_data = dict()
|
||
self._data_validation_status = None
|
||
|
||
@property
|
||
def data_validation_status(self):
|
||
return self._data_validation_status
|
||
|
||
@data_validation_status.setter
|
||
def data_validation_status(self, value):
|
||
self._data_validation_status = value
|
||
# We notify the View -
|
||
# :class:`~View.{name_screen}.{module_name}.{name_screen}View` about the
|
||
# changes that have occurred in the data model.
|
||
self.notify_observers({notify_name_screen})
|
||
|
||
@multitasking.task
|
||
def check_data(self):
|
||
"""
|
||
Get data from the database and compares this data with the data entered
|
||
by the user. This method is completely asynchronous.
|
||
It does not return any value.
|
||
"""
|
||
|
||
data = self.database.get_data_from_collection(self.database.USER_DATA)
|
||
data_validation_status = False
|
||
|
||
if data:
|
||
if self.database.name == "RestDB":
|
||
data = data[0]
|
||
else:
|
||
data = list(data.values())[0]
|
||
if (
|
||
data["login"] == self.user_data["login"]
|
||
and data["password"] == self.user_data["password"]
|
||
):
|
||
data_validation_status = True
|
||
self.data_validation_status = data_validation_status
|
||
|
||
def set_user_data(self, key: str, value: str) -> None:
|
||
"""Sets a dictionary of data that the user enters."""
|
||
|
||
self.user_data[key] = value
|
||
|
||
def reset_data_validation_status(self) -> None:
|
||
self.data_validation_status = None
|
||
'''
|
||
|
||
_without_database_model = '''from Model.base_model import BaseScreenModel
|
||
|
||
|
||
class {name_screen}Model(BaseScreenModel):
|
||
"""
|
||
Implements the logic of the
|
||
:class:`~View.{module_name}.{name_screen}.{name_screen}View` class.
|
||
"""'''
|
||
|
||
_database_controller = '''
|
||
{import_module}
|
||
|
||
|
||
class {name_screen}Controller:
|
||
"""
|
||
The `{name_screen}Controller` class represents a controller implementation.
|
||
Coordinates work of the view with the model.
|
||
The controller implements the strategy pattern. The controller connects to
|
||
the view to control its actions.
|
||
"""
|
||
|
||
def __init__(self, model):
|
||
self.model = model # Model.{module_name}.{name_screen}Model object
|
||
self.view = {name_view}(
|
||
controller=self, model=self.model
|
||
)
|
||
|
||
def set_user_data(self, key: str, value: str) -> None:
|
||
"""Called every time the user enters text into the text fields."""
|
||
|
||
self.model.set_user_data(key, value)
|
||
|
||
def on_tap_button_login(self) -> None:
|
||
"""Called when the `LOGIN` button is pressed."""
|
||
|
||
self.view.show_dialog_wait()
|
||
self.model.check_data()
|
||
|
||
def reset_data_validation_status(self, *args) -> None:
|
||
self.model.reset_data_validation_status()
|
||
'''
|
||
|
||
_without_database_controller = '''
|
||
{import_module}
|
||
|
||
|
||
class {name_screen}Controller:
|
||
"""
|
||
The `{name_screen}Controller` class represents a controller implementation.
|
||
Coordinates work of the view with the model.
|
||
The controller implements the strategy pattern. The controller connects to
|
||
the view to control its actions.
|
||
"""
|
||
|
||
def __init__(self, model):
|
||
self.model = model # Model.{module_name}.{name_screen}Model
|
||
self.view = {name_view}(controller=self, model=self.model)
|
||
|
||
def on_tap_button_login(self) -> None:
|
||
"""Called when the `LOGIN` button is pressed."""
|
||
|
||
def set_user_data(self, key, value) -> None:
|
||
"""Called every time the user enters text into the text fields."""
|
||
'''
|
||
|
||
_database_view_import = """from typing import Union
|
||
|
||
from kivy.clock import Clock
|
||
|
||
from kivymd.uix.dialog import MDDialog
|
||
from kivymd.uix.snackbar import Snackbar
|
||
"""
|
||
|
||
_without_database_view_import = """
|
||
"""
|
||
|
||
_database_view_methods = ''' def __init__(self, **kwargs):
|
||
super().__init__(**kwargs)
|
||
self.dialog = MDDialog()
|
||
self.dialog.bind(on_dismiss=self.controller.reset_data_validation_status)
|
||
|
||
def show_dialog_wait(self) -> None:
|
||
"""Displays a wait dialog while the model is processing data."""
|
||
|
||
self.dialog.auto_dismiss = False
|
||
self.dialog.text = "Data validation..."
|
||
self.dialog.open()
|
||
|
||
def show_toast(self, interval: Union[int, float]) -> None:
|
||
Snackbar(
|
||
text="You have passed the verification successfully!",
|
||
snackbar_x="10dp",
|
||
snackbar_y="10dp",
|
||
size_hint_x=.8,
|
||
bg_color=self.theme_cls.primary_color,
|
||
).open()
|
||
'''
|
||
|
||
_database_view_model_is_changed_method = """if self.model.data_validation_status:
|
||
self.dialog.dismiss()
|
||
Clock.schedule_once(self.show_toast, 1)
|
||
if self.model.data_validation_status is False:
|
||
self.dialog.text = "Wrong data!"
|
||
self.dialog.auto_dismiss = True
|
||
"""
|
||
|
||
_firebase_requirements = """kivy==2.1.0
|
||
kivymd==1.0.0
|
||
multitasking
|
||
firebase
|
||
firebase-admin
|
||
python_jwt
|
||
gcloud
|
||
sseclient
|
||
pycryptodome==3.4.3
|
||
requests_toolbelt
|
||
"""
|
||
|
||
_without_firebase_requirements = """kivy==2.1.0
|
||
kivymd==1.0.0
|
||
"""
|
||
|
||
_hot_reload_main = '''
|
||
"""
|
||
Script for managing hot reloading of the project.
|
||
For more details see the documentation page -
|
||
|
||
https://kivymd.readthedocs.io/en/latest/api/kivymd/tools/patterns/create_project/
|
||
|
||
To run the application in hot boot mode, execute the command in the console:
|
||
DEBUG=1 python main.py
|
||
"""
|
||
|
||
import importlib
|
||
import os
|
||
|
||
from kivy import Config
|
||
from kivy.uix.screenmanager import ScreenManager
|
||
|
||
from PIL import ImageGrab
|
||
|
||
# TODO: You may know an easier way to get the size of a computer display.
|
||
resolution = ImageGrab.grab().size
|
||
|
||
# Change the values of the application window size as you need.
|
||
Config.set("graphics", "height", resolution[1])
|
||
Config.set("graphics", "width", "400")
|
||
|
||
from kivy.core.window import Window%s
|
||
|
||
# Place the application window on the right side of the computer screen.
|
||
Window.top = 0
|
||
Window.left = resolution[0] - Window.width
|
||
|
||
from kivymd.tools.hotreload.app import MDApp
|
||
%s%s
|
||
|
||
class %s(MDApp):
|
||
KV_FILES = {
|
||
os.path.join(
|
||
os.getcwd(),
|
||
"View",
|
||
"%s",
|
||
"%s.kv",
|
||
),
|
||
}%s
|
||
|
||
def build_app(self) -> ScreenManager:
|
||
"""
|
||
In this method, you don't need to change anything other than the
|
||
application theme.
|
||
"""
|
||
|
||
import View.screens
|
||
|
||
self.theme_cls.primary_palette = "Orange"
|
||
self.manager_screens = ScreenManager()%s%s
|
||
Window.bind(on_key_down=self.on_keyboard_down)
|
||
importlib.reload(View.screens)
|
||
screens = View.screens.screens
|
||
|
||
for i, name_screen in enumerate(screens.keys()):
|
||
model = screens[name_screen]["model"](%s)
|
||
controller = screens[name_screen]["controller"](model)
|
||
view = controller.get_view()
|
||
view.manager_screens = self.manager_screens
|
||
view.name = name_screen
|
||
self.manager_screens.add_widget(view)
|
||
|
||
return self.manager_screens
|
||
|
||
def on_keyboard_down(self, window, keyboard, keycode, text, modifiers) -> None:
|
||
"""
|
||
The method handles keyboard events.
|
||
|
||
By default, a forced restart of an application is tied to the
|
||
`CTRL+R` key on Windows OS and `COMMAND+R` on Mac OS.
|
||
"""
|
||
|
||
if "meta" in modifiers or "ctrl" in modifiers and text == "r":
|
||
self.rebuild()%s%s
|
||
|
||
|
||
%s().run()
|
||
|
||
# After you finish the project, remove the above code and uncomment the below
|
||
# code to test the application normally without hot reloading.
|
||
'''
|
||
|
||
available_patterns = ["MVC"]
|
||
available_databases = ["firebase", "restdb"]
|
||
|
||
|
||
def main():
|
||
parser = create_argument_parser()
|
||
args = parser.parse_args()
|
||
|
||
pattern_name = args.pattern
|
||
project_directory = args.directory
|
||
project_name = "".join(args.name.split(" "))
|
||
kivy_version = args.kivy_version
|
||
python_version = args.python_version
|
||
if "3" not in python_version:
|
||
parser.error("Python must be at least version 3")
|
||
name_screen = "".join(args.name_screen.split(" "))
|
||
path_to_project = os.path.join(project_directory, project_name)
|
||
name_database = args.name_database
|
||
if name_database != "no" and name_database not in available_databases:
|
||
parser.error(
|
||
f"The database name must be one of the {available_databases} list"
|
||
)
|
||
use_hotreload = args.use_hotreload
|
||
use_localization = args.use_localization
|
||
|
||
# Check arguments.
|
||
if name_screen[-6:] != "Screen":
|
||
parser.error(
|
||
"Name of the screen must contain the word 'Screen' at the end. "
|
||
"\nFor example - '... --name_screen MyFirstScreen'"
|
||
)
|
||
module_name = chek_camel_case_name_project(name_screen)
|
||
if not module_name:
|
||
parser.error(
|
||
"The name of the screen should be written in camel case style. "
|
||
"\nFor example - 'MyFirstScreen'"
|
||
)
|
||
module_name = "_".join([name.lower() for name in module_name])
|
||
if not os.path.exists(
|
||
os.path.join(kivymd_path, "tools", "patterns", pattern_name)
|
||
):
|
||
parser.error(
|
||
f"There is no {pattern_name} pattern.\n"
|
||
f"Only {available_patterns} template is available."
|
||
)
|
||
|
||
# Call the functions of creating a project.
|
||
if not os.path.exists(path_to_project):
|
||
shutil.copytree(
|
||
os.path.join(kivymd_path, "tools", "patterns", pattern_name),
|
||
path_to_project,
|
||
)
|
||
create_main(
|
||
name_database, use_localization, path_to_project, project_name
|
||
)
|
||
create_model(name_database, name_screen, module_name, path_to_project)
|
||
create_controller(
|
||
name_database,
|
||
use_hotreload,
|
||
name_screen,
|
||
module_name,
|
||
path_to_project,
|
||
)
|
||
create_view(
|
||
name_database,
|
||
use_localization,
|
||
name_screen,
|
||
module_name,
|
||
path_to_project,
|
||
)
|
||
create_requirements(name_database, path_to_project)
|
||
os.makedirs(os.path.join(path_to_project, "assets", "images"))
|
||
os.mkdir(os.path.join(path_to_project, "assets", "fonts"))
|
||
rename_ext_py_tmp_to_py(path_to_project)
|
||
move_init(path_to_project, name_screen)
|
||
|
||
if name_database != "no":
|
||
check_databases(name_database, path_to_project)
|
||
|
||
if use_hotreload == "yes":
|
||
create_main_with_hotreload(
|
||
path_to_project,
|
||
project_name,
|
||
name_screen,
|
||
module_name,
|
||
name_database,
|
||
use_localization,
|
||
)
|
||
with open(
|
||
os.path.join(path_to_project, "requirements.txt"),
|
||
"a",
|
||
encoding="utf-8",
|
||
) as requirements:
|
||
requirements.write("watchdog")
|
||
|
||
if use_localization == "yes":
|
||
Logger.info("KivyMD: Create localization files...")
|
||
create_makefile(
|
||
path_to_project, project_name, module_name, name_screen
|
||
)
|
||
localization_po_file(path_to_project)
|
||
create_mofile(path_to_project)
|
||
else:
|
||
os.remove(os.path.join(path_to_project, "messages.pot"))
|
||
os.remove(os.path.join(path_to_project, "libs", "translation.py"))
|
||
shutil.rmtree(os.path.join(path_to_project, "data"))
|
||
Logger.info(f"KivyMD: Project '{path_to_project}' created")
|
||
Logger.info(
|
||
f"KivyMD: Create a virtual environment for '{path_to_project}' project..."
|
||
)
|
||
create_virtual_environment(python_version, path_to_project)
|
||
Logger.info(
|
||
f"KivyMD: Install requirements for '{path_to_project}' project..."
|
||
)
|
||
install_requirements(path_to_project, kivy_version, name_database)
|
||
else:
|
||
parser.error(f"The {path_to_project} project already exists")
|
||
|
||
|
||
def create_main_with_hotreload(
|
||
path_to_project: str,
|
||
project_name: str,
|
||
name_screen: str,
|
||
module_name: str,
|
||
name_database: str,
|
||
use_localization: str,
|
||
) -> None:
|
||
with open(
|
||
os.path.join(path_to_project, "main.py"), encoding="utf-8"
|
||
) as main_file:
|
||
main_code = ""
|
||
for string in main_file.readlines():
|
||
main_code += f"# {string}"
|
||
with open(
|
||
os.path.join(path_to_project, "main.py"), "w", encoding="utf-8"
|
||
) as main_file:
|
||
main_file.write(f"{_hot_reload_main}\n{main_code}")
|
||
|
||
replace_in_file(
|
||
os.path.join(path_to_project, "main.py"),
|
||
(
|
||
"\nfrom kivy.properties import StringProperty"
|
||
if use_localization == "yes"
|
||
else "",
|
||
"\nfrom Model.database import DataBase"
|
||
if name_database != "no"
|
||
else "",
|
||
"\nfrom libs.translation import Translation\n"
|
||
if use_localization == "yes"
|
||
else "",
|
||
project_name,
|
||
name_screen,
|
||
module_name,
|
||
'\n lang = StringProperty("en")\n'
|
||
if use_localization == "yes"
|
||
else "",
|
||
"\n self.base = DataBase()\n"
|
||
if name_database != "no"
|
||
else "",
|
||
"\n self.translation = Translation(\n"
|
||
' self.lang, "%s", f"{self.directory}/data/locales"'
|
||
"\n )" % project_name
|
||
if use_localization == "yes"
|
||
else "",
|
||
"self.database" if name_database != "no" else "",
|
||
"\n\n def on_lang(self, instance_app, lang_value: str) -> None:\n"
|
||
" self.translation.switch_lang(lang_value)\n"
|
||
if use_localization == "yes"
|
||
else "",
|
||
"\n def switch_lang(self) -> None:\n"
|
||
' """Switch lang."""\n\n'
|
||
' self.lang = "ru" if self.lang == "en" else "en"'
|
||
if use_localization == "yes"
|
||
else "",
|
||
project_name,
|
||
),
|
||
)
|
||
|
||
|
||
def create_main(
|
||
name_database: str,
|
||
use_localization: str,
|
||
path_to_project: str,
|
||
project_name: str,
|
||
) -> None:
|
||
replace_in_file(
|
||
os.path.join(path_to_project, "main.py_tmp"),
|
||
(
|
||
"\nfrom kivy.properties import StringProperty"
|
||
if use_localization == "yes"
|
||
else "",
|
||
"\nfrom libs.translation import Translation"
|
||
if use_localization == "yes"
|
||
else "",
|
||
"from Model.database import DataBase\n"
|
||
if name_database != "no"
|
||
else "",
|
||
project_name,
|
||
'\n lang = StringProperty("en")\n'
|
||
if use_localization == "yes"
|
||
else "",
|
||
"\n self.translation = Translation(\n"
|
||
' self.lang, "%s", f"{self.directory}/data/locales"'
|
||
"\n )" % project_name
|
||
if use_localization == "yes"
|
||
else "",
|
||
"self.database = DataBase()\n" if name_database != "no" else "",
|
||
"self.database" if name_database != "no" else "",
|
||
"\n def on_lang(self, instance_app, lang_value: str) -> None:\n"
|
||
" self.translation.switch_lang(lang_value)\n"
|
||
if use_localization == "yes"
|
||
else "",
|
||
"\n def switch_lang(self) -> None:\n"
|
||
' """Switch lang."""\n\n'
|
||
' self.lang = "ru" if self.lang == "en" else "en"\n'
|
||
if use_localization == "yes"
|
||
else "",
|
||
project_name,
|
||
),
|
||
)
|
||
|
||
|
||
def create_model(
|
||
name_database: str, name_screen: str, module_name: str, path_to_project: str
|
||
) -> None:
|
||
if name_database != "no":
|
||
database_model = _database_model.format(
|
||
name_screen=name_screen,
|
||
module_name=module_name,
|
||
notify_name_screen=f'"{" ".join(module_name.split("_"))}"',
|
||
)
|
||
replace_in_file(
|
||
os.path.join(path_to_project, "Model", "first_screen.py_tmp"),
|
||
(database_model),
|
||
)
|
||
else:
|
||
without_database_model = _without_database_model.format(
|
||
module_name=module_name, name_screen=name_screen
|
||
)
|
||
replace_in_file(
|
||
os.path.join(path_to_project, "Model", "first_screen.py_tmp"),
|
||
(without_database_model),
|
||
)
|
||
os.remove(
|
||
os.path.join(path_to_project, "Model", "database_firebase.py")
|
||
)
|
||
os.remove(os.path.join(path_to_project, "Model", "database_restdb.py"))
|
||
os.rename(
|
||
os.path.join(path_to_project, "Model", "first_screen.py_tmp"),
|
||
os.path.join(path_to_project, "Model", f"{module_name}.py_tmp"),
|
||
)
|
||
|
||
|
||
def create_controller(
|
||
name_database: str,
|
||
use_hotreload: str,
|
||
name_screen: str,
|
||
module_name: str,
|
||
path_to_project: str,
|
||
) -> None:
|
||
if name_database != "no":
|
||
database_controller = _database_controller
|
||
else:
|
||
database_controller = _without_database_controller
|
||
name_view = (
|
||
f"View.{name_screen}.{module_name}.{name_screen}View"
|
||
if use_hotreload == "yes"
|
||
else f"{name_screen}View"
|
||
)
|
||
database_controller = database_controller.format(
|
||
name_screen=name_screen,
|
||
module_name=module_name,
|
||
import_module=""
|
||
f"import importlib\n\n"
|
||
f"import View.{name_screen}.{module_name}\n\n"
|
||
f"# We have to manually reload the view module in order to apply the\n"
|
||
f"# changes made to the code on a subsequent hot reload.\n"
|
||
f"# If you no longer need a hot reload, you can delete this instruction.\n"
|
||
f"importlib.reload(View.{name_screen}.{module_name})"
|
||
if use_hotreload == "yes"
|
||
else f"\nfrom View.{name_screen}.{module_name} import {name_screen}View",
|
||
name_view=name_view,
|
||
)
|
||
replace_in_file(
|
||
os.path.join(path_to_project, "Controller", "first_screen.py_tmp"),
|
||
(database_controller, name_view),
|
||
)
|
||
os.rename(
|
||
os.path.join(path_to_project, "Controller", "first_screen.py_tmp"),
|
||
os.path.join(path_to_project, "Controller", f"{module_name}.py_tmp"),
|
||
)
|
||
|
||
|
||
def create_view(
|
||
name_database: str,
|
||
use_localization: str,
|
||
name_screen: str,
|
||
module_name: str,
|
||
path_to_project: str,
|
||
) -> None:
|
||
replace_in_file(
|
||
os.path.join(path_to_project, "View", "screens.py_tmp"),
|
||
(
|
||
module_name,
|
||
f"{name_screen}Model",
|
||
module_name,
|
||
f"{name_screen}Controller",
|
||
f'"{" ".join(module_name.split("_"))}"',
|
||
f"{name_screen}Model",
|
||
f"{name_screen}Controller",
|
||
),
|
||
)
|
||
replace_in_file(
|
||
os.path.join(path_to_project, "View", "FirstScreen", "first_screen.kv"),
|
||
(
|
||
f"{name_screen}View",
|
||
name_screen,
|
||
"app.switch_lang()" if use_localization == "yes" else "x",
|
||
'app.translation._("To log in, enter your personal data:")'
|
||
if use_localization == "yes"
|
||
else '"To log in, enter your personal data:"',
|
||
'app.translation._("Login")'
|
||
if use_localization == "yes"
|
||
else '"Login"',
|
||
'app.translation._("Password")'
|
||
if use_localization == "yes"
|
||
else '"Password"',
|
||
'app.translation._("LOGIN")'
|
||
if use_localization == "yes"
|
||
else '"LOGIN"',
|
||
),
|
||
)
|
||
replace_in_file(
|
||
os.path.join(
|
||
path_to_project, "View", "FirstScreen", "first_screen.py_tmp"
|
||
),
|
||
(
|
||
_database_view_import
|
||
if name_database != "no"
|
||
else _without_database_view_import,
|
||
f"{name_screen}View",
|
||
_database_view_methods if name_database != "no" else "",
|
||
_database_view_model_is_changed_method
|
||
if name_database != "no"
|
||
else "",
|
||
),
|
||
)
|
||
replace_in_file(
|
||
os.path.join(path_to_project, "View", "base_screen.py_tmp"),
|
||
(
|
||
module_name,
|
||
f"{name_screen}Model",
|
||
module_name,
|
||
f"{name_screen}Controller",
|
||
module_name,
|
||
f"{name_screen}Model",
|
||
),
|
||
)
|
||
os.rename(
|
||
os.path.join(path_to_project, "View", "base_screen.py_tmp"),
|
||
os.path.join(path_to_project, "View", "base_screen.py"),
|
||
)
|
||
os.rename(
|
||
os.path.join(path_to_project, "View", "FirstScreen", "first_screen.kv"),
|
||
os.path.join(
|
||
path_to_project, "View", "FirstScreen", f"{module_name}.kv"
|
||
),
|
||
)
|
||
os.rename(
|
||
os.path.join(
|
||
path_to_project, "View", "FirstScreen", "first_screen.py_tmp"
|
||
),
|
||
os.path.join(
|
||
path_to_project, "View", "FirstScreen", f"{module_name}.py_tmp"
|
||
),
|
||
)
|
||
os.rename(
|
||
os.path.join(path_to_project, "View", "FirstScreen"),
|
||
os.path.join(path_to_project, "View", name_screen),
|
||
)
|
||
|
||
|
||
def create_requirements(name_database: str, path_to_project: str) -> None:
|
||
with open(
|
||
os.path.join(path_to_project, "requirements.txt"), "w", encoding="utf-8"
|
||
) as requirements:
|
||
requirements.write(
|
||
_firebase_requirements
|
||
if name_database != "no"
|
||
else _without_firebase_requirements
|
||
)
|
||
|
||
|
||
def create_makefile(
|
||
path_to_project: str, project_name: str, module_name: str, name_screen: str
|
||
) -> None:
|
||
path_to_makefile_tmp = os.path.join(path_to_project, "Makefile.tmp")
|
||
replace_in_file(
|
||
path_to_makefile_tmp,
|
||
(
|
||
name_screen,
|
||
module_name,
|
||
name_screen,
|
||
module_name,
|
||
project_name,
|
||
project_name,
|
||
),
|
||
)
|
||
os.rename(path_to_makefile_tmp, os.path.splitext(path_to_makefile_tmp)[0])
|
||
os.chdir(path_to_project)
|
||
os.system("make po")
|
||
|
||
|
||
def create_mofile(path_to_project: str) -> None:
|
||
os.chdir(path_to_project)
|
||
os.system("make mo")
|
||
|
||
|
||
def create_virtual_environment(
|
||
python_version: str, path_to_project: str
|
||
) -> None:
|
||
os.system(f"{python_version} -m pip install virtualenv")
|
||
os.system(
|
||
f"virtualenv -p {python_version} {os.path.join(path_to_project, 'venv')}"
|
||
)
|
||
|
||
|
||
def localization_po_file(path_to_project: str) -> None:
|
||
path_to_file_po = os.path.join(
|
||
path_to_project, "data", "locales", "po", "ru.po"
|
||
)
|
||
with open(path_to_file_po, "rt", encoding="utf-8") as file_po:
|
||
file_po_content = (
|
||
file_po.read()
|
||
.replace(
|
||
'msgid "To log in, enter your personal data:"\nmsgstr ""',
|
||
'msgid "To log in, enter your personal data:"\nmsgstr "Для входа введите свои личные данные"',
|
||
)
|
||
.replace(
|
||
'msgid "Login"\nmsgstr ""', 'msgid "Login"\nmsgstr "Логин"'
|
||
)
|
||
.replace(
|
||
'msgid "Password"\nmsgstr ""',
|
||
'msgid "Password"\nmsgstr "Пароль"',
|
||
)
|
||
.replace(
|
||
'msgid "LOGIN"\nmsgstr ""', 'msgid "LOGIN"\nmsgstr "ЛОГИН"'
|
||
)
|
||
)
|
||
with open(path_to_file_po, "wt", encoding="utf-8") as file_po:
|
||
file_po.write(file_po_content)
|
||
|
||
|
||
def install_requirements(
|
||
path_to_project: str, kivy_version: str, name_database: str
|
||
) -> None:
|
||
python = os.path.join(path_to_project, "venv", "bin", "python3")
|
||
if kivy_version == "master":
|
||
if platform == "macosx":
|
||
os.system(
|
||
f"{python} -m pip install 'kivy[base] @ https://github.com/kivy/kivy/archive/master.zip'"
|
||
)
|
||
else:
|
||
os.system(
|
||
f"{python} -m pip install https://github.com/kivy/kivy/archive/master.zip"
|
||
)
|
||
elif kivy_version == "stable":
|
||
os.system(f"{python} -m pip install kivy")
|
||
else:
|
||
os.system(f"{python} -m pip install kivy=={kivy_version}")
|
||
os.system(
|
||
f"{python} -m pip install https://github.com/kivymd/KivyMD/archive/master.zip"
|
||
)
|
||
os.system(f"{python} -m pip install watchdog")
|
||
if name_database == "firebase":
|
||
os.system(
|
||
f"{python} -m pip install "
|
||
f"multitasking "
|
||
f"firebase "
|
||
f"firebase-admin "
|
||
f"python_jwt "
|
||
f"gcloud "
|
||
f"sseclient "
|
||
f"pycryptodome==3.4.3 "
|
||
f"requests_toolbelt "
|
||
f"watchdog "
|
||
)
|
||
os.system(
|
||
f"{os.path.join(path_to_project, 'venv', 'bin', 'python3')} -m pip list"
|
||
)
|
||
|
||
|
||
def rename_ext_py_tmp_to_py(path_to_project: str) -> None:
|
||
for path_to_dir, dirs, files in os.walk(path_to_project):
|
||
for name_file in files:
|
||
if os.path.splitext(name_file)[1] == ".py_tmp":
|
||
os.rename(
|
||
os.path.join(path_to_dir, name_file),
|
||
os.path.join(
|
||
path_to_dir, f"{os.path.splitext(name_file)[0]}.py"
|
||
),
|
||
)
|
||
|
||
|
||
def move_init(path_to_project: str, name_screen: str) -> None:
|
||
path_to_init_file = __file__.replace("create_project", "__init__")
|
||
for name_dir in ("Controller", "Model", "Utility", "View"):
|
||
shutil.copy(
|
||
path_to_init_file,
|
||
os.path.join(path_to_project, name_dir, "__init__.py"),
|
||
)
|
||
shutil.copy(
|
||
path_to_init_file,
|
||
os.path.join(path_to_project, "View", name_screen, "__init__.py"),
|
||
)
|
||
path_to_components = os.path.join(
|
||
path_to_project, "View", name_screen, "components"
|
||
)
|
||
os.mkdir(path_to_components)
|
||
shutil.copy(
|
||
path_to_init_file, os.path.join(path_to_components, "__init__.py")
|
||
)
|
||
|
||
|
||
def check_databases(name_database: str, path_to_project: str) -> None:
|
||
databases = {"firebase": "restdb", "restdb": "firebase"}
|
||
os.remove(
|
||
os.path.join(
|
||
path_to_project, "Model", f"database_{databases[name_database]}.py"
|
||
)
|
||
)
|
||
os.rename(
|
||
os.path.join(path_to_project, "Model", f"database_{name_database}.py"),
|
||
os.path.join(path_to_project, "Model", "database.py"),
|
||
)
|
||
|
||
|
||
def chek_camel_case_name_project(name_project) -> Union[bool, list]:
|
||
result = re.findall("[A-Z][a-z]*", name_project)
|
||
if len(result) == 1:
|
||
return False
|
||
return result
|
||
|
||
|
||
def replace_in_file(path_to_file: str, args) -> None:
|
||
with open(path_to_file, "rt", encoding="utf-8") as file_content:
|
||
new_file_content = file_content.read() % (args)
|
||
with open(path_to_file, "wt", encoding="utf-8") as original_file:
|
||
original_file.write(new_file_content)
|
||
|
||
|
||
def create_argument_parser() -> ArgumentParserWithHelp:
|
||
parser = ArgumentParserWithHelp(
|
||
prog="create_project.py",
|
||
allow_abbrev=False,
|
||
)
|
||
parser.add_argument(
|
||
"pattern",
|
||
help="the name of the pattern with which the project will be created.",
|
||
)
|
||
parser.add_argument(
|
||
"directory",
|
||
help="directory in which the project will be created.",
|
||
)
|
||
parser.add_argument(
|
||
"name",
|
||
help="project name.",
|
||
)
|
||
parser.add_argument(
|
||
"python_version",
|
||
help="the version of Python (specify as `python3.9` or `python3.8`) "
|
||
"with which the virtual environment will be created.",
|
||
)
|
||
parser.add_argument(
|
||
"kivy_version",
|
||
help="version of Kivy (specify as `2.1.0` or `master`) that will be "
|
||
"used in the project.",
|
||
)
|
||
parser.add_argument(
|
||
"--name_screen",
|
||
default="MainScreen",
|
||
help="the name of the class wich be used when creating the project pattern.",
|
||
)
|
||
parser.add_argument(
|
||
"--name_database",
|
||
default="no",
|
||
help="name of the database provider ('firebase' or 'restdb').",
|
||
)
|
||
parser.add_argument(
|
||
"--use_hotreload",
|
||
default="no",
|
||
help="creates a hot reload entry point to the application.",
|
||
)
|
||
parser.add_argument(
|
||
"--use_localization",
|
||
default="no",
|
||
help="creates application localization files.",
|
||
)
|
||
return parser
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|