postgresql-access 2.0__tar.gz → 2.0.1__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.
- {postgresql_access-2.0/src/postgresql_access.egg-info → postgresql_access-2.0.1}/PKG-INFO +1 -1
- {postgresql_access-2.0 → postgresql_access-2.0.1}/pyproject.toml +1 -1
- postgresql_access-2.0.1/src/postgresql_access/certificatedatabase.py +89 -0
- {postgresql_access-2.0 → postgresql_access-2.0.1}/src/postgresql_access/database.py +5 -3
- {postgresql_access-2.0 → postgresql_access-2.0.1/src/postgresql_access.egg-info}/PKG-INFO +1 -1
- {postgresql_access-2.0 → postgresql_access-2.0.1}/src/tests/atest.py +5 -1
- postgresql_access-2.0/src/postgresql_access/certificatedatabase.py +0 -69
- {postgresql_access-2.0 → postgresql_access-2.0.1}/LICENSE +0 -0
- {postgresql_access-2.0 → postgresql_access-2.0.1}/README.md +0 -0
- {postgresql_access-2.0 → postgresql_access-2.0.1}/setup.cfg +0 -0
- {postgresql_access-2.0 → postgresql_access-2.0.1}/src/alchemy/satest.py +0 -0
- {postgresql_access-2.0 → postgresql_access-2.0.1}/src/postgresql_access/__init__.py +0 -0
- {postgresql_access-2.0 → postgresql_access-2.0.1}/src/postgresql_access/main.py +0 -0
- {postgresql_access-2.0 → postgresql_access-2.0.1}/src/postgresql_access.egg-info/SOURCES.txt +0 -0
- {postgresql_access-2.0 → postgresql_access-2.0.1}/src/postgresql_access.egg-info/dependency_links.txt +0 -0
- {postgresql_access-2.0 → postgresql_access-2.0.1}/src/postgresql_access.egg-info/entry_points.txt +0 -0
- {postgresql_access-2.0 → postgresql_access-2.0.1}/src/postgresql_access.egg-info/requires.txt +0 -0
- {postgresql_access-2.0 → postgresql_access-2.0.1}/src/postgresql_access.egg-info/top_level.txt +0 -0
- {postgresql_access-2.0 → postgresql_access-2.0.1}/src/tests/grouptest.py +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import pwd
|
|
5
|
+
from typing import Mapping
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import psycopg # psycopg3
|
|
9
|
+
PSYCOPG_VERSION = 3
|
|
10
|
+
except ImportError:
|
|
11
|
+
import psycopg2 as psycopg # fallback to psycopg2
|
|
12
|
+
PSYCOPG_VERSION = 2
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
object wrapper for psycopg2 or psycopg3
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def _current_user():
|
|
19
|
+
"""get current linux user"""
|
|
20
|
+
return pwd.getpwuid(os.geteuid()).pw_name
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CertificateDatabase:
|
|
24
|
+
|
|
25
|
+
def __init__(self, *, host: str, database: str, user: str, application_name: str,
|
|
26
|
+
client_cert: str, client_key: str, root_cert: str):
|
|
27
|
+
self.app_name = 'Python app'
|
|
28
|
+
self.schema = None
|
|
29
|
+
self.port = 5432
|
|
30
|
+
self.host = host
|
|
31
|
+
self.database = database
|
|
32
|
+
self.user = user
|
|
33
|
+
self.application_name = application_name
|
|
34
|
+
self.client_cert = client_cert
|
|
35
|
+
self.client_key = client_key
|
|
36
|
+
self.root_cert = root_cert
|
|
37
|
+
|
|
38
|
+
files = (self.client_cert, self.client_key, self.root_cert)
|
|
39
|
+
missing = [f for f in files if not os.path.exists(f)]
|
|
40
|
+
if missing:
|
|
41
|
+
raise ValueError(f"Missing configuration file(s): {','.join(missing)}")
|
|
42
|
+
permissions = [f for f in files if not os.access(f, os.R_OK)]
|
|
43
|
+
if permissions:
|
|
44
|
+
raise ValueError(f"No read access file(s): {','.join(permissions)}")
|
|
45
|
+
|
|
46
|
+
def connect(self):
|
|
47
|
+
"""Connect to database, set schema if present, return connection"""
|
|
48
|
+
conninfo = (
|
|
49
|
+
f"host={self.host} "
|
|
50
|
+
f"dbname={self.database} "
|
|
51
|
+
f"user={self.user} "
|
|
52
|
+
f"port={self.port} "
|
|
53
|
+
f"application_name={self.application_name} "
|
|
54
|
+
f"sslmode=verify-full "
|
|
55
|
+
f"sslcert={self.client_cert} "
|
|
56
|
+
f"sslkey={self.client_key} "
|
|
57
|
+
f"sslrootcert={self.root_cert}"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
if PSYCOPG_VERSION == 3:
|
|
62
|
+
conn = psycopg.connect(conninfo)
|
|
63
|
+
else:
|
|
64
|
+
conn = psycopg.connect(conninfo)
|
|
65
|
+
except psycopg.OperationalError as oe:
|
|
66
|
+
if 'no password' in str(oe):
|
|
67
|
+
raise ValueError(f"Invalid certificates or key for user {self.user}, or not authorized by server")
|
|
68
|
+
raise
|
|
69
|
+
|
|
70
|
+
if self.schema is not None:
|
|
71
|
+
with conn.cursor() as cursor:
|
|
72
|
+
cursor.execute(f"SET search_path TO {self.schema}")
|
|
73
|
+
conn.commit()
|
|
74
|
+
|
|
75
|
+
return conn
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def create_from_dict(data: Mapping, application_name: str):
|
|
79
|
+
h = data['host']
|
|
80
|
+
d = data['database']
|
|
81
|
+
u = data.get('user', _current_user())
|
|
82
|
+
cc = data['client certificate']
|
|
83
|
+
rc = data['root certificate']
|
|
84
|
+
ck = data['client key']
|
|
85
|
+
return CertificateDatabase(
|
|
86
|
+
host=h, database=d, user=u, application_name=application_name,
|
|
87
|
+
client_cert=cc, client_key=ck, root_cert=rc
|
|
88
|
+
)
|
|
89
|
+
|
|
@@ -15,11 +15,13 @@ try:
|
|
|
15
15
|
psycopg_module = 'psycopg3'
|
|
16
16
|
connect = psycopg.connect
|
|
17
17
|
OperationalError = psycopg.OperationalError
|
|
18
|
+
readonly = 'read_only'
|
|
18
19
|
except ImportError:
|
|
19
20
|
import psycopg2 as psycopg
|
|
20
21
|
psycopg_module = 'psycopg2'
|
|
21
22
|
connect = psycopg.connect
|
|
22
23
|
OperationalError = psycopg.OperationalError
|
|
24
|
+
readonly = 'readonly'
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
class AbstractDatabase(ABC):
|
|
@@ -161,15 +163,15 @@ class ReadOnlyCursor:
|
|
|
161
163
|
self._factory = cursor_factory
|
|
162
164
|
|
|
163
165
|
def __enter__(self):
|
|
164
|
-
self.existing_readonly = self._conn
|
|
165
|
-
self._conn
|
|
166
|
+
self.existing_readonly = getattr(self._conn,readonly)
|
|
167
|
+
setattr(self._conn,readonly, True)
|
|
166
168
|
self._curs = self._conn.cursor(cursor_factory=self._factory)
|
|
167
169
|
return self._curs
|
|
168
170
|
|
|
169
171
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
170
172
|
self._curs.close()
|
|
171
173
|
self._conn.rollback()
|
|
172
|
-
self._conn
|
|
174
|
+
setattr(self._conn,readonly, self.existing_readonly)
|
|
173
175
|
|
|
174
176
|
@property
|
|
175
177
|
def connection(self): return self._conn
|
|
@@ -7,7 +7,7 @@ import keyring
|
|
|
7
7
|
from keyring import backend
|
|
8
8
|
|
|
9
9
|
from postgresql_access import postgresql_access_logger
|
|
10
|
-
from postgresql_access.database import DatabaseConfig
|
|
10
|
+
from postgresql_access.database import DatabaseConfig, ReadOnlyCursor
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def main():
|
|
@@ -31,6 +31,10 @@ def main():
|
|
|
31
31
|
with c.cursor() as cursor:
|
|
32
32
|
cursor.execute('select now()')
|
|
33
33
|
print(cursor.fetchone()[0])
|
|
34
|
+
c.rollback()
|
|
35
|
+
with ReadOnlyCursor(c) as cursor:
|
|
36
|
+
cursor.execute('select now()')
|
|
37
|
+
print(cursor.fetchone()[0])
|
|
34
38
|
|
|
35
39
|
|
|
36
40
|
if __name__ == "__main__":
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
import os
|
|
3
|
-
import pwd
|
|
4
|
-
from typing import Mapping
|
|
5
|
-
|
|
6
|
-
import psycopg2
|
|
7
|
-
|
|
8
|
-
"""
|
|
9
|
-
object wrapper for psycopg2
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
def _current_user():
|
|
13
|
-
"""get current linux user"""
|
|
14
|
-
return pwd.getpwuid(os.geteuid()).pw_name
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class CertificateDatabase:
|
|
18
|
-
|
|
19
|
-
def __init__(self,*,host:str,database:str,user:str,application_name:str,client_cert:str,client_key:str,
|
|
20
|
-
root_cert:str):
|
|
21
|
-
self.app_name = 'Python app'
|
|
22
|
-
self.schema = None
|
|
23
|
-
self.port = 5432
|
|
24
|
-
self.host = host
|
|
25
|
-
self.database = database
|
|
26
|
-
self.user = user
|
|
27
|
-
self.application_name = application_name
|
|
28
|
-
self.client_cert = client_cert
|
|
29
|
-
self.client_key = client_key
|
|
30
|
-
self.root_cert = root_cert
|
|
31
|
-
files = (self.client_cert,self.client_key,self.root_cert)
|
|
32
|
-
missing = [f for f in files if not os.path.exists(f)]
|
|
33
|
-
if missing:
|
|
34
|
-
raise ValueError(f"Missing configuration file(s): {','.join(missing)}")
|
|
35
|
-
permissions = [f for f in files if not os.access(f,os.R_OK)]
|
|
36
|
-
if permissions:
|
|
37
|
-
raise ValueError(f"No read access file(s): {','.join(permissions)}")
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def connect(self):
|
|
41
|
-
"""Connect to database, set schema if present, return connection"""
|
|
42
|
-
connect_string = f"""host='{self.host}' dbname='{self.database}' user='{self.user}' port={self.port}"""
|
|
43
|
-
try:
|
|
44
|
-
conn = psycopg2.connect(connect_string, application_name=self.application_name,
|
|
45
|
-
sslmode='verify-full',
|
|
46
|
-
sslcert=self.client_cert,
|
|
47
|
-
sslkey=self.client_key,sslrootcert=self.root_cert)
|
|
48
|
-
except psycopg2.OperationalError as oe:
|
|
49
|
-
if 'no password' in str(oe):
|
|
50
|
-
raise ValueError(f"Invalid certificates or key for user {self.user}, or not authorized by server")
|
|
51
|
-
raise
|
|
52
|
-
if self.schema is not None:
|
|
53
|
-
with conn.cursor() as cursor:
|
|
54
|
-
cursor.execute(f"set search_path to {self.schema}")
|
|
55
|
-
conn.commit()
|
|
56
|
-
|
|
57
|
-
return conn
|
|
58
|
-
|
|
59
|
-
@staticmethod
|
|
60
|
-
def create_from_dict(data:Mapping,application_name:str):
|
|
61
|
-
h = data['host']
|
|
62
|
-
d = data['database']
|
|
63
|
-
u = data.get('user',_current_user())
|
|
64
|
-
cc = data['client certificate']
|
|
65
|
-
rc = data['root certificate']
|
|
66
|
-
ck = data['client key']
|
|
67
|
-
return CertificateDatabase(host=h, database=d, user=u, application_name=application_name, client_cert=cc,
|
|
68
|
-
client_key=ck, root_cert=rc)
|
|
69
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{postgresql_access-2.0 → postgresql_access-2.0.1}/src/postgresql_access.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{postgresql_access-2.0 → postgresql_access-2.0.1}/src/postgresql_access.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{postgresql_access-2.0 → postgresql_access-2.0.1}/src/postgresql_access.egg-info/requires.txt
RENAMED
|
File without changes
|
{postgresql_access-2.0 → postgresql_access-2.0.1}/src/postgresql_access.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|