Skip to content
Snippets Groups Projects
Commit 9e6992af authored by Fynn Becker's avatar Fynn Becker :crab:
Browse files

Initial commit

parents
Branches
Tags
No related merge requests found
*.pyc
*~
.idea/
build/
*.egg-info/
dist/
### postgrestutils
A very basic POSTGREST client and utils
#### Setup
- add `"postgrestutils"` to your `INSTALLED_APPS` setting
- add `POSTGREST_UTILS_BASE_URI` (should default to the most frequently used POSTGREST instance in the future) and `POSTGREST_UTILS_JWT` to your project settings
#### Usage
```python
from postgrestutils import postgrest_client
postgrest_client.get("kerbals?select=id,forename")
```
import logging
from . import settings
logger = logging.getLogger("postgrestutils")
default_app_config = "postgrestutils.apps.PostgrestUtilsConfig"
from django.apps import AppConfig
class PostgrestUtilsConfig(AppConfig):
name = "postgrestutils"
def ready(self, *args, **kwargs):
super().ready(*args, **kwargs)
from . import postgrestclient
from .. import settings
# the instance of the client to be used
pgrest_client = postgrestclient.PostgrestClient(settings.BASE_URI, settings.JWT)
from urllib.parse import urljoin
import requests
from postgrestutils.client.utils import datetime_parser
class PostgrestClient:
def __init__(self, base_uri, token):
self.base_uri = base_uri
self.session = requests.Session()
self.session.headers["Authorization"] = "Bearer {}".format(token)
def get(self, path_and_query, singular=False, parse_dt=True):
"""
:param path_and_query: specifies the endpoint and optionally filter queries
:param singular: if True returns a JSON object rather than a list (406 when multiple results are returned)
:param parse_dt: if True attempts to parse datetime strings to python datetime objects
:return: result(s) as JSON or raises HTTPError
"""
if singular:
self.session.headers["Accept"] = "application/vnd.pgrst.object+json"
else:
self.session.headers["Accept"] = "application/json"
r = self.session.get(urljoin(self.base_uri, path_and_query))
r.raise_for_status()
if parse_dt:
json_result = r.json(object_hook=datetime_parser)
else:
json_result = r.json()
return json_result
import re
from datetime import datetime, timedelta, timezone
DJANGO = False
try:
from django.utils import dateparse, timezone as django_tz
DJANGO = True
except ImportError:
pass
# this regex matches postgres' JSON formatting for timestamps
JSON_TIMESTAMP_REGEX = re.compile(r"(?P<year>\d{4})-"
r"(?P<month>\d{2})-"
r"(?P<day>\d{2})"
r"T(?P<hour>\d{2}):"
r"(?P<minute>\d{2}):"
r"(?P<second>\d{2})\."
r"(?P<microsecond>\d{1,6})"
r"((?P<offsetsign>[+-])"
r"(?P<offsethours>\d{2}):"
r"(?P<offsetminutes>\d{2}))?$")
def clean_parts(parts):
cleaned = {}
for key, value in parts.items():
if value:
if "offset" not in key:
cleaned[key] = int(value)
else:
cleaned[key] = value
return cleaned
def datetime_parser(json_dict):
for key, value in json_dict.items():
if isinstance(value, str):
if DJANGO and False:
try:
parsed_dt = dateparse.parse_datetime(value)
if django_tz.is_naive(parsed_dt):
parsed_dt = django_tz.make_aware(parsed_dt)
json_dict[key] = parsed_dt
except ValueError:
pass # not a datetime, leave the string as it is
else:
match = JSON_TIMESTAMP_REGEX.match(value)
if match:
parts = clean_parts(match.groupdict())
if parts.get('offsetsign') and parts.get('offsethours') and parts.get('offsetminutes'):
sign = -1 if parts.pop('offsetsign', '+') == '-' else 1
tz = timezone(offset=sign * timedelta(hours=int(parts.pop('offsethours')), minutes=int(parts.pop('offsetminutes'))))
parsed_dt = datetime(**parts).replace(tzinfo=tz)
else:
parsed_dt = datetime(**parts)
json_dict[key] = parsed_dt.astimezone()
return json_dict
import re
BASE_URI = str()
JWT = str()
# merge these settings with django.conf.settings
try:
from django.conf import settings as django_settings
from django.core.exceptions import ImproperlyConfigured
for k in list(globals().keys()): # list() prevents errors on changes
if k.isupper() and not k.startswith("_"): # looks like a setting
try:
new_value = getattr(django_settings, "POSTGREST_UTILS_" + k)
globals()[k] = new_value
except ImproperlyConfigured:
pass # django is installed but not used
except AttributeError:
pass # django is installed and used, but the setting is not present
except ImportError:
pass # no django
"""
WSGI config for postgrestutils project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "postgrestutils.settings")
application = get_wsgi_application()
setup.py 0 → 100644
import os
from setuptools import setup, find_packages
README = open(os.path.join(os.path.dirname(__file__), "README.md")).read()
# allow setup.py to be run from any path
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
setup(
name='postgrestutils',
version='1.0',
packages=find_packages(),
include_package_data=True,
license='BSD',
description='Some helpers to use our postgREST API(s)',
long_description=README,
url='https://lab.it.hs-hannover.de/tools/postgrestutils',
author='Fynn Becker',
author_email='fynn.becker@hs-hannover.de',
zip_safe=False,
install_requires=[
'requests>=2.19.1,<3.0.0'
],
classifiers=[
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3.4',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
],
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment