Package: plpy-wrapper
plpy-wrapperTable of ContentsTerminologyDocumentationWhyWho should use this?Getting StartedPrerequisitesInstallationHow to useExamplesRunning the testsTechnologiesScope of the projectProject statusWhat does plpython stand for?TODOA Note on the Development EnvironmentFurther ReadingTerminologyprocedural languageA language in which one can write user-defined functions to access database functionality. The most well known and used in the Postgres world isPL/pgsqlplpython- The procedural language plpython which allows python code to run in the Postgres RuntimeplpyThe only package that is imported automatically by the Python interpreter embedded in Postgres. This package cannot be imported outside of the Postgres runtime and is used to access Postgres DB functionality such as querying or loggingplpy-wrapperThis package which is a convenience wrapper around plpyDocumentationCheck out the documentationhereSee thedocumentation readmefor information on building the documentation.WhyThe reason I wrote plpy-wrapper was to explore how two of some of my favorite technologies, Postgres, and Python
live and cooperate with one another.Who should use this?It's difficult to justify the use of this in large-scale or security-conscious projects.
The main reason doesn't have anything to do with this package itself, but rather the fact that plpython is an untrusted procedural language - meaning that it can run arbitrary code on your Postgres server.As phrased by thepostgres documentation:PL/Python is only available as an "untrusted" language, meaning it does not offer any way of restricting what users can do in it and is therefore named plpythonHowever, there are some good reasons to use plpython in general and therefore plpy-wrapper as well.You don't want to learn PL/pgSQL but you want to write user-defined functions in PostgresYou want to use a python package that already does exactly what you want it to doYou have code you already wrote in Python and want to use it in PostgresYou are curiousIt's a personal server and you have full control over the environment and Python is coolGetting StartedPrerequisitesMake sure to have Postgres installed on your system.
The Postgres installation should have been compiled with support for plpython as a supported procedural language.
By default most installations come with that baked in. When in doubt you can look in thedockerfilefor an example of an environment that this package will definitely run in.Make sure you've installed plpython as a procedural language in a Postgres session. This package only supports plpython3.
You can do that by running the following code snippetCREATE EXTENSION plpython3u;InstallationOn the same machine as your Postgres installation, install plpy-wrapper.
However the package gets installed, it must end up in thePython Path.
The way the embedded Python interpreter running inside of the Postgres runtime knows how to access outside Python packages (such as this one), is through the Python Path.
So however you install this package, make sure it ends up on the Python Path.The two easiest ways to install plpy-wrapper are through pipUsing pypipipinstallplpy-wrapperUsing githubpipinstallgit+https://github.com/skamensky/plpy-wrapperHow to useThe best way to learn about how to use this package is by reading thetests.You can also read some examples below or open an issue if something is unclear.ExamplesThe trigger creation utility attempts to satisfy a simple use case where you want to create a trigger on every event for a given table.This codeDO
$$
from plpy_wrapper import utilities,PLPYWrapper
utilities.create_plpython_triggers(PLPYWrapper(globals()),'customer','contact')
$$ language plpython3u;Results in the following triggers definitions being executed on thecontacttable in thecustomerschema
in order to captures all row events.create trigger trig_customer_contact_after
    after insert or update or delete
    on contact
    for each row
execute procedure func_customer_contact_trigger_controller();Andcreate trigger trig_customer_contact_before
    before insert or update or delete
    on contact
    for each row
execute procedure func_customer_contact_trigger_controller();Thefunc_customer_contact_trigger_controllerwill autogenerate to the following definition, but could also be changed:create function func_customer_contact_trigger_controller() returns trigger
    language plpython3u
as
$$
from plpy_wrapper import PLPYWrapper,Trigger
class _Contact(Trigger):

    def before_insert(self):
        #put your before insert logic here (or delete this method if you don't want anything to happen before insert)
        pass

    def after_insert(self):
        #put your after insert logic here (or delete this method if you don't want anything to happen after insert)
        pass

    def before_update(self):
        #put your before update logic here (or delete this method if you don't want anything to happen before update)
        pass

    def after_update(self):
        #put your after update logic here (or delete this method if you don't want anything to happen after update)
        pass

    def before_delete(self):
        #put your before delete logic here (or delete this method if you don't want anything to happen before delete)
        pass

    def after_delete(self):
        #put your before after delete logic here (or delete this method if you don't want anything to happen after delete)
        pass

trigger_handler = _Contact(PLPYWrapper(globals()))
#this runs the appropriate method
trigger_handler.execute()
#based on changes you made to the data or events you initiated, this tells postgres to change data, skip the event, etc.
#The return value is only relevant in BEFORE/INSTEAD OF triggers
return trigger_handler.trigger_return_val
$$;Running the testsYou can read more about how tests work and how to run them in thetests readme.TechnologiesPythonPostgresScope of the projectAs of now, plpy-wrapper is a simple wrapper around plpy. The main utilities someone could find in it
are:Simplifying writing triggers in plpython and avoiding some common pitfalls (for example by using the autogenerated trigger above you don't need to worry about persisting changes you've made to the row in the trigger by returning the string 'OK'. This package does that for you).Performing a specific action before or after every function call to plpy (sort of how you would use decorators)However, I am open to contributions and extending the scope of this package to be a more full-fledged wrapper as long as the changes are useful enough to a broad audience.Project statusWhatever comes before Alpha. Given that this project was written mainly for fun and curiosity I can see it never being touched again.What does plpython3u stand for?Theplpart of plpython stands for procedural languageThepythonpart stands for Python ♥The3part stands for python 3.Theupart stands for untrusted (you can what untrusted means in theWho should use this?section).TODOFinish writing testsAdd CICreate coverage badge and improve coverageA Note on the Development EnvironmentThis package was developed on Windows and therefore bash scripts are batch scripts and some file paths may be in Windows formats.Sorry! I'm still in the process of transferring my development environment to WSL.Further Readinghttps://www.postgresql.org/docs/current/plpython.html