Repository: samuelcolvin/pydantic (python)

Issue:
Can't use `postgresql+psycopg2://...` with `PostgresDsn `
### Checks

* [x] I added a descriptive title to this issue
* [x] I have searched (google, github) for similar issues and couldn't find anything
* [x] I have read and followed [the docs](https://pydantic-docs.helpmanual.io/) and still think this is a bug

<!-- Sorry to sound so draconian, but every second saved replying to issues is time spend improving pydantic :-) -->

# Bug

I'm expecting to be able to use a database connection url such as `postgresql+psycopg2://user:pass@localhost:5432/app` (a format [SQLAlchemy is supporting](https://docs.sqlalchemy.org/en/14/dialects/postgresql.html#dialect-postgresql-psycopg2-connect)). 

However, it doesn't seem to accept the `+psycopg2` part. 

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.8.1
            pydantic compiled: False
                 install path: /Users/krisb/Code/fork/pydantic/pydantic
               python version: 3.7.10 (default, Apr 12 2021, 03:46:49)  [Clang 12.0.0 (clang-1200.0.32.29)]
                     platform: Darwin-20.3.0-x86_64-i386-64bit
     optional deps. installed: ['devtools', 'dotenv', 'email-validator', 'typing-extensions']
```
<!-- or if you're using pydantic prior to v1.3, manually include: OS, python version and pydantic version -->

<!-- Please read the [docs](https://pydantic-docs.helpmanual.io/) and search through issues to
confirm your bug hasn't already been reported. -->

<!-- Where possible please include a self-contained code snippet describing your bug: -->

Changed test https://github.com/samuelcolvin/pydantic/blob/7af90a8d6c1c406799ad95b81e353b386cde5745/tests/test_networks.py#L318-L318 to include 
```py
assert Model(a='postgresql+psycopg2://user:pass@localhost:5432/app').a == 'postgresql+psycopg2://user:pass@localhost:5432/app'
```
pytest error output:
```
...
>           raise validation_error
E           pydantic.error_wrappers.ValidationError: 1 validation error for Model
E           a
E             URL scheme not permitted (type=value_error.url.scheme; allowed_schemes={'postgres', 'postgresql'})

pydantic/main.py:400: ValidationError
```

This is similar to https://github.com/samuelcolvin/pydantic/issues/1142, and a workaround for now is to use `AnyUrl` where this type of URL is supported. 


Pull request:
Support postgresql+asyncpg schema for PostgresDsn
## Change Summary

SQLAchemy 1.4 [added alpha support](https://docs.sqlalchemy.org/en/14/changelog/migration_14.html) to async calls. The support to PostgreSQL needs [to add asyncpg](https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html) in the schema, e.g.: `postgresql+asyncpg://scott:tiger@localhost/test`


## Related issue number

#2545
Closes #2679 

## Checklist

* [ X ] Unit tests for the changes exist
* [ X ] Tests pass on CI and coverage remains at 100%
* [ X ] Documentation reflects the changes where applicable
* [ X ] `changes/<pull request or issue id>-<github username>.md` file added describing change
  (see [changes/README.md](https://github.com/samuelcolvin/pydantic/blob/master/changes/README.md) for details)


Patch:
diff --git a/changes/2567-postgres-asyncpg.md b/changes/2567-postgres-asyncpg.md
new file mode 100644
index 000000000..fc63af863
--- /dev/null
+++ b/changes/2567-postgres-asyncpg.md
@@ -0,0 +1,1 @@
+Add `postgresql+asyncpg`, `postgresql+pg8000`, `postgresql+psycopg2`, `postgresql+psycopg2cffi`, `postgresql+py-postgresql` and `postgresql+pygresql` schemes for `PostgresDsn`
diff --git a/docs/usage/types.md b/docs/usage/types.md
index dfc3dcab2..dae8e5ac4 100644
--- a/docs/usage/types.md
+++ b/docs/usage/types.md
@@ -573,7 +573,13 @@ For URI/URL validation the following types are available:
 - `AnyUrl`: any scheme allowed, TLD not required
 - `AnyHttpUrl`: scheme `http` or `https`, TLD not required
 - `HttpUrl`: scheme `http` or `https`, TLD required, max length 2083
-- `PostgresDsn`: scheme `postgres` or `postgresql`, user info required, TLD not required
+- `PostgresDsn`: scheme `postgres`, `postgresql`, user info required, TLD not required. Also, its supported DBAPI dialects:
+  - `postgresql+asyncpg`
+  - `postgresql+pg8000`
+  - `postgresql+psycopg2`
+  - `postgresql+psycopg2cffi`
+  - `postgresql+py-postgresql`
+  - `postgresql+pygresql`
 - `RedisDsn`: scheme `redis` or `rediss`, user info not required, tld not required (CHANGED: user info
   not required from **v1.6** onwards), user info may be passed without user part (e.g., `rediss://:pass@localhost`)
 - `stricturl`, method with the following keyword arguments:
diff --git a/pydantic/networks.py b/pydantic/networks.py
index 69909967e..c9fccaeec 100644
--- a/pydantic/networks.py
+++ b/pydantic/networks.py
@@ -298,7 +298,16 @@ class HttpUrl(AnyUrl):
 
 
 class PostgresDsn(AnyUrl):
-    allowed_schemes = {'postgres', 'postgresql'}
+    allowed_schemes = {
+        'postgres',
+        'postgresql',
+        'postgresql+asyncpg',
+        'postgresql+pg8000',
+        'postgresql+psycopg2',
+        'postgresql+psycopg2cffi',
+        'postgresql+py-postgresql',
+        'postgresql+pygresql',
+    }
     user_required = True
 
 


Test patch:
diff --git a/tests/test_networks.py b/tests/test_networks.py
index cf681b2ed..bb10ae9b8 100644
--- a/tests/test_networks.py
+++ b/tests/test_networks.py
@@ -18,7 +18,12 @@ except ImportError:
         'https://example.org/whatever/next/',
         'postgres://user:pass@localhost:5432/app',
         'postgres://just-user@localhost:5432/app',
+        'postgresql+asyncpg://user:pass@localhost:5432/app',
+        'postgresql+pg8000://user:pass@localhost:5432/app',
         'postgresql+psycopg2://postgres:postgres@localhost:5432/hatch',
+        'postgresql+psycopg2cffi://user:pass@localhost:5432/app',
+        'postgresql+py-postgresql://user:pass@localhost:5432/app',
+        'postgresql+pygresql://user:pass@localhost:5432/app',
         'foo-bar://example.org',
         'foo.bar://example.org',
         'foo0bar://example.org',
@@ -333,6 +338,10 @@ def test_postgres_dsns():
 
     assert Model(a='postgres://user:pass@localhost:5432/app').a == 'postgres://user:pass@localhost:5432/app'
     assert Model(a='postgresql://user:pass@localhost:5432/app').a == 'postgresql://user:pass@localhost:5432/app'
+    assert (
+        Model(a='postgresql+asyncpg://user:pass@localhost:5432/app').a
+        == 'postgresql+asyncpg://user:pass@localhost:5432/app'
+    )
 
     with pytest.raises(ValidationError) as exc_info:
         Model(a='http://example.org')