tortoise-orm icon indicating copy to clipboard operation
tortoise-orm copied to clipboard

Cannot connect to PosgreSQL using unix domain socket

Open otsuka opened this issue 5 years ago • 8 comments

I can connect to PostgreSQL database using asyncpg with specifying DSN string as follows:

conn = await asyncpg.connect(
    'postgres:///sampledb?host=/cloudsql/xxx:asia-northeast1:yyy/.s.PGSQL.5432&user=postgres&password=pass')

(This PostgreSQL database is GCP Cloud SQL)

UsingTortoise.init(), however, I cannot connect to the database with the same DSN string.

await Tortoise.init(
    config={
        'connections': {
            'default': 'postgres:///sampledb?host=/cloudsql/xxx:asia-northeast1:yyy/.s.PGSQL.5432&user=postgres&password=pass'
        },
        'apps': {
            'models': {
                'models': ['__main__'],
                'default_connection': 'default',
            }
        }})

The code above raises OSError: Multiple exceptions: [Errno 61] Connect call failed ('::1', 5432, 0, 0), [Errno 61] Connect call failed ('127.0.0.1', 5432)

I also tried the following connection settings, but TypeError: __init__() missing 1 required positional argument: 'port' occurred.

await Tortoise.init(
    config={
        'connections': {
            'default': {
                "engine": "tortoise.backends.asyncpg",
                "credentials": {
                    "database": 'sampledb',
                    "host": '/cloudsql/xxx:asia-northeast1:yyy/.s.PGSQL.5432',
                    "password": "pass",
                    "user": "postgres",
                }
            }
        'apps': {
            'models': {
                'models': ['__main__'],
                'default_connection': 'default',
            }
        }})

How can I connect to the database using unix socket?

otsuka avatar May 10 '20 06:05 otsuka

Hello. Using db_url instead of config works well?

await Tortoise.init(
    db_url='postgres:///sampledb?host=/cloudsql/xxx:asia-northeast1:yyy/.s.PGSQL.5432&user=postgres&password=pass',
    ...
)

Maybe or this form?

await Tortoise.init(
    config={
        'connections': {
            'default': 'postgres:///sampledb?host=/cloudsql/xxx:asia-northeast1:yyy/.s.PGSQL.5432&user=postgres&password=pass'
        },
        'apps': {
            'models': {
                'models': ['__main__'],
                'default_connection': 'default',
            }
        }})

hqsz avatar May 10 '20 06:05 hqsz

Thank you, @lntuition

Unfortunately using db_url argument didn't work. It raised OSError: Multiple exceptions: [Errno 61] Connect call failed ('::1', 5432, 0, 0), [Errno 61] Connect call failed ('127.0.0.1', 5432) as well.

otsuka avatar May 10 '20 08:05 otsuka

I found that I can connect by giving port key to the credentials dict. Any number is ok for its value.

await Tortoise.init(
    config={
        'connections': {
            'default': {
                "engine": "tortoise.backends.asyncpg",
                "credentials": {
                    "database": 'sampledb',
                    "host": '/cloudsql/xxx:asia-northeast1:yyy/.s.PGSQL.5432',
                    "password": "pass",
                    "user": "postgres",
                    "port": -1
                }
            }
        'apps': {
            'models': {
                'models': ['__main__'],
                'default_connection': 'default',
            }
        }})

I have solved my problem.

If it's not a bug that we can't connect using a unix domain socket's DSN, it's ok to close this issue.

otsuka avatar May 10 '20 08:05 otsuka

Lets fix the issue of expecting a port at least?

grigi avatar May 10 '20 09:05 grigi

Are there any news? I have been trying the solutions above but to no success.

await Tortoise.init(
    config={
        'connections': {
            'default': {
                "engine": "tortoise.backends.asyncpg",
                "credentials": {
                    "database": 'sampledb',
                    "host": '/cloudsql/xxx:asia-northeast1:yyy/.s.PGSQL.5432',
                    "password": "pass",
                    "user": "postgres",
                    "port": -1
                }
            }
        'apps': {
            'models': {
                'models': ['__main__'],
                'default_connection': 'default',
            }
        }})

generates the error

ERROR:    Traceback (most recent call last):
  File ".../env/lib/python3.9/site-packages/starlette/routing.py", line 621, in lifespan
    async with self.lifespan_context(app):
  File ".../env/lib/python3.9/site-packages/starlette/routing.py", line 518, in __aenter__
    await self._router.startup()
  File ".../env/lib/python3.9/site-packages/starlette/routing.py", line 598, in startup
    await handler()
  File ".../project/./app/main.py", line 39, in init_orm
    await Tortoise.init(config)
  File ".../env/lib/python3.9/site-packages/tortoise/__init__.py", line 598, in init
    await cls._init_connections(connections_config, _create_db)
  File ".../env/lib/python3.9/site-packages/tortoise/__init__.py", line 390, in _init_connections
    await connection.create_connection(with_db=True)
  File ".../env/lib/python3.9/site-packages/tortoise/backends/asyncpg/client.py", line 103, in create_connection
    self._pool = await asyncpg.create_pool(None, password=self.password, **self._template)
  File ".../env/lib/python3.9/site-packages/asyncpg/pool.py", line 407, in _async__init__
    await self._initialize()
  File ".../env/lib/python3.9/site-packages/asyncpg/pool.py", line 435, in _initialize
    await first_ch.connect()
  File ".../env/lib/python3.9/site-packages/asyncpg/pool.py", line 127, in connect
    self._con = await self._pool._get_new_connection()
  File ".../env/lib/python3.9/site-packages/asyncpg/pool.py", line 477, in _get_new_connection
    con = await connection.connect(
  File ".../env/lib/python3.9/site-packages/asyncpg/connection.py", line 2045, in connect
    return await connect_utils._connect(
  File ".../env/lib/python3.9/site-packages/asyncpg/connect_utils.py", line 790, in _connect
    raise last_error
  File ".../env/lib/python3.9/site-packages/asyncpg/connect_utils.py", line 776, in _connect
    return await _connect_addr(
  File ".../env/lib/python3.9/site-packages/asyncpg/connect_utils.py", line 671, in _connect_addr
    return await __connect_addr(params, timeout, False, *args)
  File ".../env/lib/python3.9/site-packages/asyncpg/connect_utils.py", line 720, in __connect_addr
    tr, pr = await compat.wait_for(connector, timeout=timeout)
  File ".../env/lib/python3.9/site-packages/asyncpg/compat.py", line 66, in wait_for
    return await asyncio.wait_for(fut, timeout)
  File "/usr/local/Cellar/[email protected]/3.9.7_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/tasks.py", line 481, in wait_for
    return fut.result()
  File "/usr/local/Cellar/[email protected]/3.9.7_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/unix_events.py", line 244, in create_unix_connection
    await self.sock_connect(sock, path)
  File "/usr/local/Cellar/[email protected]/3.9.7_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/selector_events.py", line 502, in sock_connect
    return await fut
  File "/usr/local/Cellar/[email protected]/3.9.7_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/selector_events.py", line 507, in _sock_connect
    sock.connect(address)
FileNotFoundError: [Errno 2] No such file or directory

ERROR:    Application startup failed. Exiting.```

JeffryCA avatar Nov 04 '21 10:11 JeffryCA

I can confirm this solution with credentials dict in 2022.

Other forms of url don't work

Kulikovpavel avatar Jan 29 '22 20:01 Kulikovpavel

I'm using tortoise-orm v0.23.0 + asyncpg v0.29.0, and I'm in the same situation.

  • I need to use a domain socket to access my Google Cloud SQL instance from a Google Cloud Run container
  • My code's been working fine using asyncpg directly, but now I want to add Tortoise to my stack.
  • I only got it working by using the config param as demonstrated by otsuka. (I'd have preferred to stick with the db_url and modules params.)

Three more caveats I'd like to mention for the benefit of others in the same situation:

  1. I was using a postgresql:// URI before, which asyncpg supports, but had to change it to postgres:// to satisfy Tortoise. (Fortunately, asyncpg supports both.)
  2. I had to add /.s.PGSQL.5432 to the host to make Tortoise work, whereas I could leave it off before. (asyncpg will automatically add it for you.)
  3. You don't actually have to specify `"default_connection": "default"', because that's the default.

ofer-pd avatar Jan 06 '25 00:01 ofer-pd

Update: I found a hack that works. :) I feel dirty even sharing, but...

from tortoise.backends.base.config_generator import DB_LOOKUP
DB_LOOKUP[ "asyncpg" ][ "vmap" ].pop( "hostname" )

Tortoise.init(
    db_url = "postgres://USER:PASS@/DBNAME?host=/cloudsql/myproj:us-central1:mydb/.s.PGSQL.5432",
    modules=dict( myapp=[ "path.to.models" ] ),
)

Now you can pass a unix-domain-socket-style URI — asyncpg will respect the host and ignore the port. Note, however, that this hack depends on internal details and has only been tested against v0.23.0 — and will almost certainly break some day.


The real solution is to modify Tortoise itself to support unix domain socket paths properly. I think the smallest change to make this possible is to add a few lines of code to Tortoise.init() that check if db_url is a dict, and to treat that like the "credentials" block in the config instead of calling "expand_db_url()" on it as if it were a URI to be parsed.

I don't have much experience contributing code to FOSS projects, but I can try to create a PR for this if the maintainers are interested.

ofer-pd avatar Jan 13 '25 01:01 ofer-pd