cloud-sql-python-connector 1.13.0__tar.gz → 1.14.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/PKG-INFO +83 -13
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/README.md +81 -11
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/cloud_sql_python_connector.egg-info/PKG-INFO +83 -13
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/cloud_sql_python_connector.egg-info/SOURCES.txt +1 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/cloud_sql_python_connector.egg-info/requires.txt +1 -1
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/asyncpg.py +1 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/client.py +4 -4
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/connection_info.py +2 -2
- cloud_sql_python_connector-1.14.0/google/cloud/sql/connector/connection_name.py +51 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/connector.py +3 -3
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/instance.py +17 -34
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/lazy.py +12 -10
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/pg8000.py +1 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/pymysql.py +1 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/pytds.py +1 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/refresh_utils.py +3 -3
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/utils.py +2 -4
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/version.py +1 -1
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/setup.py +1 -1
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/LICENSE +0 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/cloud_sql_python_connector.egg-info/dependency_links.txt +0 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/cloud_sql_python_connector.egg-info/not-zip-safe +0 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/cloud_sql_python_connector.egg-info/top_level.txt +0 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/__init__.py +0 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/enums.py +0 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/exceptions.py +0 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/py.typed +0 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/google/cloud/sql/connector/rate_limiter.py +0 -0
- {cloud_sql_python_connector-1.13.0 → cloud_sql_python_connector-1.14.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cloud-sql-python-connector
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.14.0
|
|
4
4
|
Summary: The Cloud SQL Python Connector is a library that can be used alongside a database driver to allow users with sufficient permissions to connect to a Cloud SQL database without having to manually allowlist IPs or manage SSL certificates.
|
|
5
5
|
Home-page: https://github.com/GoogleCloudPlatform/cloud-sql-python-connector
|
|
6
6
|
Author: Google LLC
|
|
@@ -31,7 +31,7 @@ Requires-Dist: pg8000>=1.31.1; extra == "pg8000"
|
|
|
31
31
|
Provides-Extra: pytds
|
|
32
32
|
Requires-Dist: python-tds>=1.15.0; extra == "pytds"
|
|
33
33
|
Provides-Extra: asyncpg
|
|
34
|
-
Requires-Dist: asyncpg>=0.
|
|
34
|
+
Requires-Dist: asyncpg>=0.30.0; extra == "asyncpg"
|
|
35
35
|
|
|
36
36
|
<p align="center">
|
|
37
37
|
<a href="https://cloud.google.com/blog/topics/developers-practitioners/how-connect-cloud-sql-using-python-easy-way">
|
|
@@ -537,6 +537,42 @@ The `create_async_connector` allows all the same input arguments as the
|
|
|
537
537
|
Once a `Connector` object is returned by `create_async_connector` you can call
|
|
538
538
|
its `connect_async` method, just as you would the `connect` method:
|
|
539
539
|
|
|
540
|
+
#### Asyncpg Connection Pool
|
|
541
|
+
|
|
542
|
+
```python
|
|
543
|
+
import asyncpg
|
|
544
|
+
from google.cloud.sql.connector import Connector, create_async_connector
|
|
545
|
+
|
|
546
|
+
async def main():
|
|
547
|
+
# initialize Connector object for connections to Cloud SQL
|
|
548
|
+
connector = create_async_connector()
|
|
549
|
+
|
|
550
|
+
# creation function to generate asyncpg connections as the 'connect' arg
|
|
551
|
+
async def getconn(instance_connection_name, **kwargs) -> asyncpg.Connection:
|
|
552
|
+
return await connector.connect_async(
|
|
553
|
+
instance_connection_name,
|
|
554
|
+
"asyncpg",
|
|
555
|
+
user="my-user",
|
|
556
|
+
password="my-password",
|
|
557
|
+
db="my-db",
|
|
558
|
+
**kwargs, # ... additional asyncpg args
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
# initialize connection pool
|
|
562
|
+
pool = await asyncpg.create_pool(
|
|
563
|
+
"my-project:my-region:my-instance", connect=getconn
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
# acquire connection and query Cloud SQL database
|
|
567
|
+
async with pool.acquire() as conn:
|
|
568
|
+
res = await conn.fetch("SELECT NOW()")
|
|
569
|
+
|
|
570
|
+
# close Connector
|
|
571
|
+
await connector.close_async()
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
#### SQLAlchemy Async Engine
|
|
575
|
+
|
|
540
576
|
```python
|
|
541
577
|
import asyncpg
|
|
542
578
|
|
|
@@ -546,7 +582,7 @@ from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
|
|
|
546
582
|
from google.cloud.sql.connector import Connector, create_async_connector
|
|
547
583
|
|
|
548
584
|
async def init_connection_pool(connector: Connector) -> AsyncEngine:
|
|
549
|
-
#
|
|
585
|
+
# creation function to generate asyncpg connections as 'async_creator' arg
|
|
550
586
|
async def getconn() -> asyncpg.Connection:
|
|
551
587
|
conn: asyncpg.Connection = await connector.connect_async(
|
|
552
588
|
"project:region:instance", # Cloud SQL instance connection name
|
|
@@ -599,6 +635,40 @@ calls to `connector.close_async()` to cleanup resources.
|
|
|
599
635
|
> This alternative requires that the running event loop be
|
|
600
636
|
> passed in as the `loop` argument to `Connector()`.
|
|
601
637
|
|
|
638
|
+
#### Asyncpg Connection Pool
|
|
639
|
+
|
|
640
|
+
```python
|
|
641
|
+
import asyncpg
|
|
642
|
+
from google.cloud.sql.connector import Connector, create_async_connector
|
|
643
|
+
|
|
644
|
+
async def main():
|
|
645
|
+
# initialize Connector object for connections to Cloud SQL
|
|
646
|
+
loop = asyncio.get_running_loop()
|
|
647
|
+
async with Connector(loop=loop) as connector:
|
|
648
|
+
|
|
649
|
+
# creation function to generate asyncpg connections as the 'connect' arg
|
|
650
|
+
async def getconn(instance_connection_name, **kwargs) -> asyncpg.Connection:
|
|
651
|
+
return await connector.connect_async(
|
|
652
|
+
instance_connection_name,
|
|
653
|
+
"asyncpg",
|
|
654
|
+
user="my-user",
|
|
655
|
+
password="my-password",
|
|
656
|
+
db="my-db",
|
|
657
|
+
**kwargs, # ... additional asyncpg args
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
# create connection pool
|
|
661
|
+
pool = await asyncpg.create_pool(
|
|
662
|
+
"my-project:my-region:my-instance", connect=getconn
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
# acquire connection and query Cloud SQL database
|
|
666
|
+
async with pool.acquire() as conn:
|
|
667
|
+
res = await conn.fetch("SELECT NOW()")
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
#### SQLAlchemy Async Engine
|
|
671
|
+
|
|
602
672
|
```python
|
|
603
673
|
import asyncio
|
|
604
674
|
import asyncpg
|
|
@@ -609,17 +679,17 @@ from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
|
|
|
609
679
|
from google.cloud.sql.connector import Connector
|
|
610
680
|
|
|
611
681
|
async def init_connection_pool(connector: Connector) -> AsyncEngine:
|
|
612
|
-
#
|
|
682
|
+
# creation function to generate asyncpg connections as 'async_creator' arg
|
|
613
683
|
async def getconn() -> asyncpg.Connection:
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
684
|
+
conn: asyncpg.Connection = await connector.connect_async(
|
|
685
|
+
"project:region:instance", # Cloud SQL instance connection name
|
|
686
|
+
"asyncpg",
|
|
687
|
+
user="my-user",
|
|
688
|
+
password="my-password",
|
|
689
|
+
db="my-db-name"
|
|
690
|
+
# ... additional database driver args
|
|
691
|
+
)
|
|
692
|
+
return conn
|
|
623
693
|
|
|
624
694
|
# The Cloud SQL Python Connector can be used along with SQLAlchemy using the
|
|
625
695
|
# 'async_creator' argument to 'create_async_engine'
|
|
@@ -502,6 +502,42 @@ The `create_async_connector` allows all the same input arguments as the
|
|
|
502
502
|
Once a `Connector` object is returned by `create_async_connector` you can call
|
|
503
503
|
its `connect_async` method, just as you would the `connect` method:
|
|
504
504
|
|
|
505
|
+
#### Asyncpg Connection Pool
|
|
506
|
+
|
|
507
|
+
```python
|
|
508
|
+
import asyncpg
|
|
509
|
+
from google.cloud.sql.connector import Connector, create_async_connector
|
|
510
|
+
|
|
511
|
+
async def main():
|
|
512
|
+
# initialize Connector object for connections to Cloud SQL
|
|
513
|
+
connector = create_async_connector()
|
|
514
|
+
|
|
515
|
+
# creation function to generate asyncpg connections as the 'connect' arg
|
|
516
|
+
async def getconn(instance_connection_name, **kwargs) -> asyncpg.Connection:
|
|
517
|
+
return await connector.connect_async(
|
|
518
|
+
instance_connection_name,
|
|
519
|
+
"asyncpg",
|
|
520
|
+
user="my-user",
|
|
521
|
+
password="my-password",
|
|
522
|
+
db="my-db",
|
|
523
|
+
**kwargs, # ... additional asyncpg args
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
# initialize connection pool
|
|
527
|
+
pool = await asyncpg.create_pool(
|
|
528
|
+
"my-project:my-region:my-instance", connect=getconn
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
# acquire connection and query Cloud SQL database
|
|
532
|
+
async with pool.acquire() as conn:
|
|
533
|
+
res = await conn.fetch("SELECT NOW()")
|
|
534
|
+
|
|
535
|
+
# close Connector
|
|
536
|
+
await connector.close_async()
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
#### SQLAlchemy Async Engine
|
|
540
|
+
|
|
505
541
|
```python
|
|
506
542
|
import asyncpg
|
|
507
543
|
|
|
@@ -511,7 +547,7 @@ from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
|
|
|
511
547
|
from google.cloud.sql.connector import Connector, create_async_connector
|
|
512
548
|
|
|
513
549
|
async def init_connection_pool(connector: Connector) -> AsyncEngine:
|
|
514
|
-
#
|
|
550
|
+
# creation function to generate asyncpg connections as 'async_creator' arg
|
|
515
551
|
async def getconn() -> asyncpg.Connection:
|
|
516
552
|
conn: asyncpg.Connection = await connector.connect_async(
|
|
517
553
|
"project:region:instance", # Cloud SQL instance connection name
|
|
@@ -564,6 +600,40 @@ calls to `connector.close_async()` to cleanup resources.
|
|
|
564
600
|
> This alternative requires that the running event loop be
|
|
565
601
|
> passed in as the `loop` argument to `Connector()`.
|
|
566
602
|
|
|
603
|
+
#### Asyncpg Connection Pool
|
|
604
|
+
|
|
605
|
+
```python
|
|
606
|
+
import asyncpg
|
|
607
|
+
from google.cloud.sql.connector import Connector, create_async_connector
|
|
608
|
+
|
|
609
|
+
async def main():
|
|
610
|
+
# initialize Connector object for connections to Cloud SQL
|
|
611
|
+
loop = asyncio.get_running_loop()
|
|
612
|
+
async with Connector(loop=loop) as connector:
|
|
613
|
+
|
|
614
|
+
# creation function to generate asyncpg connections as the 'connect' arg
|
|
615
|
+
async def getconn(instance_connection_name, **kwargs) -> asyncpg.Connection:
|
|
616
|
+
return await connector.connect_async(
|
|
617
|
+
instance_connection_name,
|
|
618
|
+
"asyncpg",
|
|
619
|
+
user="my-user",
|
|
620
|
+
password="my-password",
|
|
621
|
+
db="my-db",
|
|
622
|
+
**kwargs, # ... additional asyncpg args
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
# create connection pool
|
|
626
|
+
pool = await asyncpg.create_pool(
|
|
627
|
+
"my-project:my-region:my-instance", connect=getconn
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
# acquire connection and query Cloud SQL database
|
|
631
|
+
async with pool.acquire() as conn:
|
|
632
|
+
res = await conn.fetch("SELECT NOW()")
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
#### SQLAlchemy Async Engine
|
|
636
|
+
|
|
567
637
|
```python
|
|
568
638
|
import asyncio
|
|
569
639
|
import asyncpg
|
|
@@ -574,17 +644,17 @@ from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
|
|
|
574
644
|
from google.cloud.sql.connector import Connector
|
|
575
645
|
|
|
576
646
|
async def init_connection_pool(connector: Connector) -> AsyncEngine:
|
|
577
|
-
#
|
|
647
|
+
# creation function to generate asyncpg connections as 'async_creator' arg
|
|
578
648
|
async def getconn() -> asyncpg.Connection:
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
649
|
+
conn: asyncpg.Connection = await connector.connect_async(
|
|
650
|
+
"project:region:instance", # Cloud SQL instance connection name
|
|
651
|
+
"asyncpg",
|
|
652
|
+
user="my-user",
|
|
653
|
+
password="my-password",
|
|
654
|
+
db="my-db-name"
|
|
655
|
+
# ... additional database driver args
|
|
656
|
+
)
|
|
657
|
+
return conn
|
|
588
658
|
|
|
589
659
|
# The Cloud SQL Python Connector can be used along with SQLAlchemy using the
|
|
590
660
|
# 'async_creator' argument to 'create_async_engine'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cloud-sql-python-connector
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.14.0
|
|
4
4
|
Summary: The Cloud SQL Python Connector is a library that can be used alongside a database driver to allow users with sufficient permissions to connect to a Cloud SQL database without having to manually allowlist IPs or manage SSL certificates.
|
|
5
5
|
Home-page: https://github.com/GoogleCloudPlatform/cloud-sql-python-connector
|
|
6
6
|
Author: Google LLC
|
|
@@ -31,7 +31,7 @@ Requires-Dist: pg8000>=1.31.1; extra == "pg8000"
|
|
|
31
31
|
Provides-Extra: pytds
|
|
32
32
|
Requires-Dist: python-tds>=1.15.0; extra == "pytds"
|
|
33
33
|
Provides-Extra: asyncpg
|
|
34
|
-
Requires-Dist: asyncpg>=0.
|
|
34
|
+
Requires-Dist: asyncpg>=0.30.0; extra == "asyncpg"
|
|
35
35
|
|
|
36
36
|
<p align="center">
|
|
37
37
|
<a href="https://cloud.google.com/blog/topics/developers-practitioners/how-connect-cloud-sql-using-python-easy-way">
|
|
@@ -537,6 +537,42 @@ The `create_async_connector` allows all the same input arguments as the
|
|
|
537
537
|
Once a `Connector` object is returned by `create_async_connector` you can call
|
|
538
538
|
its `connect_async` method, just as you would the `connect` method:
|
|
539
539
|
|
|
540
|
+
#### Asyncpg Connection Pool
|
|
541
|
+
|
|
542
|
+
```python
|
|
543
|
+
import asyncpg
|
|
544
|
+
from google.cloud.sql.connector import Connector, create_async_connector
|
|
545
|
+
|
|
546
|
+
async def main():
|
|
547
|
+
# initialize Connector object for connections to Cloud SQL
|
|
548
|
+
connector = create_async_connector()
|
|
549
|
+
|
|
550
|
+
# creation function to generate asyncpg connections as the 'connect' arg
|
|
551
|
+
async def getconn(instance_connection_name, **kwargs) -> asyncpg.Connection:
|
|
552
|
+
return await connector.connect_async(
|
|
553
|
+
instance_connection_name,
|
|
554
|
+
"asyncpg",
|
|
555
|
+
user="my-user",
|
|
556
|
+
password="my-password",
|
|
557
|
+
db="my-db",
|
|
558
|
+
**kwargs, # ... additional asyncpg args
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
# initialize connection pool
|
|
562
|
+
pool = await asyncpg.create_pool(
|
|
563
|
+
"my-project:my-region:my-instance", connect=getconn
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
# acquire connection and query Cloud SQL database
|
|
567
|
+
async with pool.acquire() as conn:
|
|
568
|
+
res = await conn.fetch("SELECT NOW()")
|
|
569
|
+
|
|
570
|
+
# close Connector
|
|
571
|
+
await connector.close_async()
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
#### SQLAlchemy Async Engine
|
|
575
|
+
|
|
540
576
|
```python
|
|
541
577
|
import asyncpg
|
|
542
578
|
|
|
@@ -546,7 +582,7 @@ from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
|
|
|
546
582
|
from google.cloud.sql.connector import Connector, create_async_connector
|
|
547
583
|
|
|
548
584
|
async def init_connection_pool(connector: Connector) -> AsyncEngine:
|
|
549
|
-
#
|
|
585
|
+
# creation function to generate asyncpg connections as 'async_creator' arg
|
|
550
586
|
async def getconn() -> asyncpg.Connection:
|
|
551
587
|
conn: asyncpg.Connection = await connector.connect_async(
|
|
552
588
|
"project:region:instance", # Cloud SQL instance connection name
|
|
@@ -599,6 +635,40 @@ calls to `connector.close_async()` to cleanup resources.
|
|
|
599
635
|
> This alternative requires that the running event loop be
|
|
600
636
|
> passed in as the `loop` argument to `Connector()`.
|
|
601
637
|
|
|
638
|
+
#### Asyncpg Connection Pool
|
|
639
|
+
|
|
640
|
+
```python
|
|
641
|
+
import asyncpg
|
|
642
|
+
from google.cloud.sql.connector import Connector, create_async_connector
|
|
643
|
+
|
|
644
|
+
async def main():
|
|
645
|
+
# initialize Connector object for connections to Cloud SQL
|
|
646
|
+
loop = asyncio.get_running_loop()
|
|
647
|
+
async with Connector(loop=loop) as connector:
|
|
648
|
+
|
|
649
|
+
# creation function to generate asyncpg connections as the 'connect' arg
|
|
650
|
+
async def getconn(instance_connection_name, **kwargs) -> asyncpg.Connection:
|
|
651
|
+
return await connector.connect_async(
|
|
652
|
+
instance_connection_name,
|
|
653
|
+
"asyncpg",
|
|
654
|
+
user="my-user",
|
|
655
|
+
password="my-password",
|
|
656
|
+
db="my-db",
|
|
657
|
+
**kwargs, # ... additional asyncpg args
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
# create connection pool
|
|
661
|
+
pool = await asyncpg.create_pool(
|
|
662
|
+
"my-project:my-region:my-instance", connect=getconn
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
# acquire connection and query Cloud SQL database
|
|
666
|
+
async with pool.acquire() as conn:
|
|
667
|
+
res = await conn.fetch("SELECT NOW()")
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
#### SQLAlchemy Async Engine
|
|
671
|
+
|
|
602
672
|
```python
|
|
603
673
|
import asyncio
|
|
604
674
|
import asyncpg
|
|
@@ -609,17 +679,17 @@ from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
|
|
|
609
679
|
from google.cloud.sql.connector import Connector
|
|
610
680
|
|
|
611
681
|
async def init_connection_pool(connector: Connector) -> AsyncEngine:
|
|
612
|
-
#
|
|
682
|
+
# creation function to generate asyncpg connections as 'async_creator' arg
|
|
613
683
|
async def getconn() -> asyncpg.Connection:
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
684
|
+
conn: asyncpg.Connection = await connector.connect_async(
|
|
685
|
+
"project:region:instance", # Cloud SQL instance connection name
|
|
686
|
+
"asyncpg",
|
|
687
|
+
user="my-user",
|
|
688
|
+
password="my-password",
|
|
689
|
+
db="my-db-name"
|
|
690
|
+
# ... additional database driver args
|
|
691
|
+
)
|
|
692
|
+
return conn
|
|
623
693
|
|
|
624
694
|
# The Cloud SQL Python Connector can be used along with SQLAlchemy using the
|
|
625
695
|
# 'async_creator' argument to 'create_async_engine'
|
|
@@ -12,6 +12,7 @@ google/cloud/sql/connector/__init__.py
|
|
|
12
12
|
google/cloud/sql/connector/asyncpg.py
|
|
13
13
|
google/cloud/sql/connector/client.py
|
|
14
14
|
google/cloud/sql/connector/connection_info.py
|
|
15
|
+
google/cloud/sql/connector/connection_name.py
|
|
15
16
|
google/cloud/sql/connector/connector.py
|
|
16
17
|
google/cloud/sql/connector/enums.py
|
|
17
18
|
google/cloud/sql/connector/exceptions.py
|
|
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
import asyncio
|
|
18
18
|
import datetime
|
|
19
19
|
import logging
|
|
20
|
-
from typing import Any,
|
|
20
|
+
from typing import Any, Optional, TYPE_CHECKING
|
|
21
21
|
|
|
22
22
|
import aiohttp
|
|
23
23
|
from cryptography.hazmat.backends import default_backend
|
|
@@ -98,7 +98,7 @@ class CloudSQLClient:
|
|
|
98
98
|
project: str,
|
|
99
99
|
region: str,
|
|
100
100
|
instance: str,
|
|
101
|
-
) ->
|
|
101
|
+
) -> dict[str, Any]:
|
|
102
102
|
"""Requests metadata from the Cloud SQL Instance
|
|
103
103
|
and returns a dictionary containing the IP addresses and certificate
|
|
104
104
|
authority of the Cloud SQL Instance.
|
|
@@ -113,7 +113,7 @@ class CloudSQLClient:
|
|
|
113
113
|
:type instance: str
|
|
114
114
|
:param instance: A string representing the name of the instance.
|
|
115
115
|
|
|
116
|
-
:rtype:
|
|
116
|
+
:rtype: dict[str: Union[dict, str]]
|
|
117
117
|
:returns: Returns a dictionary containing a dictionary of all IP
|
|
118
118
|
addresses and their type and a string representing the
|
|
119
119
|
certificate authority.
|
|
@@ -161,7 +161,7 @@ class CloudSQLClient:
|
|
|
161
161
|
instance: str,
|
|
162
162
|
pub_key: str,
|
|
163
163
|
enable_iam_auth: bool = False,
|
|
164
|
-
) ->
|
|
164
|
+
) -> tuple[str, datetime.datetime]:
|
|
165
165
|
"""Asynchronously requests an ephemeral certificate from the Cloud SQL Instance.
|
|
166
166
|
|
|
167
167
|
:type project: str
|
|
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
from dataclasses import dataclass
|
|
18
18
|
import logging
|
|
19
19
|
import ssl
|
|
20
|
-
from typing import Any,
|
|
20
|
+
from typing import Any, Optional, TYPE_CHECKING
|
|
21
21
|
|
|
22
22
|
from aiofiles.tempfile import TemporaryDirectory
|
|
23
23
|
|
|
@@ -41,7 +41,7 @@ class ConnectionInfo:
|
|
|
41
41
|
client_cert: str
|
|
42
42
|
server_ca_cert: str
|
|
43
43
|
private_key: bytes
|
|
44
|
-
ip_addrs:
|
|
44
|
+
ip_addrs: dict[str, Any]
|
|
45
45
|
database_version: str
|
|
46
46
|
expiration: datetime.datetime
|
|
47
47
|
context: Optional[ssl.SSLContext] = None
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Copyright 2024 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
import re
|
|
17
|
+
|
|
18
|
+
# Instance connection name is the format <PROJECT>:<REGION>:<INSTANCE_NAME>
|
|
19
|
+
# Additionally, we have to support legacy "domain-scoped" projects
|
|
20
|
+
# (e.g. "google.com:PROJECT")
|
|
21
|
+
CONN_NAME_REGEX = re.compile(("([^:]+(:[^:]+)?):([^:]+):([^:]+)"))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ConnectionName:
|
|
26
|
+
"""ConnectionName represents a Cloud SQL instance's "instance connection name".
|
|
27
|
+
|
|
28
|
+
Takes the format "<PROJECT>:<REGION>:<INSTANCE_NAME>".
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
project: str
|
|
32
|
+
region: str
|
|
33
|
+
instance_name: str
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
return f"{self.project}:{self.region}:{self.instance_name}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _parse_instance_connection_name(connection_name: str) -> ConnectionName:
|
|
40
|
+
if CONN_NAME_REGEX.fullmatch(connection_name) is None:
|
|
41
|
+
raise ValueError(
|
|
42
|
+
"Arg `instance_connection_string` must have "
|
|
43
|
+
"format: PROJECT:REGION:INSTANCE, "
|
|
44
|
+
f"got {connection_name}."
|
|
45
|
+
)
|
|
46
|
+
connection_name_split = CONN_NAME_REGEX.split(connection_name)
|
|
47
|
+
return ConnectionName(
|
|
48
|
+
connection_name_split[1],
|
|
49
|
+
connection_name_split[3],
|
|
50
|
+
connection_name_split[4],
|
|
51
|
+
)
|
|
@@ -21,7 +21,7 @@ from functools import partial
|
|
|
21
21
|
import logging
|
|
22
22
|
from threading import Thread
|
|
23
23
|
from types import TracebackType
|
|
24
|
-
from typing import Any,
|
|
24
|
+
from typing import Any, Optional, Type, Union
|
|
25
25
|
|
|
26
26
|
import google.auth
|
|
27
27
|
from google.auth.credentials import Credentials
|
|
@@ -133,8 +133,8 @@ class Connector:
|
|
|
133
133
|
)
|
|
134
134
|
# initialize dict to store caches, key is a tuple consisting of instance
|
|
135
135
|
# connection name string and enable_iam_auth boolean flag
|
|
136
|
-
self._cache:
|
|
137
|
-
|
|
136
|
+
self._cache: dict[
|
|
137
|
+
tuple[str, bool], Union[RefreshAheadCache, LazyRefreshCache]
|
|
138
138
|
] = {}
|
|
139
139
|
self._client: Optional[CloudSQLClient] = None
|
|
140
140
|
|
|
@@ -21,13 +21,12 @@ from datetime import datetime
|
|
|
21
21
|
from datetime import timedelta
|
|
22
22
|
from datetime import timezone
|
|
23
23
|
import logging
|
|
24
|
-
import re
|
|
25
|
-
from typing import Tuple
|
|
26
24
|
|
|
27
25
|
import aiohttp
|
|
28
26
|
|
|
29
27
|
from google.cloud.sql.connector.client import CloudSQLClient
|
|
30
28
|
from google.cloud.sql.connector.connection_info import ConnectionInfo
|
|
29
|
+
from google.cloud.sql.connector.connection_name import _parse_instance_connection_name
|
|
31
30
|
from google.cloud.sql.connector.exceptions import RefreshNotValidError
|
|
32
31
|
from google.cloud.sql.connector.rate_limiter import AsyncRateLimiter
|
|
33
32
|
from google.cloud.sql.connector.refresh_utils import _is_valid
|
|
@@ -37,22 +36,6 @@ logger = logging.getLogger(name=__name__)
|
|
|
37
36
|
|
|
38
37
|
APPLICATION_NAME = "cloud-sql-python-connector"
|
|
39
38
|
|
|
40
|
-
# Instance connection name is the format <PROJECT>:<REGION>:<INSTANCE>
|
|
41
|
-
# Additionally, we have to support legacy "domain-scoped" projects
|
|
42
|
-
# (e.g. "google.com:PROJECT")
|
|
43
|
-
CONN_NAME_REGEX = re.compile(("([^:]+(:[^:]+)?):([^:]+):([^:]+)"))
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def _parse_instance_connection_name(connection_name: str) -> Tuple[str, str, str]:
|
|
47
|
-
if CONN_NAME_REGEX.fullmatch(connection_name) is None:
|
|
48
|
-
raise ValueError(
|
|
49
|
-
"Arg `instance_connection_string` must have "
|
|
50
|
-
"format: PROJECT:REGION:INSTANCE, "
|
|
51
|
-
f"got {connection_name}."
|
|
52
|
-
)
|
|
53
|
-
connection_name_split = CONN_NAME_REGEX.split(connection_name)
|
|
54
|
-
return connection_name_split[1], connection_name_split[3], connection_name_split[4]
|
|
55
|
-
|
|
56
39
|
|
|
57
40
|
class RefreshAheadCache:
|
|
58
41
|
"""Cache that refreshes connection info in the background prior to expiration.
|
|
@@ -82,10 +65,13 @@ class RefreshAheadCache:
|
|
|
82
65
|
connections.
|
|
83
66
|
"""
|
|
84
67
|
# validate and parse instance connection name
|
|
85
|
-
|
|
86
|
-
|
|
68
|
+
conn_name = _parse_instance_connection_name(instance_connection_string)
|
|
69
|
+
self._project, self._region, self._instance = (
|
|
70
|
+
conn_name.project,
|
|
71
|
+
conn_name.region,
|
|
72
|
+
conn_name.instance_name,
|
|
87
73
|
)
|
|
88
|
-
self.
|
|
74
|
+
self._conn_name = conn_name
|
|
89
75
|
|
|
90
76
|
self._enable_iam_auth = enable_iam_auth
|
|
91
77
|
self._keys = keys
|
|
@@ -122,8 +108,7 @@ class RefreshAheadCache:
|
|
|
122
108
|
"""
|
|
123
109
|
self._refresh_in_progress.set()
|
|
124
110
|
logger.debug(
|
|
125
|
-
f"['{self.
|
|
126
|
-
"operation started"
|
|
111
|
+
f"['{self._conn_name}']: Connection info refresh " "operation started"
|
|
127
112
|
)
|
|
128
113
|
|
|
129
114
|
try:
|
|
@@ -136,17 +121,16 @@ class RefreshAheadCache:
|
|
|
136
121
|
self._enable_iam_auth,
|
|
137
122
|
)
|
|
138
123
|
logger.debug(
|
|
139
|
-
f"['{self.
|
|
140
|
-
"refresh operation complete"
|
|
124
|
+
f"['{self._conn_name}']: Connection info " "refresh operation complete"
|
|
141
125
|
)
|
|
142
126
|
logger.debug(
|
|
143
|
-
f"['{self.
|
|
127
|
+
f"['{self._conn_name}']: Current certificate "
|
|
144
128
|
f"expiration = {connection_info.expiration.isoformat()}"
|
|
145
129
|
)
|
|
146
130
|
|
|
147
131
|
except aiohttp.ClientResponseError as e:
|
|
148
132
|
logger.debug(
|
|
149
|
-
f"['{self.
|
|
133
|
+
f"['{self._conn_name}']: Connection info "
|
|
150
134
|
f"refresh operation failed: {str(e)}"
|
|
151
135
|
)
|
|
152
136
|
if e.status == 403:
|
|
@@ -155,7 +139,7 @@ class RefreshAheadCache:
|
|
|
155
139
|
|
|
156
140
|
except Exception as e:
|
|
157
141
|
logger.debug(
|
|
158
|
-
f"['{self.
|
|
142
|
+
f"['{self._conn_name}']: Connection info "
|
|
159
143
|
f"refresh operation failed: {str(e)}"
|
|
160
144
|
)
|
|
161
145
|
raise
|
|
@@ -189,18 +173,17 @@ class RefreshAheadCache:
|
|
|
189
173
|
# check that refresh is valid
|
|
190
174
|
if not await _is_valid(refresh_task):
|
|
191
175
|
raise RefreshNotValidError(
|
|
192
|
-
f"['{self.
|
|
176
|
+
f"['{self._conn_name}']: Invalid refresh operation. Certficate appears to be expired."
|
|
193
177
|
)
|
|
194
178
|
except asyncio.CancelledError:
|
|
195
179
|
logger.debug(
|
|
196
|
-
f"['{self.
|
|
197
|
-
" operation cancelled"
|
|
180
|
+
f"['{self._conn_name}']: Scheduled refresh" " operation cancelled"
|
|
198
181
|
)
|
|
199
182
|
raise
|
|
200
183
|
# bad refresh attempt
|
|
201
184
|
except Exception as e:
|
|
202
185
|
logger.exception(
|
|
203
|
-
f"['{self.
|
|
186
|
+
f"['{self._conn_name}']: "
|
|
204
187
|
"An error occurred while performing refresh. "
|
|
205
188
|
"Scheduling another refresh attempt immediately",
|
|
206
189
|
exc_info=e,
|
|
@@ -217,7 +200,7 @@ class RefreshAheadCache:
|
|
|
217
200
|
# calculate refresh delay based on certificate expiration
|
|
218
201
|
delay = _seconds_until_refresh(refresh_data.expiration)
|
|
219
202
|
logger.debug(
|
|
220
|
-
f"['{self.
|
|
203
|
+
f"['{self._conn_name}']: Connection info refresh"
|
|
221
204
|
" operation scheduled for "
|
|
222
205
|
f"{(datetime.now(timezone.utc) + timedelta(seconds=delay)).isoformat(timespec='seconds')} "
|
|
223
206
|
f"(now + {timedelta(seconds=delay)})"
|
|
@@ -241,7 +224,7 @@ class RefreshAheadCache:
|
|
|
241
224
|
graceful exit.
|
|
242
225
|
"""
|
|
243
226
|
logger.debug(
|
|
244
|
-
f"['{self.
|
|
227
|
+
f"['{self._conn_name}']: Canceling connection info "
|
|
245
228
|
"refresh operation tasks"
|
|
246
229
|
)
|
|
247
230
|
self._current.cancel()
|
|
@@ -21,7 +21,7 @@ from typing import Optional
|
|
|
21
21
|
|
|
22
22
|
from google.cloud.sql.connector.client import CloudSQLClient
|
|
23
23
|
from google.cloud.sql.connector.connection_info import ConnectionInfo
|
|
24
|
-
from google.cloud.sql.connector.
|
|
24
|
+
from google.cloud.sql.connector.connection_name import _parse_instance_connection_name
|
|
25
25
|
from google.cloud.sql.connector.refresh_utils import _refresh_buffer
|
|
26
26
|
|
|
27
27
|
logger = logging.getLogger(name=__name__)
|
|
@@ -56,10 +56,13 @@ class LazyRefreshCache:
|
|
|
56
56
|
connections.
|
|
57
57
|
"""
|
|
58
58
|
# validate and parse instance connection name
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
conn_name = _parse_instance_connection_name(instance_connection_string)
|
|
60
|
+
self._project, self._region, self._instance = (
|
|
61
|
+
conn_name.project,
|
|
62
|
+
conn_name.region,
|
|
63
|
+
conn_name.instance_name,
|
|
61
64
|
)
|
|
62
|
-
self.
|
|
65
|
+
self._conn_name = conn_name
|
|
63
66
|
|
|
64
67
|
self._enable_iam_auth = enable_iam_auth
|
|
65
68
|
self._keys = keys
|
|
@@ -91,13 +94,12 @@ class LazyRefreshCache:
|
|
|
91
94
|
< (self._cached.expiration - timedelta(seconds=_refresh_buffer))
|
|
92
95
|
):
|
|
93
96
|
logger.debug(
|
|
94
|
-
f"['{self.
|
|
97
|
+
f"['{self._conn_name}']: Connection info "
|
|
95
98
|
"is still valid, using cached info"
|
|
96
99
|
)
|
|
97
100
|
return self._cached
|
|
98
101
|
logger.debug(
|
|
99
|
-
f"['{self.
|
|
100
|
-
"refresh operation started"
|
|
102
|
+
f"['{self._conn_name}']: Connection info " "refresh operation started"
|
|
101
103
|
)
|
|
102
104
|
try:
|
|
103
105
|
conn_info = await self._client.get_connection_info(
|
|
@@ -109,16 +111,16 @@ class LazyRefreshCache:
|
|
|
109
111
|
)
|
|
110
112
|
except Exception as e:
|
|
111
113
|
logger.debug(
|
|
112
|
-
f"['{self.
|
|
114
|
+
f"['{self._conn_name}']: Connection info "
|
|
113
115
|
f"refresh operation failed: {str(e)}"
|
|
114
116
|
)
|
|
115
117
|
raise
|
|
116
118
|
logger.debug(
|
|
117
|
-
f"['{self.
|
|
119
|
+
f"['{self._conn_name}']: Connection info "
|
|
118
120
|
"refresh operation completed successfully"
|
|
119
121
|
)
|
|
120
122
|
logger.debug(
|
|
121
|
-
f"['{self.
|
|
123
|
+
f"['{self._conn_name}']: Current certificate "
|
|
122
124
|
f"expiration = {str(conn_info.expiration)}"
|
|
123
125
|
)
|
|
124
126
|
self._cached = conn_info
|
|
@@ -21,7 +21,7 @@ import copy
|
|
|
21
21
|
import datetime
|
|
22
22
|
import logging
|
|
23
23
|
import random
|
|
24
|
-
from typing import Any, Callable
|
|
24
|
+
from typing import Any, Callable
|
|
25
25
|
|
|
26
26
|
import aiohttp
|
|
27
27
|
from google.auth.credentials import Credentials
|
|
@@ -77,7 +77,7 @@ async def _is_valid(task: asyncio.Task) -> bool:
|
|
|
77
77
|
|
|
78
78
|
def _downscope_credentials(
|
|
79
79
|
credentials: Credentials,
|
|
80
|
-
scopes:
|
|
80
|
+
scopes: list[str] = ["https://www.googleapis.com/auth/sqlservice.login"],
|
|
81
81
|
) -> Credentials:
|
|
82
82
|
"""Generate a down-scoped credential.
|
|
83
83
|
|
|
@@ -85,7 +85,7 @@ def _downscope_credentials(
|
|
|
85
85
|
:param credentials
|
|
86
86
|
Credentials object used to generate down-scoped credentials.
|
|
87
87
|
|
|
88
|
-
:type scopes:
|
|
88
|
+
:type scopes: list[str]
|
|
89
89
|
:param scopes
|
|
90
90
|
List of Google scopes to include in down-scoped credentials object.
|
|
91
91
|
|
|
@@ -14,15 +14,13 @@ See the License for the specific language governing permissions and
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
from typing import Tuple
|
|
18
|
-
|
|
19
17
|
import aiofiles
|
|
20
18
|
from cryptography.hazmat.backends import default_backend
|
|
21
19
|
from cryptography.hazmat.primitives import serialization
|
|
22
20
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
23
21
|
|
|
24
22
|
|
|
25
|
-
async def generate_keys() ->
|
|
23
|
+
async def generate_keys() -> tuple[bytes, str]:
|
|
26
24
|
"""A helper function to generate the private and public keys.
|
|
27
25
|
|
|
28
26
|
backend - The value specified is default_backend(). This is because the
|
|
@@ -61,7 +59,7 @@ async def generate_keys() -> Tuple[bytes, str]:
|
|
|
61
59
|
|
|
62
60
|
async def write_to_file(
|
|
63
61
|
dir_path: str, serverCaCert: str, ephemeralCert: str, priv_key: bytes
|
|
64
|
-
) ->
|
|
62
|
+
) -> tuple[str, str, str]:
|
|
65
63
|
"""
|
|
66
64
|
Helper function to write the serverCaCert, ephemeral certificate and
|
|
67
65
|
private key to .pem files in a given directory
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|