Skip to content

Commit 7e2ab4a

Browse files
Merge pull request #4 from py-package/feature/commands
Feature/commands
2 parents 709ab46 + 14aab7d commit 7e2ab4a

File tree

10 files changed

+171
-14
lines changed

10 files changed

+171
-14
lines changed

.github/workflows/pythonapp.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
name: Test Application
22

3-
on: [push, pull_request]
3+
on:
4+
pull_request:
5+
branches: [master]
46

57
jobs:
68
build:

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ coverage.xml
1313
dist
1414
.env
1515
*.db
16-
src/masonite_backup.egg-info
16+
src/masonite_backup.egg-info
17+
storage

makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ ci: ## [CI] Run package tests and lint
1717
lint: ## Run code linting
1818
python -m flake8 .
1919
format: ## Format code with Black
20-
black .
20+
black src
2121
coverage: ## Run package tests and upload coverage reports
2222
python -m pytest --cov-report term --cov-report xml --cov=src/masonite/backup tests
2323
publish: ## Publish package to pypi

src/backup/Backup.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# flake8: noqa F501
2+
import pathlib
3+
import shutil
4+
import tempfile
5+
from unittest.mock import patch
6+
from masonite.configuration import config
7+
from shutil import make_archive
8+
from datetime import datetime
9+
from masonite.utils.location import base_path
10+
import subprocess
11+
import gzip
12+
13+
14+
class Backup:
15+
def __init__(self, application) -> None:
16+
self.app = application
17+
self.backup_config = config("backup")
18+
19+
def accept(self, path):
20+
for pattern in self.backup_config.get("source").get("excludes"):
21+
if pattern in path:
22+
return False
23+
return True
24+
25+
def database(self):
26+
"""
27+
Backup the database.
28+
"""
29+
30+
db_config = config("database.databases")
31+
default = db_config.get("default")
32+
connection = db_config.get(default)
33+
driver = connection.get("driver")
34+
35+
db_file_path = base_path(
36+
"{}.gz".format("database-" + str(datetime.timestamp(datetime.now())))
37+
)
38+
39+
if driver == "sqlite":
40+
pass
41+
elif driver == "mysql":
42+
command_str = f"mysqldump -u {connection.get('user')} -p{connection.get('password')} {connection.get('database')}"
43+
44+
elif driver == "postgres":
45+
command_str = f"pg_dump -U{connection.get('user')} -h{connection.get('host')} -p{connection.get('port')} -d{connection.get('database')}"
46+
47+
elif driver == "mssql":
48+
command_str = f"sqlcmd -S{connection.get('host')} -U{connection.get('user')} -P{connection.get('port')} -d{connection.get('database')}"
49+
50+
elif driver == "sqlserver":
51+
command_str = f"sqlcmd -S{connection.get('host')} -U{connection.get('user')} -P{connection.get('port')} -d{connection.get('database')}"
52+
53+
elif driver == "oracle":
54+
command_str = f"sqlplus -S{connection.get('user')}/{connection.get('password')}@{connection.get('host')}:{connection.get('port')}/{connection.get('database')}"
55+
56+
if command_str:
57+
with gzip.open(db_file_path, "wb") as f:
58+
popen = subprocess.Popen(
59+
[command_str],
60+
stdout=subprocess.PIPE,
61+
shell=True,
62+
universal_newlines=True,
63+
)
64+
for stdout_line in iter(popen.stdout.readline, ""):
65+
f.write(stdout_line.encode("utf-8"))
66+
popen.stdout.close()
67+
popen.wait()
68+
return db_file_path
69+
70+
def files(self):
71+
"""
72+
Backup the files.
73+
"""
74+
filename = (
75+
self.backup_config.get("filename", "backup")
76+
+ "-"
77+
+ str(datetime.timestamp(datetime.now()))
78+
)
79+
80+
output_dir = base_path("storage/backup")
81+
82+
if not pathlib.Path(output_dir).exists():
83+
pathlib.Path(output_dir).mkdir(parents=True, exist_ok=True)
84+
85+
path_to_archive = pathlib.Path(output_dir).joinpath(filename)
86+
87+
with tempfile.TemporaryDirectory() as tmp:
88+
shutil.copytree(
89+
self.backup_config.get("source").get("root"),
90+
pathlib.Path(tmp).joinpath("backup"),
91+
ignore=shutil.ignore_patterns(*self.backup_config.get("source").get("excludes")),
92+
)
93+
94+
with patch("os.path.isfile", side_effect=self.accept):
95+
make_archive(path_to_archive, "zip", tmp)
96+
return path_to_archive
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import os
2+
from masonite.commands import Command
3+
4+
5+
class BackupRunCommand(Command):
6+
"""
7+
Start the backup process.
8+
9+
backup:run
10+
{--filename : Backup filename}
11+
{--only-db : Backup database only}
12+
{--only-files : Backup files only}
13+
"""
14+
15+
def __init__(self, application):
16+
super().__init__()
17+
self.app = application
18+
19+
def handle(self):
20+
if not self.validate_options():
21+
return
22+
23+
if self.option("only-db"):
24+
self.app.make("backup").database()
25+
elif self.option("only-files"):
26+
self.app.make("backup").files()
27+
else:
28+
self.info("Backuping database...")
29+
database_file_path = self.app.make("backup").database()
30+
self.info("Backuping files...")
31+
self.app.make("backup").files()
32+
33+
# delete the database file
34+
os.remove(database_file_path)
35+
36+
self.info("============ Backup complete ============")
37+
38+
def validate_options(self):
39+
if self.option("only-db") and self.option("only-files"):
40+
self.error("You can only pass either files or database backup option.")
41+
return False
42+
return True

src/backup/commands/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# flake8: noqa F401
2+
from .BackupRunCommand import BackupRunCommand

src/backup/config/backup.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
1+
# flake8: noqa F501
12
"""Backup Settings"""
23

4+
from masonite.utils.location import base_path
5+
36
"""
47
|--------------------------------------------------------------------------
5-
| A Heading of The Setting Being Set
8+
| Masonite Backup
69
|--------------------------------------------------------------------------
710
|
8-
| A quick description
11+
| This is the configuration file for the Masonite Backup package.
912
|
1013
"""
1114

12-
SETTING = "some value"
15+
FILENAME = "backup" # The filename of the backup file. (without the extension)
16+
DIRECTORY = "backup" # storage/backup
17+
SOURCE = {
18+
"root": base_path(),
19+
"excludes": [
20+
".git",
21+
"storage",
22+
"venv",
23+
"node_modules",
24+
"__pycache__",
25+
],
26+
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
"""A BackupProvider Service Provider."""
22

33
from masonite.packages import PackageProvider
4+
from ..Backup import Backup
5+
from ..commands import BackupRunCommand
46

57

68
class BackupProvider(PackageProvider):
7-
89
def configure(self):
910
"""Register objects into the Service Container."""
10-
(
11-
self.root("backup")
12-
.name("backup")
13-
.config("config/backup.py", publish=True)
14-
)
11+
(self.root("backup").name("backup").config("config/backup.py", publish=True))
1512

1613
def register(self):
1714
super().register()
1815

16+
self.application.bind("backup", Backup(application=self.application))
17+
self.application.make("commands").add(BackupRunCommand(application=self.application))
18+
1919
def boot(self):
2020
"""Boots services required by the container."""
2121
pass

src/backup/providers/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# flake8: noqa: E501
2-
from .BackupProvider import BackupProvider
2+
from .BackupProvider import BackupProvider

tests/integrations/config/providers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,4 @@
5050
ORMProvider
5151
]
5252

53-
PROVIDERS += [ BackupProvider ]
53+
PROVIDERS += [BackupProvider]

0 commit comments

Comments
 (0)