Repository: ploomber/jupysql
Pull request #818 (2023-08-19 16:59:09)

changes dsn_filename default value
## Describe your changes

* [API Change] `%config SqlMagic.dsn_filename` default value changed from `odbc.ini` to `~/.jupysql/connections.ini`

## Issue number

Closes #X

## Checklist before requesting a review

- [ ] Performed a self-review of my code
- [ ] Formatted my code with [`pkgmt format`](https://ploomber-contributing.readthedocs.io/en/latest/contributing/pr.html#linting-formatting)
- [ ] Added [tests](https://ploomber-contributing.readthedocs.io/en/latest/contributing/pr.html#testing) (when necessary).
- [ ] Added [docstring](https://ploomber-contributing.readthedocs.io/en/latest/contributing/pr.html#documenting-changes-and-new-features) documentation and update the [changelog](https://ploomber-contributing.readthedocs.io/en/latest/contributing/pr.html#changelog) (when needed)



<!-- readthedocs-preview jupysql start -->
----
:books: Documentation preview :books:: https://jupysql--818.org.readthedocs.build/en/818/

<!-- readthedocs-preview jupysql end -->


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4a55c6c..5954724 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@
 * [Fix] Fix an error that caused a connection error message to be turned into a `print` statement
 * [Fix] Fix Twice message printing when switching to the current connection (#772)
 * [Fix] Error when using %sqlplot in snowflake (#697)
+* [API Change] `%config SqlMagic.dsn_filename` default value changed from `odbc.ini` to `~/.jupysql/connections.ini`
 
 ## 0.9.1 (2023-08-10)
 
diff --git a/doc/api/configuration.md b/doc/api/configuration.md
index f79fe5f..0a324bc 100644
--- a/doc/api/configuration.md
+++ b/doc/api/configuration.md
@@ -130,6 +130,18 @@ One displayed, but all results fetched:
 len(res)
 ```
 
+## `dsn_filename`
+
+```{versionchanged} 0.10.0
+`dsn_filename` default changed from `odbc.ini` to `~/.jupysql/connections.ini`.
+```
+
+Default: `~/.jupysql/connections.ini`
+
+File to load connections configuration from. For an example, see: [](../user-guide/connection-file.md)
+
++++
+
 ## `autopandas`
 
 Default: `False`
diff --git a/doc/api/magic-sql.md b/doc/api/magic-sql.md
index b7fba8b..37e502e 100644
--- a/doc/api/magic-sql.md
+++ b/doc/api/magic-sql.md
@@ -5,7 +5,7 @@ jupytext:
     extension: .md
     format_name: myst
     format_version: 0.13
-    jupytext_version: 1.14.7
+    jupytext_version: 1.15.0
 kernelspec:
   display_name: Python 3 (ipykernel)
   language: python
@@ -163,10 +163,24 @@ def creator():
 %sql --creator creator
 ```
 
-## Start a connection from `.ini file`
+## Start a connection from `.ini` file
+
+```{versionchanged} 0.10.0
+`dsn_filename` default changed from `odbc.ini` to `~/.jupysql/connections.ini`.
+```
 
 Use `--section` to start a connection from the `dsn_filename`. To learn more, see: [](../user-guide/connection-file.md)
 
+By default, JupySQL reads connections from `~/.jupysql/connections.ini`, but you can set it to a different value:
+
+```{code-cell} ipython3
+%config SqlMagic.dsn_filename
+```
+
+```{code-cell} ipython3
+%config SqlMagic.dsn_filename = "connections.ini"
+```
+
 ```{code-cell} ipython3
 %config SqlMagic.dsn_filename
 ```
@@ -174,16 +188,16 @@ Use `--section` to start a connection from the `dsn_filename`. To learn more, se
 ```{code-cell} ipython3
 from pathlib import Path
 
-Path("odbc.ini").write_text(
+_ = Path("connections.ini").write_text(
     """
-[duck]
+[mydb]
 drivername = duckdb
 """
 )
 ```
 
 ```{code-cell} ipython3
-%sql --section duck
+%sql --section mydb
 ```
 
 ```{code-cell} ipython3
diff --git a/doc/user-guide/connection-file.md b/doc/user-guide/connection-file.md
index 405b803..1ed55d3 100644
--- a/doc/user-guide/connection-file.md
+++ b/doc/user-guide/connection-file.md
@@ -5,7 +5,7 @@ jupytext:
     extension: .md
     format_name: myst
     format_version: 0.13
-    jupytext_version: 1.14.7
+    jupytext_version: 1.15.0
 kernelspec:
   display_name: Python 3 (ipykernel)
   language: python
@@ -29,7 +29,7 @@ Using a connection file is the recommended way to manage connections, it helps y
 %load_ext sql
 ```
 
-By default, connections are read/stored in a `odbc.ini` file:
+By default, connections are read/stored in a `~/.jupysql/connections.ini` file:
 
 ```{code-cell} ipython3
 %config SqlMagic.dsn_filename
@@ -38,7 +38,7 @@ By default, connections are read/stored in a `odbc.ini` file:
 However, you can change this:
 
 ```{code-cell} ipython3
-%config SqlMagic.dsn_filename = "my-connections.ini"
+%config SqlMagic.dsn_filename = "connections.ini"
 ```
 
 The `.ini` format defines sections and you can define key-value pairs within each section. For example:
@@ -68,7 +68,7 @@ drivername = duckdb
 ```{code-cell} ipython3
 from pathlib import Path
 
-_ = Path("my-connections.ini").write_text(
+_ = Path("connections.ini").write_text(
     """
 [duck]
 drivername = duckdb
@@ -122,7 +122,7 @@ select * from penguins
 Let's now define another connection so we can show how we can manage multiple ones:
 
 ```{code-cell} ipython3
-_ = Path("my-connections.ini").write_text(
+_ = Path("connections.ini").write_text(
     """
 [duck]
 drivername = duckdb
@@ -168,3 +168,7 @@ We can change back to the other connection:
 ```{code-cell} ipython3
 %sqlcmd tables
 ```
+
+```{code-cell} ipython3
+
+```
diff --git a/src/sql/magic.py b/src/sql/magic.py
index 1f53b67..5a31e5f 100644
--- a/src/sql/magic.py
+++ b/src/sql/magic.py
@@ -1,5 +1,6 @@
 import json
 import re
+from pathlib import Path
 
 try:
     from ipywidgets import interact
@@ -159,7 +160,7 @@ class SqlMagic(Magics, Configurable):
     )
 
     dsn_filename = Unicode(
-        default_value="odbc.ini",
+        default_value=str(Path("~/.jupysql/connections.ini").expanduser()),
         config=True,
         help="Path to DSN file. "
         "When the first argument is of the form [section], "
@@ -188,6 +189,11 @@ class SqlMagic(Magics, Configurable):
         # Add ourself to the list of module configurable via %config
         self.shell.configurables.append(self)
 
+    @validate("dsn_filename")
+    def _valid_dsn_filename(self, proposal):
+        path = Path(proposal["value"]).expanduser()
+        return str(path)
+
     # To verify displaylimit is valid positive integer
     # If:
     #   None -> We treat it as 0 (no limit)


Test patch:
diff --git a/src/tests/conftest.py b/src/tests/conftest.py
index 5ee402f..1a7e674 100644
--- a/src/tests/conftest.py
+++ b/src/tests/conftest.py
@@ -103,6 +103,27 @@ def ip_empty():
     ConnectionManager.close_all()
 
 
+@pytest.fixture
+def sql_magic():
+    c = Config()
+    # By default, InteractiveShell will record command's history in a SQLite database
+    # which leads to "too many open files" error when running tests; this setting
+    # disables the history recording.
+    # https://ipython.readthedocs.io/en/stable/config/options/terminal.html#configtrait-HistoryAccessor.enabled
+    c.HistoryAccessor.enabled = False
+    ip_session = TestingShell(config=c)
+
+    sql_magic = SqlMagic(ip_session)
+
+    # there is some weird bug in ipython that causes this function to hang the pytest
+    # process when all tests have been executed (an internal call to gc.collect()
+    # hangs). This is a workaround.
+    ip_session.displayhook.flush = lambda: None
+
+    yield sql_magic
+    ConnectionManager.close_all()
+
+
 def insert_sample_data(ip):
     ip.run_cell(
         """%%sql
diff --git a/src/tests/test_command.py b/src/tests/test_command.py
index 7b73b14..b616676 100644
--- a/src/tests/test_command.py
+++ b/src/tests/test_command.py
@@ -72,6 +72,8 @@ def test_parsed(
     parsed_return_result_var,
     tmp_empty,
 ):
+    ip.run_cell("%config SqlMagic.dsn_filename = 'odbc.ini'")
+
     # needed for the last test case
     Path("odbc.ini").write_text(
         """
diff --git a/src/tests/test_config.py b/src/tests/test_config.py
new file mode 100644
index 0000000..cfefb0a
--- /dev/null
+++ b/src/tests/test_config.py
@@ -0,0 +1,21 @@
+# we have a lot of config tests in test_magic.py, we should move them here
+from pathlib import Path
+
+
+def test_dsn_filename_default_value(sql_magic):
+    assert sql_magic.dsn_filename == str(
+        Path("~/.jupysql/connections.ini").expanduser()
+    )
+
+
+def test_dsn_filename_resolves_user_directory(sql_magic):
+    sql_magic.dsn_filename = "~/connections.ini"
+
+    path = Path("~/connections.ini").expanduser()
+    expected = str(path)
+
+    # setting the value should not create the file
+    assert not path.exists()
+
+    # but it should resolve the path
+    assert sql_magic.dsn_filename == expected
diff --git a/src/tests/test_magic.py b/src/tests/test_magic.py
index 1558530..dd39084 100644
--- a/src/tests/test_magic.py
+++ b/src/tests/test_magic.py
@@ -1803,9 +1803,8 @@ def test_error_when_using_section_argument_but_dsn_is_missing(ip_empty, tmp_empt
         ip_empty.run_cell("%sql --section some_section")
 
     assert excinfo.value.error_type == "FileNotFoundError"
-    assert "%config SqlMagic.dsn_filename ('path/to/connections.ini') not found" in str(
-        excinfo.value
-    )
+    assert "%config SqlMagic.dsn_filename" in str(excinfo.value)
+    assert "not found" in str(excinfo.value)
 
 
 def test_error_when_using_section_argument_but_dsn_section_is_missing(