opensipscli 0.3.5__tar.gz → 0.4.0__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 (61) hide show
  1. {opensipscli-0.3.5 → opensipscli-0.4.0}/PKG-INFO +5 -6
  2. {opensipscli-0.3.5 → opensipscli-0.4.0}/README.md +2 -2
  3. {opensipscli-0.3.5 → opensipscli-0.4.0}/bin/opensips-cli +1 -1
  4. {opensipscli-0.3.5 → opensipscli-0.4.0}/docker/Dockerfile +2 -2
  5. {opensipscli-0.3.5 → opensipscli-0.4.0}/docs/INSTALLATION.md +7 -8
  6. {opensipscli-0.3.5 → opensipscli-0.4.0}/docs/modules/database.md +4 -2
  7. {opensipscli-0.3.5 → opensipscli-0.4.0}/docs/modules/user.md +2 -2
  8. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/__init__.py +1 -1
  9. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/args.py +1 -1
  10. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/cli.py +10 -1
  11. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/comm.py +1 -1
  12. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/config.py +1 -1
  13. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/db.py +48 -44
  14. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/defaults.py +1 -1
  15. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/libs/__init__.py +1 -1
  16. opensipscli-0.4.0/opensipscli/libs/sqlalchemy_utils.py +166 -0
  17. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/logger.py +1 -1
  18. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/main.py +1 -1
  19. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/module.py +1 -1
  20. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/modules/__init__.py +1 -1
  21. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/modules/database.py +9 -3
  22. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/modules/diagnose.py +1 -1
  23. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/modules/instance.py +1 -1
  24. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/modules/mi.py +12 -1
  25. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/modules/tls.py +1 -1
  26. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/modules/trace.py +1 -1
  27. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/modules/trap.py +1 -1
  28. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/modules/user.py +1 -1
  29. {opensipscli-0.3.5 → opensipscli-0.4.0}/opensipscli/version.py +2 -2
  30. {opensipscli-0.3.5 → opensipscli-0.4.0}/packaging/debian/changelog +6 -0
  31. {opensipscli-0.3.5 → opensipscli-0.4.0}/packaging/debian/control +2 -2
  32. {opensipscli-0.3.5 → opensipscli-0.4.0}/packaging/debian/rules +1 -1
  33. {opensipscli-0.3.5 → opensipscli-0.4.0}/packaging/redhat_fedora/opensips-cli.spec +8 -7
  34. {opensipscli-0.3.5 → opensipscli-0.4.0}/pyproject.toml +2 -3
  35. {opensipscli-0.3.5 → opensipscli-0.4.0}/setup.py +3 -4
  36. opensipscli-0.3.5/opensipscli/libs/sqlalchemy_utils.py +0 -244
  37. {opensipscli-0.3.5 → opensipscli-0.4.0}/.github/workflows/docker-push-image.yml +0 -0
  38. {opensipscli-0.3.5 → opensipscli-0.4.0}/.github/workflows/docker-readme.yml +0 -0
  39. {opensipscli-0.3.5 → opensipscli-0.4.0}/.github/workflows/pypi.yml +0 -0
  40. {opensipscli-0.3.5 → opensipscli-0.4.0}/.gitignore +0 -0
  41. {opensipscli-0.3.5 → opensipscli-0.4.0}/LICENSE +0 -0
  42. {opensipscli-0.3.5 → opensipscli-0.4.0}/SECURITY.md +0 -0
  43. {opensipscli-0.3.5 → opensipscli-0.4.0}/docker/Makefile +0 -0
  44. {opensipscli-0.3.5 → opensipscli-0.4.0}/docker/docker.md +0 -0
  45. {opensipscli-0.3.5 → opensipscli-0.4.0}/docker/run.sh +0 -0
  46. {opensipscli-0.3.5 → opensipscli-0.4.0}/docs/modules/diagnose.md +0 -0
  47. {opensipscli-0.3.5 → opensipscli-0.4.0}/docs/modules/instance.md +0 -0
  48. {opensipscli-0.3.5 → opensipscli-0.4.0}/docs/modules/mi.md +0 -0
  49. {opensipscli-0.3.5 → opensipscli-0.4.0}/docs/modules/tls.md +0 -0
  50. {opensipscli-0.3.5 → opensipscli-0.4.0}/docs/modules/trace.md +0 -0
  51. {opensipscli-0.3.5 → opensipscli-0.4.0}/docs/modules/trap.md +0 -0
  52. {opensipscli-0.3.5 → opensipscli-0.4.0}/etc/default.cfg +0 -0
  53. {opensipscli-0.3.5 → opensipscli-0.4.0}/packaging/debian/.gitignore +0 -0
  54. {opensipscli-0.3.5 → opensipscli-0.4.0}/packaging/debian/compat +0 -0
  55. {opensipscli-0.3.5 → opensipscli-0.4.0}/packaging/debian/copyright +0 -0
  56. {opensipscli-0.3.5 → opensipscli-0.4.0}/packaging/debian/source/format +0 -0
  57. {opensipscli-0.3.5 → opensipscli-0.4.0}/packaging/debian/watch +0 -0
  58. {opensipscli-0.3.5 → opensipscli-0.4.0}/setup.cfg +0 -0
  59. {opensipscli-0.3.5 → opensipscli-0.4.0}/test/alltests.py +0 -0
  60. {opensipscli-0.3.5 → opensipscli-0.4.0}/test/test-database.sh +0 -0
  61. {opensipscli-0.3.5 → opensipscli-0.4.0}/test/test.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensipscli
3
- Version: 0.3.5
3
+ Version: 0.4.0
4
4
  Summary: OpenSIPS Command Line Interface
5
5
  Project-URL: Homepage, https://github.com/OpenSIPS/opensips-cli
6
6
  Project-URL: Source, https://github.com/OpenSIPS/opensips-cli
@@ -14,10 +14,9 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Requires-Python: >=3.8
17
- Requires-Dist: mysqlclient<1.4.0rc1
18
17
  Requires-Dist: opensips
19
- Requires-Dist: sqlalchemy-utils
20
- Requires-Dist: sqlalchemy<2,>=1.3.3
18
+ Requires-Dist: pymysql
19
+ Requires-Dist: sqlalchemy>=1.3.16
21
20
  Description-Content-Type: text/markdown
22
21
 
23
22
  # OpenSIPS CLI (Command Line Interface)
@@ -92,7 +91,7 @@ The OpenSIPSCLI object can receive a set of arguments/modifiers through the
92
91
  ```
93
92
  from opensipscli import args
94
93
  ...
95
- args = OpenSIPSCLIArgs(debug=True)
94
+ args = args.OpenSIPSCLIArgs(debug=True)
96
95
  opensipscli = cli.OpenSIPSCLI(args)
97
96
  ...
98
97
  ```
@@ -100,7 +99,7 @@ opensipscli = cli.OpenSIPSCLI(args)
100
99
  Custom settings can be provided through the arguments, i.e.:
101
100
  ```
102
101
  # run commands over http
103
- args = OpenSIPSCLIArgs(communication_type = "http",
102
+ args = args.OpenSIPSCLIArgs(communication_type = "http",
104
103
  url="http://127.0.0.1:8080/mi")
105
104
  ...
106
105
  ```
@@ -70,7 +70,7 @@ The OpenSIPSCLI object can receive a set of arguments/modifiers through the
70
70
  ```
71
71
  from opensipscli import args
72
72
  ...
73
- args = OpenSIPSCLIArgs(debug=True)
73
+ args = args.OpenSIPSCLIArgs(debug=True)
74
74
  opensipscli = cli.OpenSIPSCLI(args)
75
75
  ...
76
76
  ```
@@ -78,7 +78,7 @@ opensipscli = cli.OpenSIPSCLI(args)
78
78
  Custom settings can be provided through the arguments, i.e.:
79
79
  ```
80
80
  # run commands over http
81
- args = OpenSIPSCLIArgs(communication_type = "http",
81
+ args = args.OpenSIPSCLIArgs(communication_type = "http",
82
82
  url="http://127.0.0.1:8080/mi")
83
83
  ...
84
84
  ```
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env python
1
+ #!/usr/bin/env python3
2
2
 
3
3
  from opensipscli import main
4
4
 
@@ -8,7 +8,7 @@ ENV DEBIAN_FRONTEND noninteractive
8
8
 
9
9
  #install basic components
10
10
  RUN apt-get -y update -qq && \
11
- apt-get -y install git default-libmysqlclient-dev gcc
11
+ apt-get -y install git
12
12
 
13
13
  #add keyserver, repository
14
14
  RUN git clone https://github.com/OpenSIPS/opensips-cli.git /usr/src/opensips-cli && \
@@ -16,7 +16,7 @@ RUN git clone https://github.com/OpenSIPS/opensips-cli.git /usr/src/opensips-cli
16
16
  python3 -m pip install . && \
17
17
  cd / && rm -rf /usr/src/opensips-cli
18
18
 
19
- RUN apt-get purge -y git gcc && \
19
+ RUN apt-get purge -y git && \
20
20
  apt-get autoremove -y && \
21
21
  apt-get clean
22
22
 
@@ -56,27 +56,26 @@ will vary on every supported operating system.
56
56
 
57
57
  ```
58
58
  # required OS packages
59
- sudo apt install python3 python3-pip python3-dev gcc default-libmysqlclient-dev \
60
- python3-mysqldb python3-sqlalchemy python3-sqlalchemy-utils \
59
+ sudo apt install python3 python3-pip python3-pymysql python3-sqlalchemy \
61
60
  python3-openssl
62
61
 
63
62
  # alternatively, you can build the requirements from source
64
- sudo pip3 install mysqlclient sqlalchemy sqlalchemy-utils pyOpenSSL
63
+ sudo pip3 install PyMySQL sqlalchemy pyOpenSSL
65
64
  ```
66
65
 
67
66
  #### Red Hat / CentOS
68
67
 
69
68
  ```
70
69
  # required CentOS 7 packages
71
- sudo yum install python36 python36-pip python36-devel gcc mysql-devel \
72
- python36-mysql python36-sqlalchemy python36-pyOpenSSL
70
+ sudo yum install python36 python36-pip python36-PyMySQL \
71
+ python36-sqlalchemy python36-pyOpenSSL
73
72
 
74
73
  # required CentOS 8 packages
75
- sudo yum install python3 python3-pip python3-devel gcc mysql-devel \
76
- python3-mysqlclient python3-sqlalchemy python3-pyOpenSSL
74
+ sudo yum install python3 python3-pip python3-PyMySQL \
75
+ python3-sqlalchemy python3-pyOpenSSL
77
76
 
78
77
  # alternatively, you can build the requirements from source
79
- sudo pip3 install mysqlclient sqlalchemy sqlalchemy-utils pyOpenSSL
78
+ sudo pip3 install PyMySQL sqlalchemy pyOpenSSL
80
79
  ```
81
80
 
82
81
  ### Download, Build & Install
@@ -132,8 +132,10 @@ opensips-cli -o database_schema_path=~/src/opensips-3.1/scripts \
132
132
 
133
133
  ## Dependencies
134
134
 
135
- * [sqlalchemy and sqlalchemy_utils](https://www.sqlalchemy.org/) - used to
136
- abstract the SQL database regardless of the backend used
135
+ * [sqlalchemy](https://www.sqlalchemy.org/) - used to abstract the SQL
136
+ database regardless of the backend used
137
+ * [PyMySQL](https://github.com/PyMySQL/PyMySQL) - pure-Python MySQL/MariaDB
138
+ driver used by SQLAlchemy when connecting to MySQL backends
137
139
 
138
140
  ## Limitations
139
141
 
@@ -53,8 +53,8 @@ opensips-cli -x user delete username@domain.com
53
53
 
54
54
  ## Dependencies
55
55
 
56
- * [sqlalchemy and sqlalchemy_utils](https://www.sqlalchemy.org/) - used to
57
- abstract the database manipulation, regardless of the backend used
56
+ * [sqlalchemy](https://www.sqlalchemy.org/) - used to abstract the database
57
+ manipulation, regardless of the backend used
58
58
 
59
59
  ## Limitations
60
60
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env python
1
+ #!/usr/bin/env python3
2
2
  ##
3
3
  ## This file is part of OpenSIPS CLI
4
4
  ## (see https://github.com/OpenSIPS/opensips-cli).
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env python
1
+ #!/usr/bin/env python3
2
2
  ##
3
3
  ## This file is part of OpenSIPS CLI
4
4
  ## (see https://github.com/OpenSIPS/opensips-cli).
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env python
1
+ #!/usr/bin/env python3
2
2
  ##
3
3
  ## This file is part of OpenSIPS CLI
4
4
  ## (see https://github.com/OpenSIPS/opensips-cli).
@@ -195,6 +195,7 @@ class OpenSIPSCLI(cmd.Cmd, object):
195
195
  """
196
196
  preload a history file
197
197
  """
198
+ self.configure_completion_delims()
198
199
  history_file = cfg.get('history_file')
199
200
  logger.debug("using history file {}".format(history_file))
200
201
  try:
@@ -210,6 +211,14 @@ class OpenSIPSCLI(cmd.Cmd, object):
210
211
  if not self.registered_atexit:
211
212
  atexit.register(self.history_write)
212
213
 
214
+ def configure_completion_delims(self):
215
+ """
216
+ keep ':' inside tokens for command completion (e.g. "mi evi:")
217
+ """
218
+ delims = readline.get_completer_delims()
219
+ if ':' in delims:
220
+ readline.set_completer_delims(delims.replace(':', ''))
221
+
213
222
  def postcmd(self, stop, line):
214
223
  """
215
224
  post command after switching instance
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env python
1
+ #!/usr/bin/env python3
2
2
  ##
3
3
  ## This file is part of OpenSIPS CLI
4
4
  ## (see https://github.com/OpenSIPS/opensips-cli).
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env python
1
+ #!/usr/bin/env python3
2
2
  ##
3
3
  ## This file is part of OpenSIPS CLI
4
4
  ## (see https://github.com/OpenSIPS/opensips-cli).
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env python
1
+ #!/usr/bin/env python3
2
2
  ##
3
3
  ## This file is part of OpenSIPS CLI
4
4
  ## (see https://github.com/OpenSIPS/opensips-cli).
@@ -23,8 +23,12 @@ import re
23
23
 
24
24
  try:
25
25
  import sqlalchemy
26
- from sqlalchemy.ext.declarative import declarative_base
27
- from sqlalchemy import Column, Date, Integer, String, Boolean
26
+ from sqlalchemy import Column, Date, Integer, String, Boolean, text
27
+ try:
28
+ from sqlalchemy.orm import declarative_base # SA 1.4+
29
+ except ImportError:
30
+ # keep this for Debian Bullseye, which still has SA 1.3 in stable
31
+ from sqlalchemy.ext.declarative import declarative_base # SA 1.3
28
32
  from sqlalchemy.orm import sessionmaker, deferred
29
33
 
30
34
  # for now, we use our own make_url(), since Alchemy API is highly unstable
@@ -32,13 +36,14 @@ try:
32
36
  #from sqlalchemy.engine.url import make_url
33
37
 
34
38
  sqlalchemy_available = True
35
- logger.debug("SQLAlchemy version: ", sqlalchemy.__version__)
36
- try:
37
- import sqlalchemy_utils
38
- except ImportError:
39
- logger.debug("using embedded implementation of SQLAlchemy_Utils")
40
- # copied from SQLAlchemy_utils repository
41
- from opensipscli.libs import sqlalchemy_utils
39
+ logger.debug("SQLAlchemy version: %s", sqlalchemy.__version__)
40
+ # always use the vendored shim — the system sqlalchemy-utils package
41
+ # varies a lot across distros (0.36.x ships broken database_exists for
42
+ # PostgreSQL on Bullseye; 0.41.x is required for SA 2.0); the shim is
43
+ # tested against SA 1.3–2.0 and behaves consistently
44
+ from opensipscli.libs import sqlalchemy_utils
45
+ import pymysql
46
+ pymysql.install_as_MySQLdb()
42
47
  except ImportError:
43
48
  logger.info("sqlalchemy not available!")
44
49
  sqlalchemy_available = False
@@ -205,14 +210,10 @@ class osdb(object):
205
210
 
206
211
  # TODO: do this only for SQLAlchemy
207
212
  try:
208
- if self.dialect == "postgresql":
209
- self.__engine = sqlalchemy.create_engine(db_url, isolation_level='AUTOCOMMIT')
210
- else:
211
- self.__engine = sqlalchemy.create_engine(db_url)
213
+ self.__engine = sqlalchemy.create_engine(db_url, isolation_level='AUTOCOMMIT')
212
214
 
213
215
  logger.debug("connecting to %s", db_url)
214
- self.__conn = self.__engine.connect().\
215
- execution_options(autocommit=True)
216
+ self.__conn = self.__engine.connect()
216
217
  # connect the Session object to our engine
217
218
  self.Session.configure(bind=self.__engine)
218
219
  # instantiate the Session object
@@ -266,7 +267,7 @@ class osdb(object):
266
267
  msg += " and password '********'"
267
268
  msg += " on database '{}'".format(self.db_name)
268
269
  try:
269
- result = self.__conn.execute(sqlcmd)
270
+ result = self.__conn.execute(text(sqlcmd))
270
271
  if result:
271
272
  logger.info( "{} was successful".format(msg))
272
273
  except:
@@ -296,7 +297,7 @@ class osdb(object):
296
297
  self.session = self.Session()
297
298
  logger.debug("connected to database URL '%s'", self.db_url)
298
299
  elif self.dialect != "sqlite":
299
- self.__conn.execute("USE {}".format(self.db_name))
300
+ self.__conn.execute(text("USE {}".format(self.db_name)))
300
301
  except Exception as e:
301
302
  logger.error("failed to connect to %s", self.db_url)
302
303
  logger.error(e)
@@ -319,15 +320,13 @@ class osdb(object):
319
320
 
320
321
  # all good - it's time to create the database
321
322
  if self.dialect == "postgresql":
322
- self.__conn.connection.connection.set_isolation_level(0)
323
323
  try:
324
- self.__conn.execute("CREATE DATABASE {}".format(self.db_name))
325
- self.__conn.connection.connection.set_isolation_level(1)
324
+ self.__conn.execute(text("CREATE DATABASE {}".format(self.db_name)))
326
325
  except sqlalchemy.exc.OperationalError as se:
327
326
  logger.error("cannot create database: {}!".format(se))
328
327
  return False
329
328
  elif self.dialect != "sqlite":
330
- self.__conn.execute("CREATE DATABASE {}".format(self.db_name))
329
+ self.__conn.execute(text("CREATE DATABASE {}".format(self.db_name)))
331
330
 
332
331
  logger.debug("success")
333
332
  return True
@@ -344,11 +343,11 @@ class osdb(object):
344
343
  logger.error("database URL does not include a password")
345
344
  return False
346
345
 
347
- if url.drivername.lower() == "mysql":
346
+ if url.drivername.lower().split('+')[0] == "mysql":
348
347
  sqlcmd = "CREATE USER IF NOT EXISTS '{}' IDENTIFIED BY '{}'".format(
349
348
  url.username, url.password)
350
349
  try:
351
- result = self.__conn.execute(sqlcmd)
350
+ result = self.__conn.execute(text(sqlcmd))
352
351
  if result:
353
352
  logger.info("created user '%s'", url.username)
354
353
  except:
@@ -368,7 +367,7 @@ class osdb(object):
368
367
  sqlcmd = "SET PASSWORD FOR '{}' = PASSWORD('{}')".format(
369
368
  url.username, url.password)
370
369
  try:
371
- result = self.__conn.execute(sqlcmd)
370
+ result = self.__conn.execute(text(sqlcmd))
372
371
  if result:
373
372
  logger.info("set password '%s%s%s' for '%s' (MariaDB)",
374
373
  url.password[0] if len(url.password) >= 1 else '',
@@ -381,7 +380,7 @@ class osdb(object):
381
380
  # syntax error! OK, now try Oracle MySQL syntax
382
381
  sqlcmd = "ALTER USER '{}' IDENTIFIED BY '{}'".format(
383
382
  url.username, url.password)
384
- result = self.__conn.execute(sqlcmd)
383
+ result = self.__conn.execute(text(sqlcmd))
385
384
  if result:
386
385
  logger.info("set password '%s%s%s' for '%s' (MySQL)",
387
386
  url.password[0] if len(url.password) >= 1 else '',
@@ -397,7 +396,7 @@ class osdb(object):
397
396
 
398
397
  sqlcmd = "GRANT ALL ON {}.* TO '{}'".format(self.db_name, url.username)
399
398
  try:
400
- result = self.__conn.execute(sqlcmd)
399
+ result = self.__conn.execute(text(sqlcmd))
401
400
  if result:
402
401
  logger.info("granted access to user '%s' on DB '%s'",
403
402
  url.username, self.db_name)
@@ -408,13 +407,13 @@ class osdb(object):
408
407
 
409
408
  sqlcmd = "FLUSH PRIVILEGES"
410
409
  try:
411
- result = self.__conn.execute(sqlcmd)
410
+ result = self.__conn.execute(text(sqlcmd))
412
411
  logger.info("flushed privileges")
413
412
  except:
414
413
  logger.exception("failed to flush privileges")
415
414
  return False
416
415
 
417
- elif url.drivername.lower() == "postgresql":
416
+ elif url.drivername.lower().split('+')[0] == "postgresql":
418
417
  if not self.exists_role(url.username):
419
418
  logger.info("creating role %s", url.username)
420
419
  if not self.create_role(url.username, url.password):
@@ -427,7 +426,7 @@ class osdb(object):
427
426
  logger.info(sqlcmd)
428
427
 
429
428
  try:
430
- result = self.__conn.execute(sqlcmd)
429
+ result = self.__conn.execute(text(sqlcmd))
431
430
  if result:
432
431
  logger.debug("... OK")
433
432
  except:
@@ -459,7 +458,7 @@ class osdb(object):
459
458
  logger.info(sqlcmd)
460
459
 
461
460
  try:
462
- result = self.__conn.execute(sqlcmd)
461
+ result = self.__conn.execute(text(sqlcmd))
463
462
  if result:
464
463
  logger.info("role '{}' with options '{}' created".
465
464
  format(role_name, role_options))
@@ -481,7 +480,7 @@ class osdb(object):
481
480
  where_str = self.get_where(filter_keys)
482
481
  statement = "DELETE FROM {}{}".format(table, where_str)
483
482
  try:
484
- self.__conn.execute(statement)
483
+ self.__conn.execute(text(statement))
485
484
  except sqlalchemy.exc.SQLAlchemyError as ex:
486
485
  logger.error("cannot execute query: {}".format(statement))
487
486
  logger.error(ex)
@@ -535,7 +534,7 @@ class osdb(object):
535
534
 
536
535
  sqlcmd = "DROP ROLE IF EXISTS {}".format(role_name)
537
536
  try:
538
- result = self.__conn.execute(sqlcmd)
537
+ result = self.__conn.execute(text(sqlcmd))
539
538
  if result:
540
539
  logger.debug("Role '%s' dropped", role_name)
541
540
  except:
@@ -572,7 +571,7 @@ class osdb(object):
572
571
  # DROP/CREATE PROCEDURE statements seem to only work separately
573
572
  sql = re.sub(r'DROP PROCEDURE .*\n', '', sql)
574
573
 
575
- self.__conn.execute(sql)
574
+ self.__conn.execute(text(sql))
576
575
  except sqlalchemy.exc.IntegrityError as ie:
577
576
  raise osdbError("cannot deploy {} file: {}".
578
577
  format(sql_file, ie)) from None
@@ -582,7 +581,7 @@ class osdb(object):
582
581
  if not sql:
583
582
  continue
584
583
  try:
585
- self.__conn.execute(sql)
584
+ self.__conn.execute(text(sql))
586
585
  except sqlalchemy.exc.IntegrityError as ie:
587
586
  raise osdbModuleAlreadyExistsError(
588
587
  "cannot deploy {} file: {}".format(sql_file, ie)) from None
@@ -665,7 +664,7 @@ class osdb(object):
665
664
  table,
666
665
  where_str)
667
666
  try:
668
- result = self.__conn.execute(statement)
667
+ result = self.__conn.execute(text(statement))
669
668
  except sqlalchemy.exc.SQLAlchemyError as ex:
670
669
  logger.error("cannot execute query: {}".format(statement))
671
670
  logger.error(ex)
@@ -676,7 +675,7 @@ class osdb(object):
676
675
  """
677
676
  extract database dialect from an url
678
677
  """
679
- return url.split('://')[0]
678
+ return url.split('://')[0].split('+')[0]
680
679
 
681
680
  def get_where(self, filter_keys):
682
681
  """
@@ -738,7 +737,7 @@ class osdb(object):
738
737
  logger.info(sqlcmd)
739
738
 
740
739
  try:
741
- self.__conn.execute(sqlcmd)
740
+ self.__conn.execute(text(sqlcmd))
742
741
  except Exception as e:
743
742
  logger.exception(e)
744
743
  logger.error("failed to grant '%s' '%s' to '%s'", privs, on_statement, role_name)
@@ -767,6 +766,11 @@ class osdb(object):
767
766
  sqlalchemy.create_engine('{}://'.format(dialect))
768
767
  except sqlalchemy.exc.NoSuchModuleError:
769
768
  return False
769
+ except Exception:
770
+ # dialect is known to SQLAlchemy but its default DBAPI is not
771
+ # installed (e.g. MySQLdb when only PyMySQL is available);
772
+ # a compatible driver is still usable via the +driver URL syntax
773
+ pass
770
774
  return True
771
775
 
772
776
  def insert(self, table, keys):
@@ -781,14 +785,14 @@ class osdb(object):
781
785
  for v in keys.values():
782
786
  values += ", "
783
787
  if type(v) == int:
784
- values += v
788
+ values += str(v)
785
789
  else:
786
790
  values += "'{}'".format(
787
791
  v.translate(str.maketrans({'\'': '\\\''})))
788
792
  statement = "INSERT INTO {} ({}) VALUES ({})".format(
789
793
  table, ", ".join(keys.keys()), values[2:])
790
794
  try:
791
- result = self.__conn.execute(statement)
795
+ result = self.__conn.execute(text(statement))
792
796
  except sqlalchemy.exc.SQLAlchemyError as ex:
793
797
  logger.error("cannot execute query: {}".format(statement))
794
798
  logger.error(ex)
@@ -873,7 +877,7 @@ class osdb(object):
873
877
  for k, v in update_keys.items():
874
878
  update_str += ", {} = ".format(k)
875
879
  if type(v) == int:
876
- update_str += v
880
+ update_str += str(v)
877
881
  else:
878
882
  update_str += "'{}'".format(
879
883
  v.translate(str.maketrans({'\'': '\\\''})))
@@ -881,7 +885,7 @@ class osdb(object):
881
885
  statement = "UPDATE {} SET {}{}".format(table,
882
886
  update_str[2:], where_str)
883
887
  try:
884
- result = self.__conn.execute(statement)
888
+ result = self.__conn.execute(text(statement))
885
889
  except sqlalchemy.exc.SQLAlchemyError as ex:
886
890
  logger.error("cannot execute query: {}".format(statement))
887
891
  logger.error(ex)
@@ -961,8 +965,8 @@ class osdb(object):
961
965
 
962
966
  @staticmethod
963
967
  def get_url_driver(url, capitalize=False):
968
+ driver = make_url(url).drivername.lower().split('+')[0]
964
969
  if capitalize:
965
- driver = make_url(url).drivername.lower()
966
970
  capitalized = {
967
971
  'mysql': 'MySQL',
968
972
  'postgresql': 'PostgreSQL',
@@ -971,7 +975,7 @@ class osdb(object):
971
975
  }
972
976
  return capitalized.get(driver, driver.capitalize())
973
977
  else:
974
- return make_url(url).drivername.lower()
978
+ return driver
975
979
 
976
980
 
977
981
  @staticmethod
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env python
1
+ #!/usr/bin/env python3
2
2
  ##
3
3
  ## This file is part of OpenSIPS CLI
4
4
  ## (see https://github.com/OpenSIPS/opensips-cli).
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env python
1
+ #!/usr/bin/env python3
2
2
  ##
3
3
  ## This file is part of OpenSIPS CLI
4
4
  ## (see https://github.com/OpenSIPS/opensips-cli).