Commit 23538543 authored by Amyll Angelin's avatar Amyll Angelin

Initial Commit

o homepage
o default profile picture
o edit nickname and bio
o add key
o add weekly task
o add daily task
o goes back home
o committed to git

x upload profile pic
x edit, delete, mark weekly
x edit delete, mark daily
x not proofread for cleancode
parent 111ceaf9
SECRET_KEY = '#@!6hsq8b96w#4lv#vy1$_xuhj7r5^6_*e034x0+gy5e@1!)-d'
DB_NAME='labdb'
DB_USER='postgres'
DB_PASS='123'
\ No newline at end of file
......@@ -12,7 +12,9 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
from pathlib import Path
import os
from dotenv import load_dotenv
load_dotenv()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
......@@ -79,8 +81,12 @@ WSGI_APPLICATION = 'Bujo.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': os.getenv('DB_NAME'),
'USER': os.getenv('DB_USER'),
'PASSWORD': os.getenv('DB_PASS'),
'HOST': 'localhost',
'PORT': '5432',
}
}
......
......@@ -21,9 +21,25 @@ from Pages import views
urlpatterns = [
path('', views.redir_view, name='home'),
path('home/', views.home_view, name='home'),
path('admin/', admin.site.urls),
path('profile/', views.profile_view, name='profile'),
path('profile/edit_nickname/', views.edit_nickname_view, name='profile_edit_nickname'),
path('profile/edit_bio/', views.edit_bio_view, name='profile_edit_bio'),
path('key/', views.key_view, name='key'),
path('key/add_key/', views.add_key_view, name='key_add'),
path('this_week/', views.week_view, name='this_week'),
path('this_week/add_week/', views.add_week_view, name='this_week_add'),
path('this_week/edit_week/)', views.edit_week_view, name='this_week_edit'),
path('this_week/delete_week/', views.delete_week_view, name='this_week_delete'),
path('this_week/delete_week/<int:pk>/', views.delete_week_view, name='this_week_delete_pk'),
path('today/', views.today_view, name='today'),
path('admin/', admin.site.urls),
path('today/add_today/', views.add_today_view, name='today_add'),
path('today/edit_today/)', views.edit_today_view, name='today_edit'),
path('today/delete_today/', views.delete_today_view, name='today_delete'),
path('today/delete_today/<int:pk>/', views.delete_today_view, name='today_delete_pk'),
]
from django.contrib import admin
from .models import User, Key, Nickname, Bio, Week
# Register your models here.
admin.site.register(User)
admin.site.register(Nickname)
admin.site.register(Bio)
admin.site.register(Key)
admin.site.register(Week)
\ No newline at end of file
from django import forms
from .models import User, Nickname, Bio, Key, Week, Today
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ["name"]
class NicknameForm(forms.ModelForm):
class Meta:
model = Nickname
fields = ["nickname"]
class BioForm(forms.ModelForm):
class Meta:
model = Bio
fields = ["bio"]
class KeyForm(forms.ModelForm):
class Meta:
model = Key
fields = ["keyName", "keyDesc"]
class WeekForm(forms.ModelForm):
class Meta:
model = Week
fields = ["weekKey", "weekDesc"]
class TodayForm(forms.ModelForm):
class Meta:
model = Today
fields = ["todayKey", "todayDesc"]
\ No newline at end of file
# Generated by Django 3.1.7 on 2021-04-09 09:38
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Key',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('keyName', models.CharField(max_length=50)),
('keyDesc', models.CharField(max_length=100)),
],
),
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
],
),
]
# Generated by Django 3.1.7 on 2021-04-09 11:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Pages', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Bio',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('bio', models.TextField()),
],
),
migrations.CreateModel(
name='Nickname',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nickname', models.CharField(max_length=50)),
],
),
]
# Generated by Django 3.1.7 on 2021-04-09 16:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Pages', '0002_bio_nickname'),
]
operations = [
migrations.CreateModel(
name='Week',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('weekKey', models.CharField(max_length=50)),
('weekDesc', models.CharField(max_length=100)),
],
),
]
# Generated by Django 3.1.7 on 2021-04-09 17:14
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('Pages', '0003_week'),
]
operations = [
migrations.AlterField(
model_name='week',
name='weekKey',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='Pages.key'),
),
]
# Generated by Django 3.1.7 on 2021-04-09 17:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('Pages', '0004_auto_20210410_0114'),
]
operations = [
migrations.AddField(
model_name='week',
name='weekDone',
field=models.BooleanField(default=False),
),
]
# Generated by Django 3.1.7 on 2021-04-10 02:52
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('Pages', '0005_week_weekdone'),
]
operations = [
migrations.CreateModel(
name='Today',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('todayDesc', models.CharField(max_length=100)),
('todayDone', models.BooleanField(default=False)),
('todayKey', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='Pages.key')),
],
),
]
from django.db import models
# Create your models here.
class User(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Nickname(models.Model):
nickname = models.CharField(max_length=50)
def __str__(self):
return self.nickname
class Bio(models.Model):
bio = models.TextField()
def __str__(self):
return self.bio
class Key(models.Model):
keyName = models.CharField(max_length=50)
keyDesc = models.CharField(max_length=100)
def __str__(self):
keyName = self.keyName
keyDesc = self.keyDesc
fullEntry = keyName +" ("+ keyDesc+")"
return fullEntry
class Week(models.Model):
weekKey = models.ForeignKey(Key, on_delete=models.SET_NULL, null=True)
weekDesc = models.CharField(max_length=100)
weekDone = models.BooleanField(default=False)
def __str__(self):
weekKey = self.weekKey
weekDesc = self.weekDesc
fullEntry = weekKey.__str__() + " - " + weekDesc
return fullEntry
class Today(models.Model):
todayKey = models.ForeignKey(Key, on_delete=models.SET_NULL, null=True)
todayDesc = models.CharField(max_length=100)
todayDone = models.BooleanField(default=False)
def __str__(self):
todayKey = self.todayKey
todayDesc = self.todayDesc
fullEntry = todayKey.__str__() + " - " + todayDesc
return fullEntry
\ No newline at end of file
......@@ -2,22 +2,113 @@ from django.http import HttpResponse
from django.shortcuts import render
from django.shortcuts import redirect
from .forms import UserForm, NicknameForm, BioForm, KeyForm, WeekForm, TodayForm
from .models import User, Nickname, Bio, Key, Week, Today
# Create your views here.
def redir_view(request):
response = redirect("/home")
return response
def home_view(request, *args, **kwargs):
return render(request, "home.html", {})
user=User.objects.first()
if user==None:
form=UserForm(request.POST or None)
if form.is_valid():
form.save()
context = {"form": form, "message": "Hello! What is your name?"}
return render(request, "home.html", context)
else:
context={"name": user}
return render(request, "home2.html", context)
def profile_view(request, *args, **kwargs):
return render(request, "profile.html", {})
if Nickname.objects.last()==None:
nickname = "Your Nickname"
else:
nickname = Nickname.objects.last()
if Bio.objects.last()==None:
bio = "A short description about yourself."
else:
bio = Bio.objects.last()
context = {"nickname": nickname, "bio": bio}
return render(request, "profile.html", context)
def edit_nickname_view(request, *args, **kwargs):
form=NicknameForm(request.POST or None)
if form.is_valid():
form.save()
return render(request, "edit_nickname.html", {"form": form})
def edit_bio_view(request, *args, **kwargs):
form=BioForm(request.POST or None)
if form.is_valid():
form.save()
return render(request, "edit_bio.html", {"form": form})
def key_view(request, *args, **kwargs):
return render(request, "key.html", {})
keyList=Key.objects.all()
return render(request, "key.html", {"KeyList": keyList})
def add_key_view(request, *args, **kwargs):
form=KeyForm(request.POST or None)
if form.is_valid():
form.save()
return render(request, "add_key.html", {"form": form})
def week_view(request, *args, **kwargs):
return render(request, "this_week.html", {})
weekList=Week.objects.all()
return render(request, "this_week.html", {"WeekList": weekList})
def add_week_view(request, *args, **kwargs):
form=WeekForm(request.GET)
form=WeekForm(request.POST or None)
if form.is_valid():
form.save()
return render(request, "add_week.html", {"form": form})
def edit_week_view(request, *args, **kwargs):
form=WeekForm(request.POST)
if form.is_valid():
form.save()
return render(request, "edit_week.html", {"form": form})
def delete_week_view(request, *args, **kwargs):
task = Week.objects.first()
if request.method == "POST":
task.delete()
return render(request, "delete_week.html", {"task": task} )
def today_view(request, *args, **kwargs):
return render(request, "today.html", {})
todayList=Today.objects.all()
return render(request, "today.html", {"TodayList": todayList})
def add_today_view(request, *args, **kwargs):
form=TodayForm(request.GET)
form=TodayForm(request.POST or None)
if form.is_valid():
form.save()
return render(request, "add_today.html", {"form": form})
def edit_today_view(request, *args, **kwargs):
form=TodayForm(request.POST)
if form.is_valid():
form.save()
return render(request, "edit_today.html", {"form": form})
def delete_today_view(request, *args, **kwargs):
task = Today.objects.first()
if request.method == "POST":
task.delete()
return render(request, "delete_week.html", {"task": task} )
\ No newline at end of file
<html>
<head>
<title> Key </title>
</head>
<body>
<center> <h1> Key </h1>
<form method=POST>
{{form.as_p}}
<input type="submit" value="Go!" onclick=alert("Added!")>{% csrf_token %}
</form>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
<a href="http://127.0.0.1:8000/profile/"><button> Profile </button></a>
<a href="http://127.0.0.1:8000/key/"><button> Key </button></a>
<a href="http://127.0.0.1:8000/this_week/"><button> This Week </button></a>
<a href="http://127.0.0.1:8000/today/"><button> Today </button></a></center>
</body>
</html>
\ No newline at end of file
<html>
<head>
<title> Today </title>
</head>
<body>
<center> <h1> Today </h1>
<form method=POST>
{{form.as_p}}
<input type="submit" value="Go!" onclick=alert("Added!")>{% csrf_token %}
</form>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
<a href="http://127.0.0.1:8000/profile/"><button> Profile </button></a>
<a href="http://127.0.0.1:8000/key/"><button> Key </button></a>
<a href="http://127.0.0.1:8000/this_week/"><button> This Week </button></a>
<a href="http://127.0.0.1:8000/today/"><button> Today </button></a></center>
</body>
</html>
\ No newline at end of file
<html>
<head>
<title> Week </title>
</head>
<body>
<center> <h1> Week </h1>
<form method=POST>
{{form.as_p}}
<input type="submit" value="Go!" onclick=alert("Added!")>{% csrf_token %}
</form>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
<a href="http://127.0.0.1:8000/profile/"><button> Profile </button></a>
<a href="http://127.0.0.1:8000/key/"><button> Key </button></a>
<a href="http://127.0.0.1:8000/this_week/"><button> This Week </button></a>
<a href="http://127.0.0.1:8000/today/"><button> Today </button></a></center>
</body>
</html>
\ No newline at end of file
<html>
<head>
<title> This Week </title>
</head>
<body>
<center>
<h1>Delete Task</h1>
<form method ="POST"> {% csrf_token %}
Are you sure you would like to delete the {{task}}? <br> <br>
<a href="{% url 'this_week' %}"><input button type="submit" value="Yes"></a> </input> </form>
<a href="{% url 'this_week' %}"><button> No </button></a> <br> <br>
<br>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
<a href="http://127.0.0.1:8000/profile/"><button> Profile </button></a>
<a href="http://127.0.0.1:8000/key/"><button> Key </button></a>
<a href="http://127.0.0.1:8000/this_week/"><button> This Week </button></a>
<a href="http://127.0.0.1:8000/today/"><button> Today </button></a></center>
</center>
</body>
<html>
\ No newline at end of file
<html>
<head>
<title> Profile </title>
</head>
<body>
<center> <h1> Profile </h1>
<form method=POST>
{{form.as_p}}
<input type="submit" value="Go!" onclick=alert("Changed!")>{% csrf_token %}
</form>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
<a href="http://127.0.0.1:8000/profile/"><button> Profile </button></a>
<a href="http://127.0.0.1:8000/key/"><button> Key </button></a>
<a href="http://127.0.0.1:8000/this_week/"><button> This Week </button></a>
<a href="http://127.0.0.1:8000/today/"><button> Today </button></a></center>
</body>
</html>
\ No newline at end of file
<html>
<head>
<title> Profile </title>
</head>
<body>
<center> <h1> Profile </h1>
<form method=POST>
{{form.as_p}}
<input type="submit" value="Go!" onclick=alert("Changed!")>{% csrf_token %}
</form>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
<a href="http://127.0.0.1:8000/profile/"><button> Profile </button></a>
<a href="http://127.0.0.1:8000/key/"><button> Key </button></a>
<a href="http://127.0.0.1:8000/this_week/"><button> This Week </button></a>
<a href="http://127.0.0.1:8000/today/"><button> Today </button></a></center>
</body>
</html>
\ No newline at end of file
<html>
<head>
<title> This Week </title>
</head>
<body>
<center>
<h1>Edit Task</h1>
<form method=POST>
{{form.as_p}}
<input type="submit" value="Edit!" onclick=alert("Edited!")>{% csrf_token %}
</form>
<br>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
<a href="http://127.0.0.1:8000/profile/"><button> Profile </button></a>
<a href="http://127.0.0.1:8000/key/"><button> Key </button></a>
<a href="http://127.0.0.1:8000/this_week/"><button> This Week </button></a>
<a href="http://127.0.0.1:8000/today/"><button> Today </button></a></center>
</center>
</body>
<html>
\ No newline at end of file
<html>
<head>
<title> This Week </title>
</head>
<body>
<center>
<h1>Edit Task</h1>
<form method=POST>
{{form.as_p}}
<input type="submit" value="Edit!" onclick=alert("Edited!")>{% csrf_token %}
</form>
<br>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
<a href="http://127.0.0.1:8000/profile/"><button> Profile </button></a>
<a href="http://127.0.0.1:8000/key/"><button> Key </button></a>
<a href="http://127.0.0.1:8000/this_week/"><button> This Week </button></a>
<a href="http://127.0.0.1:8000/today/"><button> Today </button></a></center>
</center>
</body>
<html>
\ No newline at end of file
<html>
<head>
<title> Your Bujo </title>
<script>
function setName()
{
var name = document.getElementById("textbox").value;
var name1="Hello, ";
var name2="! Today is going to be a great day!";
var nameFinal=name1+name+name2;
document.getElementById("greeting").innerHTML=nameFinal;
document.getElementById("textbox").style.display="none";
document.getElementById("go").style.display="none";
}
</script>
</head>
<body>
<center><h1 id="greeting"> Hello! What is your name? <br></h1>
<input id="textbox"></input>
<button id="go" onclick="setName()">Go!</button>
<center><h1 id="greeting"> {{message}} <br></h1>
<form method=POST>
{{form.as_p}}
<input type="submit" value="Go!" action="http://127.0.0.1:8000/home/" onclick=alert("Noted!")>{% csrf_token %}
</form>
<h2>Your Bullet Journal</h2>
......
<html>
<head>
<title> Your Bujo </title>
</head>
<body>
<center><h1 id="greeting"> Hello, {{name}}! Today is going to be a great day! <br></h1>
<h2>Your Bullet Journal</h2>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
<a href="http://127.0.0.1:8000/profile/"><button> Profile </button></a>
<a href="http://127.0.0.1:8000/key/"><button> Key </button></a>
<a href="http://127.0.0.1:8000/this_week/"><button> This Week </button></a>
<a href="http://127.0.0.1:8000/today/"><button> Today </button></a></center>
</body>
</html>
\ No newline at end of file
<html>
<head>
<title> Key </title>
<style>
table, th, td
{
border: 2px solid black;
padding: 5px;
}
table.center
{
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<center>
<h1> Key </h1>
<b> Tasks </b>: things you have to do <br>
- <b> Notes </b>: things you don't want to forget <br>
<b> Events </b>: noteworthy moments in time <br> <br>
● Task Incomplete <br>
X Task complete <br><br>
{% for key in KeyList %}
<li> {{key}}</li>
{% endfor %}
<br>
<a href="http://127.0.0.1:8000/key/add_key/"><button> Add Key </button> <br> <br> </a>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
......
<html>
<head>
<title> Your Bujo </title>
<title> Profile </title>
</head>
<body>
<center> <h1> Profile </h1>
<b> Nickname: </b> Amew <br>
<b> Bio: </b> Hello this is a short bio about myself hehe <br><br>
<img src="https://scontent.fceb1-2.fna.fbcdn.net/v/t1.6435-1/p160x160/70313766_2906045252757275_4233093116665528320_n.jpg?_nc_cat=100&ccb=1-3&_nc_sid=7206a8&_nc_ohc=U76gNU24AIYAX9IY8iC&_nc_ht=scontent.fceb1-2.fna&tp=6&oh=a3880eb43eb4e4ce3a38dfe955841349&oe=608F3379"></img><br><br>
<b> Nickname: </b> {{nickname}} <a href=http://127.0.0.1:8000/profile/edit_nickname/><button> Edit </button> </a> <br>
<b> Bio: </b> {{bio}} <a href=http://127.0.0.1:8000/profile/edit_bio/><button> Edit </button> </a> <br><br>
<img src="https://spng.subpng.com/20180402/qye/kisspng-computer-icons-user-login-gender-5ac29ccd8f04c2.0984432615227035655858.jpg"></img><br><br>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
......
var nameFinal
\ No newline at end of file
......@@ -4,26 +4,28 @@
</head>
<body>
<center>
<h1>This Week</h1>
<h3>03.01.MON - 03.07.SUN</h3>
{% for week in WeekList %}
<li> {{week}} </li>
<br><a href="http://127.0.0.1:8000/this_week/edit_week"><button>Edit</button></a>
<a href="{% url 'this_week_delete_pk' pk=week.pk %}"><button>Delete</button></a>
<a href="http://127.0.0.1:8000/this_week/mark_week"><button>Mark as Done </button></a> <br> <br>
{% endfor %}
<br>
</form>
<h1> This Week </h1>
<a href="http://127.0.0.1:8000/this_week/add_week/"><button> Add Item </button> <br> <br> </a>
<h2> 03.01.MON - 03.07.SUN </h2>
● workout <br>
● buy coffee and milk <br>
● laundry <br>
○ 3, 2PM meeting <br>
- 30g of coffee too strong <br>
● review ethics <br> <br>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
<a href="http://127.0.0.1:8000/profile/"><button> Profile </button></a>
<a href="http://127.0.0.1:8000/profile/"><button> Profile </button></a>
<a href="http://127.0.0.1:8000/key/"><button> Key </button></a>
<a href="http://127.0.0.1:8000/key/"><button> Key </button></a>
<a href="http://127.0.0.1:8000/this_week/"><button> This Week </button></a>
<a href="http://127.0.0.1:8000/today/"><button> Today </button></a></center>
<a href="http://127.0.0.1:8000/this_week/"><button> This Week </button></a>
<a href="http://127.0.0.1:8000/today/"><button> Today </button></a></center>
</center>
</body>
<html>
\ No newline at end of file
......@@ -2,30 +2,30 @@
<head>
<title> Today </title>
</head>
<body>
<center>
<h1>Today</h1>
<h3>03.01.MON - 03.07.SUN</h3>
{% for today in TodayList %}
<li> {{today}} </li>
<br><a href="http://127.0.0.1:8000/today/edit_today"><button>Edit</button></a>
<a href="{% url 'today_delete_pk' pk=today.pk %}"><button>Delete</button></a>
<a href="http://127.0.0.1:8000/this_week/mark_today"><button>Mark as Done </button></a> <br> <br>
{% endfor %}
<br>
</form>
<h1> Today </h1>
<h2> 03.02.TUES </h2>
● laundry bedsheet <br>
● review ethics protocol <br>
● laundry <br>
○ 3, 2PM meeting <br>
- 30g of coffee too strong <br>
● review ethics more <br> <br>
<a href="http://127.0.0.1:8000/today/add_today/"><button> Add Item </button> <br> <br> </a>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
<a href="http://127.0.0.1:8000/home/"><button> Home </button></a>
<a href="http://127.0.0.1:8000/profile/"><button> Profile </button></a>
<a href="http://127.0.0.1:8000/profile/"><button> Profile </button></a>
<a href="http://127.0.0.1:8000/key/"><button> Key </button></a>
<a href="http://127.0.0.1:8000/key/"><button> Key </button></a>
<a href="http://127.0.0.1:8000/this_week/"><button> This Week </button></a>
<a href="http://127.0.0.1:8000/today/"><button> Today </button></a></center>
<a href="http://127.0.0.1:8000/this_week/"><button> This Week </button></a>
<a href="http://127.0.0.1:8000/today/"><button> Today </button></a></center>
</center>
</body>
</html>
<html>
\ No newline at end of file
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class App2Config(AppConfig):
name = 'app2'
from django.db import models
# Create your models here.
from django.test import TestCase
# Create your tests here.
from django.shortcuts import render
# Create your views here.
No preview for this file type
from .compat import IS_TYPE_CHECKING
from .main import load_dotenv, get_key, set_key, unset_key, find_dotenv, dotenv_values
if IS_TYPE_CHECKING:
from typing import Any, Optional
def load_ipython_extension(ipython):
# type: (Any) -> None
from .ipython import load_ipython_extension
load_ipython_extension(ipython)
def get_cli_string(path=None, action=None, key=None, value=None, quote=None):
# type: (Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]) -> str
"""Returns a string suitable for running as a shell script.
Useful for converting a arguments passed to a fabric task
to be passed to a `local` or `run` command.
"""
command = ['dotenv']
if quote:
command.append('-q %s' % quote)
if path:
command.append('-f %s' % path)
if action:
command.append(action)
if key:
command.append(key)
if value:
if ' ' in value:
command.append('"%s"' % value)
else:
command.append(value)
return ' '.join(command).strip()
__all__ = ['get_cli_string',
'load_dotenv',
'dotenv_values',
'get_key',
'set_key',
'unset_key',
'find_dotenv',
'load_ipython_extension']
import os
import sys
from subprocess import Popen
try:
import click
except ImportError:
sys.stderr.write('It seems python-dotenv is not installed with cli option. \n'
'Run pip install "python-dotenv[cli]" to fix this.')
sys.exit(1)
from .compat import IS_TYPE_CHECKING, to_env
from .main import dotenv_values, get_key, set_key, unset_key
from .version import __version__
if IS_TYPE_CHECKING:
from typing import Any, List, Dict
@click.group()
@click.option('-f', '--file', default=os.path.join(os.getcwd(), '.env'),
type=click.Path(file_okay=True),
help="Location of the .env file, defaults to .env file in current working directory.")
@click.option('-q', '--quote', default='always',
type=click.Choice(['always', 'never', 'auto']),
help="Whether to quote or not the variable values. Default mode is always. This does not affect parsing.")
@click.option('-e', '--export', default=False,
type=click.BOOL,
help="Whether to write the dot file as an executable bash script.")
@click.version_option(version=__version__)
@click.pass_context
def cli(ctx, file, quote, export):
# type: (click.Context, Any, Any, Any) -> None
'''This script is used to set, get or unset values from a .env file.'''
ctx.obj = {}
ctx.obj['QUOTE'] = quote
ctx.obj['EXPORT'] = export
ctx.obj['FILE'] = file
@cli.command()
@click.pass_context
def list(ctx):
# type: (click.Context) -> None
'''Display all the stored key/value.'''
file = ctx.obj['FILE']
if not os.path.isfile(file):
raise click.BadParameter(
'Path "%s" does not exist.' % (file),
ctx=ctx
)
dotenv_as_dict = dotenv_values(file)
for k, v in dotenv_as_dict.items():
click.echo('%s=%s' % (k, v))
@cli.command()
@click.pass_context
@click.argument('key', required=True)
@click.argument('value', required=True)
def set(ctx, key, value):
# type: (click.Context, Any, Any) -> None
'''Store the given key/value.'''
file = ctx.obj['FILE']
quote = ctx.obj['QUOTE']
export = ctx.obj['EXPORT']
success, key, value = set_key(file, key, value, quote, export)
if success:
click.echo('%s=%s' % (key, value))
else:
exit(1)
@cli.command()
@click.pass_context
@click.argument('key', required=True)
def get(ctx, key):
# type: (click.Context, Any) -> None
'''Retrieve the value for the given key.'''
file = ctx.obj['FILE']
if not os.path.isfile(file):
raise click.BadParameter(
'Path "%s" does not exist.' % (file),
ctx=ctx
)
stored_value = get_key(file, key)
if stored_value:
click.echo(stored_value)
else:
exit(1)
@cli.command()
@click.pass_context
@click.argument('key', required=True)
def unset(ctx, key):
# type: (click.Context, Any) -> None
'''Removes the given key.'''
file = ctx.obj['FILE']
quote = ctx.obj['QUOTE']
success, key = unset_key(file, key, quote)
if success:
click.echo("Successfully removed %s" % key)
else:
exit(1)
@cli.command(context_settings={'ignore_unknown_options': True})
@click.pass_context
@click.option(
"--override/--no-override",
default=True,
help="Override variables from the environment file with those from the .env file.",
)
@click.argument('commandline', nargs=-1, type=click.UNPROCESSED)
def run(ctx, override, commandline):
# type: (click.Context, bool, List[str]) -> None
"""Run command with environment variables present."""
file = ctx.obj['FILE']
if not os.path.isfile(file):
raise click.BadParameter(
'Invalid value for \'-f\' "%s" does not exist.' % (file),
ctx=ctx
)
dotenv_as_dict = {
to_env(k): to_env(v)
for (k, v) in dotenv_values(file).items()
if v is not None and (override or to_env(k) not in os.environ)
}
if not commandline:
click.echo('No command given.')
exit(1)
ret = run_command(commandline, dotenv_as_dict)
exit(ret)
def run_command(command, env):
# type: (List[str], Dict[str, str]) -> int
"""Run command in sub process.
Runs the command in a sub process with the variables from `env`
added in the current environment variables.
Parameters
----------
command: List[str]
The command and it's parameters
env: Dict
The additional environment variables
Returns
-------
int
The return code of the command
"""
# copy the current environment variables and add the vales from
# `env`
cmd_env = os.environ.copy()
cmd_env.update(env)
p = Popen(command,
universal_newlines=True,
bufsize=0,
shell=False,
env=cmd_env)
_, _ = p.communicate()
return p.returncode
if __name__ == "__main__":
cli()
import sys
PY2 = sys.version_info[0] == 2 # type: bool
if PY2:
from StringIO import StringIO # noqa
else:
from io import StringIO # noqa
def is_type_checking():
# type: () -> bool
try:
from typing import TYPE_CHECKING
except ImportError:
return False
return TYPE_CHECKING
IS_TYPE_CHECKING = is_type_checking()
if IS_TYPE_CHECKING:
from typing import Text
def to_env(text):
# type: (Text) -> str
"""
Encode a string the same way whether it comes from the environment or a `.env` file.
"""
if PY2:
return text.encode(sys.getfilesystemencoding() or "utf-8")
else:
return text
def to_text(string):
# type: (str) -> Text
"""
Make a string Unicode if it isn't already.
This is useful for defining raw unicode strings because `ur"foo"` isn't valid in
Python 3.
"""
if PY2:
return string.decode("utf-8")
else:
return string
from __future__ import print_function
from IPython.core.magic import Magics, line_magic, magics_class # type: ignore
from IPython.core.magic_arguments import (argument, magic_arguments, # type: ignore
parse_argstring) # type: ignore
from .main import find_dotenv, load_dotenv
@magics_class
class IPythonDotEnv(Magics):
@magic_arguments()
@argument(
'-o', '--override', action='store_true',
help="Indicate to override existing variables"
)
@argument(
'-v', '--verbose', action='store_true',
help="Indicate function calls to be verbose"
)
@argument('dotenv_path', nargs='?', type=str, default='.env',
help='Search in increasingly higher folders for the `dotenv_path`')
@line_magic
def dotenv(self, line):
args = parse_argstring(self.dotenv, line)
# Locate the .env file
dotenv_path = args.dotenv_path
try:
dotenv_path = find_dotenv(dotenv_path, True, True)
except IOError:
print("cannot find .env file")
return
# Load the .env file
load_dotenv(dotenv_path, verbose=args.verbose, override=args.override)
def load_ipython_extension(ipython):
"""Register the %dotenv magic."""
ipython.register_magics(IPythonDotEnv)
This diff is collapsed.
import codecs
import re
from .compat import IS_TYPE_CHECKING, to_text
if IS_TYPE_CHECKING:
from typing import ( # noqa:F401
IO, Iterator, Match, NamedTuple, Optional, Pattern, Sequence, Text,
Tuple
)
def make_regex(string, extra_flags=0):
# type: (str, int) -> Pattern[Text]
return re.compile(to_text(string), re.UNICODE | extra_flags)
_newline = make_regex(r"(\r\n|\n|\r)")
_multiline_whitespace = make_regex(r"\s*", extra_flags=re.MULTILINE)
_whitespace = make_regex(r"[^\S\r\n]*")
_export = make_regex(r"(?:export[^\S\r\n]+)?")
_single_quoted_key = make_regex(r"'([^']+)'")
_unquoted_key = make_regex(r"([^=\#\s]+)")
_equal_sign = make_regex(r"(=[^\S\r\n]*)")
_single_quoted_value = make_regex(r"'((?:\\'|[^'])*)'")
_double_quoted_value = make_regex(r'"((?:\\"|[^"])*)"')
_unquoted_value = make_regex(r"([^\r\n]*)")
_comment = make_regex(r"(?:[^\S\r\n]*#[^\r\n]*)?")
_end_of_line = make_regex(r"[^\S\r\n]*(?:\r\n|\n|\r|$)")
_rest_of_line = make_regex(r"[^\r\n]*(?:\r|\n|\r\n)?")
_double_quote_escapes = make_regex(r"\\[\\'\"abfnrtv]")
_single_quote_escapes = make_regex(r"\\[\\']")
try:
# this is necessary because we only import these from typing
# when we are type checking, and the linter is upset if we
# re-import
import typing
Original = typing.NamedTuple(
"Original",
[
("string", typing.Text),
("line", int),
],
)
Binding = typing.NamedTuple(
"Binding",
[
("key", typing.Optional[typing.Text]),
("value", typing.Optional[typing.Text]),
("original", Original),
("error", bool),
],
)
except (ImportError, AttributeError):
from collections import namedtuple
Original = namedtuple( # type: ignore
"Original",
[
"string",
"line",
],
)
Binding = namedtuple( # type: ignore
"Binding",
[
"key",
"value",
"original",
"error",
],
)
class Position:
def __init__(self, chars, line):
# type: (int, int) -> None
self.chars = chars
self.line = line
@classmethod
def start(cls):
# type: () -> Position
return cls(chars=0, line=1)
def set(self, other):
# type: (Position) -> None
self.chars = other.chars
self.line = other.line
def advance(self, string):
# type: (Text) -> None
self.chars += len(string)
self.line += len(re.findall(_newline, string))
class Error(Exception):
pass
class Reader:
def __init__(self, stream):
# type: (IO[Text]) -> None
self.string = stream.read()
self.position = Position.start()
self.mark = Position.start()
def has_next(self):
# type: () -> bool
return self.position.chars < len(self.string)
def set_mark(self):
# type: () -> None
self.mark.set(self.position)
def get_marked(self):
# type: () -> Original
return Original(
string=self.string[self.mark.chars:self.position.chars],
line=self.mark.line,
)
def peek(self, count):
# type: (int) -> Text
return self.string[self.position.chars:self.position.chars + count]
def read(self, count):
# type: (int) -> Text
result = self.string[self.position.chars:self.position.chars + count]
if len(result) < count:
raise Error("read: End of string")
self.position.advance(result)
return result
def read_regex(self, regex):
# type: (Pattern[Text]) -> Sequence[Text]
match = regex.match(self.string, self.position.chars)
if match is None:
raise Error("read_regex: Pattern not found")
self.position.advance(self.string[match.start():match.end()])
return match.groups()
def decode_escapes(regex, string):
# type: (Pattern[Text], Text) -> Text
def decode_match(match):
# type: (Match[Text]) -> Text
return codecs.decode(match.group(0), 'unicode-escape') # type: ignore
return regex.sub(decode_match, string)
def parse_key(reader):
# type: (Reader) -> Optional[Text]
char = reader.peek(1)
if char == "#":
return None
elif char == "'":
(key,) = reader.read_regex(_single_quoted_key)
else:
(key,) = reader.read_regex(_unquoted_key)
return key
def parse_unquoted_value(reader):
# type: (Reader) -> Text
(part,) = reader.read_regex(_unquoted_value)
return re.sub(r"\s+#.*", "", part).rstrip()
def parse_value(reader):
# type: (Reader) -> Text
char = reader.peek(1)
if char == u"'":
(value,) = reader.read_regex(_single_quoted_value)
return decode_escapes(_single_quote_escapes, value)
elif char == u'"':
(value,) = reader.read_regex(_double_quoted_value)
return decode_escapes(_double_quote_escapes, value)
elif char in (u"", u"\n", u"\r"):
return u""
else:
return parse_unquoted_value(reader)
def parse_binding(reader):
# type: (Reader) -> Binding
reader.set_mark()
try:
reader.read_regex(_multiline_whitespace)
if not reader.has_next():
return Binding(
key=None,
value=None,
original=reader.get_marked(),
error=False,
)
reader.read_regex(_export)
key = parse_key(reader)
reader.read_regex(_whitespace)
if reader.peek(1) == "=":
reader.read_regex(_equal_sign)
value = parse_value(reader) # type: Optional[Text]
else:
value = None
reader.read_regex(_comment)
reader.read_regex(_end_of_line)
return Binding(
key=key,
value=value,
original=reader.get_marked(),
error=False,
)
except Error:
reader.read_regex(_rest_of_line)
return Binding(
key=None,
value=None,
original=reader.get_marked(),
error=True,
)
def parse_stream(stream):
# type: (IO[Text]) -> Iterator[Binding]
reader = Reader(stream)
while reader.has_next():
yield parse_binding(reader)
import re
from abc import ABCMeta
from .compat import IS_TYPE_CHECKING
if IS_TYPE_CHECKING:
from typing import Iterator, Mapping, Optional, Pattern, Text
_posix_variable = re.compile(
r"""
\$\{
(?P<name>[^\}:]*)
(?::-
(?P<default>[^\}]*)
)?
\}
""",
re.VERBOSE,
) # type: Pattern[Text]
class Atom():
__metaclass__ = ABCMeta
def __ne__(self, other):
# type: (object) -> bool
result = self.__eq__(other)
if result is NotImplemented:
return NotImplemented
return not result
def resolve(self, env):
# type: (Mapping[Text, Optional[Text]]) -> Text
raise NotImplementedError
class Literal(Atom):
def __init__(self, value):
# type: (Text) -> None
self.value = value
def __repr__(self):
# type: () -> str
return "Literal(value={})".format(self.value)
def __eq__(self, other):
# type: (object) -> bool
if not isinstance(other, self.__class__):
return NotImplemented
return self.value == other.value
def __hash__(self):
# type: () -> int
return hash((self.__class__, self.value))
def resolve(self, env):
# type: (Mapping[Text, Optional[Text]]) -> Text
return self.value
class Variable(Atom):
def __init__(self, name, default):
# type: (Text, Optional[Text]) -> None
self.name = name
self.default = default
def __repr__(self):
# type: () -> str
return "Variable(name={}, default={})".format(self.name, self.default)
def __eq__(self, other):
# type: (object) -> bool
if not isinstance(other, self.__class__):
return NotImplemented
return (self.name, self.default) == (other.name, other.default)
def __hash__(self):
# type: () -> int
return hash((self.__class__, self.name, self.default))
def resolve(self, env):
# type: (Mapping[Text, Optional[Text]]) -> Text
default = self.default if self.default is not None else ""
result = env.get(self.name, default)
return result if result is not None else ""
def parse_variables(value):
# type: (Text) -> Iterator[Atom]
cursor = 0
for match in _posix_variable.finditer(value):
(start, end) = match.span()
name = match.groupdict()["name"]
default = match.groupdict()["default"]
if start > cursor:
yield Literal(value=value[cursor:start])
yield Variable(name=name, default=default)
cursor = end
length = len(value)
if cursor < length:
yield Literal(value=value[cursor:length])
psycopg2 and the LGPL
---------------------
psycopg2 is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
psycopg2 is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
In addition, as a special exception, the copyright holders give
permission to link this program with the OpenSSL library (or with
modified versions of OpenSSL that use the same license as OpenSSL),
and distribute linked combinations including the two.
You must obey the GNU Lesser General Public License in all respects for
all of the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete
this exception statement from your version. If you delete this exception
statement from all source files in the program, then also delete it here.
You should have received a copy of the GNU Lesser General Public License
along with psycopg2 (see the doc/ directory.)
If not, see <https://www.gnu.org/licenses/>.
Alternative licenses
--------------------
The following BSD-like license applies (at your option) to the files following
the pattern ``psycopg/adapter*.{h,c}`` and ``psycopg/microprotocol*.{h,c}``:
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not
be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Metadata-Version: 2.1
Name: psycopg2
Version: 2.8.6
Summary: psycopg2 - Python-PostgreSQL Database Adapter
Home-page: https://psycopg.org/
Author: Federico Di Gregorio
Author-email: fog@initd.org
Maintainer: Daniele Varrazzo
Maintainer-email: daniele.varrazzo@gmail.org
License: LGPL with exceptions
Project-URL: Homepage, https://psycopg.org/
Project-URL: Documentation, https://www.psycopg.org/docs/
Project-URL: Code, https://github.com/psycopg/psycopg2
Project-URL: Issue Tracker, https://github.com/psycopg/psycopg2/issues
Project-URL: Download, https://pypi.org/project/psycopg2/
Platform: any
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: C
Classifier: Programming Language :: SQL
Classifier: Topic :: Database
Classifier: Topic :: Database :: Front-Ends
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: Unix
Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
Psycopg is the most popular PostgreSQL database adapter for the Python
programming language. Its main features are the complete implementation of
the Python DB API 2.0 specification and the thread safety (several threads can
share the same connection). It was designed for heavily multi-threaded
applications that create and destroy lots of cursors and make a large number
of concurrent "INSERT"s or "UPDATE"s.
Psycopg 2 is mostly implemented in C as a libpq wrapper, resulting in being
both efficient and secure. It features client-side and server-side cursors,
asynchronous communication and notifications, "COPY TO/COPY FROM" support.
Many Python types are supported out-of-the-box and adapted to matching
PostgreSQL data types; adaptation can be extended and customized thanks to a
flexible objects adaptation system.
Psycopg 2 is both Unicode and Python 3 friendly.
Documentation
-------------
Documentation is included in the ``doc`` directory and is `available online`__.
.. __: https://www.psycopg.org/docs/
For any other resource (source code repository, bug tracker, mailing list)
please check the `project homepage`__.
.. __: https://psycopg.org/
Installation
------------
Building Psycopg requires a few prerequisites (a C compiler, some development
packages): please check the install_ and the faq_ documents in the ``doc`` dir
or online for the details.
If prerequisites are met, you can install psycopg like any other Python
package, using ``pip`` to download it from PyPI_::
$ pip install psycopg2
or using ``setup.py`` if you have downloaded the source package locally::
$ python setup.py build
$ sudo python setup.py install
You can also obtain a stand-alone package, not requiring a compiler or
external libraries, by installing the `psycopg2-binary`_ package from PyPI::
$ pip install psycopg2-binary
The binary package is a practical choice for development and testing but in
production it is advised to use the package built from sources.
.. _PyPI: https://pypi.org/project/psycopg2/
.. _psycopg2-binary: https://pypi.org/project/psycopg2-binary/
.. _install: https://www.psycopg.org/docs/install.html#install-from-source
.. _faq: https://www.psycopg.org/docs/faq.html#faq-compile
:Linux/OSX: |travis|
:Windows: |appveyor|
.. |travis| image:: https://travis-ci.org/psycopg/psycopg2.svg?branch=master
:target: https://travis-ci.org/psycopg/psycopg2
:alt: Linux and OSX build status
.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/psycopg/psycopg2?branch=master&svg=true
:target: https://ci.appveyor.com/project/psycopg/psycopg2/branch/master
:alt: Windows build status
psycopg2-2.8.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
psycopg2-2.8.6.dist-info/LICENSE,sha256=lhS4XfyacsWyyjMUTB1-HtOxwpdFnZ-yimpXYsLo1xs,2238
psycopg2-2.8.6.dist-info/METADATA,sha256=htTa9QsaWzb-w-SRFJ9QVhgvA_61CejAgm6mNwAzTLA,4389
psycopg2-2.8.6.dist-info/RECORD,,
psycopg2-2.8.6.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
psycopg2-2.8.6.dist-info/WHEEL,sha256=2Kg4PzfJLrLEnxRV1e1jZf0TVEjxVcXZXjp8WtjE4tI,105
psycopg2-2.8.6.dist-info/top_level.txt,sha256=7dHGpLqQ3w-vGmGEVn-7uK90qU9fyrGdWWi7S-gTcnM,9
psycopg2/__init__.py,sha256=f1RIT_o7T7LU9OYn1RAbbGvOZFv1S0y3250h6nuKoSQ,4916
psycopg2/__pycache__/__init__.cpython-39.pyc,,
psycopg2/__pycache__/_ipaddress.cpython-39.pyc,,
psycopg2/__pycache__/_json.cpython-39.pyc,,
psycopg2/__pycache__/_lru_cache.cpython-39.pyc,,
psycopg2/__pycache__/_range.cpython-39.pyc,,
psycopg2/__pycache__/compat.cpython-39.pyc,,
psycopg2/__pycache__/errorcodes.cpython-39.pyc,,
psycopg2/__pycache__/errors.cpython-39.pyc,,
psycopg2/__pycache__/extensions.cpython-39.pyc,,
psycopg2/__pycache__/extras.cpython-39.pyc,,
psycopg2/__pycache__/pool.cpython-39.pyc,,
psycopg2/__pycache__/sql.cpython-39.pyc,,
psycopg2/__pycache__/tz.cpython-39.pyc,,
psycopg2/_ipaddress.py,sha256=VTb0XXYHHhwdAgFwGt8mGQvPzcVCah2XVSNYlpW5AzI,2967
psycopg2/_json.py,sha256=IRUpp3zIdrhw7cv5PdoXHsjEGHBeW9-vArKpxY9R7IU,7296
psycopg2/_lru_cache.py,sha256=DhDTMD9aQsMcLYHyg8bAunlh62TKljZ6bLAlWd5tTrc,4261
psycopg2/_psycopg.cp39-win_amd64.pyd,sha256=w81tneSq1KkgboGjXJAKqGhNU2gWgNl5dlxuZ7-YNEc,2399744
psycopg2/_range.py,sha256=XsuiPZ-6mf9W8vxlBsp7zqwKOQPCac_vLVvEyPhthA4,17705
psycopg2/compat.py,sha256=YAozNHFrE--nrjvV-g4kHPLbcmhOKVGVN84zo58VOqA,367
psycopg2/errorcodes.py,sha256=MRcquTgL_7iTmk8x47MA6KM5Z1-MK0trPZc5KZCnxTQ,14273
psycopg2/errors.py,sha256=iaaJeyL2pU9oMt9MsLaNlOPZipR0BXL0kOKABV2Tu_g,1420
psycopg2/extensions.py,sha256=T99Lv2oAYC_pjSuYDNVj2xVmWz9gO_S4KmnUEbZcCHs,7122
psycopg2/extras.py,sha256=pGt1UJdZkVaXDWjXz21kP7JEqEH0ER5FhemiOTmkXNw,44182
psycopg2/pool.py,sha256=NdulUZrkF2h-Nv_hOX5RXUz6WeiL0WCnbIkxIgAMjPM,6319
psycopg2/sql.py,sha256=RL1AGbpT5xzzVRNYxpeGbgMUojpkyqzwTZK1PNZUwWY,14903
psycopg2/tz.py,sha256=_DahbM5JJtkiFzVyyhNWX2RbjDUTASd4xDWVGQAGP-c,4446
Wheel-Version: 1.0
Generator: bdist_wheel (0.35.1)
Root-Is-Purelib: false
Tag: cp39-cp39-win_amd64
"""A Python driver for PostgreSQL
psycopg is a PostgreSQL_ database adapter for the Python_ programming
language. This is version 2, a complete rewrite of the original code to
provide new-style classes for connection and cursor objects and other sweet
candies. Like the original, psycopg 2 was written with the aim of being very
small and fast, and stable as a rock.
Homepage: https://psycopg.org/
.. _PostgreSQL: https://www.postgresql.org/
.. _Python: https://www.python.org/
:Groups:
* `Connections creation`: connect
* `Value objects constructors`: Binary, Date, DateFromTicks, Time,
TimeFromTicks, Timestamp, TimestampFromTicks
"""
# psycopg/__init__.py - initialization of the psycopg module
#
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
# Copyright (C) 2020 The Psycopg Team
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# In addition, as a special exception, the copyright holders give
# permission to link this program with the OpenSSL library (or with
# modified versions of OpenSSL that use the same license as OpenSSL),
# and distribute linked combinations including the two.
#
# You must obey the GNU Lesser General Public License in all respects for
# all of the code used other than OpenSSL.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
# Import modules needed by _psycopg to allow tools like py2exe to do
# their work without bothering about the module dependencies.
# Note: the first internal import should be _psycopg, otherwise the real cause
# of a failed loading of the C module may get hidden, see
# https://archives.postgresql.org/psycopg/2011-02/msg00044.php
# Import the DBAPI-2.0 stuff into top-level module.
from psycopg2._psycopg import ( # noqa
BINARY, NUMBER, STRING, DATETIME, ROWID,
Binary, Date, Time, Timestamp,
DateFromTicks, TimeFromTicks, TimestampFromTicks,
Error, Warning, DataError, DatabaseError, ProgrammingError, IntegrityError,
InterfaceError, InternalError, NotSupportedError, OperationalError,
_connect, apilevel, threadsafety, paramstyle,
__version__, __libpq_version__,
)
from psycopg2 import tz # noqa
# Register default adapters.
from psycopg2 import extensions as _ext
_ext.register_adapter(tuple, _ext.SQL_IN)
_ext.register_adapter(type(None), _ext.NoneAdapter)
# Register the Decimal adapter here instead of in the C layer.
# This way a new class is registered for each sub-interpreter.
# See ticket #52
from decimal import Decimal # noqa
from psycopg2._psycopg import Decimal as Adapter # noqa
_ext.register_adapter(Decimal, Adapter)
del Decimal, Adapter
def connect(dsn=None, connection_factory=None, cursor_factory=None, **kwargs):
"""
Create a new database connection.
The connection parameters can be specified as a string:
conn = psycopg2.connect("dbname=test user=postgres password=secret")
or using a set of keyword arguments:
conn = psycopg2.connect(database="test", user="postgres", password="secret")
Or as a mix of both. The basic connection parameters are:
- *dbname*: the database name
- *database*: the database name (only as keyword argument)
- *user*: user name used to authenticate
- *password*: password used to authenticate
- *host*: database host address (defaults to UNIX socket if not provided)
- *port*: connection port number (defaults to 5432 if not provided)
Using the *connection_factory* parameter a different class or connections
factory can be specified. It should be a callable object taking a dsn
argument.
Using the *cursor_factory* parameter, a new default cursor factory will be
used by cursor().
Using *async*=True an asynchronous connection will be created. *async_* is
a valid alias (for Python versions where ``async`` is a keyword).
Any other keyword parameter will be passed to the underlying client
library: the list of supported parameters depends on the library version.
"""
kwasync = {}
if 'async' in kwargs:
kwasync['async'] = kwargs.pop('async')
if 'async_' in kwargs:
kwasync['async_'] = kwargs.pop('async_')
if dsn is None and not kwargs:
raise TypeError('missing dsn and no parameters')
dsn = _ext.make_dsn(dsn, **kwargs)
conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
if cursor_factory is not None:
conn.cursor_factory = cursor_factory
return conn
"""Implementation of the ipaddres-based network types adaptation
"""
# psycopg/_ipaddress.py - Ipaddres-based network types adaptation
#
# Copyright (C) 2016-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
# Copyright (C) 2020 The Psycopg Team
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# In addition, as a special exception, the copyright holders give
# permission to link this program with the OpenSSL library (or with
# modified versions of OpenSSL that use the same license as OpenSSL),
# and distribute linked combinations including the two.
#
# You must obey the GNU Lesser General Public License in all respects for
# all of the code used other than OpenSSL.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
from psycopg2.extensions import (
new_type, new_array_type, register_type, register_adapter, QuotedString)
from psycopg2.compat import text_type
# The module is imported on register_ipaddress
ipaddress = None
# The typecasters are created only once
_casters = None
def register_ipaddress(conn_or_curs=None):
"""
Register conversion support between `ipaddress` objects and `network types`__.
:param conn_or_curs: the scope where to register the type casters.
If `!None` register them globally.
After the function is called, PostgreSQL :sql:`inet` values will be
converted into `~ipaddress.IPv4Interface` or `~ipaddress.IPv6Interface`
objects, :sql:`cidr` values into into `~ipaddress.IPv4Network` or
`~ipaddress.IPv6Network`.
.. __: https://www.postgresql.org/docs/current/static/datatype-net-types.html
"""
global ipaddress
import ipaddress
global _casters
if _casters is None:
_casters = _make_casters()
for c in _casters:
register_type(c, conn_or_curs)
for t in [ipaddress.IPv4Interface, ipaddress.IPv6Interface,
ipaddress.IPv4Network, ipaddress.IPv6Network]:
register_adapter(t, adapt_ipaddress)
def _make_casters():
inet = new_type((869,), 'INET', cast_interface)
ainet = new_array_type((1041,), 'INET[]', inet)
cidr = new_type((650,), 'CIDR', cast_network)
acidr = new_array_type((651,), 'CIDR[]', cidr)
return [inet, ainet, cidr, acidr]
def cast_interface(s, cur=None):
if s is None:
return None
# Py2 version force the use of unicode. meh.
return ipaddress.ip_interface(text_type(s))
def cast_network(s, cur=None):
if s is None:
return None
return ipaddress.ip_network(text_type(s))
def adapt_ipaddress(obj):
return QuotedString(str(obj))
"""Implementation of the JSON adaptation objects
This module exists to avoid a circular import problem: pyscopg2.extras depends
on psycopg2.extension, so I can't create the default JSON typecasters in
extensions importing register_json from extras.
"""
# psycopg/_json.py - Implementation of the JSON adaptation objects
#
# Copyright (C) 2012-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
# Copyright (C) 2020 The Psycopg Team
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# In addition, as a special exception, the copyright holders give
# permission to link this program with the OpenSSL library (or with
# modified versions of OpenSSL that use the same license as OpenSSL),
# and distribute linked combinations including the two.
#
# You must obey the GNU Lesser General Public License in all respects for
# all of the code used other than OpenSSL.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
import json
from psycopg2._psycopg import ISQLQuote, QuotedString
from psycopg2._psycopg import new_type, new_array_type, register_type
from psycopg2.compat import PY2
# oids from PostgreSQL 9.2
JSON_OID = 114
JSONARRAY_OID = 199
# oids from PostgreSQL 9.4
JSONB_OID = 3802
JSONBARRAY_OID = 3807
class Json(object):
"""
An `~psycopg2.extensions.ISQLQuote` wrapper to adapt a Python object to
:sql:`json` data type.
`!Json` can be used to wrap any object supported by the provided *dumps*
function. If none is provided, the standard :py:func:`json.dumps()` is
used.
"""
def __init__(self, adapted, dumps=None):
self.adapted = adapted
self._conn = None
self._dumps = dumps or json.dumps
def __conform__(self, proto):
if proto is ISQLQuote:
return self
def dumps(self, obj):
"""Serialize *obj* in JSON format.
The default is to call `!json.dumps()` or the *dumps* function
provided in the constructor. You can override this method to create a
customized JSON wrapper.
"""
return self._dumps(obj)
def prepare(self, conn):
self._conn = conn
def getquoted(self):
s = self.dumps(self.adapted)
qs = QuotedString(s)
if self._conn is not None:
qs.prepare(self._conn)
return qs.getquoted()
if PY2:
def __str__(self):
return self.getquoted()
else:
def __str__(self):
# getquoted is binary in Py3
return self.getquoted().decode('ascii', 'replace')
def register_json(conn_or_curs=None, globally=False, loads=None,
oid=None, array_oid=None, name='json'):
"""Create and register typecasters converting :sql:`json` type to Python objects.
:param conn_or_curs: a connection or cursor used to find the :sql:`json`
and :sql:`json[]` oids; the typecasters are registered in a scope
limited to this object, unless *globally* is set to `!True`. It can be
`!None` if the oids are provided
:param globally: if `!False` register the typecasters only on
*conn_or_curs*, otherwise register them globally
:param loads: the function used to parse the data into a Python object. If
`!None` use `!json.loads()`, where `!json` is the module chosen
according to the Python version (see above)
:param oid: the OID of the :sql:`json` type if known; If not, it will be
queried on *conn_or_curs*
:param array_oid: the OID of the :sql:`json[]` array type if known;
if not, it will be queried on *conn_or_curs*
:param name: the name of the data type to look for in *conn_or_curs*
The connection or cursor passed to the function will be used to query the
database and look for the OID of the :sql:`json` type (or an alternative
type if *name* if provided). No query is performed if *oid* and *array_oid*
are provided. Raise `~psycopg2.ProgrammingError` if the type is not found.
"""
if oid is None:
oid, array_oid = _get_json_oids(conn_or_curs, name)
JSON, JSONARRAY = _create_json_typecasters(
oid, array_oid, loads=loads, name=name.upper())
register_type(JSON, not globally and conn_or_curs or None)
if JSONARRAY is not None:
register_type(JSONARRAY, not globally and conn_or_curs or None)
return JSON, JSONARRAY
def register_default_json(conn_or_curs=None, globally=False, loads=None):
"""
Create and register :sql:`json` typecasters for PostgreSQL 9.2 and following.
Since PostgreSQL 9.2 :sql:`json` is a builtin type, hence its oid is known
and fixed. This function allows specifying a customized *loads* function
for the default :sql:`json` type without querying the database.
All the parameters have the same meaning of `register_json()`.
"""
return register_json(conn_or_curs=conn_or_curs, globally=globally,
loads=loads, oid=JSON_OID, array_oid=JSONARRAY_OID)
def register_default_jsonb(conn_or_curs=None, globally=False, loads=None):
"""
Create and register :sql:`jsonb` typecasters for PostgreSQL 9.4 and following.
As in `register_default_json()`, the function allows to register a
customized *loads* function for the :sql:`jsonb` type at its known oid for
PostgreSQL 9.4 and following versions. All the parameters have the same
meaning of `register_json()`.
"""
return register_json(conn_or_curs=conn_or_curs, globally=globally,
loads=loads, oid=JSONB_OID, array_oid=JSONBARRAY_OID, name='jsonb')
def _create_json_typecasters(oid, array_oid, loads=None, name='JSON'):
"""Create typecasters for json data type."""
if loads is None:
loads = json.loads
def typecast_json(s, cur):
if s is None:
return None
return loads(s)
JSON = new_type((oid, ), name, typecast_json)
if array_oid is not None:
JSONARRAY = new_array_type((array_oid, ), "%sARRAY" % name, JSON)
else:
JSONARRAY = None
return JSON, JSONARRAY
def _get_json_oids(conn_or_curs, name='json'):
# lazy imports
from psycopg2.extensions import STATUS_IN_TRANSACTION
from psycopg2.extras import _solve_conn_curs
conn, curs = _solve_conn_curs(conn_or_curs)
# Store the transaction status of the connection to revert it after use
conn_status = conn.status
# column typarray not available before PG 8.3
typarray = conn.info.server_version >= 80300 and "typarray" or "NULL"
# get the oid for the hstore
curs.execute(
"SELECT t.oid, %s FROM pg_type t WHERE t.typname = %%s;"
% typarray, (name,))
r = curs.fetchone()
# revert the status of the connection as before the command
if conn_status != STATUS_IN_TRANSACTION and not conn.autocommit:
conn.rollback()
if not r:
raise conn.ProgrammingError("%s data type not found" % name)
return r
"""
LRU cache implementation for Python 2.7
Ported from http://code.activestate.com/recipes/578078/ and simplified for our
use (only support maxsize > 0 and positional arguments).
"""
from collections import namedtuple
from functools import update_wrapper
from threading import RLock
_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])
def lru_cache(maxsize=100):
"""Least-recently-used cache decorator.
Arguments to the cached function must be hashable.
See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
"""
def decorating_function(user_function):
cache = dict()
stats = [0, 0] # make statistics updateable non-locally
HITS, MISSES = 0, 1 # names for the stats fields
cache_get = cache.get # bound method to lookup key or return None
_len = len # localize the global len() function
lock = RLock() # linkedlist updates aren't threadsafe
root = [] # root of the circular doubly linked list
root[:] = [root, root, None, None] # initialize by pointing to self
nonlocal_root = [root] # make updateable non-locally
PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields
assert maxsize and maxsize > 0, "maxsize %s not supported" % maxsize
def wrapper(*args):
# size limited caching that tracks accesses by recency
key = args
with lock:
link = cache_get(key)
if link is not None:
# record recent use of the key by moving it to the
# front of the list
root, = nonlocal_root
link_prev, link_next, key, result = link
link_prev[NEXT] = link_next
link_next[PREV] = link_prev
last = root[PREV]
last[NEXT] = root[PREV] = link
link[PREV] = last
link[NEXT] = root
stats[HITS] += 1
return result
result = user_function(*args)
with lock:
root, = nonlocal_root
if key in cache:
# getting here means that this same key was added to the
# cache while the lock was released. since the link
# update is already done, we need only return the
# computed result and update the count of misses.
pass
elif _len(cache) >= maxsize:
# use the old root to store the new key and result
oldroot = root
oldroot[KEY] = key
oldroot[RESULT] = result
# empty the oldest link and make it the new root
root = nonlocal_root[0] = oldroot[NEXT]
oldkey = root[KEY]
# oldvalue = root[RESULT]
root[KEY] = root[RESULT] = None
# now update the cache dictionary for the new links
del cache[oldkey]
cache[key] = oldroot
else:
# put result in a new link at the front of the list
last = root[PREV]
link = [last, root, key, result]
last[NEXT] = root[PREV] = cache[key] = link
stats[MISSES] += 1
return result
def cache_info():
"""Report cache statistics"""
with lock:
return _CacheInfo(stats[HITS], stats[MISSES], maxsize, len(cache))
def cache_clear():
"""Clear the cache and cache statistics"""
with lock:
cache.clear()
root = nonlocal_root[0]
root[:] = [root, root, None, None]
stats[:] = [0, 0]
wrapper.__wrapped__ = user_function
wrapper.cache_info = cache_info
wrapper.cache_clear = cache_clear
return update_wrapper(wrapper, user_function)
return decorating_function
This diff is collapsed.
import sys
__all__ = ['string_types', 'text_type', 'lru_cache']
if sys.version_info[0] == 2:
# Python 2
PY2 = True
PY3 = False
string_types = basestring,
text_type = unicode
from ._lru_cache import lru_cache
else:
# Python 3
PY2 = False
PY3 = True
string_types = str,
text_type = str
from functools import lru_cache
This diff is collapsed.
"""Error classes for PostgreSQL error codes
"""
# psycopg/errors.py - SQLSTATE and DB-API exceptions
#
# Copyright (C) 2018-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
# Copyright (C) 2020 The Psycopg Team
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# In addition, as a special exception, the copyright holders give
# permission to link this program with the OpenSSL library (or with
# modified versions of OpenSSL that use the same license as OpenSSL),
# and distribute linked combinations including the two.
#
# You must obey the GNU Lesser General Public License in all respects for
# all of the code used other than OpenSSL.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
#
# NOTE: the exceptions are injected into this module by the C extention.
#
def lookup(code):
"""Lookup an error code and return its exception class.
Raise `!KeyError` if the code is not found.
"""
from psycopg2._psycopg import sqlstate_errors # avoid circular import
return sqlstate_errors[code]
"""psycopg extensions to the DBAPI-2.0
This module holds all the extensions to the DBAPI-2.0 provided by psycopg.
- `connection` -- the new-type inheritable connection class
- `cursor` -- the new-type inheritable cursor class
- `lobject` -- the new-type inheritable large object class
- `adapt()` -- exposes the PEP-246_ compatible adapting mechanism used
by psycopg to adapt Python types to PostgreSQL ones
.. _PEP-246: https://www.python.org/dev/peps/pep-0246/
"""
# psycopg/extensions.py - DBAPI-2.0 extensions specific to psycopg
#
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
# Copyright (C) 2020 The Psycopg Team
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# In addition, as a special exception, the copyright holders give
# permission to link this program with the OpenSSL library (or with
# modified versions of OpenSSL that use the same license as OpenSSL),
# and distribute linked combinations including the two.
#
# You must obey the GNU Lesser General Public License in all respects for
# all of the code used other than OpenSSL.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
import re as _re
from psycopg2._psycopg import ( # noqa
BINARYARRAY, BOOLEAN, BOOLEANARRAY, BYTES, BYTESARRAY, DATE, DATEARRAY,
DATETIMEARRAY, DECIMAL, DECIMALARRAY, FLOAT, FLOATARRAY, INTEGER,
INTEGERARRAY, INTERVAL, INTERVALARRAY, LONGINTEGER, LONGINTEGERARRAY,
ROWIDARRAY, STRINGARRAY, TIME, TIMEARRAY, UNICODE, UNICODEARRAY,
AsIs, Binary, Boolean, Float, Int, QuotedString, )
try:
from psycopg2._psycopg import ( # noqa
MXDATE, MXDATETIME, MXDATETIMETZ, MXINTERVAL, MXTIME, MXDATEARRAY,
MXDATETIMEARRAY, MXDATETIMETZARRAY, MXINTERVALARRAY, MXTIMEARRAY,
DateFromMx, TimeFromMx, TimestampFromMx, IntervalFromMx, )
except ImportError:
pass
from psycopg2._psycopg import ( # noqa
PYDATE, PYDATETIME, PYDATETIMETZ, PYINTERVAL, PYTIME, PYDATEARRAY,
PYDATETIMEARRAY, PYDATETIMETZARRAY, PYINTERVALARRAY, PYTIMEARRAY,
DateFromPy, TimeFromPy, TimestampFromPy, IntervalFromPy, )
from psycopg2._psycopg import ( # noqa
adapt, adapters, encodings, connection, cursor,
lobject, Xid, libpq_version, parse_dsn, quote_ident,
string_types, binary_types, new_type, new_array_type, register_type,
ISQLQuote, Notify, Diagnostics, Column, ConnectionInfo,
QueryCanceledError, TransactionRollbackError,
set_wait_callback, get_wait_callback, encrypt_password, )
"""Isolation level values."""
ISOLATION_LEVEL_AUTOCOMMIT = 0
ISOLATION_LEVEL_READ_UNCOMMITTED = 4
ISOLATION_LEVEL_READ_COMMITTED = 1
ISOLATION_LEVEL_REPEATABLE_READ = 2
ISOLATION_LEVEL_SERIALIZABLE = 3
ISOLATION_LEVEL_DEFAULT = None
"""psycopg connection status values."""
STATUS_SETUP = 0
STATUS_READY = 1
STATUS_BEGIN = 2
STATUS_SYNC = 3 # currently unused
STATUS_ASYNC = 4 # currently unused
STATUS_PREPARED = 5
# This is a useful mnemonic to check if the connection is in a transaction
STATUS_IN_TRANSACTION = STATUS_BEGIN
"""psycopg asynchronous connection polling values"""
POLL_OK = 0
POLL_READ = 1
POLL_WRITE = 2
POLL_ERROR = 3
"""Backend transaction status values."""
TRANSACTION_STATUS_IDLE = 0
TRANSACTION_STATUS_ACTIVE = 1
TRANSACTION_STATUS_INTRANS = 2
TRANSACTION_STATUS_INERROR = 3
TRANSACTION_STATUS_UNKNOWN = 4
def register_adapter(typ, callable):
"""Register 'callable' as an ISQLQuote adapter for type 'typ'."""
adapters[(typ, ISQLQuote)] = callable
# The SQL_IN class is the official adapter for tuples starting from 2.0.6.
class SQL_IN(object):
"""Adapt any iterable to an SQL quotable object."""
def __init__(self, seq):
self._seq = seq
self._conn = None
def prepare(self, conn):
self._conn = conn
def getquoted(self):
# this is the important line: note how every object in the
# list is adapted and then how getquoted() is called on it
pobjs = [adapt(o) for o in self._seq]
if self._conn is not None:
for obj in pobjs:
if hasattr(obj, 'prepare'):
obj.prepare(self._conn)
qobjs = [o.getquoted() for o in pobjs]
return b'(' + b', '.join(qobjs) + b')'
def __str__(self):
return str(self.getquoted())
class NoneAdapter(object):
"""Adapt None to NULL.
This adapter is not used normally as a fast path in mogrify uses NULL,
but it makes easier to adapt composite types.
"""
def __init__(self, obj):
pass
def getquoted(self, _null=b"NULL"):
return _null
def make_dsn(dsn=None, **kwargs):
"""Convert a set of keywords into a connection strings."""
if dsn is None and not kwargs:
return ''
# If no kwarg is specified don't mung the dsn, but verify it
if not kwargs:
parse_dsn(dsn)
return dsn
# Override the dsn with the parameters
if 'database' in kwargs:
if 'dbname' in kwargs:
raise TypeError(
"you can't specify both 'database' and 'dbname' arguments")
kwargs['dbname'] = kwargs.pop('database')
# Drop the None arguments
kwargs = {k: v for (k, v) in kwargs.items() if v is not None}
if dsn is not None:
tmp = parse_dsn(dsn)
tmp.update(kwargs)
kwargs = tmp
dsn = " ".join(["%s=%s" % (k, _param_escape(str(v)))
for (k, v) in kwargs.items()])
# verify that the returned dsn is valid
parse_dsn(dsn)
return dsn
def _param_escape(s,
re_escape=_re.compile(r"([\\'])"),
re_space=_re.compile(r'\s')):
"""
Apply the escaping rule required by PQconnectdb
"""
if not s:
return "''"
s = re_escape.sub(r'\\\1', s)
if re_space.search(s):
s = "'" + s + "'"
return s
# Create default json typecasters for PostgreSQL 9.2 oids
from psycopg2._json import register_default_json, register_default_jsonb # noqa
try:
JSON, JSONARRAY = register_default_json()
JSONB, JSONBARRAY = register_default_jsonb()
except ImportError:
pass
del register_default_json, register_default_jsonb
# Create default Range typecasters
from psycopg2. _range import Range # noqa
del Range
# Add the "cleaned" version of the encodings to the key.
# When the encoding is set its name is cleaned up from - and _ and turned
# uppercase, so an encoding not respecting these rules wouldn't be found in the
# encodings keys and would raise an exception with the unicode typecaster
for k, v in list(encodings.items()):
k = k.replace('_', '').replace('-', '').upper()
encodings[k] = v
del k, v
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment