Skip to content

Commit

Permalink
Add the load_from_plugins parameter in the Python driver to load AG…
Browse files Browse the repository at this point in the history
…E from the $libdir/plugins directory (#1935)

This is useful when a non-superuser login is used for the connection.

Additionally, fixed a small bug where a memoryview was not transformed to bytes before decoding into a UTF-8 string.

Co-authored-by: Daan Wagenaar <[email protected]>
(cherry picked from commit 7fc3448)
  • Loading branch information
daanwa authored and Daan Wagenaar committed Jul 3, 2024
1 parent 7b635c6 commit 5104a2c
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 10 deletions.
10 changes: 10 additions & 0 deletions drivers/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ SET search_path = ag_catalog, "$user", public;
* Simpler way to access Apache AGE [AGE Sample](samples/apache-age-note.ipynb) in Samples.
* Agtype converting samples: [Agtype Sample](samples/apache-age-agtypes.ipynb) in Samples.

### Non-Superuser Usage
* For non-superuser usage see: [Allow Non-Superusers to Use Apache Age](https://age.apache.org/age-manual/master/intro/setup.html).
* Make sure to give your non-superuser db account proper permissions to the graph schemas and corresponding objects
* Make sure to initiate the Apache Age python driver with the ```load_from_plugins``` parameter. This parameter tries to
load the Apache Age extension from the PostgreSQL plugins directory located at ```$libdir/plugins/age```. Example:
```python.
ag = age.connect(host='localhost', port=5432, user='dbuser', password='strong_password',
dbname=postgres, load_from_plugins=True, graph='graph_name)
```

### License
Apache-2.0 License

Expand Down
8 changes: 5 additions & 3 deletions drivers/python/age/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@ def version():
return VERSION.VERSION


def connect(dsn=None, graph=None, connection_factory=None, cursor_factory=ClientCursor, **kwargs):
def connect(dsn=None, graph=None, connection_factory=None, cursor_factory=ClientCursor, load_from_plugins=False,
**kwargs):

dsn = conninfo.make_conninfo('' if dsn is None else dsn, **kwargs)

ag = Age()
ag.connect(dsn=dsn, graph=graph, connection_factory=connection_factory, cursor_factory=cursor_factory, **kwargs)
ag.connect(dsn=dsn, graph=graph, connection_factory=connection_factory, cursor_factory=cursor_factory,
load_from_plugins=load_from_plugins, **kwargs)
return ag

# Dummy ResultHandler
rawPrinter = DummyResultHandler()

__name__="age"
__name__="age"
24 changes: 17 additions & 7 deletions drivers/python/age/age.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,29 @@ def dump(self, obj: Any) -> bytes | bytearray | memoryview:


class AgeLoader(psycopg.adapt.Loader):
def load(self, data: bytes | bytearray | memoryview) -> Any | None:
return parseAgeValue(data.decode('utf-8'))
def load(self, data: bytes | bytearray | memoryview) -> Any | None:
if isinstance(data, memoryview):
data_bytes = data.tobytes()
else:
data_bytes = data

return parseAgeValue(data_bytes.decode('utf-8'))

def setUpAge(conn:psycopg.connection, graphName:str):

def setUpAge(conn:psycopg.connection, graphName:str, load_from_plugins:bool=False):
with conn.cursor() as cursor:
cursor.execute("LOAD 'age';")
if load_from_plugins:
cursor.execute("LOAD '$libdir/plugins/age';")
else:
cursor.execute("LOAD 'age';")

cursor.execute("SET search_path = ag_catalog, '$user', public;")

ag_info = TypeInfo.fetch(conn, 'agtype')

if not ag_info:
raise AgeNotSet()

conn.adapters.register_loader(ag_info.oid, AgeLoader)
conn.adapters.register_loader(ag_info.array_oid, AgeLoader)

Expand Down Expand Up @@ -184,9 +193,10 @@ def __init__(self):
self.graphName = None

# Connect to PostgreSQL Server and establish session and type extension environment.
def connect(self, graph:str=None, dsn:str=None, connection_factory=None, cursor_factory=ClientCursor, **kwargs):
def connect(self, graph:str=None, dsn:str=None, connection_factory=None, cursor_factory=ClientCursor,
load_from_plugins:bool=False, **kwargs):
conn = psycopg.connect(dsn, cursor_factory=cursor_factory, **kwargs)
setUpAge(conn, graph)
setUpAge(conn, graph, load_from_plugins)
self.connection = conn
self.graphName = graph
return self
Expand Down

0 comments on commit 5104a2c

Please sign in to comment.