dprojectstools 0.0.30__tar.gz → 0.0.32__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. {dprojectstools-0.0.30/src/dprojectstools.egg-info → dprojectstools-0.0.32}/PKG-INFO +1 -1
  2. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/pyproject.toml +1 -1
  3. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/commands/commands.py +49 -4
  4. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/console/__init__.py +1 -0
  5. dprojectstools-0.0.32/src/dprojectstools/console/output.py +97 -0
  6. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/db/db_inspector.py +13 -3
  7. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/db/db_schema.py +5 -2
  8. dprojectstools-0.0.32/src/dprojectstools/db/generator_json.py +140 -0
  9. dprojectstools-0.0.32/src/dprojectstools/db/generator_sql.py +237 -0
  10. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/db/generator_vb.py +221 -149
  11. {dprojectstools-0.0.30 → dprojectstools-0.0.32/src/dprojectstools.egg-info}/PKG-INFO +1 -1
  12. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools.egg-info/SOURCES.txt +2 -0
  13. dprojectstools-0.0.30/src/dprojectstools/db/generator_sql.py +0 -135
  14. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/LICENSE +0 -0
  15. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/README.md +0 -0
  16. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/setup.cfg +0 -0
  17. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/__init__.py +0 -0
  18. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/backups/__init__.py +0 -0
  19. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/backups/restic.py +0 -0
  20. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/clipboard/__init__.py +0 -0
  21. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/clipboard/clipboard.py +0 -0
  22. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/commands/__init__.py +0 -0
  23. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/commands/p.py +0 -0
  24. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/config/__init__.py +0 -0
  25. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/config/config.py +0 -0
  26. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/console/keys.py +0 -0
  27. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/console/readKey.py +0 -0
  28. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/console/read_password.py +0 -0
  29. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/console/sequences.py +0 -0
  30. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/crypto/__init__.py +0 -0
  31. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/crypto/aes.py +0 -0
  32. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/db/__main__.py +0 -0
  33. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/db/generator_cs.py +0 -0
  34. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/db/generator_xml.py +0 -0
  35. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/docker/__init__.py +0 -0
  36. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/docker/docker.py +0 -0
  37. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/git/__init__.py +0 -0
  38. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/git/git.py +0 -0
  39. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/i18n/__init__.py +0 -0
  40. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/i18n/translator.py +0 -0
  41. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/i18n/xgettext.py +0 -0
  42. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/images/__init__.py +0 -0
  43. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/images/fonticons.py +0 -0
  44. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/math/bc.py +0 -0
  45. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/secrets/__init__.py +0 -0
  46. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/secrets/__main__.py +0 -0
  47. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/secrets/secrets.py +0 -0
  48. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/utils/__init__.py +0 -0
  49. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/utils/env.py +0 -0
  50. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/utils/process.py +0 -0
  51. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/utils/versions.py +0 -0
  52. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/utils/xml.py +0 -0
  53. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/xeditor/__init__.py +0 -0
  54. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/xeditor/__main__.py +0 -0
  55. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/xeditor/hightlight_env.py +0 -0
  56. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/xeditor/hightlight_json.py +0 -0
  57. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/xeditor/hightlight_md.py +0 -0
  58. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/xeditor/hightlight_none.py +0 -0
  59. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools/xeditor/xeditor.py +0 -0
  60. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools.egg-info/dependency_links.txt +0 -0
  61. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools.egg-info/entry_points.txt +0 -0
  62. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools.egg-info/requires.txt +0 -0
  63. {dprojectstools-0.0.30 → dprojectstools-0.0.32}/src/dprojectstools.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dprojectstools
3
- Version: 0.0.30
3
+ Version: 0.0.32
4
4
  Summary: A set of development tools
5
5
  Author-email: Marc Delos <marcdp@dprojects.com>
6
6
  License-Expression: MIT
@@ -10,7 +10,7 @@ where = ["src"]
10
10
 
11
11
  [project]
12
12
  name = "dprojectstools"
13
- version = "0.0.30"
13
+ version = "0.0.32"
14
14
  authors = [
15
15
  { name="Marc Delos", email="marcdp@dprojects.com" },
16
16
  ]
@@ -131,6 +131,26 @@ class CommandsManager:
131
131
  # self.registerFunction(instance, obj, prefix)
132
132
  self._registerOrder += 1
133
133
 
134
+ def registerDirectory(self, directory: str, prefix: str = ""):
135
+ # register main decorated functions from all modules in a directory
136
+ directory = os.path.abspath(directory)
137
+ for filename in sorted(os.listdir(directory)):
138
+ if not filename.endswith(".py") or filename.startswith("_") or filename == "__init__.py":
139
+ continue
140
+
141
+ file_path = os.path.join(directory, filename)
142
+ module_name = f"_cmd_{os.path.splitext(filename)[0]}"
143
+
144
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
145
+ if spec is None or spec.loader is None:
146
+ raise ImportError(f"Cannot load module from: {file_path}")
147
+
148
+ module = importlib.util.module_from_spec(spec)
149
+ sys.modules[module_name] = module
150
+ spec.loader.exec_module(module)
151
+
152
+ self.register(module=module, prefix=prefix)
153
+
134
154
  def registerFunction(self, instance, func, prefix: str = ""):
135
155
  # create a command fron function
136
156
  func_name = func.__name__
@@ -215,14 +235,19 @@ class CommandsManager:
215
235
  metadata = param.annotation.__metadata__[0]
216
236
  flag_char = metadata.chr
217
237
  # required
218
- flag_required = (param.default == inspect.Parameter.empty or param.default == None)
238
+ # flag_required = (param.default == inspect.Parameter.empty or param.default == None)
239
+ flag_required = (param.default == inspect.Parameter.empty)
240
+ if command_name[0] == "module" and command_name[1] == "build":
241
+ flag_required = flag_required
242
+
219
243
  # type
220
244
  flag_type = None
221
245
  if hasattr(param.annotation, "__metadata__"):
222
246
  flag_type = param.annotation.__origin__.__name__
223
247
  # argument default
224
248
  flag_default = None
225
- if param.default != inspect.Parameter.empty and param.default != None:
249
+ # if param.default != inspect.Parameter.empty and param.default != None:
250
+ if param.default != inspect.Parameter.empty:
226
251
  flag_default = param.default
227
252
  # crea CommandFlag
228
253
  flag = CommandFlag(flag_name, flag_alias, flag_title, flag_subtitle, flag_required, flag_type, flag_default, flag_char)
@@ -519,6 +544,16 @@ class CommandsManager:
519
544
  # subcommands
520
545
  if len(subcommands) > 0:
521
546
  print("Commands:")
547
+ # compute indent_subcommands
548
+ for subcommand in subcommands:
549
+ subcommand_name = subcommand.name
550
+ if command != None:
551
+ subcommand_name = subcommand_name[len(command.name):]
552
+ if subcommand.alias != None:
553
+ subcommand_name = subcommand.alias
554
+ if indent_subcommands < len(' '.join(subcommand_name)) + 2:
555
+ indent_subcommands = len(' '.join(subcommand_name)) + 2
556
+ # print
522
557
  for subcommand in subcommands:
523
558
  subcommand_name = subcommand.name
524
559
  if command != None:
@@ -640,11 +675,13 @@ class CommandsManager:
640
675
 
641
676
  # exec Command
642
677
  command_to_execute = None
678
+ command_to_execute_args_len = 0
643
679
  for command in reversed(self._commands):
644
680
  command_name = command.alias if command.alias else command.name
645
681
  if len(command_name) <= len(self._argv) - 1:
646
682
  if command_name == self._argv[1:len(command_name)+1]:
647
683
  command_to_execute = command
684
+ command_to_execute_args_len = len(command_name)
648
685
  break
649
686
  if command_to_execute:
650
687
  break
@@ -700,7 +737,8 @@ class CommandsManager:
700
737
  self._argv.append("--" + flag.alias)
701
738
  self._argv.append(flag_value_separator.join(flag_values))
702
739
  # read flags
703
- command_args = self._argv[len(command_to_execute.name)+1:]
740
+ #command_args = self._argv[len(command_to_execute.name)+1:]
741
+ command_args = self._argv[command_to_execute_args_len+1:]
704
742
  command_args_dict = {}
705
743
  command_args_errors = []
706
744
  for flag in command_to_execute.flags:
@@ -741,6 +779,10 @@ class CommandsManager:
741
779
  command_args_dict[flag.name] = flag_value
742
780
  command_args.pop(index + 1)
743
781
  command_args.pop(index)
782
+ # get invalid flags
783
+ for argument in command_args:
784
+ if argument.startswith("-"):
785
+ command_args_errors.append(f"invalid flag: {argument}")
744
786
  # read arguments
745
787
  for argument in command_to_execute.arguments:
746
788
  argument_value = None
@@ -786,7 +828,10 @@ class CommandsManager:
786
828
  return func_bounded(**command_args_dict)
787
829
  else:
788
830
  # invoke
789
- return command_to_execute.func(**command_args_dict)
831
+ result = command_to_execute.func(**command_args_dict)
832
+ if isinstance(result, bool):
833
+ result = 1 if not result else 0
834
+ return result
790
835
 
791
836
 
792
837
 
@@ -3,4 +3,5 @@ from .sequences import Sequences
3
3
  from .keys import Keys
4
4
  from .readKey import readKey
5
5
  from .read_password import read_password
6
+ from .output import print_info, print_warning, print_error, print_table, print_yaml
6
7
 
@@ -0,0 +1,97 @@
1
+ import sys
2
+ import yaml
3
+ from dataclasses import asdict, is_dataclass
4
+ from .sequences import *
5
+ from pathlib import Path
6
+
7
+ # print utils
8
+ def print_info(message: str = "") -> None:
9
+ GRAY = "\033[90m"
10
+ RESET = "\033[0m"
11
+ if sys.stdout.isatty():
12
+ print(f"{GRAY}{message}{RESET}", file=sys.stdout, flush=True)
13
+ else:
14
+ print(message, file=sys.stdout, flush=True)
15
+
16
+ def print_warning(message: str = "") -> None:
17
+ YELLOW = "\033[33m"
18
+ RESET = "\033[0m"
19
+ if sys.stdout.isatty():
20
+ print(f"{YELLOW}{message}{RESET}", file=sys.stderr, flush=True)
21
+ else:
22
+ print(message, file=sys.stdout, flush=True)
23
+
24
+ def print_error(message: str = "") -> None:
25
+ RED = "\033[31m"
26
+ RESET = "\033[0m"
27
+ if sys.stdout.isatty():
28
+ print(f"{RED}{message}{RESET}", file=sys.stderr, flush=True)
29
+ else:
30
+ print(message, file=sys.stderr, flush=True)
31
+
32
+ def print_table(items, fields) -> str:
33
+ def resolve_value(item, field_path):
34
+ value = item
35
+ for part in field_path.split("."):
36
+ if isinstance(value, dict):
37
+ value = value.get(part, "")
38
+ else:
39
+ value = getattr(value, part, "")
40
+ return value
41
+ def normalize_value(value):
42
+ if isinstance(value, list):
43
+ return ", ".join(str(x) for x in value)
44
+ return str(value)
45
+
46
+ headers = [field.split(".")[-1].upper() for field in fields]
47
+
48
+ rows = []
49
+ for item in items:
50
+ row = []
51
+ for field in fields:
52
+ value = resolve_value(item, field)
53
+ row.append(normalize_value(value))
54
+ rows.append(row)
55
+
56
+ widths = []
57
+ for i, header in enumerate(headers):
58
+ width = len(header)
59
+ for row in rows:
60
+ width = max(width, len(row[i]))
61
+ widths.append(width)
62
+
63
+ header_line = " ".join(headers[i].ljust(widths[i]) for i in range(len(headers)))
64
+ sep_line = " ".join("-" * widths[i] for i in range(len(headers)))
65
+ body_lines = [
66
+ " ".join(row[i].ljust(widths[i]) for i in range(len(fields)))
67
+ for row in rows
68
+ ]
69
+ print("\n".join([header_line, sep_line] + body_lines))
70
+
71
+
72
+ # yaml utils
73
+ def to_yaml_safe(value):
74
+ if isinstance(value, Path):
75
+ return str(value)
76
+ if isinstance(value, dict):
77
+ return {k: to_yaml_safe(v) for k, v in value.items()}
78
+ if isinstance(value, list):
79
+ return [to_yaml_safe(v) for v in value]
80
+ if isinstance(value, tuple):
81
+ return [to_yaml_safe(v) for v in value]
82
+ return value
83
+
84
+ class IndentYamlDumper(yaml.SafeDumper):
85
+ def increase_indent(self, flow=False, indentless=False):
86
+ return super().increase_indent(flow, False)
87
+
88
+ def print_yaml( value: object, indent: int = 2) -> None:
89
+ if is_dataclass(value):
90
+ value = asdict(value)
91
+ value = to_yaml_safe(value)
92
+ print_info(yaml.dump(value,
93
+ Dumper=IndentYamlDumper,
94
+ sort_keys=False,
95
+ allow_unicode=True,
96
+ default_flow_style=False,
97
+ indent=indent))
@@ -6,7 +6,7 @@ def datatype_name_to_class(column_type_name):
6
6
  return DataType.Bigint
7
7
  elif column_type_name == "BINARY":
8
8
  return DataType.Binary
9
- elif column_type_name == "BLOB":
9
+ elif column_type_name == "BLOB" or column_type_name == "IMAGE":
10
10
  return DataType.Varbinary
11
11
  elif column_type_name == "BOOLEAN" or column_type_name == "BIT":
12
12
  return DataType.Boolean
@@ -230,6 +230,14 @@ class Inspector:
230
230
  # sql server
231
231
  class InspectorSqlServer(Inspector):
232
232
 
233
+ def get_table_names(self):
234
+ result = []
235
+ for table in self._inspector.get_table_names():
236
+ if table == "dtproperties":
237
+ continue
238
+ result.append(table)
239
+ return result
240
+
233
241
  def get_database_collation(self):
234
242
  return self.execute_scalar("SELECT DATABASEPROPERTYEX(DB_NAME(), 'Collation') AS Collation")
235
243
 
@@ -244,7 +252,10 @@ class InspectorSqlServer(Inspector):
244
252
  def get_procedure_names(self):
245
253
  result = []
246
254
  for row in self.execute_fetchall("SELECT name FROM sys.procedures ORDER BY name"):
247
- result.append(row[0])
255
+ name = row[0]
256
+ if name.startswith("sp_") or name.startswith("fn_") or name.startswith("xp_") or name.startswith("dt_"):
257
+ continue
258
+ result.append(name)
248
259
  return result
249
260
 
250
261
  def get_procedure(self, procedure_name):
@@ -281,7 +292,6 @@ class InspectorPostgresql(Inspector):
281
292
  class InspectorOracle(Inspector):
282
293
 
283
294
 
284
-
285
295
  def get_database_collation(self):
286
296
  return self.execute_scalar("SELECT VALUE FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER = 'NLS_CHARACTERSET'")
287
297
  def get_table_names(self):
@@ -2,6 +2,7 @@ from xml.etree import ElementTree
2
2
  from xml.dom import minidom
3
3
  from dataclasses import dataclass
4
4
  from enum import Enum
5
+ from flask import json
5
6
  from lxml import etree
6
7
  import os
7
8
 
@@ -35,7 +36,9 @@ class DataType(Enum):
35
36
  for member in DataType:
36
37
  if member.name.lower() == value:
37
38
  return member
38
-
39
+ def to_str(self):
40
+ return self.name.lower()
41
+
39
42
  class OnDelete(Enum):
40
43
  NO_ACTION = 1
41
44
  CASCADE = 2
@@ -158,7 +161,7 @@ class Schema:
158
161
  self.scripts = []
159
162
 
160
163
  # static methods
161
- def create(source: str, create_inspector):
164
+ def create(source: str, create_inspector = None):
162
165
  if os.path.exists(source):
163
166
  with open(source, "r", encoding='utf-8') as file:
164
167
  xml = file.read()
@@ -0,0 +1,140 @@
1
+ import json
2
+ from xml.etree import ElementTree
3
+ from xml.dom import minidom
4
+ from xml.sax.saxutils import escape
5
+ from .db_schema import OnDelete, OnUpdate
6
+
7
+ def remove_none(obj):
8
+ if isinstance(obj, dict):
9
+ return {
10
+ k: remove_none(v)
11
+ for k, v in obj.items()
12
+ if v is not None
13
+ }
14
+ elif isinstance(obj, list):
15
+ return [
16
+ remove_none(item)
17
+ for item in obj
18
+ if item is not None
19
+ ]
20
+ else:
21
+ return obj
22
+
23
+
24
+ class GeneratorJson:
25
+
26
+ def __init__(self, schema):
27
+ self._separator = ";"
28
+ self._schema = schema
29
+
30
+ def create(schema):
31
+ return GeneratorJson(schema)
32
+
33
+ def generate(self):
34
+ xml_database = ElementTree.Element("database")
35
+ result = {
36
+ "name": self._schema.name,
37
+ "description": self._schema.description if self._schema.description else None,
38
+ "collation": self._schema.collation if self._schema.collation else None,
39
+ "tables": [],
40
+ "views": [],
41
+ "procedures": [],
42
+ "sequences": [],
43
+ "scripts": []
44
+ }
45
+ for table in self._schema.tables:
46
+ tableDict = {
47
+ "description": table.description if table.description else None,
48
+ "columns": [],
49
+ "primary_key": {
50
+ "name": table.primary_key.name,
51
+ "description": table.primary_key.description if table.primary_key.description else None,
52
+ "columns": table.primary_key.columns
53
+ } if table.primary_key else None,
54
+ "foreign_keys": [],
55
+ "indexes": [],
56
+ "records": []
57
+ }
58
+ result["tables"].append({table.name: tableDict})
59
+ for column in table.columns:
60
+ columnDict = {
61
+ "name": column.name,
62
+ "description": column.description if column.description else None,
63
+ "data_type": column.data_type.to_str(),
64
+ "is_autoincrement": True if column.is_autoincrement else None,
65
+ "null": True if column.null else None,
66
+ "size": column.size if column.size > 0 else None,
67
+ "default": column.default if column.default else None,
68
+ "precision": column.precision if column.precision > 0 else None,
69
+ "scale": column.scale if column.scale > 0 else None,
70
+ "collation": column.collation if column.collation else None
71
+ }
72
+ tableDict["columns"].append(columnDict)
73
+ for foreign_key in table.foreign_keys:
74
+ foreignKeyDict = {
75
+ "name": foreign_key.name,
76
+ "description": foreign_key.description if foreign_key.description else None,
77
+ "columns": foreign_key.columns,
78
+ "ref_table": foreign_key.ref_table,
79
+ "ref_columns": foreign_key.ref_columns,
80
+ "on_delete": foreign_key.on_delete.name if foreign_key.on_delete != OnDelete.NO_ACTION else None,
81
+ "on_update": foreign_key.on_update.name if foreign_key.on_update != OnUpdate.NO_ACTION else None
82
+ }
83
+ tableDict["foreign_keys"].append(foreignKeyDict)
84
+ for index in table.indexes:
85
+ indexDict = {
86
+ "name": index.name,
87
+ "description": index.description if index.description else None,
88
+ "unique": index.unique if index.unique else None,
89
+ "columns": index.columns
90
+ }
91
+ tableDict["indexes"].append(indexDict)
92
+ for record in table.records:
93
+ recordDict = {
94
+ "values": record.values
95
+ }
96
+ tableDict["records"].append(recordDict)
97
+ for view in self._schema.views:
98
+ viewDict = {
99
+ "name": view.name,
100
+ "description": view.description if view.description else None,
101
+ "content": view.content
102
+ }
103
+ result["views"].append(viewDict)
104
+ for procedure in self._schema.procedures:
105
+ procedureDict = {
106
+ "name": procedure.name,
107
+ "description": procedure.description if procedure.description else None,
108
+ "arguments": [],
109
+ "content": procedure.content
110
+ }
111
+ result["procedures"].append(procedureDict)
112
+ for argument in procedure.arguments:
113
+ argumentDict = {
114
+ "name": argument.name,
115
+ "description": argument.description if argument.description else None,
116
+ "data_type": argument.data_type.to_str(),
117
+ "size": argument.size if argument.size > 0 else None,
118
+ "precision": argument.precision if argument.precision > 0 else None,
119
+ "scale": argument.scale if argument.scale > 0 else None,
120
+ "null": True if argument.null else None,
121
+ "direction": argument.direction
122
+ }
123
+ procedureDict["arguments"].append(argumentDict)
124
+ for sequence in self._schema.sequences:
125
+ sequenceDict = {
126
+ "name": sequence.name,
127
+ "description": sequence.description if sequence.description else None,
128
+ "init_value": sequence.init_value,
129
+ "increment_by": sequence.increment_by
130
+ }
131
+ result["sequences"].append(sequenceDict)
132
+ for script in self._schema.scripts:
133
+ scriptDict = {
134
+ "name": script.name,
135
+ "description": script.description if script.description else None,
136
+ "content": script.content
137
+ }
138
+ result["scripts"].append(scriptDict)
139
+ clean_obj = remove_none(result)
140
+ return json.dumps(clean_obj, indent=4)
@@ -0,0 +1,237 @@
1
+ from .db_schema import OnDelete, OnUpdate
2
+
3
+ class GeneratorSql:
4
+
5
+ def __init__(self, schema):
6
+ self._separator = ";"
7
+ self._schema = schema
8
+
9
+ # methods
10
+ def create(schema):
11
+ return GeneratorSql(schema)
12
+
13
+ # to override
14
+ def identity(self):
15
+ return "IDENTITY"
16
+ def default(self, value):
17
+ if value == "now":
18
+ return "getDate()"
19
+ return value
20
+ def data_type_name(self, column):
21
+ return column.data_type.name.upper()
22
+ def disable_foreign_keys(self):
23
+ return ""
24
+ def enable_foreign_keys(self):
25
+ return ""
26
+
27
+ # methods
28
+ def generate(self):
29
+ sql = []
30
+ for table in self._schema.tables:
31
+ sql.append(self.create_table(table))
32
+ # fks
33
+ sql.append(f"--- fks")
34
+ for table in self._schema.tables:
35
+ for foreign_key in table.foreign_keys:
36
+ line = self.create_table_foreign_key(table, foreign_key)
37
+ sql.append(line)
38
+ # inserts
39
+ sql.append(f"--- inserts")
40
+ for table in self._schema.tables:
41
+ for record in table.records:
42
+ sql.append(self.insert_record(table, record))
43
+ # return
44
+ return "\n".join(sql)
45
+
46
+ def create_table(self, table):
47
+ sql = []
48
+ sql.append(f"--- table {table.name}")
49
+ sql.append(f"CREATE TABLE {table.name} (")
50
+ column_index = 0
51
+ for column in table.columns:
52
+ line = []
53
+ line.append(" ")
54
+ line.append(column.name)
55
+ if column.precision > 0 or column.scale > 0:
56
+ line.append(f"{self.data_type_name(column)}({column.precision},{column.scale})")
57
+ elif column.size > 0:
58
+ line.append(f"{self.data_type_name(column)}({column.size})")
59
+ else:
60
+ line.append(f"{self.data_type_name(column)}")
61
+ if column.is_autoincrement:
62
+ line.append(self.identity())
63
+ if column.null:
64
+ line.append(f"NULL")
65
+ else:
66
+ line.append(f"NOT NULL")
67
+ if column.default != None:
68
+ line.append(f" DEFAULT {self.default(column.default)}")
69
+ if column.description != "":
70
+ line.append(f"--- {column.description}")
71
+ sql.append(" ".join(line) + ",")
72
+ column_index += 1
73
+ if table.primary_key != None:
74
+ sql.append(f" PRIMARY KEY ({",".join(table.primary_key.columns)}), ")
75
+ sql.append(f"){self._separator}")
76
+ for index in table.indexes:
77
+ sql.append(self.create_table_index(table, index))
78
+ return "\n".join(sql)
79
+
80
+ def alter_table(self, table):
81
+ return ""
82
+
83
+ def drop_table(self, table):
84
+ return f"DROP TABLE {table.name}{self._separator}"
85
+
86
+ def create_table_foreign_key(self, table, foreign_key):
87
+ line = f"ALTER TABLE {table.name} ADD CONSTRAINT {foreign_key.name} FOREIGN KEY ({",".join(foreign_key.columns)}) REFERENCES {foreign_key.ref_table} ({",".join(foreign_key.ref_columns)}) "
88
+ if foreign_key.on_delete != OnDelete.NO_ACTION:
89
+ line += "ON DELETE " + foreign_key.on_delete.name.replace("_"," ")
90
+ if foreign_key.on_update != OnUpdate.NO_ACTION:
91
+ line += "ON UPDATE " + foreign_key.on_update.name.replace("_"," ")
92
+ return line + self._separator
93
+
94
+ def create_table_index(self, table, index):
95
+ return f"CREATE {"UNIQUE" if index.unique else ""} INDEX {index.name} ON {table.name} ({' '.join(index.columns)}){self._separator}"
96
+
97
+ def insert_record(self, table, record):
98
+ sql = []
99
+ sql.append(f"INSERT INTO {table.name} (")
100
+ index = 0
101
+ for key in record.values:
102
+ if index > 0:
103
+ sql.append(",")
104
+ sql.append(key)
105
+ index += 1
106
+ sql.append(") VALUES (")
107
+ index = 0
108
+ for key in record.values:
109
+ if index > 0:
110
+ sql.append(",")
111
+ value = record.values[key]
112
+ sql.append(self.format_value(value))
113
+ index += 1
114
+ sql.append(f"){self._separator}")
115
+ return " ".join(sql)
116
+
117
+ def format_value(self, value):
118
+ return "'" + value.replace("'","''") + "'"
119
+
120
+ def create_view(self, view):
121
+ pass
122
+ def alter_view(self, view):
123
+ pass
124
+ def drop_view(self, view):
125
+ pass
126
+
127
+ def create_procedure(self, procedure):
128
+ pass
129
+ def alter_procedure(self, procedure):
130
+ pass
131
+ def drop_procedure(self, procedure):
132
+ pass
133
+
134
+ def create_sequence(self, sequence):
135
+ pass
136
+ def alter_sequence(self, sequence):
137
+ pass
138
+ def drop_sequence(self, sequence):
139
+ pass
140
+
141
+ def generate_diff(self, other_schema):
142
+ result = []
143
+ for table in self._schema.tables:
144
+ # get table with same name in other schema
145
+ other_table = None
146
+ for target_table in other_schema.tables:
147
+ if target_table.name == table.name:
148
+ other_table = target_table
149
+ break
150
+ # if not found, create table
151
+ if other_table == None:
152
+ result.append(self.create_table(table))
153
+ # add fks
154
+ for foreign_key in table.foreign_keys:
155
+ result.append(self.create_table_foreign_key(table, foreign_key))
156
+ # add indexes
157
+ for index in table.indexes:
158
+ result.append(self.create_table_index(table, index))
159
+ continue
160
+ else:
161
+ # add new columns
162
+ for column in table.columns:
163
+ other_column = None
164
+ for target_column in other_table.columns:
165
+ if target_column.name == column.name:
166
+ other_column = target_column
167
+ break
168
+ if other_column == None:
169
+ result.append(self.alter_table(table))
170
+ # drop unexisting old columns
171
+ pass
172
+ # change columns data types, defaults, nulls, autoincrements, ...
173
+ pass
174
+
175
+ # create fks
176
+ for table in self._schema.tables:
177
+ # get table with same name in other schema
178
+ other_table = None
179
+ for target_table in other_schema.tables:
180
+ if target_table.name == table.name:
181
+ other_table = target_table
182
+ break
183
+ # for each fk
184
+ if other_table != None:
185
+ for foreign_key in table.foreign_keys:
186
+ other_foreign_key = None
187
+ for target_foreign_key in other_table.foreign_keys:
188
+ if target_foreign_key.name == foreign_key.name:
189
+ other_foreign_key = target_foreign_key
190
+ break
191
+ if other_foreign_key == None:
192
+ result.append(self.create_table_foreign_key(table, foreign_key))
193
+ # drop unexisting foreign keys
194
+ pass
195
+ # create indexes
196
+ for table in self._schema.tables:
197
+ # get table with same name in other schema
198
+ other_table = None
199
+ for target_table in other_schema.tables:
200
+ if target_table.name == table.name:
201
+ other_table = target_table
202
+ break
203
+ # for each index
204
+ if other_table != None:
205
+ for index in table.indexes:
206
+ other_index = None
207
+ for target_index in other_table.indexes:
208
+ if target_index.name == index.name:
209
+ other_index = target_index
210
+ break
211
+ if other_index == None:
212
+ result.append(self.create_table_index(table, index))
213
+ # drop unexisting indexes
214
+ pass
215
+
216
+ # drop unexisting tables
217
+ for table in other_schema.tables:
218
+ # get table with same name in other schema
219
+ other_table = None
220
+ for target_table in self._schema.tables:
221
+ if target_table.name == table.name:
222
+ other_table = target_table
223
+ break
224
+ # if not found, drop table
225
+ if other_table == None:
226
+ result.append(self.drop_table(table))
227
+ continue
228
+ # return
229
+ return "\n".join(result)
230
+
231
+ class GeneratorSqlSqllite(GeneratorSql):
232
+
233
+ def disable_foreign_keys(self):
234
+ return "PRAGMA foreign_keys = OFF;"
235
+
236
+ def enable_foreign_keys(self):
237
+ return "PRAGMA foreign_keys = ON;"