diff --git a/README.md b/README.md index 6ab6327..f59438a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ It requires an appropriate **Python module to be installed separately** - depend The library consists of some keywords designed to perform different checks on your database. Here you can find the [keyword docs](http://marketsquare.github.io/Robotframework-Database-Library/). -Wath the [talk at Robocon 2024 about the Database Library update](https://youtu.be/A96NTUps8sU) +Wath the [talk at Robocon 2024 about the Database Library update](https://youtu.be/A96NTUps8sU). [![Talk at Robocon 2024 about the Database Library update](http://img.youtube.com/vi/A96NTUps8sU/0.jpg)](https://youtu.be/A96NTUps8sU) @@ -18,8 +18,7 @@ Wath the [talk at Robocon 2024 about the Database Library update](https://youtu. ``` pip install robotframework-databaselibrary ``` -# Usage examples -## Basic usage +# Basic usage examples ```RobotFramework *** Settings *** Library DatabaseLibrary @@ -29,25 +28,40 @@ Test Setup Connect To My Oracle DB Connect To My Oracle DB Connect To Database ... oracledb - ... dbName=db - ... dbUsername=my_user - ... dbPassword=my_pass - ... dbHost=127.0.0.1 - ... dbPort=1521 + ... db_name=db + ... db_user=my_user + ... db_password=my_pass + ... db_host=127.0.0.1 + ... db_port=1521 *** Test Cases *** +Get All Names + ${Rows}= Query select FIRST_NAME, LAST_NAME from person + Should Be Equal ${Rows}[0][0] Franz Allan + Should Be Equal ${Rows}[0][1] See + Should Be Equal ${Rows}[1][0] Jerry + Should Be Equal ${Rows}[1][1] Schneider + Person Table Contains Expected Records - ${output}= Query select LAST_NAME from person - Length Should Be ${output} 2 - Should Be Equal ${output}[0][0] See - Should Be Equal ${output}[1][0] Schneider + ${sql}= Catenate select LAST_NAME from person + Check Query Result ${sql} contains See + Check Query Result ${sql} equals Schneider row=1 + +Wait Until Table Gets New Record + ${sql}= Catenate select LAST_NAME from person + Check Row Count ${sql} > 2 retry_timeout=5s Person Table Contains No Joe ${sql}= Catenate SELECT id FROM person - ... WHERE FIRST_NAME= 'Joe' - Check If Not Exists In Database ${sql} + ... WHERE FIRST_NAME= 'Joe' + Check Row Count ${sql} == 0 ``` -## Handling multiple database connections +See more examples in the folder `tests`. + +# Handling multiple database connections +The library can handle multiple connections to different databases using *aliases*. +An alias is set while creating a connection and can be passed to library keywords in a corresponding argument. +## Example ```RobotFramework *** Settings *** Library DatabaseLibrary @@ -56,9 +70,21 @@ Test Teardown Disconnect From All Databases *** Keywords *** Connect To All Databases - Connect To Database psycopg2 db db_user pass 127.0.0.1 5432 + Connect To Database + ... psycopg2 + ... db_name=db + ... db_user=db_user + ... db_password=pass + ... db_host=127.0.0.1 + ... db_port=5432 ... alias=postgres - Connect To Database pymysql db db_user pass 127.0.0.1 3306 + Connect To Database + ... pymysql + ... db_name=db + ... db_user=db_user + ... db_password=pass + ... db_host=127.0.0.1 + ... db_port=3306 ... alias=mysql *** Test Cases *** @@ -73,7 +99,130 @@ Switching Default Alias Execute Sql String drop table XYZ ``` -See more examples in the folder `tests`. + + +# Using configuration file +The `Connect To Database` keyword allows providing the connection parameters in two ways: +- As keyword arguments +- In a configuration file - a simple list of _key=value_ pairs, set inside an _alias_ section. + +You can use only one way or you can combine them: +- The keyword arguments are taken by default +- If no keyword argument is provided, a parameter value is searched in the config file + +Along with commonly used connection parameters, named exactly as keyword arguments, a config file +can contain any other DB module specific parameters as key/value pairs. +If same custom parameter is provided both as a keyword argument *and* in config file, +the *keyword argument value takes precedence*. + +The path to the config file is set by default to `./resources/db.cfg`. +You can change it using an according parameter in the `Connect To Database` keyword. + +A config file *must* contain at least one section name - +the connection alias, if used (see [Handling multiple database connections](#handling-multiple-database-connections)), or +`[default]` if no aliases are used. + +## Config file examples +### Config file with default alias (equal to using no aliases at all) +``` +[default] +db_module=psycopg2 +db_name=yourdbname +db_user=yourusername +db_password=yourpassword +db_host=yourhost +db_port=yourport +``` +### Config file with a specific alias +``` +[myoracle] +db_module=oracledb +db_name=yourdbname +db_user=yourusername +db_password=yourpassword +db_host=yourhost +db_port=yourport +``` + +### Config file with some params only +``` +[default] +db_password=mysecret +``` +### Config file with some custom DB module specific params +``` +[default] +my_custom_param=value +``` + +# Inline assertions +Keywords, that accept arguments ``assertion_operator`` and ``expected_value``, +perform a check according to the specified condition - using the [Assertion Engine](https://github.com/MarketSquare/AssertionEngine). + +## Examples +```RobotFramework +Check Row Count SELECT id FROM person == 2 +Check Query Result SELECT first_name FROM person contains Allan +``` + +# Retry mechanism +Assertion keywords, that accept arguments ``retry_timeout`` and ``retry_pause``, support waiting for assertion to pass. + +Setting the ``retry_timeout`` argument enables the mechanism - +in this case the SQL request and the assertion are executed in a loop, +until the assertion is passed or the ``retry_timeout`` is reached. +The pause between the loop iterations is set using the ``retry_pause`` argument. + +The argument values are set in [Robot Framework time format](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#time-format) - e.g. ``5 seconds``. + +The retry mechanism is disabled by default - ``retry_timeout`` is set to ``0``. + +## Examples +```RobotFramework +${sql}= Catenate SELECT first_name FROM person +Check Row Count ${sql} == 2 retry_timeout=10 seconds +Check Query Result ${sql} contains Allan retry_timeout=5s retry_pause=1s +```` + +# Logging query results +Keywords, that fetch results of a SQL query, print the result rows as a table in RF log. +- A log head limit of *50 rows* is applied, other table rows are truncated in the log message. +- The limit and the logging in general can be adjusted any time in your tests using the Keyword `Set Logging Query Results`. + +You can also setup the limit or disable the logging during the library import. +## Examples +```RobotFramework +*** Settings *** +# Default behavior - logging of query results is enabled, log head is 50 rows. +Library DatabaseLibrary + +# Logging of query results is disabled, log head is 50 rows (default). +Library DatabaseLibrary log_query_results=False + +# Logging of query results is enabled (default), log head is 10 rows. +Library DatabaseLibrary log_query_results_head=10 + +# Logging of query results is enabled (default), log head limit is disabled (log all rows). +Library DatabaseLibrary log_query_results_head=0 +```` + +# Commit behavior +While creating a database connection, the library doesn't explicitly set the _autocommit_ behavior - +so the default value of the Python DB module is used. +According to Python DB API specification it should be disabled by default - +which means each SQL transaction must contain a dedicated commit statement, if necessary. + +The library manages it for you: +- Keywords like `Execute SQL String` perform automatically a commit after running the query - or a rollback in case of error +- Keywords like `Query` don't perform a commit, but also do a rollback in case of error + +You can turn off this automatic commit/rollback behavior using the ``no_transaction`` parameter. +See docs of a particular keyword. + +It's also possible to explicitly set the _autocommit_ behavior on the Python DB module level - +using the `Set Auto Commit` keyword. +This has no impact on the automatic commit/rollback behavior in library keywords (described above). + # Database modules compatibility The library is basically compatible with any [Python Database API Specification 2.0](https://peps.python.org/pep-0249/) module. @@ -83,7 +232,7 @@ Therefore there are some modules, which are "natively" supported in the library ## Python modules currently "natively" supported ### Oracle - [oracledb](https://oracle.github.io/python-oracledb/) - - Both thick and thin client modes are supported - you can select one using the `driverMode` parameter. + - Both thick and thin client modes are supported - you can select one using the `oracle_driver_mode` parameter. - However, due to current limitations of the oracledb module, **it's not possible to switch between thick and thin modes during a test execution session** - even in different suites. - [cx_Oracle](https://oracle.github.io/python-cx_Oracle/) ### MySQL diff --git a/doc/index.html b/doc/index.html index bed2160..b82912c 100644 --- a/doc/index.html +++ b/doc/index.html @@ -1191,7 +1191,7 @@ jQuery.extend({highlight:function(e,t,n,r){if(e.nodeType===3){var i=e.data.match(t);if(i){var s=document.createElement(n||"span");s.className=r||"highlight";var o=e.splitText(i.index);o.splitText(i[0].length);var u=o.cloneNode(true);s.appendChild(u);o.parentNode.replaceChild(s,o);return 1}}else if(e.nodeType===1&&e.childNodes&&!/(script|style)/i.test(e.tagName)&&!(e.tagName===n.toUpperCase()&&e.className===r)){for(var a=0;a diff --git a/src/DatabaseLibrary/__init__.py b/src/DatabaseLibrary/__init__.py index d0a8967..c89d9f8 100644 --- a/src/DatabaseLibrary/__init__.py +++ b/src/DatabaseLibrary/__init__.py @@ -39,8 +39,7 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): | pip install robotframework-databaselibrary Don't forget to install the required Python database module! - = Usage example = - == Basic usage == + = Basic usage examples = | *** Settings *** | Library DatabaseLibrary | Test Setup Connect To My Oracle DB @@ -49,26 +48,39 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): | Connect To My Oracle DB | Connect To Database | ... oracledb - | ... dbName=db - | ... dbUsername=my_user - | ... dbPassword=my_pass - | ... dbHost=127.0.0.1 - | ... dbPort=1521 + | ... db_name=db + | ... db_user=my_user + | ... db_password=my_pass + | ... db_host=127.0.0.1 + | ... db_port=1521 | | *** Test Cases *** + | Get All Names + | ${Rows}= Query select FIRST_NAME, LAST_NAME from person + | Should Be Equal ${Rows}[0][0] Franz Allan + | Should Be Equal ${Rows}[0][1] See + | Should Be Equal ${Rows}[1][0] Jerry + | Should Be Equal ${Rows}[1][1] Schneider + | | Person Table Contains Expected Records - | ${output}= Query select LAST_NAME from person - | Length Should Be ${output} 2 - | Should Be Equal ${output}[0][0] See - | Should Be Equal ${output}[1][0] Schneider + | ${sql}= Catenate select LAST_NAME from person + | Check Query Result ${sql} contains See + | Check Query Result ${sql} equals Schneider row=1 + | + | Wait Until Table Gets New Record + | ${sql}= Catenate select LAST_NAME from person + | Check Row Count ${sql} > 2 retry_timeout=5s | | Person Table Contains No Joe | ${sql}= Catenate SELECT id FROM person | ... WHERE FIRST_NAME= 'Joe' - | Check If Not Exists In Database ${sql} + | Check Row Count ${sql} == 0 | - == Handling multiple database connections == + = Handling multiple database connections = + The library can handle multiple connections to different databases using *aliases*. + An alias is set while creating a connection and can be passed to library keywords in a corresponding argument. + == Example == | *** Settings *** | Library DatabaseLibrary | Test Setup Connect To All Databases @@ -76,9 +88,21 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): | | *** Keywords *** | Connect To All Databases - | Connect To Database psycopg2 db db_user pass 127.0.0.1 5432 + | Connect To Database + | ... psycopg2 + | ... db_name=db + | ... db_user=db_user + | ... db_password=pass + | ... db_host=127.0.0.1 + | ... db_port=5432 | ... alias=postgres - | Connect To Database pymysql db db_user pass 127.0.0.1 3306 + | Connect To Database + | ... pymysql + | ... db_name=db + | ... db_user=db_user + | ... db_password=pass + | ... db_host=127.0.0.1 + | ... db_port=3306 | ... alias=mysql | | *** Test Cases *** @@ -117,25 +141,25 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): == Config file examples == === Config file with default alias (equal to using no aliases at all) === | [default] - | dbapiModuleName=psycopg2 - | dbName=yourdbname - | dbUsername=yourusername - | dbPassword=yourpassword - | dbHost=yourhost - | dbPort=yourport + | db_module=psycopg2 + | db_name=yourdbname + | db_user=yourusername + | db_password=yourpassword + | db_host=yourhost + | db_port=yourport === Config file with a specific alias === | [myoracle] - | dbapiModuleName=oracledb - | dbName=yourdbname - | dbUsername=yourusername - | dbPassword=yourpassword - | dbHost=yourhost - | dbPort=yourport + | db_module=oracledb + | db_name=yourdbname + | db_user=yourusername + | db_password=yourpassword + | db_host=yourhost + | db_port=yourport === Config file with some params only === | [default] - | dbPassword=mysecret + | db_password=mysecret === Config file with some custom DB module specific params === | [default] @@ -161,11 +185,11 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): The argument values are set in [http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#time-format|Robot Framework time format] - e.g. ``5 seconds``. - The retry mechanism is disabled by default - the ``retry_timeout`` is set to ``0``. + The retry mechanism is disabled by default - ``retry_timeout`` is set to ``0``. Examples: | Check Row Count | SELECT id FROM person | *==* | 2 | retry_timeout=10 seconds | - | Check Query Result | SELECT first_name FROM person | *contains* | Allan | retry_timeout=5s | retry_timeout=1s | + | Check Query Result | SELECT first_name FROM person | *contains* | Allan | retry_timeout=5s | retry_pause=1s | = Logging query results = Keywords, that fetch results of a SQL query, print the result rows as a table in RF log. @@ -175,8 +199,8 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): You can also setup the limit or disable the logging during the library import. Examples: - | # Default behavior - logging of query results is enabled, log head is 50 rows. | *** Settings *** + | # Default behavior - logging of query results is enabled, log head is 50 rows. | Library DatabaseLibrary | | # Logging of query results is disabled, log head is 50 rows (default). @@ -188,13 +212,56 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): | # Logging of query results is enabled (default), log head limit is disabled (log all rows). | Library DatabaseLibrary log_query_results_head=0 + = Commit behavior = + While creating a database connection, the library doesn't explicitly set the _autocommit_ behavior - + so the default value of the Python DB module is used. + According to Python DB API specification it should be disabled by default - + which means each SQL transaction must contain a dedicated commit statement, if necessary. + + The library manages it for you: + - Keywords like `Execute SQL String` perform automatically a commit after running the query - or a rollback in case of error + - Keywords like `Query` don't perform a commit, but also do a rollback in case of error + + You can turn off this automatic commit/rollback behavior using the ``no_transaction`` parameter. + See docs of a particular keyword. + + It's also possible to explicitly set the _autocommit_ behavior on the Python DB module level - + using the `Set Auto Commit` keyword. + This has no impact on the automatic commit/rollback behavior in library keywords (described above). + = Database modules compatibility = The library is basically compatible with any [https://peps.python.org/pep-0249|Python Database API Specification 2.0] module. However, the actual implementation in existing Python modules is sometimes quite different, which requires custom handling in the library. Therefore, there are some modules, which are "natively" supported in the library - and others, which may work and may not. - See more on the [https://github.com/MarketSquare/Robotframework-Database-Library|project page on GitHub]. + == Python modules currently "natively" supported == + === Oracle === + [https://oracle.github.io/python-oracledb/|oracledb] + - Both thick and thin client modes are supported - you can select one using the `oracle_driver_mode` parameter. + - However, due to current limitations of the oracledb module, **it's not possible to switch between thick and thin modes during a test execution session** - even in different suites. + + [https://oracle.github.io/python-cx_Oracle/|cx_Oracle] + + === MySQL === + - [https://github.com/PyMySQL/PyMySQL|pymysql] + - [https://mysqlclient.readthedocs.io/index.html|MySQLdb] + === PostgreSQL === + - [https://www.psycopg.org/docs/|psycopg2] + === MS SQL Server === + - [https://github.com/pymssql/pymssql|pymssql] + === SQLite === + - [https://docs.python.org/3/library/sqlite3.html|sqlite3] + === Teradata === + - [https://github.com/teradata/PyTd|teradata] + === IBM DB2 === + - [https://github.com/ibmdb/python-ibmdb|ibm_db] + - [https://github.com/ibmdb/python-ibmdb|ibm_db_dbi] + === ODBC === + - [https://github.com/mkleehammer/pyodbc|pyodbc] + - [https://github.com/pypyodbc/pypyodbc|pypyodbc] + === Kingbase === + - ksycopg2 """ ROBOT_LIBRARY_SCOPE = "GLOBAL" diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 256fad5..3d2fd56 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -18,6 +18,8 @@ from robot.libraries.BuiltIn import BuiltIn from robot.utils import timestr_to_secs +from .params_decorator import renamed_args + class Assertion: """ @@ -26,8 +28,9 @@ class Assertion: def check_if_exists_in_database( self, - selectStatement: str, - sansTran: bool = False, + select_statement: str, + *, + no_transaction: bool = False, msg: Optional[str] = None, alias: Optional[str] = None, parameters: Optional[Tuple] = None, @@ -36,10 +39,10 @@ def check_if_exists_in_database( *DEPRECATED* Use new `Check Row Count` keyword with assertion engine instead. The deprecated keyword will be removed in future versions. - Check if any row would be returned by given the input ``selectStatement``. If there are no results, then this will + Check if any row would be returned by given the input ``select_statement``. If there are no results, then this will throw an AssertionError. - Set optional input ``sansTran`` to _True_ to run command without an explicit transaction + Set optional input ``no_transaction`` to _True_ to run command without an explicit transaction commit or rollback. The default error message can be overridden with the ``msg`` argument. @@ -47,20 +50,20 @@ def check_if_exists_in_database( Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. - Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + Use ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client). Examples: | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | msg=my error message | | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | alias=my_alias | - | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | sansTran=True | + | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | no_transaction=True | | @{parameters} | Create List | John | | Check If Exists In Database | SELECT id FROM person WHERE first_name = %s | parameters=${parameters} | """ - if not self.query(selectStatement, sansTran, alias=alias, parameters=parameters): + if not self.query(select_statement, no_transaction, alias=alias, parameters=parameters): raise AssertionError( - msg or f"Expected to have have at least one row, but got 0 rows from: '{selectStatement}'" + msg or f"Expected to have have at least one row, but got 0 rows from: '{select_statement}'" ) def check_if_not_exists_in_database( @@ -87,7 +90,7 @@ def check_if_not_exists_in_database( Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. - Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + Use ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client). Examples: @@ -127,7 +130,7 @@ def row_count_is_0( Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. - Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + Use ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client). Examples: @@ -165,7 +168,7 @@ def row_count_is_equal_to_x( Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. - Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + Use ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client). Examples: @@ -205,7 +208,7 @@ def row_count_is_greater_than_x( Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. - Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + Use ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client). Examples: @@ -245,7 +248,7 @@ def row_count_is_less_than_x( Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. - Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + Use ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client). Examples: @@ -262,40 +265,50 @@ def row_count_is_less_than_x( msg or f"Expected less than {numRows} rows, but {num_rows} were returned from '{selectStatement}'" ) + @renamed_args(mapping={"selectStatement": "select_statement", "sansTran": "no_transaction"}) def check_row_count( self, - selectStatement: str, + select_statement: str, assertion_operator: AssertionOperator, expected_value: int, assertion_message: Optional[str] = None, - sansTran: bool = False, + no_transaction: bool = False, alias: Optional[str] = None, parameters: Optional[Tuple] = None, retry_timeout="0 seconds", retry_pause="0.5 seconds", + *, + selectStatement: Optional[str] = None, + sansTran: Optional[bool] = None, ): """ - Check the number of rows returned from ``selectStatement`` using ``assertion_operator`` + Check the number of rows returned from ``select_statement`` using ``assertion_operator`` and ``expected_value``. See `Inline assertions` for more details. - Use optional ``assertion_message`` to override the default error message. + Use ``assertion_message`` to override the default error message. - Set optional input ``sansTran`` to _True_ to run command without an explicit transaction commit or rollback. + Set ``no_transaction`` to _True_ to run command without explicit transaction rollback in case of error. + See `Commit behavior` for details. - Use optional ``alias`` parameter to specify what connection should be used for the query if you have more - than one connection open. + Use ``alias`` to specify what connection should be used if `Handling multiple database connections`. - Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + Use ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client). Use ``retry_timeout`` and ``retry_pause`` parameters to enable waiting for assertion to pass. See `Retry mechanism` for more details. - Examples: + === Some parameters were renamed in version 2.0 === + The old parameters ``selectStatement`` and ``sansTran`` are *deprecated*, + please use new parameters ``select_statement`` and ``no_transaction`` instead. + + *The old parameters will be removed in future versions.* + + === Examples === | Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *==* | 1 | | Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *>=* | 2 | assertion_message=my error message | | Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *inequal* | 3 | alias=my_alias | - | Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *less than* | 4 | sansTran=True | + | Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *less than* | 4 | no_transaction=True | | @{parameters} | Create List | John | | Check Row Count | SELECT id FROM person WHERE first_name = %s | *equals* | 5 | parameters=${parameters} | """ @@ -303,7 +316,9 @@ def check_row_count( time_counter = 0 while not check_ok: try: - num_rows = self.row_count(selectStatement, sansTran, alias=alias, parameters=parameters) + num_rows = self.row_count( + select_statement, no_transaction=no_transaction, alias=alias, parameters=parameters + ) verify_assertion(num_rows, assertion_operator, expected_value, "Wrong row count:", assertion_message) check_ok = True except AssertionError as e: @@ -313,22 +328,26 @@ def check_row_count( BuiltIn().sleep(retry_pause) time_counter += timestr_to_secs(retry_pause) + @renamed_args(mapping={"selectStatement": "select_statement", "sansTran": "no_transaction"}) def check_query_result( self, - selectStatement, + select_statement: str, assertion_operator: AssertionOperator, expected_value: Any, row=0, col=0, assertion_message: Optional[str] = None, - sansTran: bool = False, + no_transaction: bool = False, alias: Optional[str] = None, parameters: Optional[Tuple] = None, retry_timeout="0 seconds", retry_pause="0.5 seconds", + *, + selectStatement: Optional[str] = None, + sansTran: Optional[bool] = None, ): """ - Check value in query result returned from ``selectStatement`` using ``assertion_operator`` and ``expected_value``. + Check value in query result returned from ``select_statement`` using ``assertion_operator`` and ``expected_value``. The value position in results can be adjusted using ``row`` and ``col`` parameters (0-based). See `Inline assertions` for more details. @@ -338,25 +357,31 @@ def check_query_result( Use optional ``assertion_message`` to override the default error message. - Set optional input ``sansTran`` to _True_ to run command without an explicit transaction commit or rollback. + Set ``no_transaction`` to _True_ to run command without explicit transaction rollback in case of error. + See `Commit behavior` for details. - Use optional ``alias`` parameter to specify what connection should be used for the query if you have more - than one connection open. + Use ``alias`` to specify what connection should be used if `Handling multiple database connections`. - Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + Use ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client). Use ``retry_timeout`` and ``retry_pause`` parameters to enable waiting for assertion to pass. See `Retry mechanism` for more details. - Examples: + === Some parameters were renamed in version 2.0 === + The old parameters ``selectStatement`` and ``sansTran`` are *deprecated*, + please use new parameters ``select_statement`` and ``no_transaction`` instead. + + *The old parameters will be removed in future versions.* + + === Examples === | Check Query Result | SELECT first_name FROM person | *contains* | Allan | | Check Query Result | SELECT first_name, last_name FROM person | *==* | Schneider | row=1 | col=1 | | Check Query Result | SELECT id FROM person WHERE first_name = 'John' | *==* | 2 | # Fails, if query returns an integer value | | Check Query Result | SELECT id FROM person WHERE first_name = 'John' | *==* | ${2} | # Works, if query returns an integer value | | Check Query Result | SELECT first_name FROM person | *equal* | Franz Allan | assertion_message=my error message | | Check Query Result | SELECT first_name FROM person | *inequal* | John | alias=my_alias | - | Check Query Result | SELECT first_name FROM person | *contains* | Allan | sansTran=True | + | Check Query Result | SELECT first_name FROM person | *contains* | Allan | no_transaction=True | | @{parameters} | Create List | John | | Check Query Result | SELECT first_name FROM person | *contains* | Allan | parameters=${parameters} | """ @@ -364,7 +389,9 @@ def check_query_result( time_counter = 0 while not check_ok: try: - query_results = self.query(selectStatement, sansTran, alias=alias, parameters=parameters) + query_results = self.query( + select_statement, no_transaction=no_transaction, alias=alias, parameters=parameters + ) row_count = len(query_results) assert ( row < row_count @@ -385,52 +412,65 @@ def check_query_result( BuiltIn().sleep(retry_pause) time_counter += timestr_to_secs(retry_pause) + @renamed_args(mapping={"tableName": "table_name", "sansTran": "no_transaction"}) def table_must_exist( - self, tableName: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None + self, + table_name: str, + no_transaction: bool = False, + msg: Optional[str] = None, + alias: Optional[str] = None, + *, + tableName: Optional[str] = None, + sansTran: Optional[bool] = None, ): """ - Check if the given table exists in the database. + Check if the table with `table_name` exists in the database. - Set optional input ``sansTran`` to True to run command without an - explicit transaction commit or rollback. + Use ``msg`` for custom error message. - The default error message can be overridden with the ``msg`` argument. + Set ``no_transaction`` to _True_ to run command without explicit transaction rollback in case of error. + See `Commit behavior` for details. - Use optional ``alias`` parameter to specify what connection should be used for the query if you have more - than one connection open. + Use ``alias`` to specify what connection should be used if `Handling multiple database connections`. - Examples: + === Some parameters were renamed in version 2.0 === + The old parameters ``tableName`` and ``sansTran`` are *deprecated*, + please use new parameters ``table_name`` and ``no_transaction`` instead. + + *The old parameters will be removed in future versions.* + + === Examples === | Table Must Exist | person | | Table Must Exist | person | msg=my error message | | Table Must Exist | person | alias=my_alias | - | Table Must Exist | person | sansTran=True | + | Table Must Exist | person | no_transaction=True | """ db_connection = self.connection_store.get_connection(alias) if db_connection.module_name in ["cx_Oracle", "oracledb"]: query = ( "SELECT * FROM all_objects WHERE object_type IN ('TABLE','VIEW') AND " - f"owner = SYS_CONTEXT('USERENV', 'SESSION_USER') AND object_name = UPPER('{tableName}')" + f"owner = SYS_CONTEXT('USERENV', 'SESSION_USER') AND object_name = UPPER('{table_name}')" ) - table_exists = self.row_count(query, sansTran, alias=alias) > 0 + table_exists = self.row_count(query, no_transaction=no_transaction, alias=alias) > 0 elif db_connection.module_name in ["sqlite3"]: - query = f"SELECT name FROM sqlite_master WHERE type='table' AND name='{tableName}' COLLATE NOCASE" - table_exists = self.row_count(query, sansTran, alias=alias) > 0 + query = f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}' COLLATE NOCASE" + table_exists = self.row_count(query, no_transaction=no_transaction, alias=alias) > 0 elif db_connection.module_name in ["ibm_db", "ibm_db_dbi"]: - query = f"SELECT name FROM SYSIBM.SYSTABLES WHERE type='T' AND name=UPPER('{tableName}')" - table_exists = self.row_count(query, sansTran, alias=alias) > 0 + query = f"SELECT name FROM SYSIBM.SYSTABLES WHERE type='T' AND name=UPPER('{table_name}')" + table_exists = self.row_count(query, no_transaction=no_transaction, alias=alias) > 0 elif db_connection.module_name in ["teradata"]: - query = f"SELECT TableName FROM DBC.TablesV WHERE TableKind='T' AND TableName='{tableName}'" - table_exists = self.row_count(query, sansTran, alias=alias) > 0 + query = f"SELECT TableName FROM DBC.TablesV WHERE TableKind='T' AND TableName='{table_name}'" + table_exists = self.row_count(query, no_transaction=no_transaction, alias=alias) > 0 else: try: - query = f"SELECT * FROM information_schema.tables WHERE table_name='{tableName}'" - table_exists = self.row_count(query, sansTran, alias=alias) > 0 + query = f"SELECT * FROM information_schema.tables WHERE table_name='{table_name}'" + table_exists = self.row_count(query, no_transaction=no_transaction, alias=alias) > 0 except: logger.info("Database doesn't support information schema, try using a simple SQL request") try: - query = f"SELECT 1 from {tableName} where 1=0" - self.row_count(query, sansTran, alias=alias) + query = f"SELECT 1 from {table_name} where 1=0" + self.row_count(query, no_transaction=no_transaction, alias=alias) table_exists = True except: table_exists = False - assert table_exists, msg or f"Table '{tableName}' does not exist in the db" + assert table_exists, msg or f"Table '{table_name}' does not exist in the db" diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index c0fe4ce..1969b32 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -20,6 +20,8 @@ from robot.api import logger +from .params_decorator import renamed_args + @dataclass class Connection: @@ -156,58 +158,82 @@ def _hide_password_values(string_with_pass, params_separator=","): ) return string_with_hidden_pass + @renamed_args( + mapping={ + "dbapiModuleName": "db_module", + "dbName": "db_name", + "dbUsername": "db_user", + "dbPassword": "db_password", + "dbHost": "db_host", + "dbPort": "db_port", + "dbCharset": "db_charset", + "dbDriver": "odbc_driver", + "dbConfigFile": "config_file", + "driverMode": "oracle_driver_mode", + } + ) def connect_to_database( self, - dbapiModuleName: Optional[str] = None, - dbName: Optional[str] = None, - dbUsername: Optional[str] = None, - dbPassword: Optional[str] = None, - dbHost: Optional[str] = None, - dbPort: Optional[int] = None, - dbCharset: Optional[str] = None, - dbDriver: Optional[str] = None, - dbConfigFile: Optional[str] = None, - driverMode: Optional[str] = None, + db_module: Optional[str] = None, + db_name: Optional[str] = None, + db_user: Optional[str] = None, + db_password: Optional[str] = None, + db_host: Optional[str] = None, + db_port: Optional[int] = None, + db_charset: Optional[str] = None, + odbc_driver: Optional[str] = None, + config_file: Optional[str] = None, + oracle_driver_mode: Optional[str] = None, alias: str = "default", **custom_connection_params, ): """ - Creates a database connection using the DB API 2.0 module ``dbapiModuleName`` and the parameters provided. - Along with listed commonly used arguments (`dbName`, `dbHost` etc.) + Creates a database connection using the DB API 2.0 ``db_module`` and the parameters provided. + Along with listed commonly used arguments (`db_name`, `db_host` etc.) you can set any other DB module specific parameters as key/value pairs. - Use ``dbConfigFile`` to provide a path to configuration file with connection parameters + Use ``config_file`` to provide a path to configuration file with connection parameters to be used along with / instead of keyword arguments. If no specified, it defaults to `./resources/db.cfg`. See `Using configuration file` for more details. - All params are optional, although ``dbapiModuleName`` must be set - either as keyword argument or in config file. - If some of the listed keyword arguments (`dbName`, `dbHost` etc.) are not provided (i.e. left on default value `None`), + All params are optional, although ``db_module`` must be set - either as keyword argument or in config file. + If some of the listed keyword arguments (`db_name`, `db_host` etc.) are not provided (i.e. left on default value `None`), they are normally not passed to the Python DB module at all, except: - - _dbPort_ - commonly used port number for known databases is set as fallback - - _dbCharset_ - _UTF8_ is used as fallback for _pymysql_, _pymssql_ and _pyodbc_ - - _driverMode_ - _thin_ is used as fallback for _oracledb_ + - _db_port_ - commonly used port number for known databases is set as fallback + - _db_charset_ - _UTF8_ is used as fallback for _pymysql_, _pymssql_ and _pyodbc_ + - _oracle_driver_mode_ - _thin_ is used as fallback for _oracledb_ Other custom params from keyword arguments and config file are passed to the Python DB module as provided - normally as arguments for the _connect()_ function. However, when using *pyodbc*, the connection is established using a connection string - so all the custom params are added into it instead of function arguments. - Optional ``alias`` parameter can be used for creating multiple open connections, even for different databases. - If the same alias is given twice then previous connection will be overridden. + Set ``alias`` for `Handling multiple database connections`. + If the same alias is given twice, then previous connection will be overridden. - The ``driverMode`` is used to select the *oracledb* client mode. + The ``oracle_driver_mode`` is used to select the *oracledb* client mode. Allowed values are: - _thin_ (default if omitted) - _thick_ - _thick,lib_dir=_ - Examples + === Some parameters were renamed in version 2.0 === + The old parameters ``dbapiModuleName``, ``dbName``, ``dbUsername``, + ``dbPassword``, ``dbHost``, ``dbPort``, ``dbCharset``, ``dbDriver``, + ``dbConfigFile`` and ``driverMode`` are *deprecated*, + please use new parameters ``db_module``, ``db_name``, ``db_user``, + ``db_password``, ``db_host``, ``db_port``, ``db_charset``, ``odbc_driver``, + ``config_file`` and ``oracle_driver_mode`` instead. + + *The old parameters will be removed in future versions.* + + === Examples === | Connect To Database | psycopg2 | my_db | user | pass | tiger.foobar.com | 5432 | | Connect To Database | psycopg2 | my_db | user | pass | tiger.foobar.com | 5432 | my_custom_param=value | | Connect To Database | psycopg2 | my_db | user | pass | tiger.foobar.com | 5432 | alias=my_alias | - | Connect To Database | dbConfigFile=my_db_params.cfg | + | Connect To Database | config_file=my_db_params.cfg | """ - config = ConfigReader(dbConfigFile, alias) + config = ConfigReader(config_file, alias) def _build_connection_params(custom_params=True, **basic_params): con_params = basic_params.copy() @@ -221,7 +247,7 @@ def _build_connection_params(custom_params=True, **basic_params): return con_params def _log_all_connection_params(*, connection_object=None, connection_string=None, **connection_params): - connection_object = connection_object or dbapiModuleName + connection_object = connection_object or db_module msg = f"Connect to DB using : {connection_object}.connect(" params_separator = "," if connection_string: @@ -233,15 +259,22 @@ def _log_all_connection_params(*, connection_object=None, connection_string=None msg += f"'{param_value}'" else: msg += f"{param_value}" - if dbPassword: - msg = msg.replace(f"'{dbPassword}'", "***") + if db_password: + msg = msg.replace(f"'{db_password}'", "***") msg = self._hide_password_values(msg, params_separator) msg = msg.replace("connect(, ", "connect(") msg += ")" logger.info(msg) - def _arg_or_config(arg_value, param_name, mandatory=False): + def _arg_or_config(arg_value, param_name, *, old_param_name=None, mandatory=False): val_from_config = config.pop(param_name) + + # support deprecated old param names + if val_from_config is None and old_param_name is not None: + val_from_config = config.pop(old_param_name) + if val_from_config is not None: + logger.warn(f"Config file: argument '{old_param_name}' is deprecated, use '{param_name}' instead") + if arg_value is not None: final_value = arg_value if val_from_config is not None: @@ -259,18 +292,18 @@ def _arg_or_config(arg_value, param_name, mandatory=False): return final_value # mandatory parameter - dbapiModuleName = _arg_or_config(dbapiModuleName, "dbapiModuleName", mandatory=True) + db_module = _arg_or_config(db_module, "db_module", mandatory=True, old_param_name="dbapiModuleName") # optional named params - named because of custom module specific handling - dbName = _arg_or_config(dbName, "dbName") - dbUsername = _arg_or_config(dbUsername, "dbUsername") - dbPassword = _arg_or_config(dbPassword, "dbPassword") - dbHost = _arg_or_config(dbHost, "dbHost") - dbPort = _arg_or_config(dbPort, "dbPort") - if dbPort is not None: - dbPort = int(dbPort) - dbCharset = _arg_or_config(dbCharset, "dbCharset") - dbDriver = _arg_or_config(dbDriver, "dbDriver") - driverMode = _arg_or_config(driverMode, "driverMode") + db_name = _arg_or_config(db_name, "db_name", old_param_name="dbName") + db_user = _arg_or_config(db_user, "db_user", old_param_name="dbUsername") + db_password = _arg_or_config(db_password, "db_password", old_param_name="dbPassword") + db_host = _arg_or_config(db_host, "db_host", old_param_name="dbHost") + db_port = _arg_or_config(db_port, "db_port", old_param_name="dbPort") + if db_port is not None: + db_port = int(db_port) + db_charset = _arg_or_config(db_charset, "db_charset", old_param_name="dbCharset") + odbc_driver = _arg_or_config(odbc_driver, "odbc_driver", old_param_name="dbDriver") + oracle_driver_mode = _arg_or_config(oracle_driver_mode, "oracle_driver_mode", old_param_name="driverMode") for param_name, param_value in custom_connection_params.items(): _arg_or_config(param_value, param_name) @@ -278,61 +311,61 @@ def _arg_or_config(arg_value, param_name, mandatory=False): if other_config_file_params: logger.info(f"Other params from configuration file: {list(other_config_file_params.keys())}") - if dbapiModuleName == "excel" or dbapiModuleName == "excelrw": + if db_module == "excel" or db_module == "excelrw": db_api_module_name = "pyodbc" else: - db_api_module_name = dbapiModuleName + db_api_module_name = db_module db_api_2 = importlib.import_module(db_api_module_name) - if dbapiModuleName in ["MySQLdb", "pymysql"]: - dbPort = dbPort or 3306 - dbCharset = dbCharset or "utf8mb4" + if db_module in ["MySQLdb", "pymysql"]: + db_port = db_port or 3306 + db_charset = db_charset or "utf8mb4" con_params = _build_connection_params( - db=dbName, user=dbUsername, passwd=dbPassword, host=dbHost, port=dbPort, charset=dbCharset + db=db_name, user=db_user, passwd=db_password, host=db_host, port=db_port, charset=db_charset ) _log_all_connection_params(**con_params) db_connection = db_api_2.connect(**con_params) - elif dbapiModuleName in ["pymssql"]: - dbPort = dbPort or 1433 - dbCharset = dbCharset or "UTF-8" + elif db_module in ["pymssql"]: + db_port = db_port or 1433 + db_charset = db_charset or "UTF-8" con_params = _build_connection_params( - database=dbName, user=dbUsername, password=dbPassword, host=dbHost, port=dbPort, charset=dbCharset + database=db_name, user=db_user, password=db_password, host=db_host, port=db_port, charset=db_charset ) _log_all_connection_params(**con_params) db_connection = db_api_2.connect(**con_params) - elif dbapiModuleName in ["psycopg2"]: - dbPort = dbPort or 5432 + elif db_module in ["psycopg2"]: + db_port = db_port or 5432 con_params = _build_connection_params( - database=dbName, user=dbUsername, password=dbPassword, host=dbHost, port=dbPort + database=db_name, user=db_user, password=db_password, host=db_host, port=db_port ) _log_all_connection_params(**con_params) db_connection = db_api_2.connect(**con_params) - elif dbapiModuleName in ["pyodbc", "pypyodbc"]: - dbPort = dbPort or 1433 - dbCharset = dbCharset or "utf8mb4" + elif db_module in ["pyodbc", "pypyodbc"]: + db_port = db_port or 1433 + db_charset = db_charset or "utf8mb4" - if dbDriver: - con_str = f"DRIVER={dbDriver};" + if odbc_driver: + con_str = f"DRIVER={odbc_driver};" else: con_str = "" logger.info("No ODBC driver specified") logger.info(f"List of installed ODBC drivers: {db_api_2.drivers()}") - if dbName: - con_str += f"DATABASE={dbName};" - if dbUsername: - con_str += f"UID={dbUsername};" - if dbPassword: - con_str += f"PWD={dbPassword};" - if dbCharset: - con_str += f"charset={dbCharset};" - if dbHost and dbPort: - if dbDriver and "mysql" in dbDriver.lower(): - con_str += f"SERVER={dbHost}:{dbPort};" + if db_name: + con_str += f"DATABASE={db_name};" + if db_user: + con_str += f"UID={db_user};" + if db_password: + con_str += f"PWD={db_password};" + if db_charset: + con_str += f"charset={db_charset};" + if db_host and db_port: + if odbc_driver and "mysql" in odbc_driver.lower(): + con_str += f"SERVER={db_host}:{db_port};" else: - con_str += f"SERVER={dbHost},{dbPort};" + con_str += f"SERVER={db_host},{db_port};" for param_name, param_value in custom_connection_params.items(): con_str += f"{param_name}={param_value};" @@ -343,41 +376,41 @@ def _arg_or_config(arg_value, param_name, mandatory=False): _log_all_connection_params(connection_string=con_str) db_connection = db_api_2.connect(con_str) - elif dbapiModuleName in ["excel", "excelrw"]: - con_str = f"DRIVER={{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}};DBQ={dbName};" + elif db_module in ["excel", "excelrw"]: + con_str = f"DRIVER={{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}};DBQ={db_name};" con_str += "ReadOnly=" - if dbapiModuleName == "excel": + if db_module == "excel": con_str += "1;" - elif dbapiModuleName == "excelrw": + elif db_module == "excelrw": con_str += "0;" con_str += 'Extended Properties="Excel 8.0;HDR=YES";)' logger.info(f"Connecting using : {db_api_module_name}.connect({con_str}, autocommit=True)") db_connection = db_api_2.connect(con_str, autocommit=True) - elif dbapiModuleName in ["ibm_db", "ibm_db_dbi"]: - dbPort = dbPort or 50000 + elif db_module in ["ibm_db", "ibm_db_dbi"]: + db_port = db_port or 50000 con_str = ( - f"DATABASE={dbName};HOSTNAME={dbHost};PORT={dbPort};PROTOCOL=TCPIP;UID={dbUsername};PWD={dbPassword};" + f"DATABASE={db_name};HOSTNAME={db_host};PORT={db_port};PROTOCOL=TCPIP;UID={db_user};PWD={db_password};" ) con_params = _build_connection_params(userID="", userPassword="") _log_all_connection_params(connection_string=con_str, **con_params) db_connection = db_api_2.connect(con_str, **con_params) - elif dbapiModuleName in ["cx_Oracle"]: - dbPort = dbPort or 1521 - oracle_dsn = db_api_2.makedsn(host=dbHost, port=dbPort, service_name=dbName) - con_params = _build_connection_params(user=dbUsername, password=dbPassword, dsn=oracle_dsn) + elif db_module in ["cx_Oracle"]: + db_port = db_port or 1521 + oracle_dsn = db_api_2.makedsn(host=db_host, port=db_port, service_name=db_name) + con_params = _build_connection_params(user=db_user, password=db_password, dsn=oracle_dsn) _log_all_connection_params(**con_params) db_connection = db_api_2.connect(**con_params) self.omit_trailing_semicolon = True - elif dbapiModuleName in ["oracledb"]: - dbPort = dbPort or 1521 - driverMode = driverMode or "thin" - oracle_connection_params = db_api_2.ConnectParams(host=dbHost, port=dbPort, service_name=dbName) - if "thick" in driverMode.lower(): + elif db_module in ["oracledb"]: + db_port = db_port or 1521 + oracle_driver_mode = oracle_driver_mode or "thin" + oracle_connection_params = db_api_2.ConnectParams(host=db_host, port=db_port, service_name=db_name) + if "thick" in oracle_driver_mode.lower(): logger.info("Using thick Oracle client mode") - mode_param = driverMode.lower().split(",lib_dir=") + mode_param = oracle_driver_mode.lower().split(",lib_dir=") if len(mode_param) == 2 and mode_param[0].lower() == "thick": lib_dir = mode_param[1] logger.info(f"Oracle client lib dir specified: {lib_dir}") @@ -386,12 +419,12 @@ def _arg_or_config(arg_value, param_name, mandatory=False): logger.info("No Oracle client lib dir specified, oracledb will search it in usual places") db_api_2.init_oracle_client() oracle_thin_mode = False - elif "thin" in driverMode.lower(): + elif "thin" in oracle_driver_mode.lower(): oracle_thin_mode = True logger.info("Using thin Oracle client mode") else: - raise ValueError(f"Invalid Oracle client mode provided: {driverMode}") - con_params = _build_connection_params(user=dbUsername, password=dbPassword, params=oracle_connection_params) + raise ValueError(f"Invalid Oracle client mode provided: {oracle_driver_mode}") + con_params = _build_connection_params(user=db_user, password=db_password, params=oracle_connection_params) _log_all_connection_params(**con_params) db_connection = db_api_2.connect(**con_params) assert db_connection.thin == oracle_thin_mode, ( @@ -400,88 +433,111 @@ def _arg_or_config(arg_value, param_name, mandatory=False): ) self.omit_trailing_semicolon = True - elif dbapiModuleName in ["teradata"]: - dbPort = dbPort or 1025 + elif db_module in ["teradata"]: + db_port = db_port or 1025 teradata_udaExec = db_api_2.UdaExec(appName="RobotFramework", version="1.0", logConsole=False) con_params = _build_connection_params( method="odbc", - system=dbHost, - database=dbName, - username=dbUsername, - password=dbPassword, - host=dbHost, - port=dbPort, + system=db_host, + database=db_name, + username=db_user, + password=db_password, + host=db_host, + port=db_port, ) - _log_all_connection_params(connection_object=f"{dbapiModuleName}.UdaExec", **con_params) + _log_all_connection_params(connection_object=f"{db_module}.UdaExec", **con_params) db_connection = teradata_udaExec.connect(**con_params) - elif dbapiModuleName in ["ksycopg2"]: - dbPort = dbPort or 54321 + elif db_module in ["ksycopg2"]: + db_port = db_port or 54321 con_params = _build_connection_params( - database=dbName, user=dbUsername, password=dbPassword, host=dbHost, port=dbPort + database=db_name, user=db_user, password=db_password, host=db_host, port=db_port ) _log_all_connection_params(**con_params) db_connection = db_api_2.connect(**con_params) else: con_params = _build_connection_params( - database=dbName, user=dbUsername, password=dbPassword, host=dbHost, port=dbPort + database=db_name, user=db_user, password=db_password, host=db_host, port=db_port ) _log_all_connection_params(**con_params) db_connection = db_api_2.connect(**con_params) self.connection_store.register_connection(db_connection, db_api_module_name, alias) + @renamed_args(mapping={"dbapiModuleName": "db_module"}) def connect_to_database_using_custom_params( - self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "", alias: str = "default" + self, + db_module: Optional[str] = None, + db_connect_string: str = "", + alias: str = "default", + *, + dbapiModuleName: Optional[str] = None, ): """ *DEPRECATED* Use new `Connect To Database` keyword with custom parameters instead. The deprecated keyword will be removed in future versions. - Loads the DB API 2.0 module given `dbapiModuleName` then uses it to - connect to the database using the map string `db_connect_string` + Loads the DB API 2.0 module given ``db_module`` then uses it to + connect to the database using the map string ``db_connect_string`` (parsed as a list of named arguments). Use `connect_to_database_using_custom_connection_string` for passing all params in a single connection string or URI. - Example usage: + === Some parameters were renamed in version 2.0 === + The old parameter ``dbapiModuleName`` is *deprecated*, + please use new parameter ``db_module`` instead. + + *The old parameter will be removed in future versions.* + + === Examples === | Connect To Database Using Custom Params | psycopg2 | database='my_db_test', user='postgres', password='s3cr3t', host='tiger.foobar.com', port=5432 | | Connect To Database Using Custom Params | jaydebeapi | 'oracle.jdbc.driver.OracleDriver', 'my_db_test', 'system', 's3cr3t' | | Connect To Database Using Custom Params | oracledb | user="username", password="pass", dsn="localhost/orclpdb" | | Connect To Database Using Custom Params | sqlite3 | database="./my_database.db", isolation_level=None | """ - db_api_2 = importlib.import_module(dbapiModuleName) - db_api_module_name = dbapiModuleName + db_api_2 = importlib.import_module(db_module) + db_api_module_name = db_module db_connect_string = f"db_api_2.connect({db_connect_string})" logger.info( - f"Executing : Connect To Database Using Custom Params : {dbapiModuleName}.connect(" + f"Executing : Connect To Database Using Custom Params : {db_module}.connect(" f"{self._hide_password_values(db_connect_string)})" ) db_connection = eval(db_connect_string) self.connection_store.register_connection(db_connection, db_api_module_name, alias) + @renamed_args(mapping={"dbapiModuleName": "db_module"}) def connect_to_database_using_custom_connection_string( - self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "", alias: str = "default" + self, + db_module: Optional[str] = None, + db_connect_string: str = "", + alias: str = "default", + *, + dbapiModuleName: Optional[str] = None, ): """ - Loads the DB API 2.0 module given `dbapiModuleName` then uses it to - connect to the database using the `db_connect_string` + Loads the DB API 2.0 module given ``db_module`` then uses it to + connect to the database using the ``db_connect_string`` (parsed as single connection string or URI). - Use `connect_to_database_using_custom_params` for passing - connection params as named arguments. + Use `Connect To Database` for passing custom connection params as named arguments. + + === Some parameters were renamed in version 2.0 === + The old parameter ``dbapiModuleName`` is *deprecated*, + please use new parameter ``db_module`` instead. + + *The old parameter will be removed in future versions.* Example usage: | Connect To Database Using Custom Connection String | psycopg2 | postgresql://postgres:s3cr3t@tiger.foobar.com:5432/my_db_test | | Connect To Database Using Custom Connection String | oracledb | username/pass@localhost:1521/orclpdb | """ - db_api_2 = importlib.import_module(dbapiModuleName) - db_api_module_name = dbapiModuleName + db_api_2 = importlib.import_module(db_module) + db_api_module_name = db_module logger.info( - f"Executing : Connect To Database Using Custom Connection String : {dbapiModuleName}.connect(" + f"Executing : Connect To Database Using Custom Connection String : {db_module}.connect(" f"'{db_connect_string}')" ) db_connection = db_api_2.connect(db_connect_string) @@ -493,11 +549,13 @@ def disconnect_from_database(self, error_if_no_connection: bool = False, alias: By default, it's not an error if there was no open database connection - suitable for usage as a teardown. - However, you can enforce it using the `error_if_no_connection` parameter. + However, you can enforce it using the ``error_if_no_connection`` parameter. - Example usage: - | Disconnect From Database | # disconnects from current connection to the database | - | Disconnect From Database | alias=my_alias | # disconnects from current connection to the database | + Use ``alias`` to specify what connection should be closed if `Handling multiple database connections`. + + === Examples === + | Disconnect From Database | + | Disconnect From Database | alias=postgres | """ db_connection = self.connection_store.pop_connection(alias) if db_connection is None: @@ -512,33 +570,34 @@ def disconnect_from_all_databases(self): """ Disconnects from all the databases - useful when testing with multiple database connections (aliases). - - For example: - | Disconnect From All Databases | # Closes connections to all databases | """ for db_connection in self.connection_store: db_connection.client.close() self.connection_store.clear() - def set_auto_commit(self, autoCommit: bool = True, alias: Optional[str] = None): + @renamed_args(mapping={"autoCommit": "auto_commit"}) + def set_auto_commit( + self, auto_commit: bool = True, alias: Optional[str] = None, *, autoCommit: Optional[bool] = None + ): """ - Turn the autocommit on the database connection ON or OFF. + Explicitly sets the autocommit behavior of the database connection to ``auto_commit``. + See `Commit behavior` for details. - The default behaviour on a newly created database connection is to automatically start a - transaction, which means that database actions that won't work if there is an active - transaction will fail. Common examples of these actions are creating or deleting a database - or database snapshot. By turning on auto commit on the database connection these actions - can be performed. + Use ``alias`` to specify what connection should be used if `Handling multiple database connections`. - Example usage: - | # Default behaviour, sets auto commit to true + === Some parameters were renamed in version 2.0 === + The old parameter ``autoCommit`` is *deprecated*, + please use new parameter ``auto_commit`` instead. + + *The old parameter will be removed in future versions.* + + === Examples === | Set Auto Commit - | Set Auto Commit | alias=my_alias | - | # Explicitly set the desired state - | Set Auto Commit | False + | Set Auto Commit | False | + | Set Auto Commit | True | alias=postgres | """ db_connection = self.connection_store.get_connection(alias) - db_connection.client.autocommit = autoCommit + db_connection.client.autocommit = auto_commit def switch_database(self, alias: str): """ diff --git a/src/DatabaseLibrary/params_decorator.py b/src/DatabaseLibrary/params_decorator.py new file mode 100644 index 0000000..b122b3f --- /dev/null +++ b/src/DatabaseLibrary/params_decorator.py @@ -0,0 +1,33 @@ +""" +These decorators are introduced for the transition from old argument naming / positioning to the new one. +""" +from functools import wraps + +from robot.api import logger + + +def renamed_args(mapping): + """ + Decorator to rename arguments and warn users about deprecated argument names. + + :param mapping: Dictionary mapping old argument names to new argument names. + :return: The decorated function with remapped arguments. + """ + + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + # Check if any old argument names are used + for old_name, new_name in mapping.items(): + if old_name in kwargs: + # Issue a warning to the user + logger.warn(f"Argument '{old_name}' is deprecated, use '{new_name}' instead") + # Move the argument value to the new name + logger.info(f"Replacing '{old_name}' with '{new_name}'") + kwargs[new_name] = kwargs.pop(old_name) + # Call the original function with updated kwargs + return func(*args, **kwargs) + + return wrapper + + return decorator diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 28239b9..29dd202 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -20,6 +20,8 @@ from robot.api import logger +from .params_decorator import renamed_args + class Query: """ @@ -30,104 +32,105 @@ def __init__(self, log_query_results, log_query_results_head): self.LOG_QUERY_RESULTS = log_query_results self.LOG_QUERY_RESULTS_HEAD = log_query_results_head + @renamed_args( + mapping={"selectStatement": "select_statement", "sansTran": "no_transaction", "returnAsDict": "return_dict"} + ) def query( self, - selectStatement: str, - sansTran: bool = False, - returnAsDict: bool = False, + select_statement: str, + no_transaction: bool = False, + return_dict: bool = False, alias: Optional[str] = None, parameters: Optional[Tuple] = None, + *, + selectStatement: Optional[str] = None, + sansTran: Optional[bool] = None, + returnAsDict: Optional[bool] = None, ): """ - Runs a query with the ``selectStatement`` and returns the result as a list of rows. + Runs a query with the ``select_statement`` and returns the result as list of rows. The type of row values depends on the database module - usually they are tuples or tuple-like objects. - Set optional input ``returnAsDict`` to _True_ to explicitely convert the return values - into a list of dictionaries. - - Use optional ``alias`` parameter to specify what connection should be used for the query if you have more - than one connection open. - - Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different - depending on the database client): - | @{parameters} | Create List | person | - | Query | SELECT * FROM %s | parameters=${parameters} | - - Use optional ``sansTran`` to run command without an explicit transaction commit or rollback: - | @{queryResults} | Query | SELECT * FROM person | True | + Set ``no_transaction`` to _True_ to run command without explicit transaction rollback in case of error. + See `Commit behavior` for details. - Tip: Unless you want to log all column values of the specified rows, - try specifying the column names in your select statements - as much as possible to prevent any unnecessary surprises with schema - changes and to easily see what your [] indexing is trying to retrieve - (i.e. instead of `"select * from my_table"`, try - `"select id, col_1, col_2 from my_table"`). + Set ``return_dict`` to _True_ to explicitly convert the return values into list of dictionaries. - For example, given we have a table `person` with the following data: - | id | first_name | last_name | - | 1 | Franz Allan | See | + Use ``alias`` to specify what connection should be used if `Handling multiple database connections`. - When you do the following: - | @{queryResults} | Query | SELECT * FROM person | - | @{queryResults} | Query | SELECT * FROM person | alias=my_alias | - | Log Many | @{queryResults} | + Use ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client). - You will get the following: - [1, 'Franz Allan', 'See'] + === Some parameters were renamed in version 2.0 === + The old parameters ``selectStatement``, ``sansTran`` and ``returnAsDict`` are *deprecated*, + please use new parameters ``select_statement``, ``no_transaction`` and ``return_dict`` instead. - Also, you can do something like this: - | ${queryResults} | Query | SELECT first_name, last_name FROM person | - | Log | ${queryResults[0][1]}, ${queryResults[0][0]} | + *The old parameters will be removed in future versions.* - And get the following - See, Franz Allan + === Examples === + | ${Results}= | Query | select LAST_NAME from person | + | ${Results}= | Query | select LAST_NAME from person | no_transaction=True | + | ${Results}= | Query | select LAST_NAME from person | return_dict=True | + | ${Results}= | Query | select LAST_NAME from person | alias=postgres | + | @{parameters} | Create List | person | + | ${Results}= | Query | SELECT * FROM %s | parameters=${parameters} | """ db_connection = self.connection_store.get_connection(alias) cur = None try: cur = db_connection.client.cursor() - self._execute_sql(cur, selectStatement, parameters=parameters) + self._execute_sql(cur, select_statement, parameters=parameters) all_rows = cur.fetchall() col_names = [c[0] for c in cur.description] self._log_query_results(col_names, all_rows) - if returnAsDict: + if return_dict: return [dict(zip(col_names, row)) for row in all_rows] return all_rows finally: - if cur and not sansTran: + if cur and not no_transaction: db_connection.client.rollback() + @renamed_args(mapping={"selectStatement": "select_statement", "sansTran": "no_transaction"}) def row_count( self, - selectStatement: str, - sansTran: bool = False, + select_statement: str, + no_transaction: bool = False, alias: Optional[str] = None, parameters: Optional[Tuple] = None, + *, + selectStatement: Optional[str] = None, + sansTran: Optional[bool] = None, ): """ - Uses the input ``selectStatement`` to query the database and returns the number of rows from the query. + Runs a query with the ``select_statement`` and returns the number of rows in the result. - Use optional ``alias`` parameter to specify what connection should be used for the query if you have more - than one connection open. + Set ``no_transaction`` to _True_ to run command without explicit transaction rollback in case of error. + See `Commit behavior` for details. - Use optional ``sansTran`` to run command without an explicit transaction commit or rollback: + Use ``alias`` to specify what connection should be used if `Handling multiple database connections`. - Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different - depending on the database client): + Use ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client). - Examples: - | ${rowCount} | Row Count | SELECT * FROM person | - | ${rowCount} | Row Count | SELECT * FROM person | sansTran=True | - | ${rowCount} | Row Count | SELECT * FROM person | alias=my_alias | + === Some parameters were renamed in version 2.0 === + The old parameters ``selectStatement`` and ``sansTran`` are *deprecated*, + please use new parameters ``select_statement`` and ``no_transaction`` instead. + + *The old parameters will be removed in future versions.* + + === Examples === + | ${Rows}= | Row Count | select LAST_NAME from person | + | ${Rows}= | Row Count | select LAST_NAME from person | no_transaction=True | + | ${Rows}= | Row Count | select LAST_NAME from person | alias=postgres | | @{parameters} | Create List | person | - | ${rowCount} | Row Count | SELECT * FROM %s | parameters=${parameters} | + | ${Rows}= | Row Count | SELECT * FROM %s | parameters=${parameters} | """ db_connection = self.connection_store.get_connection(alias) cur = None try: cur = db_connection.client.cursor() - self._execute_sql(cur, selectStatement, parameters=parameters) + self._execute_sql(cur, select_statement, parameters=parameters) data = cur.fetchall() col_names = [c[0] for c in cur.description] if db_connection.module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc"]: @@ -138,155 +141,142 @@ def row_count( self._log_query_results(col_names, data) return current_row_count finally: - if cur and not sansTran: + if cur and not no_transaction: db_connection.client.rollback() + @renamed_args(mapping={"selectStatement": "select_statement", "sansTran": "no_transaction"}) def description( self, - selectStatement: str, - sansTran: bool = False, + select_statement: str, + no_transaction: bool = False, alias: Optional[str] = None, parameters: Optional[Tuple] = None, + *, + selectStatement: Optional[str] = None, + sansTran: Optional[bool] = None, ): """ - Uses the input ``selectStatement`` to query a table in the db which will be used to determine the description. + Runs a query with the ``select_statement`` to determine the table description. - For example, given we have a table `person` with the following data: - | id | first_name | last_name | - | 1 | Franz Allan | See | + Set ``no_transaction`` to _True_ to run command without explicit transaction rollback in case of error. + See `Commit behavior` for details. - When you do the following: - | @{queryResults} | Description | SELECT * FROM person | - | @{queryResults} | Description | SELECT * FROM person | alias=my_alias | - | Log Many | @{queryResults} | + Use ``alias`` to specify what connection should be used if `Handling multiple database connections`. - You will get the following: - [Column(name='id', type_code=1043, display_size=None, internal_size=255, precision=None, scale=None, null_ok=None)] - [Column(name='first_name', type_code=1043, display_size=None, internal_size=255, precision=None, scale=None, null_ok=None)] - [Column(name='last_name', type_code=1043, display_size=None, internal_size=255, precision=None, scale=None, null_ok=None)] + Use ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client). - Use optional ``alias`` parameter to specify what connection should be used for the query if you have more - than one connection open. + === Some parameters were renamed in version 2.0 === + The old parameters ``selectStatement`` and ``sansTran`` are *deprecated*, + please use new parameters ``select_statement`` and ``no_transaction`` instead. - Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different - depending on the database client): - | @{parameters} | Create List | person | - | ${desc} | Description | SELECT * FROM %s | parameters=${parameters} | + *The old parameters will be removed in future versions.* - Using optional `sansTran` to run command without an explicit transaction commit or rollback: - | @{queryResults} | Description | SELECT * FROM person | True | + === Examples === + | ${Person table description}= | Description | select LAST_NAME from person | + | ${Person table description}= | Description | select LAST_NAME from person | no_transaction=True | + | ${Person table description}= | Description | select LAST_NAME from person | alias=postgres | + | @{parameters} | Create List | person | + | ${Person table description}= | Description | SELECT * FROM %s | parameters=${parameters} | """ db_connection = self.connection_store.get_connection(alias) cur = None try: cur = db_connection.client.cursor() - self._execute_sql(cur, selectStatement, parameters=parameters) + self._execute_sql(cur, select_statement, parameters=parameters) description = list(cur.description) if sys.version_info[0] < 3: for row in range(0, len(description)): description[row] = (description[row][0].encode("utf-8"),) + description[row][1:] return description finally: - if cur and not sansTran: + if cur and not no_transaction: db_connection.client.rollback() - def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, alias: Optional[str] = None): + @renamed_args(mapping={"tableName": "table_name", "sansTran": "no_transaction"}) + def delete_all_rows_from_table( + self, + table_name: str, + no_transaction: bool = False, + alias: Optional[str] = None, + *, + tableName: Optional[str] = None, + sansTran: Optional[bool] = None, + ): """ - Delete all the rows within a given table. + Deletes all rows from table with ``table_name``. - Use optional `sansTran` to run command without an explicit transaction commit or rollback. + Set ``no_transaction`` to _True_ to run command without explicit transaction commit + or rollback in case of error. + See `Commit behavior` for details. - Use optional ``alias`` parameter to specify what connection should be used for the query if you have more - than one connection open. + Use ``alias`` to specify what connection should be used if `Handling multiple database connections`. - Examples: + === Some parameters were renamed in version 2.0 === + The old parameters ``tableName`` and ``sansTran`` are *deprecated*, + please use new parameters ``table_name`` and ``no_transaction`` instead. + + *The old parameters will be removed in future versions.* + + === Examples === | Delete All Rows From Table | person | + | Delete All Rows From Table | person | no_transaction=True | | Delete All Rows From Table | person | alias=my_alias | - | Delete All Rows From Table | person | sansTran=True | """ db_connection = self.connection_store.get_connection(alias) cur = None - query = f"DELETE FROM {tableName}" + query = f"DELETE FROM {table_name}" try: cur = db_connection.client.cursor() result = self._execute_sql(cur, query) if result is not None: - if not sansTran: + if not no_transaction: db_connection.client.commit() return result - if not sansTran: + if not no_transaction: db_connection.client.commit() finally: - if cur and not sansTran: + if cur and not no_transaction: db_connection.client.rollback() + @renamed_args(mapping={"sqlScriptFileName": "script_path", "sansTran": "no_transaction"}) def execute_sql_script( - self, sqlScriptFileName: str, sansTran: bool = False, alias: Optional[str] = None, split: bool = True + self, + script_path: str, + no_transaction: bool = False, + alias: Optional[str] = None, + split: bool = True, + *, + sqlScriptFileName: Optional[str] = None, + sansTran: Optional[bool] = None, ): """ - Executes the content of the `sqlScriptFileName` as SQL commands. Useful for setting the database to a known - state before running your tests, or clearing out your test data after running each a test. + Executes the content of the SQL script file loaded from `script_path` as SQL commands. SQL commands are expected to be delimited by a semicolon (';') - they will be split and executed separately. - You can disable this behaviour setting the parameter `split` to _False_ - - in this case the entire script content will be passed to the database module for execution. - - Sample usage : - | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | - | Execute Sql Script | ${EXECDIR}${/}resources${/}DML-setup.sql | - | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | alias=my_alias | - | #interesting stuff here | - | Execute Sql Script | ${EXECDIR}${/}resources${/}DML-teardown.sql | - | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-teardown.sql | - - - For example: - DELETE FROM person_employee_table; - DELETE FROM person_table; - DELETE FROM employee_table; - - Also, the last SQL command can optionally omit its trailing semi-colon. - - For example: - DELETE FROM person_employee_table; - DELETE FROM person_table; - DELETE FROM employee_table - - Given this, that means you can create spread your SQL commands in several - lines. - - For example: - DELETE - FROM person_employee_table; - DELETE - FROM person_table; - DELETE - FROM employee_table - - However, lines that starts with a number sign (`#`) or a double dash ("--") - are treated as a commented line. Thus, none of the contents of that line will be executed. - - - For example: - # Delete the bridging table first... - DELETE - FROM person_employee_table; - # ...and then the bridged tables. - DELETE - FROM person_table; - DELETE - FROM employee_table - - The slash signs ("/") are always ignored and have no impact on execution order. - - Use optional ``alias`` parameter to specify what connection should be used for the query if you have more - than one connection open. - - Use optional `sansTran` to run command without an explicit transaction commit or rollback: - | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | True | + Set ``split`` to _False_ to disable this behavior - in this case the entire script content + will be passed to the database module for execution as a single command. + + Set ``no_transaction`` to _True_ to run command without explicit transaction commit + or rollback in case of error. + See `Commit behavior` for details. + + Use ``alias`` to specify what connection should be used if `Handling multiple database connections`. + + === Some parameters were renamed in version 2.0 === + The old parameters ``sqlScriptFileName`` and ``sansTran`` are *deprecated*, + please use new parameters ``script_path`` and ``no_transaction`` instead. + + *The old parameters will be removed in future versions.* + + === Examples === + | Execute SQL Script | insert_data_in_person_table.sql | + | Execute SQL Script | insert_data_in_person_table.sql | no_transaction=True | + | Execute SQL Script | insert_data_in_person_table.sql | alias=postgres | + | Execute SQL Script | insert_data_in_person_table.sql | split=False | """ db_connection = self.connection_store.get_connection(alias) - with open(sqlScriptFileName, encoding="UTF-8") as sql_file: + with open(script_path, encoding="UTF-8") as sql_file: cur = None try: cur = db_connection.client.cursor() @@ -358,83 +348,113 @@ def execute_sql_script( line_ends_with_proc_end = re.compile(r"(\s|;)" + proc_end_pattern.pattern + "$") omit_semicolon = not line_ends_with_proc_end.search(statement.lower()) self._execute_sql(cur, statement, omit_semicolon) - if not sansTran: + if not no_transaction: db_connection.client.commit() finally: - if cur and not sansTran: + if cur and not no_transaction: db_connection.client.rollback() + @renamed_args( + mapping={ + "sqlString": "sql_string", + "sansTran": "no_transaction", + "omitTrailingSemicolon": "omit_trailing_semicolon", + } + ) def execute_sql_string( self, - sqlString: str, - sansTran: bool = False, + sql_string: str, + no_transaction: bool = False, alias: Optional[str] = None, parameters: Optional[Tuple] = None, + omit_trailing_semicolon: Optional[bool] = None, + *, + sqlString: Optional[str] = None, + sansTran: Optional[bool] = None, omitTrailingSemicolon: Optional[bool] = None, ): """ - Executes the ``sqlString`` as a single SQL command. + Executes the ``sql_string`` as a single SQL command. + + Set ``no_transaction`` to _True_ to run command without explicit transaction commit + or rollback in case of error. + See `Commit behavior` for details. - Use optional ``sansTran`` to run command without an explicit transaction commit or rollback. + Use ``alias`` to specify what connection should be used if `Handling multiple database connections`. - Use optional ``omitTrailingSemicolon`` parameter for explicit instruction, + Use ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client). + + Use ``omit_trailing_semicolon`` for explicit instruction, if the trailing semicolon (;) at the SQL string end should be removed or not: - Some database modules (e.g. Oracle) throw an exception, if you leave a semicolon at the string end - - However, there are exceptional cases, when you need it even for Oracle - e.g. at the end of a PL/SQL block. - - If not specified, it's decided based on the current database module in use. For Oracle, the semicolon is removed by default. + - However, there are exceptional cases, when you need it even for Oracle - e.g. at the end of a PL/SQL block + - If not explicitly specified, it's decided based on the current database module in use. For Oracle, the semicolon is removed by default. - Use optional ``alias`` parameter to specify what connection should be used for the query if you have more - than one connection open. + === Some parameters were renamed in version 2.0 === + The old parameters ``sqlString``, ``sansTran`` and ``omitTrailingSemicolon`` are *deprecated*, + please use new parameters ``sql_string``, ``no_transaction`` and ``omit_trailing_semicolon`` instead. - Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different - depending on the database client). + *The old parameters will be removed in future versions.* - For example: + === Examples === | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | + | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | no_transaction=True | | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | alias=my_alias | - | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | sansTran=True | - | Execute Sql String | CREATE PROCEDURE proc AS BEGIN DBMS_OUTPUT.PUT_LINE('Hello!'); END; | omitTrailingSemicolon=False | + | Execute Sql String | CREATE PROCEDURE proc AS BEGIN DBMS_OUTPUT.PUT_LINE('Hello!'); END; | omit_trailing_semicolon=False | | @{parameters} | Create List | person_employee_table | - | Execute Sql String | SELECT * FROM %s | parameters=${parameters} | + | Execute Sql String | DELETE FROM %s | parameters=${parameters} | """ db_connection = self.connection_store.get_connection(alias) cur = None try: cur = db_connection.client.cursor() - self._execute_sql(cur, sqlString, omit_trailing_semicolon=omitTrailingSemicolon, parameters=parameters) - if not sansTran: + self._execute_sql(cur, sql_string, omit_trailing_semicolon=omit_trailing_semicolon, parameters=parameters) + if not no_transaction: db_connection.client.commit() finally: - if cur and not sansTran: + if cur and not no_transaction: db_connection.client.rollback() + @renamed_args(mapping={"spName": "procedure_name", "spParams": "procedure_params", "sansTran": "no_transaction"}) def call_stored_procedure( self, - spName: str, - spParams: Optional[List] = None, - sansTran: bool = False, + procedure_name: str, + procedure_params: Optional[List] = None, + no_transaction: bool = False, alias: Optional[str] = None, additional_output_params: Optional[List] = None, + *, + spName: Optional[str] = None, + spParams: Optional[List] = None, + sansTran: Optional[bool] = None, ): """ - Calls a stored procedure `spName` with the `spParams` - a *list* of parameters the procedure requires. + Calls a stored procedure `procedure_name` with the `procedure_params` - a *list* of parameters the procedure requires. *Returns two lists* - the _parameter values_ and the _result sets_. Use the special *CURSOR* value for OUT params, which should receive result sets - relevant only for some databases (e.g. Oracle or PostgreSQL). - Use the `additional_output_params` list for OUT params of a procedure in MSSQL. + Set ``no_transaction`` to _True_ to run command without explicit transaction commit + or rollback in case of error. + See `Commit behavior` for details. + + Use ``alias`` to specify what connection should be used if `Handling multiple database connections`. + + Use the ``additional_output_params`` list for OUT params of a procedure in MSSQL. - Use optional ``alias`` parameter to specify what connection should be used for the query if you have more - than one connection open. + === Some parameters were renamed in version 2.0 === + The old parameters ``spName``, ``spParams`` and ``sansTran`` are *deprecated*, please use + new parameters ``procedure_name``, ``procedure_params`` and ``no_transaction`` instead. - Use optional `sansTran` to run command without an explicit transaction commit or rollback. + *The old parameters will be removed in future versions.* = Handling parameters and result sets = Handling the input and output parameters and the result sets is very different depending on the database itself and on the Python database driver - i.e. how it implements the `cursor.callproc()` function. == Common case (e.g. MySQL) == - Generally a procedure call requires all parameter values (IN and OUT) put together in a list - `spParams`. + Generally a procedure call requires all parameter values (IN and OUT) put together in a list - `procedure_params`. Calling the procedure returns *two lists*: - *Param values* - the copy of procedure parameters (modified, if the procedure changes the OUT params). The list is empty, if procedures receives no params. @@ -566,7 +586,7 @@ def call_stored_procedure( | END | END; - Calling the procedure in Robot Framework requires putting the IN parameters as usual in the `spParams` argument, + Calling the procedure in Robot Framework requires putting the IN parameters as usual in the `procedure_params` argument, but the sample values of OUT parameters must be put in the argument `additional_output_params`. | @{params}= Create List give me 1 @@ -599,8 +619,8 @@ def call_stored_procedure( This case is *not fully supported* by the library - the OUT params won't be fetched. """ db_connection = self.connection_store.get_connection(alias) - if spParams is None: - spParams = [] + if procedure_params is None: + procedure_params = [] if additional_output_params is None: additional_output_params = [] cur = None @@ -614,7 +634,7 @@ def call_stored_procedure( result_sets = [] if db_connection.module_name == "pymysql": - cur.callproc(spName, spParams) + cur.callproc(procedure_name, procedure_params) # first proceed the result sets if available result_sets_available = True @@ -626,35 +646,35 @@ def call_stored_procedure( result_sets.pop() # now go on with single values - modified input params - for i in range(0, len(spParams)): - cur.execute(f"select @_{spName}_{i}") + for i in range(0, len(procedure_params)): + cur.execute(f"select @_{procedure_name}_{i}") param_values.append(cur.fetchall()[0][0]) elif db_connection.module_name in ["oracledb", "cx_Oracle"]: # check if "CURSOR" params were passed - they will be replaced # with cursor variables for storing the result sets - params_substituted = spParams.copy() + params_substituted = procedure_params.copy() cursor_params = [] - for i in range(0, len(spParams)): - if spParams[i] == "CURSOR": + for i in range(0, len(procedure_params)): + if procedure_params[i] == "CURSOR": cursor_param = db_connection.client.cursor() params_substituted[i] = cursor_param cursor_params.append(cursor_param) - param_values = cur.callproc(spName, params_substituted) + param_values = cur.callproc(procedure_name, params_substituted) for result_set in cursor_params: result_sets.append(list(result_set)) elif db_connection.module_name in ["psycopg2", "psycopg3"]: # check if "CURSOR" params were passed - they will be replaced # with cursor variables for storing the result sets - params_substituted = spParams.copy() + params_substituted = procedure_params.copy() cursor_params = [] - for i in range(0, len(spParams)): - if spParams[i] == "CURSOR": + for i in range(0, len(procedure_params)): + if procedure_params[i] == "CURSOR": cursor_param = f"CURSOR_{i}" params_substituted[i] = cursor_param cursor_params.append(cursor_param) - param_values = cur.callproc(spName, params_substituted) + param_values = cur.callproc(procedure_name, params_substituted) if cursor_params: for cursor_param in cursor_params: cur.execute(f'FETCH ALL IN "{cursor_param}"') @@ -673,9 +693,9 @@ def call_stored_procedure( else: if db_connection.module_name == "pymssql": mssql = importlib.import_module("pymssql") - spParams = spParams.copy() + procedure_params = procedure_params.copy() for param in additional_output_params: - spParams.append(mssql.output(type(param), param)) + procedure_params.append(mssql.output(type(param), param)) else: logger.info( @@ -683,7 +703,7 @@ def call_stored_procedure( "No special handling is known, so trying the common way with return params and result sets." ) - param_values = cur.callproc(spName, spParams) + param_values = cur.callproc(procedure_name, procedure_params) logger.info("Reading the procedure result sets..") result_sets_available = True while result_sets_available: @@ -697,12 +717,12 @@ def call_stored_procedure( else: result_sets_available = False - if not sansTran: + if not no_transaction: db_connection.client.commit() return param_values, result_sets finally: - if cur and not sansTran: + if cur and not no_transaction: db_connection.client.rollback() def set_logging_query_results(self, enabled: Optional[bool] = None, log_head: Optional[int] = None): diff --git a/test/resources/common.resource b/test/resources/common.resource index d98ca37..487fa10 100644 --- a/test/resources/common.resource +++ b/test/resources/common.resource @@ -49,7 +49,7 @@ Connect To DB ... ${DB_HOST} ... ${DB_PORT} IF "${DB_MODULE}" == "pyodbc" - Set To Dictionary ${DB_KWARGS} dbDriver=${DB_DRIVER} + Set To Dictionary ${DB_KWARGS} odbc_driver=${DB_DRIVER} END Connect To Database @{DB_ARGS} &{DB_KWARGS} ELSE diff --git a/test/resources/config_files/connect_config_file.resource b/test/resources/config_files/connect_config_file.resource index 569c1f3..621ecd3 100644 --- a/test/resources/config_files/connect_config_file.resource +++ b/test/resources/config_files/connect_config_file.resource @@ -8,4 +8,4 @@ Connect Using Config File ... the path is build relative to the resource directory [Arguments] ${File name}=${None} &{Params} ${Path}= Set Variable ${CURDIR}/${File name}.cfg - Connect To Database dbConfigFile=${Path} &{Params} + Connect To Database config_file=${Path} &{Params} diff --git a/test/resources/config_files/oracledb/custom_param_password.cfg b/test/resources/config_files/oracledb/custom_param_password.cfg index 4835811..d6d93b9 100644 --- a/test/resources/config_files/oracledb/custom_param_password.cfg +++ b/test/resources/config_files/oracledb/custom_param_password.cfg @@ -1,6 +1,6 @@ [default] -dbapiModuleName=oracledb -dbName=db +db_module=oracledb +db_name=db password=pass -dbHost=127.0.0.1 -dbPort=1521 \ No newline at end of file +db_host=127.0.0.1 +db_port=1521 \ No newline at end of file diff --git a/test/resources/config_files/oracledb/invalid_custom_params.cfg b/test/resources/config_files/oracledb/invalid_custom_params.cfg index 3f2a48b..1214b8b 100644 --- a/test/resources/config_files/oracledb/invalid_custom_params.cfg +++ b/test/resources/config_files/oracledb/invalid_custom_params.cfg @@ -1,8 +1,8 @@ [default] -dbapiModuleName=oracledb -dbName=db -dbUsername=db_user -dbPassword=pass -dbHost=127.0.0.1 -dbPort=1521 +db_module=oracledb +db_name=db +db_user=db_user +db_password=pass +db_host=127.0.0.1 +db_port=1521 blah=blah \ No newline at end of file diff --git a/test/resources/config_files/oracledb/old_param_names.cfg b/test/resources/config_files/oracledb/old_param_names.cfg new file mode 100644 index 0000000..2d81cd4 --- /dev/null +++ b/test/resources/config_files/oracledb/old_param_names.cfg @@ -0,0 +1,8 @@ +[default] +dbapiModuleName=oracledb +dbName=db +dbUsername=db_user +dbPassword=pass +dbHost=127.0.0.1 +dbPort=1521 +driverMode=thin \ No newline at end of file diff --git a/test/resources/config_files/oracledb/simple_default_alias.cfg b/test/resources/config_files/oracledb/simple_default_alias.cfg index 2d81cd4..fe487e8 100644 --- a/test/resources/config_files/oracledb/simple_default_alias.cfg +++ b/test/resources/config_files/oracledb/simple_default_alias.cfg @@ -1,8 +1,8 @@ [default] -dbapiModuleName=oracledb -dbName=db -dbUsername=db_user -dbPassword=pass -dbHost=127.0.0.1 -dbPort=1521 -driverMode=thin \ No newline at end of file +db_module=oracledb +db_name=db +db_user=db_user +db_password=pass +db_host=127.0.0.1 +db_port=1521 +oracle_driver_mode=thin \ No newline at end of file diff --git a/test/resources/config_files/oracledb/some_basic_params_missing.cfg b/test/resources/config_files/oracledb/some_basic_params_missing.cfg index 83454d0..7e845af 100644 --- a/test/resources/config_files/oracledb/some_basic_params_missing.cfg +++ b/test/resources/config_files/oracledb/some_basic_params_missing.cfg @@ -1,3 +1,3 @@ [default] -dbapiModuleName=oracledb -dbName=db \ No newline at end of file +db_module=oracledb +db_name=db \ No newline at end of file diff --git a/test/resources/config_files/oracledb/thick_mode.cfg b/test/resources/config_files/oracledb/thick_mode.cfg index 7878033..bd1a487 100644 --- a/test/resources/config_files/oracledb/thick_mode.cfg +++ b/test/resources/config_files/oracledb/thick_mode.cfg @@ -1,8 +1,8 @@ [default] -dbapiModuleName=oracledb -dbName=db -dbUsername=db_user -dbPassword=pass -dbHost=127.0.0.1 -dbPort=1521 -driverMode=thick \ No newline at end of file +db_module=oracledb +db_name=db +db_user=db_user +db_password=pass +db_host=127.0.0.1 +db_port=1521 +oracle_driver_mode=thick \ No newline at end of file diff --git a/test/resources/config_files/oracledb/valid_custom_params.cfg b/test/resources/config_files/oracledb/valid_custom_params.cfg index 6538367..df3bffd 100644 --- a/test/resources/config_files/oracledb/valid_custom_params.cfg +++ b/test/resources/config_files/oracledb/valid_custom_params.cfg @@ -1,7 +1,7 @@ [default] -dbapiModuleName=oracledb -dbName=db +db_module=oracledb +db_name=db user=db_user password=pass -dbHost=127.0.0.1 -dbPort=1521 \ No newline at end of file +db_host=127.0.0.1 +db_port=1521 \ No newline at end of file diff --git a/test/resources/config_files/oracledb/wrong_password.cfg b/test/resources/config_files/oracledb/wrong_password.cfg index 2b6b05d..87691ac 100644 --- a/test/resources/config_files/oracledb/wrong_password.cfg +++ b/test/resources/config_files/oracledb/wrong_password.cfg @@ -1,7 +1,7 @@ [default] -dbapiModuleName=oracledb -dbName=db -dbUsername=db_user -dbPassword=wrong -dbHost=127.0.0.1 -dbPort=1521 \ No newline at end of file +db_module=oracledb +db_name=db +db_user=db_user +db_password=wrong +db_host=127.0.0.1 +db_port=1521 \ No newline at end of file diff --git a/test/resources/config_files/psycopg2/custom_param_password.cfg b/test/resources/config_files/psycopg2/custom_param_password.cfg index 0b0fa77..1ae5cb9 100644 --- a/test/resources/config_files/psycopg2/custom_param_password.cfg +++ b/test/resources/config_files/psycopg2/custom_param_password.cfg @@ -1,6 +1,6 @@ [default] -dbapiModuleName=psycopg2 -dbName=db +db_module=psycopg2 +db_name=db password=pass -dbHost=127.0.0.1 -dbPort=5432 \ No newline at end of file +db_host=127.0.0.1 +db_port=5432 \ No newline at end of file diff --git a/test/resources/config_files/psycopg2/invalid_custom_params.cfg b/test/resources/config_files/psycopg2/invalid_custom_params.cfg index 343eeb4..1496965 100644 --- a/test/resources/config_files/psycopg2/invalid_custom_params.cfg +++ b/test/resources/config_files/psycopg2/invalid_custom_params.cfg @@ -1,8 +1,8 @@ [default] -dbapiModuleName=psycopg2 -dbName=db -dbUsername=db_user -dbPassword=pass -dbHost=127.0.0.1 -dbPort=5432 +db_module=psycopg2 +db_name=db +db_user=db_user +db_password=pass +db_host=127.0.0.1 +db_port=5432 blah=blah \ No newline at end of file diff --git a/test/resources/config_files/psycopg2/old_param_names.cfg b/test/resources/config_files/psycopg2/old_param_names.cfg new file mode 100644 index 0000000..d9faef7 --- /dev/null +++ b/test/resources/config_files/psycopg2/old_param_names.cfg @@ -0,0 +1,7 @@ +[default] +dbapiModuleName=psycopg2 +dbName=db +dbUsername=db_user +dbPassword=pass +dbHost=127.0.0.1 +dbPort=5432 \ No newline at end of file diff --git a/test/resources/config_files/psycopg2/simple_default_alias.cfg b/test/resources/config_files/psycopg2/simple_default_alias.cfg index d9faef7..a80ef74 100644 --- a/test/resources/config_files/psycopg2/simple_default_alias.cfg +++ b/test/resources/config_files/psycopg2/simple_default_alias.cfg @@ -1,7 +1,7 @@ [default] -dbapiModuleName=psycopg2 -dbName=db -dbUsername=db_user -dbPassword=pass -dbHost=127.0.0.1 -dbPort=5432 \ No newline at end of file +db_module=psycopg2 +db_name=db +db_user=db_user +db_password=pass +db_host=127.0.0.1 +db_port=5432 \ No newline at end of file diff --git a/test/resources/config_files/psycopg2/some_basic_params_missing.cfg b/test/resources/config_files/psycopg2/some_basic_params_missing.cfg index 7d82bff..49d1cde 100644 --- a/test/resources/config_files/psycopg2/some_basic_params_missing.cfg +++ b/test/resources/config_files/psycopg2/some_basic_params_missing.cfg @@ -1,3 +1,3 @@ [default] -dbapiModuleName=psycopg2 -dbName=db \ No newline at end of file +db_module=psycopg2 +db_name=db \ No newline at end of file diff --git a/test/resources/config_files/psycopg2/valid_custom_params.cfg b/test/resources/config_files/psycopg2/valid_custom_params.cfg index c143601..fb15ffa 100644 --- a/test/resources/config_files/psycopg2/valid_custom_params.cfg +++ b/test/resources/config_files/psycopg2/valid_custom_params.cfg @@ -1,7 +1,7 @@ [default] -dbapiModuleName=psycopg2 -dbName=db +db_module=psycopg2 +db_name=db user=db_user password=pass -dbHost=127.0.0.1 -dbPort=5432 \ No newline at end of file +db_host=127.0.0.1 +db_port=5432 \ No newline at end of file diff --git a/test/resources/config_files/psycopg2/wrong_password.cfg b/test/resources/config_files/psycopg2/wrong_password.cfg index 074080f..9e97614 100644 --- a/test/resources/config_files/psycopg2/wrong_password.cfg +++ b/test/resources/config_files/psycopg2/wrong_password.cfg @@ -1,7 +1,7 @@ [default] -dbapiModuleName=psycopg2 -dbName=db -dbUsername=db_user -dbPassword=wrong -dbHost=127.0.0.1 -dbPort=5432 \ No newline at end of file +db_module=psycopg2 +db_name=db +db_user=db_user +db_password=wrong +db_host=127.0.0.1 +db_port=5432 \ No newline at end of file diff --git a/test/resources/config_files/pymssql/charset_invalid.cfg b/test/resources/config_files/pymssql/charset_invalid.cfg index c8ed37c..dab6951 100644 --- a/test/resources/config_files/pymssql/charset_invalid.cfg +++ b/test/resources/config_files/pymssql/charset_invalid.cfg @@ -1,8 +1,8 @@ [default] -dbapiModuleName=pymssql -dbName=db +db_module=pymssql +db_name=db user=SA password=MyPass1234! -dbHost=127.0.0.1 -dbPort=1433 -dbCharset=wrong \ No newline at end of file +db_host=127.0.0.1 +db_port=1433 +db_charset=wrong \ No newline at end of file diff --git a/test/resources/config_files/pymssql/custom_param_password.cfg b/test/resources/config_files/pymssql/custom_param_password.cfg index e11f91b..ff03d15 100644 --- a/test/resources/config_files/pymssql/custom_param_password.cfg +++ b/test/resources/config_files/pymssql/custom_param_password.cfg @@ -1,6 +1,6 @@ [default] -dbapiModuleName=pymssql -dbName=db +db_module=pymssql +db_name=db password=MyPass1234! -dbHost=127.0.0.1 -dbPort=1433 \ No newline at end of file +db_host=127.0.0.1 +db_port=1433 \ No newline at end of file diff --git a/test/resources/config_files/pymssql/invalid_custom_params.cfg b/test/resources/config_files/pymssql/invalid_custom_params.cfg index 7935942..1c6c801 100644 --- a/test/resources/config_files/pymssql/invalid_custom_params.cfg +++ b/test/resources/config_files/pymssql/invalid_custom_params.cfg @@ -1,8 +1,8 @@ [default] -dbapiModuleName=pymssql -dbName=db -dbUsername=SA -dbPassword=MyPass1234! -dbHost=127.0.0.1 -dbPort=1433 +db_module=pymssql +db_name=db +db_user=SA +db_password=MyPass1234! +db_host=127.0.0.1 +db_port=1433 blah=blah \ No newline at end of file diff --git a/test/resources/config_files/pymssql/old_param_names.cfg b/test/resources/config_files/pymssql/old_param_names.cfg new file mode 100644 index 0000000..00a68ad --- /dev/null +++ b/test/resources/config_files/pymssql/old_param_names.cfg @@ -0,0 +1,7 @@ +[default] +dbapiModuleName=pymssql +dbName=db +dbUsername=SA +dbPassword=MyPass1234! +dbHost=127.0.0.1 +dbPort=1433 \ No newline at end of file diff --git a/test/resources/config_files/pymssql/simple_default_alias.cfg b/test/resources/config_files/pymssql/simple_default_alias.cfg index 00a68ad..cd111ab 100644 --- a/test/resources/config_files/pymssql/simple_default_alias.cfg +++ b/test/resources/config_files/pymssql/simple_default_alias.cfg @@ -1,7 +1,7 @@ [default] -dbapiModuleName=pymssql -dbName=db -dbUsername=SA -dbPassword=MyPass1234! -dbHost=127.0.0.1 -dbPort=1433 \ No newline at end of file +db_module=pymssql +db_name=db +db_user=SA +db_password=MyPass1234! +db_host=127.0.0.1 +db_port=1433 \ No newline at end of file diff --git a/test/resources/config_files/pymssql/some_basic_params_missing.cfg b/test/resources/config_files/pymssql/some_basic_params_missing.cfg index af0f2d7..20e4533 100644 --- a/test/resources/config_files/pymssql/some_basic_params_missing.cfg +++ b/test/resources/config_files/pymssql/some_basic_params_missing.cfg @@ -1,3 +1,3 @@ [default] -dbapiModuleName=pymssql -dbName=db \ No newline at end of file +db_module=pymssql +db_name=db \ No newline at end of file diff --git a/test/resources/config_files/pymssql/valid_custom_params.cfg b/test/resources/config_files/pymssql/valid_custom_params.cfg index 4ba6dd2..47613a2 100644 --- a/test/resources/config_files/pymssql/valid_custom_params.cfg +++ b/test/resources/config_files/pymssql/valid_custom_params.cfg @@ -1,7 +1,7 @@ [default] -dbapiModuleName=pymssql -dbName=db +db_module=pymssql +db_name=db user=SA password=MyPass1234! -dbHost=127.0.0.1 -dbPort=1433 \ No newline at end of file +db_host=127.0.0.1 +db_port=1433 \ No newline at end of file diff --git a/test/resources/config_files/pymssql/wrong_password.cfg b/test/resources/config_files/pymssql/wrong_password.cfg index df8b822..04b37f2 100644 --- a/test/resources/config_files/pymssql/wrong_password.cfg +++ b/test/resources/config_files/pymssql/wrong_password.cfg @@ -1,7 +1,7 @@ [default] -dbapiModuleName=pymssql -dbName=db -dbUsername=SA -dbPassword=wrong -dbHost=127.0.0.1 -dbPort=1433 \ No newline at end of file +db_module=pymssql +db_name=db +db_user=SA +db_password=wrong +db_host=127.0.0.1 +db_port=1433 \ No newline at end of file diff --git a/test/resources/config_files/pymysql/charset_invalid.cfg b/test/resources/config_files/pymysql/charset_invalid.cfg index e64b4d8..9eb9a14 100644 --- a/test/resources/config_files/pymysql/charset_invalid.cfg +++ b/test/resources/config_files/pymysql/charset_invalid.cfg @@ -1,8 +1,8 @@ [default] -dbapiModuleName=pymysql -dbName=db +db_module=pymysql +db_name=db user=db_user password=pass -dbHost=127.0.0.1 -dbPort=3306 -dbCharset=wrong \ No newline at end of file +db_host=127.0.0.1 +db_port=3306 +db_charset=wrong \ No newline at end of file diff --git a/test/resources/config_files/pymysql/custom_param_password.cfg b/test/resources/config_files/pymysql/custom_param_password.cfg index dd211e0..52b68e7 100644 --- a/test/resources/config_files/pymysql/custom_param_password.cfg +++ b/test/resources/config_files/pymysql/custom_param_password.cfg @@ -1,6 +1,6 @@ [default] -dbapiModuleName=pymysql -dbName=db +db_module=pymysql +db_name=db password=pass -dbHost=127.0.0.1 -dbPort=3306 \ No newline at end of file +db_host=127.0.0.1 +db_port=3306 \ No newline at end of file diff --git a/test/resources/config_files/pymysql/invalid_custom_params.cfg b/test/resources/config_files/pymysql/invalid_custom_params.cfg index 31e3a20..46975ef 100644 --- a/test/resources/config_files/pymysql/invalid_custom_params.cfg +++ b/test/resources/config_files/pymysql/invalid_custom_params.cfg @@ -1,8 +1,8 @@ [default] -dbapiModuleName=pymysql -dbName=db -dbUsername=db_user -dbPassword=pass -dbHost=127.0.0.1 -dbPort=3306 +db_module=pymysql +db_name=db +db_user=db_user +db_password=pass +db_host=127.0.0.1 +db_port=3306 blah=blah \ No newline at end of file diff --git a/test/resources/config_files/pymysql/old_param_names.cfg b/test/resources/config_files/pymysql/old_param_names.cfg new file mode 100644 index 0000000..0d73312 --- /dev/null +++ b/test/resources/config_files/pymysql/old_param_names.cfg @@ -0,0 +1,7 @@ +[default] +dbapiModuleName=pymysql +dbName=db +dbUsername=db_user +dbPassword=pass +dbHost=127.0.0.1 +dbPort=3306 \ No newline at end of file diff --git a/test/resources/config_files/pymysql/simple_default_alias.cfg b/test/resources/config_files/pymysql/simple_default_alias.cfg index 0d73312..d4242af 100644 --- a/test/resources/config_files/pymysql/simple_default_alias.cfg +++ b/test/resources/config_files/pymysql/simple_default_alias.cfg @@ -1,7 +1,7 @@ [default] -dbapiModuleName=pymysql -dbName=db -dbUsername=db_user -dbPassword=pass -dbHost=127.0.0.1 -dbPort=3306 \ No newline at end of file +db_module=pymysql +db_name=db +db_user=db_user +db_password=pass +db_host=127.0.0.1 +db_port=3306 \ No newline at end of file diff --git a/test/resources/config_files/pymysql/some_basic_params_missing.cfg b/test/resources/config_files/pymysql/some_basic_params_missing.cfg index e341dc9..f6c24e4 100644 --- a/test/resources/config_files/pymysql/some_basic_params_missing.cfg +++ b/test/resources/config_files/pymysql/some_basic_params_missing.cfg @@ -1,3 +1,3 @@ [default] -dbapiModuleName=pymysql -dbName=db \ No newline at end of file +db_module=pymysql +db_name=db \ No newline at end of file diff --git a/test/resources/config_files/pymysql/valid_custom_params.cfg b/test/resources/config_files/pymysql/valid_custom_params.cfg index 4df80c1..dcd264a 100644 --- a/test/resources/config_files/pymysql/valid_custom_params.cfg +++ b/test/resources/config_files/pymysql/valid_custom_params.cfg @@ -1,7 +1,7 @@ [default] -dbapiModuleName=pymysql -dbName=db +db_module=pymysql +db_name=db user=db_user password=pass -dbHost=127.0.0.1 -dbPort=3306 \ No newline at end of file +db_host=127.0.0.1 +db_port=3306 \ No newline at end of file diff --git a/test/resources/config_files/pymysql/wrong_password.cfg b/test/resources/config_files/pymysql/wrong_password.cfg index 9ce8ca7..5d8921e 100644 --- a/test/resources/config_files/pymysql/wrong_password.cfg +++ b/test/resources/config_files/pymysql/wrong_password.cfg @@ -1,7 +1,7 @@ [default] -dbapiModuleName=pymysql -dbName=db -dbUsername=db_user -dbPassword=wrong -dbHost=127.0.0.1 -dbPort=3306 \ No newline at end of file +db_module=pymysql +db_name=db +db_user=db_user +db_password=wrong +db_host=127.0.0.1 +db_port=3306 \ No newline at end of file diff --git a/test/resources/config_files/pyodbc/charset_invalid.cfg b/test/resources/config_files/pyodbc/charset_invalid.cfg index 73a0be8..356dbf6 100644 --- a/test/resources/config_files/pyodbc/charset_invalid.cfg +++ b/test/resources/config_files/pyodbc/charset_invalid.cfg @@ -1,9 +1,9 @@ [default] -dbapiModuleName=pyodbc -dbName=db +db_module=pyodbc +db_name=db user=db_user password=pass -dbHost=127.0.0.1 -dbPort=3306 -dbDriver={MySQL ODBC 8.0 ANSI Driver} -dbCharset=wrong \ No newline at end of file +db_host=127.0.0.1 +db_port=3306 +odbc_driver={MySQL ODBC 8.0 ANSI Driver} +db_charset=wrong \ No newline at end of file diff --git a/test/resources/config_files/pyodbc/custom_param_password.cfg b/test/resources/config_files/pyodbc/custom_param_password.cfg index 6021e0a..56650c9 100644 --- a/test/resources/config_files/pyodbc/custom_param_password.cfg +++ b/test/resources/config_files/pyodbc/custom_param_password.cfg @@ -1,7 +1,7 @@ [default] -dbapiModuleName=pyodbc -dbName=db +db_module=pyodbc +db_name=db PWD=pass -dbHost=127.0.0.1 -dbPort=3306 -dbDriver={MySQL ODBC 8.0 ANSI Driver} \ No newline at end of file +db_host=127.0.0.1 +db_port=3306 +odbc_driver={MySQL ODBC 8.0 ANSI Driver} \ No newline at end of file diff --git a/test/resources/config_files/pyodbc/invalid_custom_params.cfg b/test/resources/config_files/pyodbc/invalid_custom_params.cfg index 7fe6682..8bb0d5d 100644 --- a/test/resources/config_files/pyodbc/invalid_custom_params.cfg +++ b/test/resources/config_files/pyodbc/invalid_custom_params.cfg @@ -1,9 +1,9 @@ [default] -dbapiModuleName=pyodbc -dbName=db -dbUsername=db_user -dbPassword=pass -dbHost=127.0.0.1 -dbPort=3306 -dbDriver={MySQL ODBC 8.0 ANSI Driver} +db_module=pyodbc +db_name=db +db_user=db_user +db_password=pass +db_host=127.0.0.1 +db_port=3306 +odbc_driver={MySQL ODBC 8.0 ANSI Driver} blah=blah \ No newline at end of file diff --git a/test/resources/config_files/pyodbc/old_param_names.cfg b/test/resources/config_files/pyodbc/old_param_names.cfg new file mode 100644 index 0000000..d7cf249 --- /dev/null +++ b/test/resources/config_files/pyodbc/old_param_names.cfg @@ -0,0 +1,8 @@ +[default] +dbapiModuleName=pyodbc +dbName=db +dbUsername=db_user +dbPassword=pass +dbHost=127.0.0.1 +dbPort=3306 +dbDriver={MySQL ODBC 8.0 ANSI Driver} \ No newline at end of file diff --git a/test/resources/config_files/pyodbc/simple_default_alias.cfg b/test/resources/config_files/pyodbc/simple_default_alias.cfg index d7cf249..9feac87 100644 --- a/test/resources/config_files/pyodbc/simple_default_alias.cfg +++ b/test/resources/config_files/pyodbc/simple_default_alias.cfg @@ -1,8 +1,8 @@ [default] -dbapiModuleName=pyodbc -dbName=db -dbUsername=db_user -dbPassword=pass -dbHost=127.0.0.1 -dbPort=3306 -dbDriver={MySQL ODBC 8.0 ANSI Driver} \ No newline at end of file +db_module=pyodbc +db_name=db +db_user=db_user +db_password=pass +db_host=127.0.0.1 +db_port=3306 +odbc_driver={MySQL ODBC 8.0 ANSI Driver} \ No newline at end of file diff --git a/test/resources/config_files/pyodbc/some_basic_params_missing.cfg b/test/resources/config_files/pyodbc/some_basic_params_missing.cfg index 436ebc9..16a3448 100644 --- a/test/resources/config_files/pyodbc/some_basic_params_missing.cfg +++ b/test/resources/config_files/pyodbc/some_basic_params_missing.cfg @@ -1,3 +1,3 @@ [default] -dbapiModuleName=pyodbc -dbName=db \ No newline at end of file +db_module=pyodbc +db_name=db \ No newline at end of file diff --git a/test/resources/config_files/pyodbc/valid_custom_params.cfg b/test/resources/config_files/pyodbc/valid_custom_params.cfg index eab93c8..a51f5e2 100644 --- a/test/resources/config_files/pyodbc/valid_custom_params.cfg +++ b/test/resources/config_files/pyodbc/valid_custom_params.cfg @@ -1,8 +1,8 @@ [default] -dbapiModuleName=pyodbc -dbName=db +db_module=pyodbc +db_name=db UID=db_user PWD=pass -dbHost=127.0.0.1 -dbPort=3306 -dbDriver={MySQL ODBC 8.0 ANSI Driver} \ No newline at end of file +db_host=127.0.0.1 +db_port=3306 +odbc_driver={MySQL ODBC 8.0 ANSI Driver} \ No newline at end of file diff --git a/test/resources/config_files/pyodbc/wrong_password.cfg b/test/resources/config_files/pyodbc/wrong_password.cfg index ac7a25c..150c0c9 100644 --- a/test/resources/config_files/pyodbc/wrong_password.cfg +++ b/test/resources/config_files/pyodbc/wrong_password.cfg @@ -1,8 +1,8 @@ [default] -dbapiModuleName=pyodbc -dbName=db -dbUsername=db_user -dbPassword=wrong -dbHost=127.0.0.1 -dbPort=3306 -dbDriver={MySQL ODBC 8.0 ANSI Driver} \ No newline at end of file +db_module=pyodbc +db_name=db +db_user=db_user +db_password=wrong +db_host=127.0.0.1 +db_port=3306 +odbc_driver={MySQL ODBC 8.0 ANSI Driver} \ No newline at end of file diff --git a/test/resources/config_files/sqlite3/old_param_names.cfg b/test/resources/config_files/sqlite3/old_param_names.cfg new file mode 100644 index 0000000..4a8047b --- /dev/null +++ b/test/resources/config_files/sqlite3/old_param_names.cfg @@ -0,0 +1,4 @@ +[default] +dbapiModuleName=sqlite3 +database=./${DBName}.db +isolation_level= \ No newline at end of file diff --git a/test/resources/config_files/sqlite3/simple_default_alias.cfg b/test/resources/config_files/sqlite3/simple_default_alias.cfg index 4a8047b..bdc48bd 100644 --- a/test/resources/config_files/sqlite3/simple_default_alias.cfg +++ b/test/resources/config_files/sqlite3/simple_default_alias.cfg @@ -1,4 +1,4 @@ [default] -dbapiModuleName=sqlite3 +db_module=sqlite3 database=./${DBName}.db isolation_level= \ No newline at end of file diff --git a/test/tests/common_tests/connection_params.robot b/test/tests/common_tests/connection_params.robot index 5a7f0de..00c0810 100644 --- a/test/tests/common_tests/connection_params.robot +++ b/test/tests/common_tests/connection_params.robot @@ -34,32 +34,32 @@ Test Teardown Disconnect From Database *** Test Cases *** Mandatory params can't be missing Run Keyword And Expect Error - ... ValueError: Required parameter 'dbapiModuleName' was not provided* - ... Connect To Database dbName=${DB_NAME} + ... ValueError: Required parameter 'db_module' was not provided* + ... Connect To Database db_name=${DB_NAME} All basic params, no config file Connect To Database - ... dbapiModuleName=${DB_MODULE} - ... dbName=${DB_NAME} - ... dbUsername=${DB_USER} - ... dbPassword=${DB_PASS} - ... dbHost=${DB_HOST} - ... dbPort=${DB_PORT} - ... dbDriver=${DB_DRIVER} + ... db_module=${DB_MODULE} + ... db_name=${DB_NAME} + ... db_user=${DB_USER} + ... db_password=${DB_PASS} + ... db_host=${DB_HOST} + ... db_port=${DB_PORT} + ... odbc_driver=${DB_DRIVER} Missing basic params are accepted, error from Python DB module Run Keyword And Expect Error ... ${Errors}[${DB_MODULE}][missing basic params] ... Connect To Database - ... dbapiModuleName=${DB_MODULE} + ... db_module=${DB_MODULE} Custom params as keyword args - valid Connect To Database - ... dbapiModuleName=${DB_MODULE} - ... dbName=${DB_NAME} - ... dbHost=${DB_HOST} - ... dbPort=${DB_PORT} - ... dbDriver=${DB_DRIVER} + ... db_module=${DB_MODULE} + ... db_name=${DB_NAME} + ... db_host=${DB_HOST} + ... db_port=${DB_PORT} + ... odbc_driver=${DB_DRIVER} ... user=${DB_USER} ... password=${DB_PASS} @@ -69,18 +69,21 @@ Custom params as keyword args - invalid, error from Python DB module Run Keyword And Expect Error ... ${Errors}[${DB_MODULE}][invalid custom param] ... Connect To Database - ... dbapiModuleName=${DB_MODULE} - ... dbName=${DB_NAME} - ... dbHost=${DB_HOST} - ... dbPort=${DB_PORT} - ... dbUsername=${DB_USER} - ... dbPassword=${DB_PASS} - ... dbDriver=${DB_DRIVER} + ... db_module=${DB_MODULE} + ... db_name=${DB_NAME} + ... db_host=${DB_HOST} + ... db_port=${DB_PORT} + ... db_user=${DB_USER} + ... db_password=${DB_PASS} + ... odbc_driver=${DB_DRIVER} ... blah=blah All basic params in config file Connect Using Config File ${DB_MODULE}/simple_default_alias +Deprecated basic params in config file + Connect Using Config File ${DB_MODULE}/old_param_names + Missing basic params in config file are accepted, error from Python DB module Run Keyword And Expect Error ... ${Errors}[${DB_MODULE}][missing basic params] @@ -104,22 +107,22 @@ Custom params as keyword args combined with custom params from config file Keyword args override config file values - basic params Connect Using Config File ${DB_MODULE}/wrong_password - ... dbPassword=${DB_PASS} + ... db_password=${DB_PASS} Keyword args override config file values - custom params Connect Using Config File ${DB_MODULE}/valid_custom_params ... user=${DB_USER} -Oracle specific - basic params, no config file, driverMode +Oracle specific - basic params, no config file, oracle_driver_mode Skip If $DB_MODULE != "oracledb" Connect To Database - ... dbapiModuleName=${DB_MODULE} - ... dbName=${DB_NAME} - ... dbUsername=${DB_USER} - ... dbPassword=${DB_PASS} - ... dbHost=${DB_HOST} - ... dbPort=${DB_PORT} - ... driverMode=thin + ... db_module=${DB_MODULE} + ... db_name=${DB_NAME} + ... db_user=${DB_USER} + ... db_password=${DB_PASS} + ... db_host=${DB_HOST} + ... db_port=${DB_PORT} + ... oracle_driver_mode=thin Oracle specific - thick mode in config file - invalid [Documentation] Invalid as mode switch during test execution is not supported @@ -132,14 +135,14 @@ Oracle specific - thick mode in config file - invalid MSSQL / MySQL / PyODBC specific - charset as keyword argument Skip If $DB_MODULE not in ["pymssql", "pymysql", "pyodbc"] Connect To Database - ... dbapiModuleName=${DB_MODULE} - ... dbName=${DB_NAME} - ... dbUsername=${DB_USER} - ... dbPassword=${DB_PASS} - ... dbHost=${DB_HOST} - ... dbPort=${DB_PORT} - ... dbDriver=${DB_DRIVER} - ... dbCharset=LATIN1 + ... db_module=${DB_MODULE} + ... db_name=${DB_NAME} + ... db_user=${DB_USER} + ... db_password=${DB_PASS} + ... db_host=${DB_HOST} + ... db_port=${DB_PORT} + ... odbc_driver=${DB_DRIVER} + ... db_charset=LATIN1 MSSQL specific - charset in config file - invalid Skip If $DB_MODULE not in ["pymssql"] @@ -161,7 +164,7 @@ SQlite specific - connection params as custom keyword args [Setup] Skip If $DB_MODULE != "sqlite3" Remove File ${DBName}.db Connect To Database - ... dbapiModuleName=${DB_MODULE} + ... db_module=${DB_MODULE} ... database=./${DBName}.db ... isolation_level=${EMPTY} diff --git a/test/tests/custom_db_tests/oracle_omit_semicolon.robot b/test/tests/custom_db_tests/oracle_omit_semicolon.robot index de2db7d..2ba6bd2 100644 --- a/test/tests/custom_db_tests/oracle_omit_semicolon.robot +++ b/test/tests/custom_db_tests/oracle_omit_semicolon.robot @@ -20,8 +20,8 @@ ${PLSQL BLOCK} DECLARE ERRCODE NUMBER; ERRMSG VARCHAR2(200); BEGIN DBMS_OUTPU Explicitely Omit Semicolon [Documentation] Check if it works for Oracle - explicitely omitting the semicolon ... is equal to the default behaviour, otherwise oracle_db throws an error - Execute Sql String ${NORMAL QUERY} omitTrailingSemicolon=True + Execute Sql String ${NORMAL QUERY} omit_trailing_semicolon=True Explicitely Dont't Omit Semicolon [Documentation] Check if it works for Oracle - it throws an error without a semicolon - Execute Sql String ${PLSQL BLOCK} omitTrailingSemicolon=False + Execute Sql String ${PLSQL BLOCK} omit_trailing_semicolon=False diff --git a/test/tests/custom_db_tests/oracle_thick_mode.robot b/test/tests/custom_db_tests/oracle_thick_mode.robot index e9ba1e9..73d03e2 100644 --- a/test/tests/custom_db_tests/oracle_thick_mode.robot +++ b/test/tests/custom_db_tests/oracle_thick_mode.robot @@ -24,11 +24,11 @@ ${ORACLE_LIB_DIR} ${EMPTY} *** Test Cases *** Thick Mode Without Client Dir Specified [Documentation] No client dir --> oracledb will search it in usual places - Connect And Run Simple Query driverMode=thick + Connect And Run Simple Query oracle_driver_mode=thick Thick Mode With Client Dir Specified [Documentation] Client dir specified --> oracledb will search it in this place - Connect And Run Simple Query driverMode=thick,lib_dir=${ORACLE_LIB_DIR} + Connect And Run Simple Query oracle_driver_mode=thick,lib_dir=${ORACLE_LIB_DIR} Thin Mode - Default [Documentation] No mode specified --> thin mode is used @@ -36,12 +36,12 @@ Thin Mode - Default Thin Mode Explicitely Specified [Documentation] Thin mode specified --> thin mode is used - Connect And Run Simple Query driverMode=thin + Connect And Run Simple Query oracle_driver_mode=thin Wrong Mode [Documentation] Wrong mode --> proper error message from the library Run Keyword And Expect Error ValueError: Invalid Oracle client mode provided: wrong - ... Connect And Run Simple Query driverMode=wrong + ... Connect And Run Simple Query oracle_driver_mode=wrong *** Keywords *** diff --git a/test/tests/utests/test_connection_manager.py b/test/tests/utests/test_connection_manager.py index 1f55c45..f53d8f0 100644 --- a/test/tests/utests/test_connection_manager.py +++ b/test/tests/utests/test_connection_manager.py @@ -15,9 +15,9 @@ def test_connect_with_empty_config(self): config_path = str(TEST_DATA / "empty.cfg") with pytest.raises( ValueError, - match="Required parameter 'dbapiModuleName' was not provided - neither in keyword arguments nor in config file", + match="Required parameter 'db_module' was not provided - neither in keyword arguments nor in config file", ): - conn_manager.connect_to_database(dbConfigFile=config_path) + conn_manager.connect_to_database(config_file=config_path) def test_aliased_section(self): conn_manager = ConnectionManager() @@ -25,11 +25,11 @@ def test_aliased_section(self): with patch("importlib.import_module", new=MagicMock()) as client: conn_manager.connect_to_database( "my_client", - dbUsername="name", - dbPassword="password", - dbHost="host", - dbPort=0, - dbConfigFile=config_path, + db_user="name", + db_password="password", + db_host="host", + db_port=0, + config_file=config_path, alias="alias2", ) client.return_value.connect.assert_called_with( diff --git a/test/tests/utests/test_data/alias.cfg b/test/tests/utests/test_data/alias.cfg index 06d0431..51b5ee5 100644 --- a/test/tests/utests/test_data/alias.cfg +++ b/test/tests/utests/test_data/alias.cfg @@ -1,2 +1,2 @@ [alias2] -dbName = example +db_name = example diff --git a/test/tests/utests/test_data/no_option.cfg b/test/tests/utests/test_data/no_option.cfg index 4e0db9e..53c0731 100644 --- a/test/tests/utests/test_data/no_option.cfg +++ b/test/tests/utests/test_data/no_option.cfg @@ -1,3 +1,3 @@ [default] -dbName = example -dbUsername = example \ No newline at end of file +db_name = example +db_user = example \ No newline at end of file