mirror of
https://github.com/psycopg/psycopg2.git
synced 2025-04-22 01:11:58 +03:00
Compare commits
1587 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
dcb302493a | ||
|
5509e01108 | ||
|
6cd0fbdc49 | ||
|
cee23d83e0 | ||
|
5bfba4c961 | ||
|
b943457896 | ||
|
d0bc154f31 | ||
|
1eac4fd4da | ||
|
c8abc5ce61 | ||
|
65626ec565 | ||
|
310bc75532 | ||
|
d43e5fe092 | ||
|
3b684f91ca | ||
|
bf7fc6cfa4 | ||
|
979d56a797 | ||
|
4903f1c5d6 | ||
|
1dc7b5b70b | ||
|
ed4ba11d17 | ||
|
947f731400 | ||
|
b8d49e6280 | ||
|
4dfa680a71 | ||
|
f4282c6d87 | ||
|
a8765121d9 | ||
|
bb52bcf769 | ||
|
fa24c922e7 | ||
|
3c7889b0e7 | ||
|
e83754a414 | ||
|
a805acf59f | ||
|
78561ac99d | ||
|
5283a835dc | ||
|
f64dd397fd | ||
|
cba6d39be0 | ||
|
282360dd04 | ||
|
362cb00978 | ||
|
eaeeb76944 | ||
|
4987362fb4 | ||
|
8c9a35de38 | ||
|
563b55a725 | ||
|
dac8fa5632 | ||
|
e1cf23d9c7 | ||
|
0eccfbec47 | ||
|
26f0f13b39 | ||
|
a59079a4f2 | ||
|
f9780aa054 | ||
|
658afe4cd9 | ||
|
f79867c9f2 | ||
|
dc5249ba01 | ||
|
7c2706a8b4 | ||
|
4a4b5acdc2 | ||
|
efc5ad01e0 | ||
|
866bcef589 | ||
|
3b9aa7cf9f | ||
|
829a7a2be9 | ||
|
a971c11d50 | ||
|
00870545b7 | ||
|
bf45060074 | ||
|
5fb59cd6ee | ||
|
e0d1daf290 | ||
|
941ac9a724 | ||
|
4e473010a3 | ||
|
8947b00142 | ||
|
46191f1fde | ||
|
e73d2fa9f0 | ||
|
89005ac5b8 | ||
|
bfdffc2c57 | ||
|
ad5bee7054 | ||
|
37d1de1c8f | ||
|
abf2723c0a | ||
|
2da65a715c | ||
|
3fa60fd268 | ||
|
1c1484e43b | ||
|
c81cec604f | ||
|
7fe8cb77ca | ||
|
b39d5d6492 | ||
|
921510d5be | ||
|
999d7a6d01 | ||
|
3eee3e336d | ||
|
1e0086b1fe | ||
|
4fe28d661a | ||
|
14e06d8185 | ||
|
959339cefb | ||
|
fb77bdca0b | ||
|
ef7053c070 | ||
|
ea71fbcd46 | ||
|
0c5b5f4ec3 | ||
|
20fcfd6786 | ||
|
329f43c762 | ||
|
9f020124f8 | ||
|
8b17e218be | ||
|
c96f991a8d | ||
|
3450d159b5 | ||
|
5108191aa5 | ||
|
638be85eb6 | ||
|
0b01ded426 | ||
|
46238ba351 | ||
|
51dd59ef9d | ||
|
333b3b7ac4 | ||
|
7a8f4d6222 | ||
|
b747b5b0fd | ||
|
1781e8b2c9 | ||
|
fdb204b4e3 | ||
|
09b82e4094 | ||
|
97df29a312 | ||
|
daeec37fab | ||
|
c0666b0935 | ||
|
cc21faa4f4 | ||
|
63947e2552 | ||
|
52df8371f3 | ||
|
feeb989323 | ||
|
e8d92b74fd | ||
|
026b5bf3ab | ||
|
02b5e226f4 | ||
|
57009707b1 | ||
|
3182ea2303 | ||
|
ea32730a39 | ||
|
deb00e5454 | ||
|
8c824d0e47 | ||
|
1bf8e77ea2 | ||
|
af3ee06ec0 | ||
|
963fb1190b | ||
|
27a99dac72 | ||
|
78690cfaf8 | ||
|
259d15ae3e | ||
|
77039cad63 | ||
|
e6e465c509 | ||
|
12700a5f02 | ||
|
271dd1fce7 | ||
|
e4b2a197c6 | ||
|
20bb486663 | ||
|
f401d0b738 | ||
|
4912be0e7f | ||
|
aabac5df31 | ||
|
a12dbc4357 | ||
|
bc82c8f9cc | ||
|
c38aa27d7d | ||
|
bd96594e2d | ||
|
182a51a33f | ||
|
76b703e910 | ||
|
29a65f756c | ||
|
6d815f5df9 | ||
|
c7326f8da7 | ||
|
68d786b610 | ||
|
7054e1aadf | ||
|
ac25d3bdc0 | ||
|
9535462ce9 | ||
|
d88e4c2a3c | ||
|
31a80410db | ||
|
d6c81b4ff0 | ||
|
c6f30880a2 | ||
|
e3664380c4 | ||
|
fdf957dcbd | ||
|
3e7bb8d1aa | ||
|
07c83ef8bb | ||
|
f07b3ad0a6 | ||
|
611c610041 | ||
|
25c40f8ac3 | ||
|
ba92a22bc9 | ||
|
3c58e96e10 | ||
|
626078388a | ||
|
c34bf2f2f9 | ||
|
94ba06748f | ||
|
c5528da2dc | ||
|
2dc137975a | ||
|
bc79abace1 | ||
|
846ae52ab2 | ||
|
217f4120ca | ||
|
4d4d2bc444 | ||
|
4b637ec34a | ||
|
53bda13afa | ||
|
8ef195f2ff | ||
|
1b013b529b | ||
|
0a4a469669 | ||
|
5fb1305a14 | ||
|
898cbff5a6 | ||
|
7dd193a7f7 | ||
|
1a0c02a6f4 | ||
|
1454b14ae0 | ||
|
8e186dd7e9 | ||
|
7236a1f851 | ||
|
63d30aa397 | ||
|
6b80bd0648 | ||
|
5a96021612 | ||
|
4a46aa39a5 | ||
|
3430dcdee6 | ||
|
1b255b7dc3 | ||
|
52c4d6fad4 | ||
|
ee3a069f1d | ||
|
9cfe80ea55 | ||
|
64b159676f | ||
|
8fe9861df5 | ||
|
39f12bbfc5 | ||
|
47b93efcf8 | ||
|
7749898a94 | ||
|
46bc175cc7 | ||
|
dd9c6659bc | ||
|
62490a6bcc | ||
|
c59ff6a4b7 | ||
|
b241def64b | ||
|
2df79c5a5f | ||
|
dd2ff2af56 | ||
|
b46424447f | ||
|
bab166e2c1 | ||
|
c5aa98d8bc | ||
|
cbbf195a23 | ||
|
2b7383c9f9 | ||
|
50145014e8 | ||
|
9ac54b3615 | ||
|
f5d6366287 | ||
|
bbc5fd3180 | ||
|
9e5847222d | ||
|
cefb818105 | ||
|
37ab1d8877 | ||
|
1d3a89a0bb | ||
|
af05c3a1ec | ||
|
1d3d5e905f | ||
|
f28502663f | ||
|
2eac70786e | ||
|
476a969bd8 | ||
|
5667026883 | ||
|
521981584d | ||
|
06c3c3a557 | ||
|
808007456d | ||
|
a9db3228d3 | ||
|
52cd94442c | ||
|
d116b80c5f | ||
|
e7ce6761e1 | ||
|
efae570a07 | ||
|
d1c7e6a094 | ||
|
9b91b09f9c | ||
|
b5dd3aae86 | ||
|
367ea40b1e | ||
|
ec531bee31 | ||
|
8a2deb39ed | ||
|
e5ad0ab2d9 | ||
|
d8e6426433 | ||
|
506a10026a | ||
|
cb12317d21 | ||
|
e7c5f95bf6 | ||
|
09d6e3cf64 | ||
|
5d19c6ef7e | ||
|
9dbe8c6757 | ||
|
fed22d39e9 | ||
|
19ddbc47ca | ||
|
cdc83d64db | ||
|
3db4abcfa4 | ||
|
8f40c648af | ||
|
45599b2716 | ||
|
787a7b90ae | ||
|
55aef83fa1 | ||
|
3487f627e2 | ||
|
f59d626fe3 | ||
|
22575528be | ||
|
0f457a01d9 | ||
|
8ea5d0c0b8 | ||
|
391386cfb9 | ||
|
0d7953a521 | ||
|
73969ba3e7 | ||
|
f469331af5 | ||
|
8830e30f73 | ||
|
c3b65d63b6 | ||
|
e85ef2298b | ||
|
8d7f660309 | ||
|
8449844af3 | ||
|
d956eaa3b1 | ||
|
7babeccbec | ||
|
6c48b63ae4 | ||
|
b05a581931 | ||
|
694a20fb95 | ||
|
490c53bace | ||
|
cdca0a20e0 | ||
|
60ed2770f3 | ||
|
8764a85320 | ||
|
f1dfbd59af | ||
|
e3f8cf0702 | ||
|
d8aa60221d | ||
|
12b1432fe1 | ||
|
f5e870dcc0 | ||
|
87dc783bc6 | ||
|
8c50af551d | ||
|
b125d9dd66 | ||
|
d04a420bce | ||
|
f900fa4960 | ||
|
7ad357599f | ||
|
31b37685b7 | ||
|
163dadb6c6 | ||
|
ec39e1e406 | ||
|
7cd7b97d5d | ||
|
616dab7064 | ||
|
f54cf3b87b | ||
|
dd97344149 | ||
|
171371da5a | ||
|
b203be11a6 | ||
|
dc007e790a | ||
|
dec28a21ac | ||
|
dd1724c447 | ||
|
c203d681c4 | ||
|
58c6a07e43 | ||
|
195b254937 | ||
|
0ee9d840a1 | ||
|
9387bd3c09 | ||
|
6de8c0c6d2 | ||
|
f7618f8bf5 | ||
|
3aadecebaa | ||
|
90e8c80ed1 | ||
|
82d679cdb3 | ||
|
b0ddf6ea90 | ||
|
423a663306 | ||
|
f339bb30fb | ||
|
5d2e51e76e | ||
|
6d8382b7ed | ||
|
9c30fdbc63 | ||
|
ed3d44562d | ||
|
513b0019b1 | ||
|
6eb4fab1db | ||
|
442f300e91 | ||
|
5e957daa82 | ||
|
c8697e6c67 | ||
|
a9153ac373 | ||
|
701637b5fa | ||
|
5ccd977e2b | ||
|
9380f2a721 | ||
|
7e1e801899 | ||
|
e154cbe5aa | ||
|
bca72937d8 | ||
|
f8c1cff6a3 | ||
|
ee34198bf6 | ||
|
659910ee81 | ||
|
cecff195fc | ||
|
a61f30b2d2 | ||
|
96f0f63de2 | ||
|
779a1370ce | ||
|
1092d437c0 | ||
|
1afbaf495e | ||
|
a9b814cb53 | ||
|
ac488acee3 | ||
|
f935476b3f | ||
|
14355e9c69 | ||
|
7fdf77065d | ||
|
690772f093 | ||
|
a75afe4d83 | ||
|
f9442744af | ||
|
1ec0bb5633 | ||
|
87279d5d03 | ||
|
accd1965bb | ||
|
d0216ce68d | ||
|
a35549d0ad | ||
|
6043dac0ea | ||
|
364b0e0563 | ||
|
4c3e0e5f1d | ||
|
75c659a5e7 | ||
|
8b2450287e | ||
|
cb3353be1f | ||
|
f2852a520a | ||
|
012a20b010 | ||
|
fbba461052 | ||
|
62743c3be1 | ||
|
6de23eb793 | ||
|
2bee47efac | ||
|
054123254e | ||
|
5d96b0c024 | ||
|
34615b7629 | ||
|
34c54f3fc3 | ||
|
497ad5c7c0 | ||
|
de58332bdd | ||
|
12bc9d68c4 | ||
|
9bcca1a7b0 | ||
|
f2d13ec809 | ||
|
e14e3385b4 | ||
|
1c80c9d8d5 | ||
|
e9dc1df889 | ||
|
dbd6577d91 | ||
|
94a660aa6f | ||
|
584197f57d | ||
|
5e0b02afb2 | ||
|
93aa469f16 | ||
|
8a6b280d86 | ||
|
9bfde497e5 | ||
|
c3c24cd67b | ||
|
501b0412f6 | ||
|
9154d0920c | ||
|
3d29ace058 | ||
|
46022cb162 | ||
|
f4144615f7 | ||
|
c3b35ba510 | ||
|
c20c13c493 | ||
|
5c02fdaa0d | ||
|
9c32457c28 | ||
|
a1fd2da1dc | ||
|
d70d3ee482 | ||
|
16c07ba9c4 | ||
|
cb7109dfa9 | ||
|
eb893e65f0 | ||
|
b0b09cbb24 | ||
|
63352d7da0 | ||
|
4097b4f2a0 | ||
|
8a18ff7699 | ||
|
850c585501 | ||
|
2f094841b2 | ||
|
96156727c0 | ||
|
5e9572aff8 | ||
|
24a8d600bf | ||
|
b2a09fb404 | ||
|
b029bd80d4 | ||
|
500f438033 | ||
|
ade98c1359 | ||
|
38a411dc07 | ||
|
214a8efe64 | ||
|
d5c7ec7ae8 | ||
|
8f11821c17 | ||
|
58654990d6 | ||
|
0dec435856 | ||
|
4e13acdc88 | ||
|
f08019e356 | ||
|
33d3c074fa | ||
|
80df0553a6 | ||
|
a47fcdd508 | ||
|
9097a5b989 | ||
|
4d10f1235f | ||
|
6e972200a3 | ||
|
3465ce282e | ||
|
f40ad0f3ae | ||
|
491296e0f5 | ||
|
ee056bc6e8 | ||
|
c32dbf357c | ||
|
bc65c636ae | ||
|
91a8962770 | ||
|
be8e1a2632 | ||
|
2635f43788 | ||
|
0578c1ab92 | ||
|
842e383c0c | ||
|
527592a0a5 | ||
|
668d507c34 | ||
|
b79895186c | ||
|
90755e6f13 | ||
|
5eec11f232 | ||
|
f827e49f55 | ||
|
6cff5a3e08 | ||
|
f96982bdfd | ||
|
ed7d8ea28c | ||
|
014097c1af | ||
|
1b2c1d620f | ||
|
285c64d101 | ||
|
637a990e09 | ||
|
9eec303cf7 | ||
|
b1078b1b92 | ||
|
1178501aaf | ||
|
5c72203180 | ||
|
591476621c | ||
|
fda738c90d | ||
|
5858b0b9b4 | ||
|
169ce22228 | ||
|
c875197432 | ||
|
73f6a0cd95 | ||
|
37ce131d2c | ||
|
00fc2820a0 | ||
|
325aadbf2c | ||
|
39b1994c26 | ||
|
324cded166 | ||
|
6b740df704 | ||
|
4821a6294e | ||
|
26b61e809f | ||
|
14bfc54344 | ||
|
72fe91c02e | ||
|
5e01c47818 | ||
|
4058f363d6 | ||
|
63e4bc961e | ||
|
3357477fde | ||
|
d2dce4dd17 | ||
|
15d684134f | ||
|
5c4b8a3d1f | ||
|
cc815e8e8d | ||
|
21d16b6f67 | ||
|
7b31b39fed | ||
|
46106e1b78 | ||
|
755a128ffb | ||
|
34d45aea87 | ||
|
c64d2448e8 | ||
|
544e157649 | ||
|
ed74189acd | ||
|
813ca30953 | ||
|
b76ff2fc33 | ||
|
b8bf6d9917 | ||
|
ff91ad5186 | ||
|
f946042a79 | ||
|
3eecf34bea | ||
|
953bc66ca6 | ||
|
5b4caadd23 | ||
|
8b7506f80d | ||
|
e569e49b8b | ||
|
f8f5a77838 | ||
|
0c581380c7 | ||
|
7571ec9368 | ||
|
eedbb33226 | ||
|
17b0c61338 | ||
|
62a078fe0c | ||
|
7c5afd6977 | ||
|
458254c49e | ||
|
dc5dd30526 | ||
|
e4d365705a | ||
|
b4b470c29a | ||
|
155c739863 | ||
|
a5c0a2215e | ||
|
17a074b30a | ||
|
97220eadc6 | ||
|
e740c21ee6 | ||
|
c15e4c1a85 | ||
|
5957a7ee45 | ||
|
917335eacb | ||
|
963123812d | ||
|
734845b79a | ||
|
761c3784c4 | ||
|
e864050d07 | ||
|
2a8fa4bef7 | ||
|
5467f65122 | ||
|
0935c9d8ca | ||
|
d61c902230 | ||
|
b5c7c93092 | ||
|
e922e2a96e | ||
|
fec0a5587d | ||
|
b9d0808f95 | ||
|
8448b3b840 | ||
|
dfb301b42b | ||
|
f4a2630f1a | ||
|
4ace9544ff | ||
|
e8135ee2cf | ||
|
b0119fef81 | ||
|
8cfe176a85 | ||
|
aaba4dcf87 | ||
|
d90ad8627d | ||
|
194efc4375 | ||
|
432fdd7d32 | ||
|
8685120485 | ||
|
ad4c6a4673 | ||
|
d411dc3a73 | ||
|
6db347b5d7 | ||
|
3f890f8bbe | ||
|
afbbdd18b6 | ||
|
03bb44dd2c | ||
|
18f5d5ad05 | ||
|
f2654d3573 | ||
|
92ac3ba4fc | ||
|
3ae9dfd545 | ||
|
c929f20048 | ||
|
be7e1916d7 | ||
|
7fadb75097 | ||
|
147ff65e4a | ||
|
b17670a27e | ||
|
a68df50c7b | ||
|
0eb4560771 | ||
|
599432552a | ||
|
d177fa9bd0 | ||
|
63ce5ca94f | ||
|
f70d6fd0ed | ||
|
e5e8cec350 | ||
|
d08be18671 | ||
|
6bbfce7b89 | ||
|
80b7b845d2 | ||
|
3b7c083c3d | ||
|
7c7bbb9742 | ||
|
1dd8c7435f | ||
|
495ff79f23 | ||
|
16b35ac77b | ||
|
7c148ecee4 | ||
|
3de4d17519 | ||
|
99f680b6fe | ||
|
30c1befa64 | ||
|
f1e73507d0 | ||
|
35ec7ad9c1 | ||
|
805527fcd6 | ||
|
3f20f7934a | ||
|
4298718978 | ||
|
00cb2636f5 | ||
|
65a2a18a1b | ||
|
66d5c6da07 | ||
|
e9c476266c | ||
|
63040e5134 | ||
|
1e6d5fb32d | ||
|
c34c99aa7f | ||
|
92e615a1a4 | ||
|
37891500d9 | ||
|
eab5d5d93f | ||
|
111a71ccee | ||
|
1839806c3c | ||
|
8f17ccf784 | ||
|
7b2e8f0aa4 | ||
|
549beeea84 | ||
|
4246fdf809 | ||
|
e67028f4bc | ||
|
7a3bce8fc3 | ||
|
c77615adc9 | ||
|
594df09a63 | ||
|
2ad2b27065 | ||
|
3768d9047d | ||
|
7a1fb9a2e4 | ||
|
daff2ea1be | ||
|
4644715164 | ||
|
5b28d7b9c9 | ||
|
117f7d33f8 | ||
|
f9b798aca9 | ||
|
2a05aa2c43 | ||
|
43d779966b | ||
|
003fc6dde1 | ||
|
fe915ac461 | ||
|
15cba69a20 | ||
|
79de02d7d5 | ||
|
3e66022912 | ||
|
5b08dc45b1 | ||
|
49777de74c | ||
|
ddbe495d70 | ||
|
4ab4247189 | ||
|
fd31a7d41b | ||
|
f713dc9fc1 | ||
|
4a41c9a8cc | ||
|
07d9fb8718 | ||
|
ddcf808d56 | ||
|
b26a0b149d | ||
|
f3695e36c7 | ||
|
7c8d2f484e | ||
|
bde9fc6dea | ||
|
6fdac46137 | ||
|
107f779061 | ||
|
25fc044d13 | ||
|
de79aba66d | ||
|
eb2d1766c6 | ||
|
a1fdaebc30 | ||
|
68bacbb194 | ||
|
672bdba679 | ||
|
dc5298a919 | ||
|
b233778acd | ||
|
a739c09f67 | ||
|
8b543eaa92 | ||
|
17dc5a85a3 | ||
|
b796ca0c0a | ||
|
2e591e27f2 | ||
|
b40ac15efc | ||
|
e2b99d3a8e | ||
|
9a8d7d5a6a | ||
|
483901ea7b | ||
|
73a680f45d | ||
|
654be4784c | ||
|
f9d6430ae4 | ||
|
3279ff7507 | ||
|
991b0e02c5 | ||
|
aee6f9352b | ||
|
01f8475f53 | ||
|
d1aa1585a1 | ||
|
a83696fc50 | ||
|
2f24a2e22d | ||
|
60935b9b3d | ||
|
8fb0f694f7 | ||
|
05f9e231a0 | ||
|
1bb3d5cfe2 | ||
|
fae4284a64 | ||
|
0236c68da9 | ||
|
c567556d71 | ||
|
b205764fdd | ||
|
e7227ce87b | ||
|
5da968d6f6 | ||
|
61df7bdd8d | ||
|
7a5edff6c6 | ||
|
44bd2927c5 | ||
|
704e6797e7 | ||
|
d138e42ee5 | ||
|
9f6a3a5e96 | ||
|
cb3d5f9d92 | ||
|
4f7bbdca26 | ||
|
795522ff2b | ||
|
439dff974d | ||
|
d29aa1c437 | ||
|
1ac6359fef | ||
|
0a04c8892d | ||
|
9ddf59959f | ||
|
0e2b516a3c | ||
|
7619c91d62 | ||
|
6b3d3604bf | ||
|
f99a8de6d0 | ||
|
b3b225a9da | ||
|
e00c4e2a7f | ||
|
c314512115 | ||
|
c442b3ec46 | ||
|
9c905741da | ||
|
5010a65d77 | ||
|
9148157697 | ||
|
2e823273d6 | ||
|
cd9d74c462 | ||
|
382eeccab8 | ||
|
f5f6b420b2 | ||
|
488818eeb6 | ||
|
296c80d1f2 | ||
|
1fe9f1ac5b | ||
|
ebcfbe03f9 | ||
|
7806fc736a | ||
|
e3c791cf60 | ||
|
20647b7bcc | ||
|
67b94d0797 | ||
|
f56392a245 | ||
|
81addddaee | ||
|
1c553bb703 | ||
|
4e0b2ec9c9 | ||
|
9d83b03605 | ||
|
4aa02b7855 | ||
|
695c757dc3 | ||
|
81d6f7a7ca | ||
|
8bd7ad7bb5 | ||
|
d317977205 | ||
|
ccae5cae34 | ||
|
b07e34e0b8 | ||
|
9405d8cbc5 | ||
|
6b6b1a6e8d | ||
|
0eaa0c9d4e | ||
|
5ec573c83b | ||
|
6af55ee51c | ||
|
03fc3f9a2a | ||
|
c57fee2c92 | ||
|
f86229d98b | ||
|
344ce15261 | ||
|
782fa39647 | ||
|
0bce58d0cd | ||
|
ce1ac3aea9 | ||
|
153b0983c1 | ||
|
97a4fb92c6 | ||
|
466efe4461 | ||
|
6d8f4f9f0d | ||
|
0e89b9de2c | ||
|
a8b9c9971e | ||
|
82f52db9b3 | ||
|
61e644049f | ||
|
6becf0ef55 | ||
|
0aa7e21f91 | ||
|
a339ec3d8f | ||
|
166ad21e5c | ||
|
0bb7d0db48 | ||
|
7bdaf0affd | ||
|
e8a831dda2 | ||
|
a0f7027ad5 | ||
|
82ae44ac3a | ||
|
f947c0e6be | ||
|
9eb3e0cb79 | ||
|
9cf658ec6e | ||
|
abca14d601 | ||
|
9e4f89a2a1 | ||
|
a3063900ee | ||
|
0161d54dbb | ||
|
a8d4f37b19 | ||
|
9ceffa1cc6 | ||
|
10caf1bd74 | ||
|
a110d7dd70 | ||
|
3f0a7f9af4 | ||
|
068b15c57f | ||
|
aac8a20fc1 | ||
|
c4da939909 | ||
|
49d9edce01 | ||
|
3f389593f5 | ||
|
098c00d73e | ||
|
8c969304fd | ||
|
dd7e5c906f | ||
|
eb570488a4 | ||
|
bc84b6233e | ||
|
8dd00ee874 | ||
|
b5e4a040f8 | ||
|
548e281350 | ||
|
b52ff10153 | ||
|
36f0db81d2 | ||
|
1bec2bdc43 | ||
|
90b26c3e23 | ||
|
ea923b63a4 | ||
|
0a5db6ecf5 | ||
|
ede418a009 | ||
|
32f5a9fc1d | ||
|
750ececf08 | ||
|
8670287928 | ||
|
f766d90704 | ||
|
df952c149d | ||
|
5309da117d | ||
|
c52e49a9da | ||
|
1436ee308c | ||
|
9e6b22cc3b | ||
|
ddb87b7727 | ||
|
3354bbd1b6 | ||
|
957fd79a27 | ||
|
8cc0d06e65 | ||
|
4845393c15 | ||
|
f85e405605 | ||
|
f976c428d2 | ||
|
76f3e196d3 | ||
|
5b69adf797 | ||
|
aa2c172706 | ||
|
8decf34ad7 | ||
|
da2aba1595 | ||
|
74d2c4bef9 | ||
|
6da3e7ee69 | ||
|
ddef2e30cd | ||
|
a78ac3c125 | ||
|
e0226fc46a | ||
|
04f1f06b9f | ||
|
bad9b8b383 | ||
|
b3a70e09e9 | ||
|
77c703395b | ||
|
e50f3129c2 | ||
|
b8f2f71819 | ||
|
3fcb035126 | ||
|
852884e086 | ||
|
be3b1ba1eb | ||
|
8ad2098b74 | ||
|
f35465231f | ||
|
3a6a8e96fb | ||
|
2cd9a78a97 | ||
|
4be2b75816 | ||
|
f3d21c24fc | ||
|
389f6c08d9 | ||
|
c86e682153 | ||
|
1a8e992fcc | ||
|
9de46e416e | ||
|
ef64493b89 | ||
|
b42c35849b | ||
|
1047af03da | ||
|
afd7c6c284 | ||
|
19b1efd629 | ||
|
ec0a3d59a8 | ||
|
861e389fcf | ||
|
2218e73c28 | ||
|
7617e1dbf5 | ||
|
df646831d7 | ||
|
06396e5162 | ||
|
aae5d6364f | ||
|
5a3cf32fe5 | ||
|
2c5cad1525 | ||
|
8f6d744b5d | ||
|
ccc3820f8e | ||
|
699be52e8b | ||
|
94bb238b70 | ||
|
e335d6d223 | ||
|
d0c7126aa8 | ||
|
f5703dc3e5 | ||
|
56ec575351 | ||
|
fcc083dc12 | ||
|
6a1546adde | ||
|
a51160317c | ||
|
f4aa40da09 | ||
|
65ed5478d1 | ||
|
727b952a32 | ||
|
0d5b0f0287 | ||
|
87da2f898d | ||
|
3e52b5445b | ||
|
f939f39580 | ||
|
c3ee9cac41 | ||
|
a0229cff82 | ||
|
60b1517c55 | ||
|
c2d082e896 | ||
|
ea76504cd1 | ||
|
283de27098 | ||
|
08b479bc10 | ||
|
7282ef0d14 | ||
|
955526b200 | ||
|
b69457ccdf | ||
|
2f3c233f38 | ||
|
53c1c5dcc1 | ||
|
ffcc65d4f0 | ||
|
e7529e4823 | ||
|
f77057dd43 | ||
|
c1d3948be9 | ||
|
4c95668c72 | ||
|
914ccbacb5 | ||
|
b4f066bd43 | ||
|
1959d2403f | ||
|
a1831ef498 | ||
|
fc8574fdd8 | ||
|
06d4788811 | ||
|
e0ce35ef72 | ||
|
db0c081d03 | ||
|
a39d794308 | ||
|
7855f28785 | ||
|
dda55dbf36 | ||
|
afb42e7625 | ||
|
b4e658d29b | ||
|
296abf735e | ||
|
048f1bb95a | ||
|
d58844e548 | ||
|
dfc9932f27 | ||
|
582ec189cc | ||
|
f7d5d25651 | ||
|
05c28cce78 | ||
|
5ddac80cec | ||
|
858bc3d42a | ||
|
390e43fcb1 | ||
|
7a2dd85caa | ||
|
13b0b9d3e3 | ||
|
5983b96c55 | ||
|
d88d8f9619 | ||
|
9614e7241b | ||
|
4f1505857b | ||
|
b804c092ac | ||
|
dfee199351 | ||
|
f54783ae6e | ||
|
46d8529dde | ||
|
980145eaee | ||
|
c6e90766e1 | ||
|
f3f394ced9 | ||
|
552e49a11d | ||
|
0d90c173fb | ||
|
f1461d2d7e | ||
|
6ee8b4c9aa | ||
|
582fd95986 | ||
|
e7587caee3 | ||
|
4d68f6e414 | ||
|
1ccb61fe18 | ||
|
84d405894c | ||
|
6e0edf7779 | ||
|
3ec06b88f0 | ||
|
75198a46d4 | ||
|
c45e219387 | ||
|
302b2f962f | ||
|
22c7114168 | ||
|
2c836a2935 | ||
|
df688d6b87 | ||
|
cd3393b21c | ||
|
9358725963 | ||
|
775de754cf | ||
|
b7bd5eceed | ||
|
cfa0509d7e | ||
|
d619baf000 | ||
|
791f5fe969 | ||
|
e089d94c88 | ||
|
2c1966a7f6 | ||
|
78eb80d0cf | ||
|
cfb0937605 | ||
|
d2e86db8fb | ||
|
1a97445471 | ||
|
30d89da4fa | ||
|
d72efd2fa8 | ||
|
991f0988ee | ||
|
8ab16807c6 | ||
|
49ce622a86 | ||
|
2b5e131831 | ||
|
70a2d2238e | ||
|
324e56cfa3 | ||
|
789eb64f3a | ||
|
21ee8b62ef | ||
|
315f72862c | ||
|
30af82ef2f | ||
|
eef198ac5e | ||
|
8576d43006 | ||
|
9b4de93bdc | ||
|
338dbe70a6 | ||
|
496cef80c4 | ||
|
7ae2cb5cd0 | ||
|
602c74faa6 | ||
|
de843ef756 | ||
|
799c5aaf43 | ||
|
9ac2b8c3a9 | ||
|
63cc9ea24b | ||
|
c837261ac1 | ||
|
3d13a2cf25 | ||
|
7d9ef5f952 | ||
|
767118467f | ||
|
256910f8ff | ||
|
89169e6e53 | ||
|
75d84f0b25 | ||
|
165449c724 | ||
|
6e5abf33f2 | ||
|
9d7ff405ee | ||
|
a7e3f46431 | ||
|
bd34c86aba | ||
|
248e653c9e | ||
|
9e5621698f | ||
|
7b3ea43e92 | ||
|
38cd720369 | ||
|
4b4d2796b7 | ||
|
cd095ef0ee | ||
|
a66c34a6d0 | ||
|
47f5e97759 | ||
|
3b48918bef | ||
|
adf55babe8 | ||
|
b94548f5a3 | ||
|
ee9948fa86 | ||
|
57b1093b31 | ||
|
7214c6652e | ||
|
31f91e033f | ||
|
1e0aef032f | ||
|
f9b36433fb | ||
|
ba0329fb40 | ||
|
9f160fd820 | ||
|
c7f5690426 | ||
|
3bfbd3a0a5 | ||
|
103655d670 | ||
|
cc047a445a | ||
|
7187d6408a | ||
|
3626e961f8 | ||
|
8e28444897 | ||
|
7c2333dd81 | ||
|
12317557da | ||
|
b203a7c775 | ||
|
3c124a0b87 | ||
|
ef9f9f5fff | ||
|
5730aa9a40 | ||
|
8ed0196d02 | ||
|
09c48c76c3 | ||
|
b075c7169c | ||
|
c661695b28 | ||
|
06fbd0ee02 | ||
|
e3f56718d2 | ||
|
d007dc3249 | ||
|
a5382d7b72 | ||
|
26bd5df048 | ||
|
3a441467c0 | ||
|
6be94f1927 | ||
|
5a65ff5cf4 | ||
|
44d8edfd8c | ||
|
455f51c36c | ||
|
6f7e570a0a | ||
|
51591e2c23 | ||
|
3638d349ba | ||
|
b0e6045b63 | ||
|
a2bc3c7bcb | ||
|
c871c49f55 | ||
|
72f74d1f41 | ||
|
451e1e2e73 | ||
|
691df4952b | ||
|
3b665d35d5 | ||
|
834e9996da | ||
|
e351606b69 | ||
|
a15dfbbd4f | ||
|
14fe3ad8c9 | ||
|
f5bd6063fc | ||
|
a3193aca2a | ||
|
355d62f084 | ||
|
1cb2721a67 | ||
|
559dbe7b53 | ||
|
30efe59af7 | ||
|
afcae0a61b | ||
|
089ceb96a8 | ||
|
7819d0fc6c | ||
|
535ff672c0 | ||
|
82adf8a162 | ||
|
61101888e4 | ||
|
d7bba865f3 | ||
|
d50ed48807 | ||
|
b5d80b609d | ||
|
20c9c17457 | ||
|
fb1a47c064 | ||
|
6c79f936bd | ||
|
75747606d3 | ||
|
32e81a27b5 | ||
|
7493ea24f2 | ||
|
d52e9100d2 | ||
|
83cf908c88 | ||
|
ee4887b4c5 | ||
|
4b78ca4405 | ||
|
21fbe2bd2a | ||
|
b4b8b5f164 | ||
|
6f83c9c87a | ||
|
d2cafe2684 | ||
|
d2cd1236a8 | ||
|
3e12522bc9 | ||
|
791befca18 | ||
|
7210287e76 | ||
|
28c489f17e | ||
|
3ff350cd24 | ||
|
7485fabe4f | ||
|
705dda2cba | ||
|
7b11b95908 | ||
|
7f593d5eb0 | ||
|
2e18b42db0 | ||
|
3873c6c09f | ||
|
d23fe42873 | ||
|
e599da6308 | ||
|
f5cad47909 | ||
|
e9c5f66450 | ||
|
9f467231d9 | ||
|
c6af16c3ae | ||
|
30a833f57a | ||
|
3fbff5d848 | ||
|
c54a614c6e | ||
|
9054eeccc0 | ||
|
665e9dc665 | ||
|
9448576ba6 | ||
|
c60682c230 | ||
|
8b96bcddff | ||
|
8527144173 | ||
|
ca59fd8b3f | ||
|
c1e016e597 | ||
|
9863637f30 | ||
|
de8b335d80 | ||
|
ca42306d79 | ||
|
1911b250e3 | ||
|
44c3b77691 | ||
|
ce9be69615 | ||
|
8baf6aa372 | ||
|
6e89db020c | ||
|
95226baa9b | ||
|
dc1b4fff90 | ||
|
d2fdc5ca9f | ||
|
d8b1fbd905 | ||
|
626e57acda | ||
|
27652ed3b0 | ||
|
ad6506ff69 | ||
|
88a21689ce | ||
|
f24de0357f | ||
|
9ca51e0ed9 | ||
|
815869375b | ||
|
9bac37baf7 | ||
|
2e2dcd536b | ||
|
26952ecee4 | ||
|
a95fd3df1a | ||
|
8ac839ce95 | ||
|
9ffb61214c | ||
|
8341792c5b | ||
|
651f1b6c97 | ||
|
a8a3a298f8 | ||
|
71a168797c | ||
|
a76e665567 | ||
|
49461c2c39 | ||
|
9926942260 | ||
|
077328c1a2 | ||
|
d97399daa5 | ||
|
cf40bff2e2 | ||
|
4a55b8018a | ||
|
41b9bfe401 | ||
|
828415d476 | ||
|
ad2643266f | ||
|
8c020ca47a | ||
|
600416aafc | ||
|
c4a67fc1c1 | ||
|
f11e6d82b0 | ||
|
fad5100079 | ||
|
21f38a4c07 | ||
|
449bd4485f | ||
|
f3e47a72ed | ||
|
cb5293be1f | ||
|
3295beb777 | ||
|
a81f12f9bd | ||
|
dfe547856e | ||
|
a255e4e1c6 | ||
|
17a74cc771 | ||
|
f439ca61d6 | ||
|
7caba160b7 | ||
|
e9577e9b89 | ||
|
c46b6ea719 | ||
|
64342fcff0 | ||
|
ffeb7001eb | ||
|
d13521a6ce | ||
|
1957389bea | ||
|
a53b39efe8 | ||
|
faaef61c27 | ||
|
17698c4815 | ||
|
4c99cadabe | ||
|
dcb198e8b7 | ||
|
35b4a01b6d | ||
|
1c3e2be224 | ||
|
d48d4bab05 | ||
|
c9798ecb15 | ||
|
c22093ddd4 | ||
|
874705db42 | ||
|
b73115ac41 | ||
|
e27579292a | ||
|
c2d405116b | ||
|
b77de74f72 | ||
|
f451d35aae | ||
|
feebc8f689 | ||
|
1463bdb86d | ||
|
6758ce5eef | ||
|
def22982fb | ||
|
11ad1005e0 | ||
|
a478ba9a47 | ||
|
b3cd125d27 | ||
|
0be783c454 | ||
|
3971ee6d1f | ||
|
fb1dbc2a9b | ||
|
121cf3b8f8 | ||
|
584c7e6890 | ||
|
51aa166d52 | ||
|
643ba70bad | ||
|
706ad2f177 | ||
|
86198c1c21 | ||
|
05627ac0f9 | ||
|
91d2158de7 | ||
|
4458c9b4c9 | ||
|
b3792c7f02 | ||
|
5ddc952dbb | ||
|
3d4f6df0de | ||
|
b006190312 | ||
|
47a312cf83 | ||
|
78649f8e90 | ||
|
3b41c3a6f3 | ||
|
1d950748af | ||
|
01c552baa3 | ||
|
e0883f1967 | ||
|
e5390fed98 | ||
|
12ecb4b2ce | ||
|
e779fec5f9 | ||
|
478f43f0c8 | ||
|
bd95269c69 | ||
|
edd51aac25 | ||
|
ab671146de | ||
|
ec1e578e4b | ||
|
cde19c4d59 | ||
|
86434548a7 | ||
|
9a4f8f915f | ||
|
2a4d6027a4 | ||
|
03824a1dba | ||
|
90ee1ebba5 | ||
|
7566af145b | ||
|
00de4052d1 | ||
|
e41bff8ca4 | ||
|
80fd14463b | ||
|
5bcaf11f9d | ||
|
70af49c0a2 | ||
|
9c156d41bb | ||
|
1442655d3c | ||
|
4a450b63c4 | ||
|
2e8e61b8d4 | ||
|
bada1f1f8e | ||
|
c29b5cd46a | ||
|
7aedc61d41 | ||
|
b7330283bc | ||
|
52753b23e8 | ||
|
3ed2c54790 | ||
|
d5443c65fd | ||
|
5ce00f8e5b | ||
|
732ea90a4f | ||
|
4fa1b983e7 | ||
|
caa3e491f2 | ||
|
3033361883 | ||
|
4fb236e688 | ||
|
65ec7e8bcb | ||
|
82ef9cfadd | ||
|
48260c6406 | ||
|
654eeec24c | ||
|
1b7cebc41e | ||
|
006693421d | ||
|
2d91864977 | ||
|
22fe6e7aad | ||
|
8611d91b35 | ||
|
244f233e1c | ||
|
eb687103b4 | ||
|
499366ba2d | ||
|
c13956dc10 | ||
|
d0309333b7 | ||
|
2cdc8d61a2 | ||
|
88d3d7fc7e | ||
|
d829a75f2e | ||
|
a7887fab07 | ||
|
3f10b4dd31 | ||
|
b21c8f7a4e | ||
|
2de2ed7c63 | ||
|
1d52f34e60 | ||
|
da6e061ee8 | ||
|
cb7032554e | ||
|
ab5d8f4190 | ||
|
d43b74681f | ||
|
e33073576c | ||
|
c9fd828f8a | ||
|
7aab934ae5 | ||
|
7155d06cdc | ||
|
6893295a87 | ||
|
52087a79d9 | ||
|
2c55a1bd53 | ||
|
1c4523f0ac | ||
|
d40f81865f | ||
|
01856333c4 | ||
|
3df2c6a2b7 | ||
|
3a54e83737 | ||
|
b737757eac | ||
|
5d33b39829 | ||
|
09a4bb70a1 | ||
|
452fd56e04 | ||
|
5fd0f6c4ee | ||
|
0772d187e9 | ||
|
92109e4bba | ||
|
54e5349f53 | ||
|
4003b7c977 | ||
|
021f6d22ad | ||
|
7302f348bc | ||
|
d297976d6d | ||
|
a3eed9c9f5 | ||
|
04ce14b251 | ||
|
c205f140a0 | ||
|
37a80e9de8 | ||
|
3948e909e4 | ||
|
e9bb4a86f9 | ||
|
31c95c0922 | ||
|
1205bf9c2b | ||
|
23d279945f | ||
|
b3def74002 | ||
|
cf83470891 | ||
|
051e6d1364 | ||
|
e61db578cf | ||
|
602fefcae3 | ||
|
a4cbb088fe | ||
|
fbcf99ad07 | ||
|
433fb957cb | ||
|
7aba8b3ed0 | ||
|
4b9a6f48f3 | ||
|
fe4cb0d493 | ||
|
8b79bf43ac | ||
|
dd6bcbd04f | ||
|
e69dafbecc | ||
|
76c7f4a0b5 | ||
|
22cbfb26d6 | ||
|
089e745af6 | ||
|
b3f8e9adb5 | ||
|
23abe4f501 | ||
|
0bb81fc848 | ||
|
7aea2cef6e | ||
|
4ab7cf0157 | ||
|
0435320f34 | ||
|
cf4f2411bf | ||
|
d14fea31a3 | ||
|
8e518d4954 | ||
|
6763578cc0 | ||
|
109409bc95 | ||
|
89bb6b0711 | ||
|
9ab38ee8c5 | ||
|
28a1a00d1c | ||
|
e3097ec956 | ||
|
822d671e8b | ||
|
e05b4fd267 | ||
|
9295bce154 | ||
|
a0b42a12ff | ||
|
fea2260fc5 | ||
|
54079072db | ||
|
6ad299945f | ||
|
ea2b87eade | ||
|
0233620c26 | ||
|
cac83da5db | ||
|
95ee218c6d | ||
|
937a7a9024 | ||
|
f872a2aabb | ||
|
862eda10c2 | ||
|
c73c1c5771 | ||
|
f635547ec6 | ||
|
9e6c3322d8 | ||
|
98f2aad4ba | ||
|
ade7dba27c | ||
|
7e94ce1f14 | ||
|
14246a5689 | ||
|
58918801d6 | ||
|
5afeee3613 | ||
|
71d96293ab | ||
|
d3bbd19ccb | ||
|
d1af12187c | ||
|
6803341f21 | ||
|
0e3f5214c5 | ||
|
4e92322d74 | ||
|
71925fcc00 | ||
|
ac25ba0a3f | ||
|
26fe1f230f | ||
|
eac16d048a | ||
|
06f18237f7 | ||
|
9c1f2acf3e | ||
|
dab41c699a | ||
|
9386653d72 | ||
|
0d731aa12e | ||
|
318706f28c | ||
|
058db56430 | ||
|
61e52ce879 | ||
|
e3c3a2c19e | ||
|
9ed90b1216 | ||
|
35a3262fe3 | ||
|
9fc5bf4436 | ||
|
1ac385d1fb | ||
|
453830f80c | ||
|
f7b84ce843 | ||
|
50df864f8c | ||
|
f14521f8cb | ||
|
925fdf5731 | ||
|
06b4b1de94 | ||
|
6002c524d6 | ||
|
1f330e9cac | ||
|
44b705f88f | ||
|
2ad82b973b | ||
|
b326a27774 | ||
|
0a7261268b | ||
|
d6041271bc | ||
|
5712f30169 | ||
|
7e5715617e | ||
|
b0058c0cc8 | ||
|
cc08e14162 | ||
|
8d4ac25b56 | ||
|
c2955fb8fc | ||
|
5a21da43ee | ||
|
73d17e3c5e | ||
|
80da76d43f | ||
|
ffd98a82c0 | ||
|
4bb6f9cef2 | ||
|
3200cd77bf | ||
|
6a2f21aa14 | ||
|
e32e1b834e | ||
|
6c57e4a648 | ||
|
d66165232e | ||
|
a59704cf93 | ||
|
1b7e3c6da4 | ||
|
ad3e91a56f | ||
|
4078b89521 | ||
|
1d006ccbe9 | ||
|
4eee1207f9 | ||
|
2cab752443 | ||
|
41639c7610 | ||
|
f27ca25d2e | ||
|
70fbc8bf1f | ||
|
0b523927bc | ||
|
1e8be5bd09 | ||
|
c62c292053 | ||
|
5efe7131ff | ||
|
6d63973e08 | ||
|
569fd0975b | ||
|
296caa4556 | ||
|
7ea56b112e | ||
|
6177823811 | ||
|
2a2f306f7b | ||
|
5af5fb4cc6 | ||
|
54ebf90fc6 | ||
|
f15e9d0cc8 | ||
|
e490e3bfa3 | ||
|
7ce7fef322 | ||
|
d3c1ad5945 | ||
|
2332f2c99e | ||
|
b5ac992944 | ||
|
7139187381 | ||
|
a338da9c19 | ||
|
c008f9d1ce | ||
|
bc5e2aeead | ||
|
2f862972c9 | ||
|
79df47a146 | ||
|
44219bf366 | ||
|
b2327b0fc7 | ||
|
0205d6ca2e | ||
|
91eabf5fcb | ||
|
e5bbde9554 | ||
|
cd67d3d2fe | ||
|
e13ec67da3 | ||
|
6bccb05c90 | ||
|
85ba098cd8 | ||
|
e9f54a2d6e | ||
|
096e0729d5 | ||
|
4dbda02145 | ||
|
0d553269d5 | ||
|
ab4afd0e2f | ||
|
1b48033345 | ||
|
48a32b766b | ||
|
6de7315210 | ||
|
b4d38c455c | ||
|
7faa06ce0b | ||
|
9fdfa86200 | ||
|
6210af2763 | ||
|
8e06a51884 | ||
|
7d81c48849 | ||
|
37d80f2c03 | ||
|
6705e4051d | ||
|
ccc30e1877 | ||
|
2d601ef157 | ||
|
ea54aa77ed | ||
|
54d904138d | ||
|
df9fbc515d | ||
|
f7ee006bef | ||
|
4a4e0d8f9a | ||
|
c1da93a7b2 | ||
|
b8b15637aa | ||
|
2ad67ee56e | ||
|
843de765a1 | ||
|
afdb7422fb | ||
|
c0e94ad01f | ||
|
2be26804d3 | ||
|
d8bbaf0481 | ||
|
353b36c657 | ||
|
0ea7798122 | ||
|
1d729ab40e | ||
|
68a4308c3d | ||
|
6e841a41e6 | ||
|
6a5f778361 | ||
|
d20c03310d | ||
|
13b0852619 | ||
|
c81522079e | ||
|
478e66f761 | ||
|
283a422b4d | ||
|
a2b01cdf42 | ||
|
1b322a9b19 | ||
|
95165cef7d | ||
|
669e787919 | ||
|
31d07e5029 | ||
|
41a083cec3 | ||
|
c475a0db95 | ||
|
0b95194f74 | ||
|
e225aad042 | ||
|
f40ad93a37 | ||
|
9d547469b8 | ||
|
6bca443e37 | ||
|
634fc004fb | ||
|
44281d6692 | ||
|
6d6fd9acf7 | ||
|
40dca9924d | ||
|
081bf843d2 | ||
|
56adc590ff | ||
|
115ceea1eb | ||
|
e076e935b9 | ||
|
693dedf4da | ||
|
67354ed14f | ||
|
8746b0c6b7 | ||
|
c5f2e29dde | ||
|
97290955f6 | ||
|
d0b35adeb6 | ||
|
35545e7374 | ||
|
81b5f1fb26 | ||
|
abd975ae40 | ||
|
69605e54ec | ||
|
03b8b1e97a | ||
|
09fb131f1a | ||
|
489658cfc5 | ||
|
1654687d1b | ||
|
3752880b7b | ||
|
2e55b35d5d | ||
|
696d123550 | ||
|
a31c1a1722 | ||
|
9036299d54 | ||
|
ca98167ae4 | ||
|
81443c3a65 | ||
|
33f2301772 | ||
|
e9661bcde5 | ||
|
a8fdc74090 | ||
|
dfacc483b5 | ||
|
25e3a5f744 | ||
|
e7fc7f31b9 | ||
|
0258e90ef0 | ||
|
7b82be936d | ||
|
618f7e41de | ||
|
3c893606f5 | ||
|
79e84e432a | ||
|
63e36e22d2 | ||
|
ca99579b76 | ||
|
283dbccf56 | ||
|
dd9e476353 | ||
|
8d2744c550 | ||
|
212f4e3538 | ||
|
8937c635df | ||
|
4545d1636c | ||
|
6192a4fb17 | ||
|
6cd0647da9 | ||
|
e60266c4c5 | ||
|
bae508ffa6 | ||
|
d469c32503 | ||
|
66f85b5832 | ||
|
a35b743af7 | ||
|
4ed9dc1435 | ||
|
58c98deebb | ||
|
9100ad3e8f | ||
|
85b1914ad6 | ||
|
e11d0d39ec | ||
|
6ab1ec1c37 | ||
|
bc70902ea5 | ||
|
2e3833f7fb | ||
|
5c99d2a456 | ||
|
a0a63e3ac7 | ||
|
1e623a951c | ||
|
345077d5f7 | ||
|
81e8b869d0 | ||
|
497247a528 | ||
|
921b676471 | ||
|
83c5d12cf1 | ||
|
ea08f7e7c7 | ||
|
1361189b99 | ||
|
d50ea3d82b | ||
|
28276e3eb1 | ||
|
b6a9e0ffaf | ||
|
8bb44f3444 | ||
|
1ef234ebfc | ||
|
c796bc2cbd | ||
|
a44db81d9b | ||
|
bbe0bf955c | ||
|
1ff3ea0745 | ||
|
31b6ec63f8 | ||
|
2eba97de70 | ||
|
4547753df0 | ||
|
4de8b9577e | ||
|
fc2ca0d2e9 | ||
|
d159dfea9d | ||
|
fdc5dc750c | ||
|
e335eb98ce | ||
|
5d86d07618 | ||
|
558f7ad929 | ||
|
cda0b6a8eb | ||
|
711c092a79 | ||
|
61d496b2ed | ||
|
04c09b7b38 | ||
|
1af4bd83c4 | ||
|
af7347473e | ||
|
f2fbf18a05 | ||
|
6d2f2676ad |
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
github:
|
||||||
|
- dvarrazzo
|
||||||
|
custom:
|
||||||
|
- "https://www.paypal.me/dvarrazzo"
|
23
.github/ISSUE_TEMPLATE/problem-installing-psycopg2.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/problem-installing-psycopg2.md
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
name: Problem installing psycopg2
|
||||||
|
about: Report a case in which psycopg2 failed to install on your platform
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**This is a bug tracker**
|
||||||
|
If you have a question, such has "how do you do X with Python/PostgreSQL/psycopg2" please [write to the mailing list](https://lists.postgresql.org/manage/) or [open a question](https://github.com/psycopg/psycopg2/discussions) instead.
|
||||||
|
|
||||||
|
**Before opening this ticket, please confirm that:**
|
||||||
|
- [ ] I am running the latest version of pip, i.e. typing ``pip --version`` you get [this version](https://pypi.org/project/pip/).
|
||||||
|
- [ ] I have read the [installation documentation](https://www.psycopg.org/docs/install.html) and the [frequently asked questions](https://www.psycopg.org/docs/faq.html)
|
||||||
|
- [ ] If install failed, I typed `pg_config` on the command line and I obtained an output instead of an error.
|
||||||
|
|
||||||
|
**Please complete the following information:**
|
||||||
|
- OS:
|
||||||
|
- Psycopg version:
|
||||||
|
- Python version:
|
||||||
|
- PostgreSQL version:
|
||||||
|
- pip version
|
27
.github/ISSUE_TEMPLATE/problem-using-psycopg2.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/problem-using-psycopg2.md
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
name: Problem using psycopg2
|
||||||
|
about: Report a case in which psycopg2 is not working as expected
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**This is a bug tracker**
|
||||||
|
If you have a question, such has "how do you do X with Python/PostgreSQL/psycopg2" please [write to the mailing list](https://lists.postgresql.org/manage/) or [open a question](https://github.com/psycopg/psycopg2/discussions) instead.
|
||||||
|
|
||||||
|
**Please complete the following information:**
|
||||||
|
- OS:
|
||||||
|
- Psycopg version:
|
||||||
|
- Python version:
|
||||||
|
- PostgreSQL version:
|
||||||
|
- pip version
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
Please let us know:
|
||||||
|
|
||||||
|
1: what you did
|
||||||
|
2: what you expected to happen
|
||||||
|
3: what happened instead
|
||||||
|
|
||||||
|
If possible, provide a script reproducing the issue.
|
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
18
.github/workflows/docs.yml
vendored
Normal file
18
.github/workflows/docs.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
name: Build documentation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
# This should match the DOC_BRANCH value in the psycopg-website Makefile
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Trigger docs build
|
||||||
|
uses: peter-evans/repository-dispatch@v3
|
||||||
|
with:
|
||||||
|
repository: psycopg/psycopg-website
|
||||||
|
event-type: psycopg2-commit
|
||||||
|
token: ${{ secrets.ACCESS_TOKEN }}
|
266
.github/workflows/packages.yml
vendored
Normal file
266
.github/workflows/packages.yml
vendored
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
---
|
||||||
|
name: Build packages
|
||||||
|
on:
|
||||||
|
- workflow_dispatch
|
||||||
|
|
||||||
|
env:
|
||||||
|
PIP_BREAK_SYSTEM_PACKAGES: "1"
|
||||||
|
LIBPQ_VERSION: "16.0"
|
||||||
|
OPENSSL_VERSION: "1.1.1w"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sdist: # {{{
|
||||||
|
if: true
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- package_name: psycopg2
|
||||||
|
- package_name: psycopg2-binary
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repos
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build sdist
|
||||||
|
run: ./scripts/build/build_sdist.sh
|
||||||
|
env:
|
||||||
|
PACKAGE_NAME: ${{ matrix.package_name }}
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sdist-${{ matrix.package_name }}
|
||||||
|
path: |
|
||||||
|
dist/*.tar.gz
|
||||||
|
|
||||||
|
env:
|
||||||
|
PSYCOPG2_TESTDB: postgres
|
||||||
|
PSYCOPG2_TESTDB_HOST: 172.17.0.1
|
||||||
|
PSYCOPG2_TESTDB_USER: postgres
|
||||||
|
PSYCOPG2_TESTDB_PASSWORD: password
|
||||||
|
PSYCOPG2_TEST_FAST: 1
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgresql:
|
||||||
|
image: postgres:16
|
||||||
|
env:
|
||||||
|
POSTGRES_PASSWORD: password
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
# Set health checks to wait until postgres has started
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
linux: # {{{
|
||||||
|
if: true
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform: [manylinux, musllinux]
|
||||||
|
arch: [x86_64, i686, aarch64, ppc64le]
|
||||||
|
pyver: [cp38, cp39, cp310, cp311, cp312, cp313]
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repos
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up QEMU for multi-arch build
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Cache libpq build
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: /tmp/libpq.build
|
||||||
|
key: libpq-${{ env.LIBPQ_VERSION }}-${{ matrix.platform }}-${{ matrix.arch }}
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
uses: pypa/cibuildwheel@v2.23.2
|
||||||
|
env:
|
||||||
|
CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
|
||||||
|
CIBW_MANYLINUX_I686_IMAGE: manylinux2014
|
||||||
|
CIBW_MANYLINUX_AARCH64_IMAGE: manylinux2014
|
||||||
|
CIBW_MANYLINUX_PPC64LE_IMAGE: manylinux2014
|
||||||
|
CIBW_BUILD: ${{matrix.pyver}}-${{matrix.platform}}_${{matrix.arch}}
|
||||||
|
CIBW_ARCHS_LINUX: auto aarch64 ppc64le
|
||||||
|
CIBW_BEFORE_ALL_LINUX: ./scripts/build/wheel_linux_before_all.sh
|
||||||
|
CIBW_REPAIR_WHEEL_COMMAND: >-
|
||||||
|
./scripts/build/strip_wheel.sh {wheel}
|
||||||
|
&& auditwheel repair -w {dest_dir} {wheel}
|
||||||
|
CIBW_TEST_COMMAND: >-
|
||||||
|
export PYTHONPATH={project} &&
|
||||||
|
python -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')"
|
||||||
|
CIBW_ENVIRONMENT_PASS_LINUX: LIBPQ_VERSION OPENSSL_VERSION
|
||||||
|
CIBW_ENVIRONMENT: >-
|
||||||
|
PACKAGE_NAME=psycopg2-binary
|
||||||
|
LIBPQ_BUILD_PREFIX=/host/tmp/libpq.build
|
||||||
|
PATH="$LIBPQ_BUILD_PREFIX/bin:$PATH"
|
||||||
|
LD_LIBRARY_PATH="$LIBPQ_BUILD_PREFIX/lib:$LIBPQ_BUILD_PREFIX/lib64"
|
||||||
|
PSYCOPG2_TESTDB=postgres
|
||||||
|
PSYCOPG2_TESTDB_HOST=172.17.0.1
|
||||||
|
PSYCOPG2_TESTDB_USER=postgres
|
||||||
|
PSYCOPG2_TESTDB_PASSWORD=password
|
||||||
|
PSYCOPG2_TEST_FAST=1
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: linux-${{matrix.pyver}}-${{matrix.platform}}_${{matrix.arch}}
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgresql:
|
||||||
|
image: postgres:16
|
||||||
|
env:
|
||||||
|
POSTGRES_PASSWORD: password
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
# Set health checks to wait until postgres has started
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
macos: # {{{
|
||||||
|
runs-on: macos-latest
|
||||||
|
if: true
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
# These archs require an Apple M1 runner: [arm64, universal2]
|
||||||
|
arch: [x86_64, arm64]
|
||||||
|
pyver: [cp39, cp310, cp311, cp312, cp313]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repos
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Cache libpq build
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: /tmp/libpq.build
|
||||||
|
key: libpq-${{ env.LIBPQ_VERSION }}-macos-${{ matrix.arch }}
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
uses: pypa/cibuildwheel@v2.23.2
|
||||||
|
env:
|
||||||
|
CIBW_BUILD: ${{matrix.pyver}}-macosx_${{matrix.arch}}
|
||||||
|
CIBW_ARCHS_MACOS: ${{matrix.arch}}
|
||||||
|
MACOSX_ARCHITECTURE: ${{matrix.arch}}
|
||||||
|
CIBW_BEFORE_ALL_MACOS: ./scripts/build/wheel_macos_before_all.sh
|
||||||
|
CIBW_TEST_COMMAND: >-
|
||||||
|
export PYTHONPATH={project} &&
|
||||||
|
python -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')"
|
||||||
|
CIBW_ENVIRONMENT: >-
|
||||||
|
PG_VERSION=16
|
||||||
|
PACKAGE_NAME=psycopg2-binary
|
||||||
|
PSYCOPG2_TESTDB=postgres
|
||||||
|
PATH="/tmp/libpq.build/bin:$PATH"
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: macos-${{matrix.pyver}}-macos-${{matrix.arch}}
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
windows: # {{{
|
||||||
|
runs-on: windows-latest
|
||||||
|
if: true
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
arch: [win_amd64]
|
||||||
|
pyver: [cp38, cp39, cp310, cp311, cp312, cp313]
|
||||||
|
package_name: [psycopg2, psycopg2-binary]
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# there are some other libpq in PATH
|
||||||
|
- name: Drop spurious libpq in the path
|
||||||
|
run: rm -rf c:/tools/php C:/Strawberry/c/bin
|
||||||
|
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Start PostgreSQL service for test
|
||||||
|
run: |
|
||||||
|
$PgSvc = Get-Service "postgresql*"
|
||||||
|
Set-Service $PgSvc.Name -StartupType manual
|
||||||
|
$PgSvc.Start()
|
||||||
|
shell: powershell
|
||||||
|
|
||||||
|
- name: Export GitHub Actions cache environment variables
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const path = require('path')
|
||||||
|
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
|
||||||
|
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||||
|
core.addPath(path.join(process.env.VCPKG_INSTALLATION_ROOT, 'installed/x64-windows-release/lib'));
|
||||||
|
core.addPath(path.join(process.env.VCPKG_INSTALLATION_ROOT, 'installed/x64-windows-release/bin'));
|
||||||
|
|
||||||
|
- name: Create the binary package source tree
|
||||||
|
run: >-
|
||||||
|
sed -i 's/^setup(name="psycopg2"/setup(name="${{matrix.package_name}}"/'
|
||||||
|
setup.py
|
||||||
|
if: ${{ matrix.package_name != 'psycopg2' }}
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
uses: pypa/cibuildwheel@v2.23.2
|
||||||
|
env:
|
||||||
|
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" # cache vcpkg
|
||||||
|
CIBW_BUILD: ${{matrix.pyver}}-${{matrix.arch}}
|
||||||
|
CIBW_ARCHS_WINDOWS: AMD64 x86
|
||||||
|
CIBW_BEFORE_BUILD_WINDOWS: '.\scripts\build\wheel_win32_before_build.bat'
|
||||||
|
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: >-
|
||||||
|
delvewheel repair -w {dest_dir}
|
||||||
|
--no-mangle "libiconv-2.dll;libwinpthread-1.dll" {wheel}
|
||||||
|
CIBW_TEST_COMMAND: >-
|
||||||
|
set PYTHONPATH={project} &&
|
||||||
|
python -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')"
|
||||||
|
# Note: no fast test because we don't run Windows tests
|
||||||
|
CIBW_ENVIRONMENT_WINDOWS: >-
|
||||||
|
PSYCOPG2_TESTDB=postgres
|
||||||
|
PSYCOPG2_TESTDB_USER=postgres
|
||||||
|
PSYCOPG2_TESTDB_HOST=localhost
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows-${{ matrix.package_name }}-${{matrix.pyver}}-${{matrix.arch}}
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
merge: # {{{
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- sdist
|
||||||
|
- linux
|
||||||
|
- macos
|
||||||
|
- windows
|
||||||
|
steps:
|
||||||
|
- name: Merge Artifacts
|
||||||
|
uses: actions/upload-artifact/merge@v4
|
||||||
|
with:
|
||||||
|
name: psycopg2-artifacts
|
||||||
|
delete-merged: true
|
||||||
|
|
||||||
|
# }}}
|
79
.github/workflows/tests.yml
vendored
Normal file
79
.github/workflows/tests.yml
vendored
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
name: Tests
|
||||||
|
|
||||||
|
env:
|
||||||
|
PIP_BREAK_SYSTEM_PACKAGES: "1"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: true
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- {python: "3.8", postgres: "12"}
|
||||||
|
- {python: "3.9", postgres: "13"}
|
||||||
|
- {python: "3.10", postgres: "14"}
|
||||||
|
- {python: "3.11", postgres: "15"}
|
||||||
|
- {python: "3.12", postgres: "16"}
|
||||||
|
- {python: "3.13", postgres: "17"}
|
||||||
|
|
||||||
|
# Opposite extremes of the supported Py/PG range, other architecture
|
||||||
|
- {python: "3.8", postgres: "17", architecture: "x86"}
|
||||||
|
- {python: "3.9", postgres: "16", architecture: "x86"}
|
||||||
|
- {python: "3.10", postgres: "15", architecture: "x86"}
|
||||||
|
- {python: "3.11", postgres: "14", architecture: "x86"}
|
||||||
|
- {python: "3.12", postgres: "13", architecture: "x86"}
|
||||||
|
- {python: "3.13", postgres: "12", architecture: "x86"}
|
||||||
|
|
||||||
|
env:
|
||||||
|
PSYCOPG2_TESTDB: postgres
|
||||||
|
PSYCOPG2_TESTDB_HOST: 127.0.0.1
|
||||||
|
PSYCOPG2_TESTDB_USER: postgres
|
||||||
|
PSYCOPG2_TESTDB_PASSWORD: password
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgresql:
|
||||||
|
image: postgres:${{ matrix.postgres }}
|
||||||
|
env:
|
||||||
|
POSTGRES_PASSWORD: password
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
# Set health checks to wait until postgres has started
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Can enable to test an unreleased libpq version.
|
||||||
|
- name: install libpq 16
|
||||||
|
if: false
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
rel=$(lsb_release -c -s)
|
||||||
|
echo "deb http://apt.postgresql.org/pub/repos/apt ${rel}-pgdg main 16" \
|
||||||
|
| sudo tee -a /etc/apt/sources.list.d/pgdg.list
|
||||||
|
sudo apt-get -qq update
|
||||||
|
pqver=$(apt-cache show libpq5 | grep ^Version: | head -1 \
|
||||||
|
| awk '{print $2}')
|
||||||
|
sudo apt-get -qq -y install "libpq-dev=${pqver}" "libpq5=${pqver}"
|
||||||
|
|
||||||
|
- name: Install tox
|
||||||
|
run: pip install "tox < 4"
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python }}
|
||||||
|
- name: Run tests
|
||||||
|
env:
|
||||||
|
MATRIX_PYTHON: ${{ matrix.python }}
|
||||||
|
run: tox -e ${MATRIX_PYTHON%-dev}
|
||||||
|
timeout-minutes: 5
|
13
.gitignore
vendored
13
.gitignore
vendored
|
@ -4,10 +4,15 @@ MANIFEST
|
||||||
*.pidb
|
*.pidb
|
||||||
*.pyc
|
*.pyc
|
||||||
*.sw[po]
|
*.sw[po]
|
||||||
|
*.egg-info/
|
||||||
dist/*
|
dist/*
|
||||||
build/*
|
/build
|
||||||
doc/src/_build/*
|
|
||||||
doc/html/*
|
|
||||||
doc/psycopg2.txt
|
|
||||||
env
|
env
|
||||||
|
env?
|
||||||
|
.idea
|
||||||
.tox
|
.tox
|
||||||
|
.vscode/
|
||||||
|
/rel
|
||||||
|
/wheels
|
||||||
|
/packages
|
||||||
|
/wheelhouse
|
||||||
|
|
13
.travis.yml
13
.travis.yml
|
@ -1,13 +0,0 @@
|
||||||
language: python
|
|
||||||
|
|
||||||
python:
|
|
||||||
- 2.6
|
|
||||||
- 2.7
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- psql -c 'create database psycopg2_test;' -U postgres
|
|
||||||
|
|
||||||
install:
|
|
||||||
- python setup.py install
|
|
||||||
|
|
||||||
script: make check
|
|
105
INSTALL
105
INSTALL
|
@ -1,103 +1,4 @@
|
||||||
Compiling and installing psycopg
|
Installation instructions are included in the docs.
|
||||||
********************************
|
|
||||||
|
|
||||||
** Important note: if you plan to use psycopg2 in a multithreaded application,
|
|
||||||
make sure that your libpq has been compiled with the --with-thread-safety
|
|
||||||
option. psycopg2 will work correctly even with a non-thread-safe libpq but
|
|
||||||
libpq will leak memory.
|
|
||||||
|
|
||||||
psycopg2 uses distutils for its build process, so most of the process is
|
|
||||||
executed by the setup.py script. Before building psycopg look at
|
|
||||||
setup.cfg file and change any settings to follow your system (or taste);
|
|
||||||
then:
|
|
||||||
|
|
||||||
python setup.py build
|
|
||||||
|
|
||||||
to build in the local directory; and:
|
|
||||||
|
|
||||||
python setup.py install
|
|
||||||
|
|
||||||
to install system-wide.
|
|
||||||
|
|
||||||
|
|
||||||
Common errors and build problems
|
|
||||||
================================
|
|
||||||
|
|
||||||
One of the most common errors is trying to build psycopg without the right
|
|
||||||
development headers for PostgreSQL, Python or both. If you get errors, look
|
|
||||||
for the following messages and then take the appropriate action:
|
|
||||||
|
|
||||||
libpq-fe.h: No such file or directory
|
|
||||||
PostgreSQL headers are not properly installed on your system or are
|
|
||||||
installed in a non default path. First make sure they are installed, then
|
|
||||||
check setup.cfg and make sure pg_config points to a valid pg_config
|
|
||||||
executable. If you don't have a working pg_config try to play with the
|
|
||||||
include_dirs variable (and note that a working pg_config is better.)
|
|
||||||
|
|
||||||
|
|
||||||
Running the test suite
|
|
||||||
======================
|
|
||||||
|
|
||||||
The included Makefile allows to run all the tests included in the
|
|
||||||
distribution. Just use:
|
|
||||||
|
|
||||||
make
|
|
||||||
make check
|
|
||||||
|
|
||||||
The tests are run against a database called psycopg2_test on unix socket
|
|
||||||
and standard port. You can configure a different database to run the test
|
|
||||||
by setting the environment variables:
|
|
||||||
|
|
||||||
- PSYCOPG2_TESTDB
|
|
||||||
- PSYCOPG2_TESTDB_HOST
|
|
||||||
- PSYCOPG2_TESTDB_PORT
|
|
||||||
- PSYCOPG2_TESTDB_USER
|
|
||||||
|
|
||||||
The database should be created before running the tests.
|
|
||||||
|
|
||||||
The standard Python unittest is used to run the tests. But if unittest2 is
|
|
||||||
found it will be used instead, with the result of having more informations
|
|
||||||
about skipped tests.
|
|
||||||
|
|
||||||
|
|
||||||
Building the documentation
|
|
||||||
==========================
|
|
||||||
|
|
||||||
In order to build the documentation included in the distribution, use
|
|
||||||
|
|
||||||
make env
|
|
||||||
make docs
|
|
||||||
|
|
||||||
The first command will install all the dependencies (Sphinx, Docutils) in
|
|
||||||
an 'env' directory in the project tree. The second command will build both
|
|
||||||
the html format (in the 'doc/html' directory) and in plain text
|
|
||||||
(doc/psycopg2.txt)
|
|
||||||
|
|
||||||
|
|
||||||
Using setuptools and EasyInstall
|
|
||||||
================================
|
|
||||||
|
|
||||||
If setuptools are installed on your system you can easily create an egg for
|
|
||||||
psycopg and install it. Download the source distribution (if you're reading
|
|
||||||
this file you probably already have) and then edit setup.cfg to your taste
|
|
||||||
and build from the source distribution top-level directory using:
|
|
||||||
|
|
||||||
easy_install .
|
|
||||||
|
|
||||||
|
|
||||||
Compiling under Windows with mingw32
|
|
||||||
====================================
|
|
||||||
|
|
||||||
You can compile psycopg under Windows platform with mingw32
|
|
||||||
(http://www.mingw.org/) compiler. MinGW is also shipped with IDEs such as
|
|
||||||
Dev-C++ (http://www.bloodshed.net/devcpp.html) and Code::Blocks
|
|
||||||
(http://www.codeblocks.org). gcc binaries should be in your PATH.
|
|
||||||
|
|
||||||
You need a PostgreSQL with include and libary files installed. At least v8.0
|
|
||||||
is required.
|
|
||||||
|
|
||||||
First you need to create a libpython2X.a as described in
|
|
||||||
http://starship.python.net/crew/kernr/mingw32/Notes.html. Then run:
|
|
||||||
|
|
||||||
python setup.py build_ext --compiler=mingw32 install
|
|
||||||
|
|
||||||
|
Please check the 'doc/src/install.rst' file or online at
|
||||||
|
<https://www.psycopg.org/docs/install.html>.
|
||||||
|
|
15
LICENSE
15
LICENSE
|
@ -1,5 +1,5 @@
|
||||||
psycopg2 and the LGPL
|
psycopg2 and the LGPL
|
||||||
=====================
|
---------------------
|
||||||
|
|
||||||
psycopg2 is free software: you can redistribute it and/or modify it
|
psycopg2 is free software: you can redistribute it and/or modify it
|
||||||
under the terms of the GNU Lesser General Public License as published
|
under the terms of the GNU Lesser General Public License as published
|
||||||
|
@ -25,19 +25,14 @@ 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
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
along with psycopg2 (see the doc/ directory.)
|
along with psycopg2 (see the doc/ directory.)
|
||||||
If not, see <http://www.gnu.org/licenses/>.
|
If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
Alternative licenses
|
Alternative licenses
|
||||||
====================
|
--------------------
|
||||||
|
|
||||||
If you prefer you can use the Zope Database Adapter ZPsycopgDA (i.e.,
|
The following BSD-like license applies (at your option) to the files following
|
||||||
every file inside the ZPsycopgDA directory) user the ZPL license as
|
the pattern ``psycopg/adapter*.{h,c}`` and ``psycopg/microprotocol*.{h,c}``:
|
||||||
published on the Zope web site, http://www.zope.org/Resources/ZPL.
|
|
||||||
|
|
||||||
Also, 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,
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
including commercial applications, and to alter it and redistribute it
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
|
15
MANIFEST.in
15
MANIFEST.in
|
@ -1,16 +1,9 @@
|
||||||
recursive-include psycopg *.c *.h *.manifest
|
recursive-include psycopg *.c *.h *.manifest
|
||||||
recursive-include lib *.py
|
recursive-include lib *.py
|
||||||
recursive-include tests *.py
|
recursive-include tests *.py
|
||||||
recursive-include ZPsycopgDA *.py *.gif *.dtml
|
include doc/README.rst doc/SUCCESS doc/COPYING.LESSER doc/pep-0249.txt
|
||||||
recursive-include psycopg2da *
|
include doc/Makefile doc/requirements.txt
|
||||||
recursive-include examples *.py somehackers.jpg whereareyou.jpg
|
|
||||||
recursive-include debian *
|
|
||||||
recursive-include doc README HACKING SUCCESS COPYING* ChangeLog-1.x pep-0249.txt
|
|
||||||
recursive-include doc *.txt *.html *.css *.js Makefile
|
|
||||||
recursive-include doc/src *.rst *.py *.css Makefile
|
recursive-include doc/src *.rst *.py *.css Makefile
|
||||||
recursive-include doc/html *
|
|
||||||
prune doc/src/_build
|
|
||||||
recursive-include scripts *.py *.sh
|
recursive-include scripts *.py *.sh
|
||||||
include scripts/maketypes.sh scripts/buildtypes.py
|
include AUTHORS README.rst INSTALL LICENSE NEWS
|
||||||
include AUTHORS README INSTALL LICENSE NEWS ChangeLog
|
include MANIFEST.in setup.py setup.cfg Makefile
|
||||||
include PKG-INFO MANIFEST.in MANIFEST setup.py setup.cfg Makefile
|
|
||||||
|
|
52
Makefile
52
Makefile
|
@ -6,12 +6,11 @@
|
||||||
#
|
#
|
||||||
# Build the documentation::
|
# Build the documentation::
|
||||||
#
|
#
|
||||||
# make env
|
# make env (once)
|
||||||
# make docs
|
# make docs
|
||||||
#
|
#
|
||||||
# Create a source package::
|
# Create a source package::
|
||||||
#
|
#
|
||||||
# make env # required to build the documentation
|
|
||||||
# make sdist
|
# make sdist
|
||||||
#
|
#
|
||||||
# Run the test::
|
# Run the test::
|
||||||
|
@ -21,9 +20,6 @@
|
||||||
PYTHON := python$(PYTHON_VERSION)
|
PYTHON := python$(PYTHON_VERSION)
|
||||||
PYTHON_VERSION ?= $(shell $(PYTHON) -c 'import sys; print ("%d.%d" % sys.version_info[:2])')
|
PYTHON_VERSION ?= $(shell $(PYTHON) -c 'import sys; print ("%d.%d" % sys.version_info[:2])')
|
||||||
BUILD_DIR = $(shell pwd)/build/lib.$(PYTHON_VERSION)
|
BUILD_DIR = $(shell pwd)/build/lib.$(PYTHON_VERSION)
|
||||||
ENV_DIR = $(shell pwd)/env/py-$(PYTHON_VERSION)
|
|
||||||
ENV_BIN = $(ENV_DIR)/bin
|
|
||||||
ENV_LIB = $(ENV_DIR)/lib
|
|
||||||
|
|
||||||
SOURCE_C := $(wildcard psycopg/*.c psycopg/*.h)
|
SOURCE_C := $(wildcard psycopg/*.c psycopg/*.h)
|
||||||
SOURCE_PY := $(wildcard lib/*.py)
|
SOURCE_PY := $(wildcard lib/*.py)
|
||||||
|
@ -33,8 +29,7 @@ SOURCE := $(SOURCE_C) $(SOURCE_PY) $(SOURCE_TESTS) $(SOURCE_DOC)
|
||||||
|
|
||||||
PACKAGE := $(BUILD_DIR)/psycopg2
|
PACKAGE := $(BUILD_DIR)/psycopg2
|
||||||
PLATLIB := $(PACKAGE)/_psycopg.so
|
PLATLIB := $(PACKAGE)/_psycopg.so
|
||||||
PURELIB := $(patsubst lib/%,$(PACKAGE)/%,$(SOURCE_PY)) \
|
PURELIB := $(patsubst lib/%,$(PACKAGE)/%,$(SOURCE_PY))
|
||||||
$(patsubst tests/%,$(PACKAGE)/tests/%,$(SOURCE_TESTS))
|
|
||||||
|
|
||||||
BUILD_OPT := --build-lib=$(BUILD_DIR)
|
BUILD_OPT := --build-lib=$(BUILD_DIR)
|
||||||
BUILD_EXT_OPT := --build-lib=$(BUILD_DIR)
|
BUILD_EXT_OPT := --build-lib=$(BUILD_DIR)
|
||||||
|
@ -47,10 +42,7 @@ endif
|
||||||
VERSION := $(shell grep PSYCOPG_VERSION setup.py | head -1 | sed -e "s/.*'\(.*\)'/\1/")
|
VERSION := $(shell grep PSYCOPG_VERSION setup.py | head -1 | sed -e "s/.*'\(.*\)'/\1/")
|
||||||
SDIST := dist/psycopg2-$(VERSION).tar.gz
|
SDIST := dist/psycopg2-$(VERSION).tar.gz
|
||||||
|
|
||||||
EASY_INSTALL = PYTHONPATH=$(ENV_LIB) $(ENV_BIN)/easy_install-$(PYTHON_VERSION) -d $(ENV_LIB) -s $(ENV_BIN)
|
.PHONY: check clean
|
||||||
EZ_SETUP = $(ENV_BIN)/ez_setup.py
|
|
||||||
|
|
||||||
.PHONY: env check clean
|
|
||||||
|
|
||||||
default: package
|
default: package
|
||||||
|
|
||||||
|
@ -58,36 +50,20 @@ all: package sdist
|
||||||
|
|
||||||
package: $(PLATLIB) $(PURELIB)
|
package: $(PLATLIB) $(PURELIB)
|
||||||
|
|
||||||
docs: docs-html docs-txt
|
docs: docs-html
|
||||||
|
|
||||||
docs-html: doc/html/genindex.html
|
docs-html: doc/html/genindex.html
|
||||||
|
|
||||||
docs-txt: doc/psycopg2.txt
|
|
||||||
|
|
||||||
# for PyPI documentation
|
# for PyPI documentation
|
||||||
docs-zip: doc/docs.zip
|
docs-zip: doc/docs.zip
|
||||||
|
|
||||||
sdist: $(SDIST)
|
sdist: $(SDIST)
|
||||||
|
|
||||||
# The environment is currently required to build the documentation.
|
env:
|
||||||
# It is not clean by 'make clean'
|
$(MAKE) -C doc $@
|
||||||
|
|
||||||
env: easy_install
|
|
||||||
mkdir -p $(ENV_BIN)
|
|
||||||
mkdir -p $(ENV_LIB)
|
|
||||||
$(EASY_INSTALL) docutils
|
|
||||||
$(EASY_INSTALL) sphinx
|
|
||||||
|
|
||||||
easy_install: ez_setup
|
|
||||||
PYTHONPATH=$(ENV_LIB) $(PYTHON) $(EZ_SETUP) -d $(ENV_LIB) -s $(ENV_BIN) setuptools
|
|
||||||
|
|
||||||
ez_setup:
|
|
||||||
mkdir -p $(ENV_BIN)
|
|
||||||
mkdir -p $(ENV_LIB)
|
|
||||||
wget -O $(EZ_SETUP) http://peak.telecommunity.com/dist/ez_setup.py
|
|
||||||
|
|
||||||
check:
|
check:
|
||||||
PYTHONPATH=$(BUILD_DIR):$(PYTHONPATH) $(PYTHON) -c "from psycopg2 import tests; tests.unittest.main(defaultTest='tests.test_suite')" --verbose
|
PYTHONPATH=$(BUILD_DIR) $(PYTHON) -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')" --verbose
|
||||||
|
|
||||||
testdb:
|
testdb:
|
||||||
@echo "* Creating $(TESTDB)"
|
@echo "* Creating $(TESTDB)"
|
||||||
|
@ -113,24 +89,16 @@ $(PACKAGE)/tests/%.py: tests/%.py
|
||||||
$(PYTHON) setup.py build_py $(BUILD_OPT)
|
$(PYTHON) setup.py build_py $(BUILD_OPT)
|
||||||
touch $@
|
touch $@
|
||||||
|
|
||||||
$(SDIST): docs MANIFEST $(SOURCE)
|
$(SDIST): $(SOURCE)
|
||||||
$(PYTHON) setup.py sdist $(SDIST_OPT)
|
$(PYTHON) setup.py sdist $(SDIST_OPT)
|
||||||
|
|
||||||
MANIFEST: MANIFEST.in $(SOURCE)
|
|
||||||
# Run twice as MANIFEST.in includes MANIFEST
|
|
||||||
$(PYTHON) setup.py sdist --manifest-only
|
|
||||||
$(PYTHON) setup.py sdist --manifest-only
|
|
||||||
|
|
||||||
# docs depend on the build as it partly use introspection.
|
# docs depend on the build as it partly use introspection.
|
||||||
doc/html/genindex.html: $(PLATLIB) $(PURELIB) $(SOURCE_DOC)
|
doc/html/genindex.html: $(PLATLIB) $(PURELIB) $(SOURCE_DOC)
|
||||||
PYTHONPATH=$(ENV_LIB):$(BUILD_DIR) $(MAKE) SPHINXBUILD=$(ENV_BIN)/sphinx-build -C doc html
|
$(MAKE) -C doc html
|
||||||
|
|
||||||
doc/psycopg2.txt: $(PLATLIB) $(PURELIB) $(SOURCE_DOC)
|
|
||||||
PYTHONPATH=$(ENV_LIB):$(BUILD_DIR) $(MAKE) SPHINXBUILD=$(ENV_BIN)/sphinx-build -C doc text
|
|
||||||
|
|
||||||
doc/docs.zip: doc/html/genindex.html
|
doc/docs.zip: doc/html/genindex.html
|
||||||
(cd doc/html && zip -r ../docs.zip *)
|
(cd doc/html && zip -r ../docs.zip *)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf build MANIFEST
|
rm -rf build
|
||||||
$(MAKE) -C doc clean
|
$(MAKE) -C doc clean
|
||||||
|
|
613
NEWS
613
NEWS
|
@ -1,3 +1,591 @@
|
||||||
|
Current release
|
||||||
|
---------------
|
||||||
|
|
||||||
|
What's new in psycopg 2.9.10
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Add support for Python 3.13.
|
||||||
|
- Receive notifications on commit (:ticket:`#1728`).
|
||||||
|
- `~psycopg2.errorcodes` map and `~psycopg2.errors` classes updated to
|
||||||
|
PostgreSQL 17.
|
||||||
|
- Drop support for Python 3.7.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.9.9
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Add support for Python 3.12.
|
||||||
|
- Drop support for Python 3.6.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.9.8
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Wheel package bundled with PostgreSQL 16 libpq in order to add support for
|
||||||
|
recent features, such as ``sslcertmode``.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.9.7
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Fix propagation of exceptions raised during module initialization
|
||||||
|
(:ticket:`#1598`).
|
||||||
|
- Fix building when pg_config returns an empty string (:ticket:`#1599`).
|
||||||
|
- Wheel package bundled with OpenSSL 1.1.1v.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.9.6
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Package manylinux 2014 for aarch64 and ppc64le platforms, in order to
|
||||||
|
include libpq 15 in the binary package (:ticket:`#1396`).
|
||||||
|
- Wheel package bundled with OpenSSL 1.1.1t.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.9.5
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Add support for Python 3.11.
|
||||||
|
- Add support for rowcount in MERGE statements in binary packages
|
||||||
|
(:ticket:`#1497`).
|
||||||
|
- Wheel package bundled with OpenSSL 1.1.1r and PostgreSQL 15 libpq.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.9.4
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Fix `~psycopg2.extras.register_composite()`,
|
||||||
|
`~psycopg2.extras.register_range()` with customized :sql:`search_path`
|
||||||
|
(:ticket:`#1487`).
|
||||||
|
- Handle correctly composite types with names or in schemas requiring escape.
|
||||||
|
- Find ``pg_service.conf`` file in the ``/etc/postgresql-common`` directory in
|
||||||
|
binary packages (:ticket:`#1365`).
|
||||||
|
- `~psycopg2.errorcodes` map and `~psycopg2.errors` classes updated to
|
||||||
|
PostgreSQL 15.
|
||||||
|
- Wheel package bundled with OpenSSL 1.1.1q and PostgreSQL 14.4 libpq.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.9.3
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Alpine (musl) wheels now available (:ticket:`#1392`).
|
||||||
|
- macOS arm64 (Apple M1) wheels now available (:ticket:`1482`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.9.2
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Raise `ValueError` for dates >= Y10k (:ticket:`#1307`).
|
||||||
|
- `~psycopg2.errorcodes` map and `~psycopg2.errors` classes updated to
|
||||||
|
PostgreSQL 14.
|
||||||
|
- Add preliminary support for Python 3.11 (:tickets:`#1376, #1386`).
|
||||||
|
- Wheel package bundled with OpenSSL 1.1.1l and PostgreSQL 14.1 libpq
|
||||||
|
(:ticket:`#1388`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.9.1
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Fix regression with named `~psycopg2.sql.Placeholder` (:ticket:`#1291`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.9
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
- ``with connection`` starts a transaction on autocommit transactions too
|
||||||
|
(:ticket:`#941`).
|
||||||
|
- Timezones with fractional minutes are supported on Python 3.7 and following
|
||||||
|
(:ticket:`#1272`).
|
||||||
|
- Escape table and column names in `~cursor.copy_from()` and
|
||||||
|
`~cursor.copy_to()`.
|
||||||
|
- Connection exceptions with sqlstate ``08XXX`` reclassified as
|
||||||
|
`~psycopg2.OperationalError` (a subclass of the previously used
|
||||||
|
`~psycopg2.DatabaseError`) (:ticket:`#1148`).
|
||||||
|
- Include library dirs required from libpq to work around MacOS build problems
|
||||||
|
(:ticket:`#1200`).
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
|
||||||
|
- Dropped support for Python 2.7, 3.4, 3.5 (:tickets:`#1198, #1000, #1197`).
|
||||||
|
- Dropped support for mx.DateTime.
|
||||||
|
- Use `datetime.timezone` objects by default in datetime objects instead of
|
||||||
|
`~psycopg2.tz.FixedOffsetTimezone`.
|
||||||
|
- The `psycopg2.tz` module is deprecated and scheduled to be dropped in the
|
||||||
|
next major release.
|
||||||
|
- Provide :pep:`599` wheels packages (manylinux2014 tag) for i686 and x86_64
|
||||||
|
platforms.
|
||||||
|
- Provide :pep:`600` wheels packages (manylinux_2_24 tag) for aarch64 and
|
||||||
|
ppc64le platforms.
|
||||||
|
- Wheel package bundled with OpenSSL 1.1.1k and PostgreSQL 13.3 libpq.
|
||||||
|
- Build system for Linux/MacOS binary packages moved to GitHub Actions.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.8.7
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Accept empty params as `~psycopg2.connect()` (:ticket:`#1250`).
|
||||||
|
- Fix attributes refcount in `Column` initialisation (:ticket:`#1252`).
|
||||||
|
- Allow re-initialisation of static variables in the C module (:ticket:`#1267`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.8.6
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Fixed memory leak changing connection encoding to the current one
|
||||||
|
(:ticket:`#1101`).
|
||||||
|
- Fixed search of mxDateTime headers in virtualenvs (:ticket:`#996`).
|
||||||
|
- Added missing values from errorcodes (:ticket:`#1133`).
|
||||||
|
- `cursor.query` reports the query of the last :sql:`COPY` operation too
|
||||||
|
(:ticket:`#1141`).
|
||||||
|
- `~psycopg2.errorcodes` map and `~psycopg2.errors` classes updated to
|
||||||
|
PostgreSQL 13.
|
||||||
|
- Added wheel packages for ARM architecture (:ticket:`#1125`).
|
||||||
|
- Wheel package bundled with OpenSSL 1.1.1g.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.8.5
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Fixed use of `!connection_factory` and `!cursor_factory` together
|
||||||
|
(:ticket:`#1019`).
|
||||||
|
- Added support for `~logging.LoggerAdapter` in
|
||||||
|
`~psycopg2.extras.LoggingConnection` (:ticket:`#1026`).
|
||||||
|
- `~psycopg2.extensions.Column` objects in `cursor.description` can be sliced
|
||||||
|
(:ticket:`#1034`).
|
||||||
|
- Added AIX support (:ticket:`#1061`).
|
||||||
|
- Fixed `~copy.copy()` of `~psycopg2.extras.DictCursor` rows (:ticket:`#1073`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.8.4
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Fixed building with Python 3.8 (:ticket:`#854`).
|
||||||
|
- Don't swallow keyboard interrupts on connect when a password is specified
|
||||||
|
in the connection string (:ticket:`#898`).
|
||||||
|
- Don't advance replication cursor when the message wasn't confirmed
|
||||||
|
(:ticket:`#940`).
|
||||||
|
- Fixed inclusion of ``time.h`` on linux (:ticket:`#951`).
|
||||||
|
- Fixed int overflow for large values in `~psycopg2.extensions.Column.table_oid`
|
||||||
|
and `~psycopg2.extensions.Column.type_code` (:ticket:`#961`).
|
||||||
|
- `~psycopg2.errorcodes` map and `~psycopg2.errors` classes updated to
|
||||||
|
PostgreSQL 12.
|
||||||
|
- Wheel package bundled with OpenSSL 1.1.1d and PostgreSQL at least 11.4.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.8.3
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Added *interval_status* parameter to
|
||||||
|
`~psycopg2.extras.ReplicationCursor.start_replication()` method and other
|
||||||
|
facilities to send automatic replication keepalives at periodic intervals
|
||||||
|
(:ticket:`#913`).
|
||||||
|
- Fixed namedtuples caching introduced in 2.8 (:ticket:`#928`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.8.2
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Fixed `~psycopg2.extras.RealDictCursor` when there are repeated columns
|
||||||
|
(:ticket:`#884`).
|
||||||
|
- Binary packages built with openssl 1.1.1b. Should fix concurrency problems
|
||||||
|
(:tickets:`#543, #836`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.8.1
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Fixed `~psycopg2.extras.RealDictRow` modifiability (:ticket:`#886`).
|
||||||
|
- Fixed "there's no async cursor" error polling a connection with no cursor
|
||||||
|
(:ticket:`#887`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.8
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
New features:
|
||||||
|
|
||||||
|
- Added `~psycopg2.errors` module. Every PostgreSQL error is converted into
|
||||||
|
a specific exception class (:ticket:`#682`).
|
||||||
|
- Added `~psycopg2.extensions.encrypt_password()` function (:ticket:`#576`).
|
||||||
|
- Added `~psycopg2.extensions.BYTES` adapter to manage databases with mixed
|
||||||
|
encodings on Python 3 (:ticket:`#835`).
|
||||||
|
- Added `~psycopg2.extensions.Column.table_oid` and
|
||||||
|
`~psycopg2.extensions.Column.table_column` attributes on `cursor.description`
|
||||||
|
items (:ticket:`#661`).
|
||||||
|
- Added `connection.info` object to retrieve various PostgreSQL connection
|
||||||
|
information (:ticket:`#726`).
|
||||||
|
- Added `~connection.get_native_connection()` to expose the raw ``PGconn``
|
||||||
|
structure to C extensions via Capsule (:ticket:`#782`).
|
||||||
|
- Added `~connection.pgconn_ptr` and `~cursor.pgresult_ptr` to expose raw
|
||||||
|
C structures to Python and interact with libpq via ctypes (:ticket:`#782`).
|
||||||
|
- `~psycopg2.sql.Identifier` can represent qualified names in SQL composition
|
||||||
|
(:ticket:`#732`).
|
||||||
|
- Added `!ReplicationCursor`.\ `~psycopg2.extras.ReplicationCursor.wal_end`
|
||||||
|
attribute (:ticket:`#800`).
|
||||||
|
- Added *fetch* parameter to `~psycopg2.extras.execute_values()` function
|
||||||
|
(:ticket:`#813`).
|
||||||
|
- `!str()` on `~psycopg2.extras.Range` produces a human-readable representation
|
||||||
|
(:ticket:`#773`).
|
||||||
|
- `~psycopg2.extras.DictCursor` and `~psycopg2.extras.RealDictCursor` rows
|
||||||
|
maintain columns order (:ticket:`#177`).
|
||||||
|
- Added `~psycopg2.extensions.Diagnostics.severity_nonlocalized` attribute on
|
||||||
|
the `~psycopg2.extensions.Diagnostics` object (:ticket:`#783`).
|
||||||
|
- More efficient `~psycopg2.extras.NamedTupleCursor` (:ticket:`#838`).
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
- Fixed connections occasionally broken by the unrelated use of the
|
||||||
|
multiprocessing module (:ticket:`#829`).
|
||||||
|
- Fixed async communication blocking if results are returned in different
|
||||||
|
chunks, e.g. with notices interspersed to the results (:ticket:`#856`).
|
||||||
|
- Fixed adaptation of numeric subclasses such as `~enum.IntEnum`
|
||||||
|
(:ticket:`#591`).
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
|
||||||
|
- Dropped support for Python 2.6, 3.2, 3.3.
|
||||||
|
- Dropped `psycopg1` module.
|
||||||
|
- Dropped deprecated `!register_tstz_w_secs()` (was previously a no-op).
|
||||||
|
- Dropped deprecated `!PersistentConnectionPool`. This pool class was mostly
|
||||||
|
designed to interact with Zope. Use `!ZPsycopgDA.pool` instead.
|
||||||
|
- Binary packages no longer installed by default. The 'psycopg2-binary'
|
||||||
|
package must be used explicitly.
|
||||||
|
- Dropped `!PSYCOPG_DISPLAY_SIZE` build parameter.
|
||||||
|
- Dropped support for mxDateTime as the default date and time adapter.
|
||||||
|
mxDatetime support continues to be available as an alternative to Python's
|
||||||
|
builtin datetime.
|
||||||
|
- No longer use 2to3 during installation for Python 2 & 3 compatibility. All
|
||||||
|
source files are now compatible with Python 2 & 3 as is.
|
||||||
|
- The `!psycopg2.test` package is no longer installed by ``python setup.py
|
||||||
|
install``.
|
||||||
|
- Wheel package bundled with OpenSSL 1.0.2r and PostgreSQL 11.2 libpq.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.7.7
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Cleanup of the cursor results assignment code, which might have solved
|
||||||
|
double free and inconsistencies in concurrent usage (:tickets:`#346, #384`).
|
||||||
|
- Wheel package bundled with OpenSSL 1.0.2q.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.7.6.1
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Fixed binary package broken on OS X 10.12 (:ticket:`#807`).
|
||||||
|
- Wheel package bundled with PostgreSQL 11.1 libpq.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.7.6
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Close named cursors if exist, even if `~cursor.execute()` wasn't called
|
||||||
|
(:ticket:`#746`).
|
||||||
|
- Fixed building on modern FreeBSD versions with Python 3.7 (:ticket:`#755`).
|
||||||
|
- Fixed hang trying to :sql:`COPY` via `~cursor.execute()` in asynchronous
|
||||||
|
connections (:ticket:`#781`).
|
||||||
|
- Fixed adaptation of arrays of empty arrays (:ticket:`#788`).
|
||||||
|
- Fixed segfault accessing the connection's `~connection.readonly` and
|
||||||
|
`~connection.deferrable` attributes repeatedly (:ticket:`#790`).
|
||||||
|
- `~psycopg2.extras.execute_values()` accepts `~psycopg2.sql.Composable`
|
||||||
|
objects (:ticket:`#794`).
|
||||||
|
- `~psycopg2.errorcodes` map updated to PostgreSQL 11.
|
||||||
|
- Wheel package bundled with PostgreSQL 10.5 libpq and OpenSSL 1.0.2p.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.7.5
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Allow non-ascii chars in namedtuple fields (regression introduced fixing
|
||||||
|
:ticket:`#211`).
|
||||||
|
- Fixed adaptation of arrays of arrays of nulls (:ticket:`#325`).
|
||||||
|
- Fixed building on Solaris 11 and derivatives such as SmartOS and illumos
|
||||||
|
(:ticket:`#677`).
|
||||||
|
- Maybe fixed building on MSYS2 (as reported in :ticket:`#658`).
|
||||||
|
- Allow string subclasses in connection and other places (:ticket:`#679`).
|
||||||
|
- Don't raise an exception closing an unused named cursor (:ticket:`#716`).
|
||||||
|
- Wheel package bundled with PostgreSQL 10.4 libpq and OpenSSL 1.0.2o.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.7.4
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Moving away from installing the wheel package by default.
|
||||||
|
Packages installed from wheel raise a warning on import. Added package
|
||||||
|
``psycopg2-binary`` to install from wheel instead (:ticket:`#543`).
|
||||||
|
- Convert fields names into valid Python identifiers in
|
||||||
|
`~psycopg2.extras.NamedTupleCursor` (:ticket:`#211`).
|
||||||
|
- Fixed Solaris 10 support (:ticket:`#532`).
|
||||||
|
- `cursor.mogrify()` can be called on closed cursors (:ticket:`#579`).
|
||||||
|
- Fixed setting session characteristics in corner cases on autocommit
|
||||||
|
connections (:ticket:`#580`).
|
||||||
|
- Fixed `~psycopg2.extras.MinTimeLoggingCursor` on Python 3 (:ticket:`#609`).
|
||||||
|
- Fixed parsing of array of points as floats (:ticket:`#613`).
|
||||||
|
- Fixed `~psycopg2.__libpq_version__` building with libpq >= 10.1
|
||||||
|
(:ticket:`#632`).
|
||||||
|
- Fixed `~cursor.rowcount` after `~cursor.executemany()` with :sql:`RETURNING`
|
||||||
|
statements (:ticket:`#633`).
|
||||||
|
- Fixed compatibility problem with pypy3 (:ticket:`#649`).
|
||||||
|
- Wheel packages bundled with PostgreSQL 10.1 libpq and OpenSSL 1.0.2n.
|
||||||
|
- Wheel packages for Python 2.6 no more available (support dropped from
|
||||||
|
wheel building infrastructure).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.7.3.2
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Wheel package bundled with PostgreSQL 10.0 libpq and OpenSSL 1.0.2l
|
||||||
|
(:tickets:`#601, #602`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.7.3.1
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Dropped libresolv from wheel package to avoid incompatibility with
|
||||||
|
glibc 2.26 (wheels ticket #2).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.7.3
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Restored default :sql:`timestamptz[]` typecasting to Python `!datetime`.
|
||||||
|
Regression introduced in Psycopg 2.7.2 (:ticket:`#578`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.7.2
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Fixed inconsistent state in externally closed connections
|
||||||
|
(:tickets:`#263, #311, #443`). Was fixed in 2.6.2 but not included in
|
||||||
|
2.7 by mistake.
|
||||||
|
- Fixed Python exceptions propagation in green callback (:ticket:`#410`).
|
||||||
|
- Don't display the password in `connection.dsn` when the connection
|
||||||
|
string is specified as an URI (:ticket:`#528`).
|
||||||
|
- Return objects with timezone parsing "infinity" :sql:`timestamptz`
|
||||||
|
(:ticket:`#536`).
|
||||||
|
- Dropped dependency on VC9 runtime on Windows binary packages
|
||||||
|
(:ticket:`#541`).
|
||||||
|
- Fixed segfault in `~connection.lobject()` when *mode*\=\ `!None`
|
||||||
|
(:ticket:`#544`).
|
||||||
|
- Fixed `~connection.lobject()` keyword argument *lobject_factory*
|
||||||
|
(:ticket:`#545`).
|
||||||
|
- Fixed `~psycopg2.extras.ReplicationCursor.consume_stream()`
|
||||||
|
*keepalive_interval* argument (:ticket:`#547`).
|
||||||
|
- Maybe fixed random import error on Python 3.6 in multiprocess
|
||||||
|
environment (:ticket:`#550`).
|
||||||
|
- Fixed random `!SystemError` upon receiving abort signal (:ticket:`#551`).
|
||||||
|
- Accept `~psycopg2.sql.Composable` objects in
|
||||||
|
`~psycopg2.extras.ReplicationCursor.start_replication_expert()`
|
||||||
|
(:ticket:`#554`).
|
||||||
|
- Parse intervals returned as microseconds from Redshift (:ticket:`#558`).
|
||||||
|
- Added `~psycopg2.extras.Json` `!prepare()` method to consider connection
|
||||||
|
params when adapting (:ticket:`#562`).
|
||||||
|
- `~psycopg2.errorcodes` map updated to PostgreSQL 10 beta 1.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.7.1
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Ignore `!None` arguments passed to `~psycopg2.connect()` and
|
||||||
|
`~psycopg2.extensions.make_dsn()` (:ticket:`#517`).
|
||||||
|
- OpenSSL upgraded from major version 0.9.8 to 1.0.2 in the Linux wheel
|
||||||
|
packages (:ticket:`#518`).
|
||||||
|
- Fixed build with libpq versions < 9.3 (:ticket:`#520`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.7
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
New features:
|
||||||
|
|
||||||
|
- Added `~psycopg2.sql` module to generate SQL dynamically (:ticket:`#308`).
|
||||||
|
- Added :ref:`replication-support` (:ticket:`#322`). Main authors are
|
||||||
|
Oleksandr Shulgin and Craig Ringer, who deserve a huge thank you.
|
||||||
|
- Added `~psycopg2.extensions.parse_dsn()` and
|
||||||
|
`~psycopg2.extensions.make_dsn()` functions (:tickets:`#321, #363`).
|
||||||
|
`~psycopg2.connect()` now can take both *dsn* and keyword arguments, merging
|
||||||
|
them together.
|
||||||
|
- Added `~psycopg2.__libpq_version__` and
|
||||||
|
`~psycopg2.extensions.libpq_version()` to inspect the version of the
|
||||||
|
``libpq`` library the module was bundled with
|
||||||
|
(:tickets:`#35, #323`).
|
||||||
|
- The attributes `~connection.notices` and `~connection.notifies` can be
|
||||||
|
customized replacing them with any object exposing an `!append()` method
|
||||||
|
(:ticket:`#326`).
|
||||||
|
- Adapt network types to `ipaddress` objects when available. When not
|
||||||
|
enabled, convert arrays of network types to lists by default. The old `!Inet`
|
||||||
|
adapter is deprecated (:tickets:`#317, #343, #387`).
|
||||||
|
- Added `~psycopg2.extensions.quote_ident()` function (:ticket:`#359`).
|
||||||
|
- Added `~connection.get_dsn_parameters()` connection method (:ticket:`#364`).
|
||||||
|
- `~cursor.callproc()` now accepts a dictionary of parameters (:ticket:`#381`).
|
||||||
|
- Give precedence to `!__conform__()` over superclasses to choose an object
|
||||||
|
adapter (:ticket:`#456`).
|
||||||
|
- Using Python C API decoding functions and codecs caching for faster
|
||||||
|
unicode encoding/decoding (:ticket:`#473`).
|
||||||
|
- `~cursor.executemany()` slowness addressed by
|
||||||
|
`~psycopg2.extras.execute_batch()` and `~psycopg2.extras.execute_values()`
|
||||||
|
(:ticket:`#491`).
|
||||||
|
- Added ``async_`` as an alias for ``async`` to support Python 3.7 where
|
||||||
|
``async`` will become a keyword (:ticket:`#495`).
|
||||||
|
- Unless in autocommit, do not use :sql:`default_transaction_*` settings to
|
||||||
|
control the session characteristics as it may create problems with external
|
||||||
|
connection pools such as pgbouncer; use :sql:`BEGIN` options instead
|
||||||
|
(:ticket:`#503`).
|
||||||
|
- `~connection.isolation_level` is now writable and entirely separated from
|
||||||
|
`~connection.autocommit`; added `~connection.readonly`,
|
||||||
|
`~connection.deferrable` writable attributes.
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
- Throw an exception trying to pass ``NULL`` chars as parameters
|
||||||
|
(:ticket:`#420`).
|
||||||
|
- Fixed error caused by missing decoding `~psycopg2.extras.LoggingConnection`
|
||||||
|
(:ticket:`#483`).
|
||||||
|
- Fixed integer overflow in :sql:`interval` seconds (:ticket:`#512`).
|
||||||
|
- Make `~psycopg2.extras.Range` objects picklable (:ticket:`#462`).
|
||||||
|
- Fixed version parsing and building with PostgreSQL 10 (:ticket:`#489`).
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
|
||||||
|
- Dropped support for Python 2.5 and 3.1.
|
||||||
|
- Dropped support for client library older than PostgreSQL 9.1 (but older
|
||||||
|
server versions are still supported).
|
||||||
|
- `~connection.isolation_level` doesn't read from the database but will return
|
||||||
|
`~psycopg2.extensions.ISOLATION_LEVEL_DEFAULT` if no value was set on the
|
||||||
|
connection.
|
||||||
|
- Empty arrays no more converted into lists if they don't have a type attached
|
||||||
|
(:ticket:`#506`)
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.6.2
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Fixed inconsistent state in externally closed connections
|
||||||
|
(:tickets:`#263, #311, #443`).
|
||||||
|
- Report the server response status on errors (such as :ticket:`#281`).
|
||||||
|
- Raise `!NotSupportedError` on unhandled server response status
|
||||||
|
(:ticket:`#352`).
|
||||||
|
- Allow overriding string adapter encoding with no connection (:ticket:`#331`).
|
||||||
|
- The `~psycopg2.extras.wait_select` callback allows interrupting a
|
||||||
|
long-running query in an interactive shell using :kbd:`Ctrl-C`
|
||||||
|
(:ticket:`#333`).
|
||||||
|
- Fixed `!PersistentConnectionPool` on Python 3 (:ticket:`#348`).
|
||||||
|
- Fixed segfault on `repr()` of an unitialized connection (:ticket:`#361`).
|
||||||
|
- Allow adapting bytes using `~psycopg2.extensions.QuotedString` on Python 3
|
||||||
|
(:ticket:`#365`).
|
||||||
|
- Added support for setuptools/wheel (:ticket:`#370`).
|
||||||
|
- Fix build on Windows with Python 3.5, VS 2015 (:ticket:`#380`).
|
||||||
|
- Fixed `!errorcodes.lookup` initialization thread-safety (:ticket:`#382`).
|
||||||
|
- Fixed `!read()` exception propagation in copy_from (:ticket:`#412`).
|
||||||
|
- Fixed possible NULL TZ decref (:ticket:`#424`).
|
||||||
|
- `~psycopg2.errorcodes` map updated to PostgreSQL 9.5.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.6.1
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Lists consisting of only `None` are escaped correctly (:ticket:`#285`).
|
||||||
|
- Fixed deadlock in multithread programs using OpenSSL (:ticket:`#290`).
|
||||||
|
- Correctly unlock the connection after error in flush (:ticket:`#294`).
|
||||||
|
- Fixed `!MinTimeLoggingCursor.callproc()` (:ticket:`#309`).
|
||||||
|
- Added support for MSVC 2015 compiler (:ticket:`#350`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.6
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
New features:
|
||||||
|
|
||||||
|
- Added support for large objects larger than 2GB. Many thanks to Blake Rouse
|
||||||
|
and the MAAS Team for the feature development.
|
||||||
|
- Python `time` objects with a tzinfo specified and PostgreSQL :sql:`timetz`
|
||||||
|
data are converted into each other (:ticket:`#272`).
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
- Json adapter's `!str()` returns the adapted content instead of the `!repr()`
|
||||||
|
(:ticket:`#191`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.5.5
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Named cursors used as context manager don't swallow the exception on exit
|
||||||
|
(:ticket:`#262`).
|
||||||
|
- `cursor.description` can be pickled (:ticket:`#265`).
|
||||||
|
- Propagate read error messages in COPY FROM (:ticket:`#270`).
|
||||||
|
- PostgreSQL time 24:00 is converted to Python 00:00 (:ticket:`#278`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.5.4
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Added :sql:`jsonb` support for PostgreSQL 9.4 (:ticket:`#226`).
|
||||||
|
- Fixed segfault if COPY statements are passed to `~cursor.execute()` instead
|
||||||
|
of using the proper methods (:ticket:`#219`).
|
||||||
|
- Force conversion of pool arguments to integer to avoid potentially unbounded
|
||||||
|
pools (:ticket:`#220`).
|
||||||
|
- Cursors :sql:`WITH HOLD` don't begin a new transaction upon move/fetch/close
|
||||||
|
(:ticket:`#228`).
|
||||||
|
- Cursors :sql:`WITH HOLD` can be used in autocommit (:ticket:`#229`).
|
||||||
|
- `~cursor.callproc()` doesn't silently ignore an argument without a length.
|
||||||
|
- Fixed memory leak with large objects (:ticket:`#256`).
|
||||||
|
- Make sure the internal ``_psycopg.so`` module can be imported stand-alone (to
|
||||||
|
allow modules juggling such as the one described in :ticket:`#201`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.5.3
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Work around `pip issue #1630 <https://github.com/pypa/pip/issues/1630>`__
|
||||||
|
making installation via ``pip -e git+url`` impossible (:ticket:`#18`).
|
||||||
|
- Copy operations correctly set the `cursor.rowcount` attribute
|
||||||
|
(:ticket:`#180`).
|
||||||
|
- It is now possible to call `get_transaction_status()` on closed connections.
|
||||||
|
- Fixed unsafe access to object names causing assertion failures in
|
||||||
|
Python 3 debug builds (:ticket:`#188`).
|
||||||
|
- Mark the connection closed if found broken on `poll()` (from :ticket:`#192`
|
||||||
|
discussion)
|
||||||
|
- Fixed handling of dsn and closed attributes in connection subclasses
|
||||||
|
failing to connect (from :ticket:`#192` discussion).
|
||||||
|
- Added arbitrary but stable order to `Range` objects, thanks to
|
||||||
|
Chris Withers (:ticket:`#193`).
|
||||||
|
- Avoid blocking async connections on connect (:ticket:`#194`). Thanks to
|
||||||
|
Adam Petrovich for the bug report and diagnosis.
|
||||||
|
- Don't segfault using poorly defined cursor subclasses which forgot to call
|
||||||
|
the superclass init (:ticket:`#195`).
|
||||||
|
- Mark the connection closed when a Socket connection is broken, as it
|
||||||
|
happens for TCP connections instead (:ticket:`#196`).
|
||||||
|
- Fixed overflow opening a lobject with an oid not fitting in a signed int
|
||||||
|
(:ticket:`#203`).
|
||||||
|
- Fixed handling of explicit default ``cursor_factory=None`` in
|
||||||
|
`connection.cursor()` (:ticket:`#210`).
|
||||||
|
- Fixed possible segfault in named cursors creation.
|
||||||
|
- Fixed debug build on Windows, thanks to James Emerton.
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.5.2
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Fixed segfault pickling the exception raised on connection error
|
||||||
|
(:ticket:`#170`).
|
||||||
|
- Meaningful connection errors report a meaningful message, thanks to
|
||||||
|
Alexey Borzenkov (:ticket:`#173`).
|
||||||
|
- Manually creating `lobject` with the wrong parameter doesn't segfault
|
||||||
|
(:ticket:`#187`).
|
||||||
|
|
||||||
|
|
||||||
|
What's new in psycopg 2.5.1
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Fixed build on Solaris 10 and 11 where the round() function is already
|
||||||
|
declared (:ticket:`#146`).
|
||||||
|
- Fixed comparison of `Range` with non-range objects (:ticket:`#164`).
|
||||||
|
Thanks to Chris Withers for the patch.
|
||||||
|
- Fixed double-free on connection dealloc (:ticket:`#166`). Thanks to
|
||||||
|
Gangadharan S.A. for the report and fix suggestion.
|
||||||
|
|
||||||
|
|
||||||
What's new in psycopg 2.5
|
What's new in psycopg 2.5
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
@ -33,7 +621,7 @@ Other changes:
|
||||||
- Dropped support for Python 2.4. Please use Psycopg 2.4.x if you need it.
|
- Dropped support for Python 2.4. Please use Psycopg 2.4.x if you need it.
|
||||||
- `~psycopg2.errorcodes` map updated to PostgreSQL 9.2.
|
- `~psycopg2.errorcodes` map updated to PostgreSQL 9.2.
|
||||||
- Dropped Zope adapter from source repository. ZPsycopgDA now has its own
|
- Dropped Zope adapter from source repository. ZPsycopgDA now has its own
|
||||||
project at <http://github.com/psycopg/ZPsycopgDA>.
|
project at <https://github.com/psycopg/ZPsycopgDA>.
|
||||||
|
|
||||||
|
|
||||||
What's new in psycopg 2.4.6
|
What's new in psycopg 2.4.6
|
||||||
|
@ -51,7 +639,7 @@ What's new in psycopg 2.4.6
|
||||||
- 'register_hstore()', 'register_composite()', 'tpc_recover()' work with
|
- 'register_hstore()', 'register_composite()', 'tpc_recover()' work with
|
||||||
RealDictConnection and Cursor (:ticket:`#114`).
|
RealDictConnection and Cursor (:ticket:`#114`).
|
||||||
- Fixed broken pool for Zope and connections re-init across ZSQL methods
|
- Fixed broken pool for Zope and connections re-init across ZSQL methods
|
||||||
in the same request (tickets #123, #125, #142).
|
in the same request (:tickets:`#123, #125, #142`).
|
||||||
- connect() raises an exception instead of swallowing keyword arguments
|
- connect() raises an exception instead of swallowing keyword arguments
|
||||||
when a connection string is specified as well (:ticket:`#131`).
|
when a connection string is specified as well (:ticket:`#131`).
|
||||||
- Discard any result produced by 'executemany()' (:ticket:`#133`).
|
- Discard any result produced by 'executemany()' (:ticket:`#133`).
|
||||||
|
@ -73,7 +661,7 @@ What's new in psycopg 2.4.5
|
||||||
- Error and its subclasses are picklable, useful for multiprocessing
|
- Error and its subclasses are picklable, useful for multiprocessing
|
||||||
interaction (:ticket:`#90`).
|
interaction (:ticket:`#90`).
|
||||||
- Better efficiency and formatting of timezone offset objects thanks
|
- Better efficiency and formatting of timezone offset objects thanks
|
||||||
to Menno Smits (tickets #94, #95).
|
to Menno Smits (:tickets:`#94, #95`).
|
||||||
- Fixed 'rownumber' during iteration on cursor subclasses.
|
- Fixed 'rownumber' during iteration on cursor subclasses.
|
||||||
Regression introduced in 2.4.4 (:ticket:`#100`).
|
Regression introduced in 2.4.4 (:ticket:`#100`).
|
||||||
- Added support for 'inet' arrays.
|
- Added support for 'inet' arrays.
|
||||||
|
@ -181,7 +769,7 @@ New features and changes:
|
||||||
ISO885916, LATIN10, SHIFT_JIS_2004.
|
ISO885916, LATIN10, SHIFT_JIS_2004.
|
||||||
- Dropped repeated dictionary lookups with unicode query/parameters.
|
- Dropped repeated dictionary lookups with unicode query/parameters.
|
||||||
|
|
||||||
- Improvements to the named cusors:
|
- Improvements to the named cursors:
|
||||||
|
|
||||||
- More efficient iteration on named cursors, fetching 'itersize'
|
- More efficient iteration on named cursors, fetching 'itersize'
|
||||||
records at time from the backend.
|
records at time from the backend.
|
||||||
|
@ -244,7 +832,7 @@ Main new features:
|
||||||
- `dict` to `hstore` adapter and `hstore` to `dict` typecaster, using both
|
- `dict` to `hstore` adapter and `hstore` to `dict` typecaster, using both
|
||||||
9.0 and pre-9.0 syntax.
|
9.0 and pre-9.0 syntax.
|
||||||
- Two-phase commit protocol support as per DBAPI specification.
|
- Two-phase commit protocol support as per DBAPI specification.
|
||||||
- Support for payload in notifications received from the backed.
|
- Support for payload in notifications received from the backend.
|
||||||
- `namedtuple`-returning cursor.
|
- `namedtuple`-returning cursor.
|
||||||
- Query execution cancel.
|
- Query execution cancel.
|
||||||
|
|
||||||
|
@ -284,7 +872,7 @@ Bux fixes:
|
||||||
The old register_tstz_w_secs() function is deprecated and will raise a
|
The old register_tstz_w_secs() function is deprecated and will raise a
|
||||||
warning if called.
|
warning if called.
|
||||||
- Exceptions raised by the column iterator are propagated.
|
- Exceptions raised by the column iterator are propagated.
|
||||||
- Exceptions raised by executemany() interators are propagated.
|
- Exceptions raised by executemany() iterators are propagated.
|
||||||
|
|
||||||
|
|
||||||
What's new in psycopg 2.2.1
|
What's new in psycopg 2.2.1
|
||||||
|
@ -401,7 +989,7 @@ New features:
|
||||||
|
|
||||||
Bug fixes:
|
Bug fixes:
|
||||||
|
|
||||||
- Fixed exeception in setup.py.
|
- Fixed exception in setup.py.
|
||||||
- More robust detection of PostgreSQL development versions.
|
- More robust detection of PostgreSQL development versions.
|
||||||
- Fixed exception in RealDictCursor, introduced in 2.0.10.
|
- Fixed exception in RealDictCursor, introduced in 2.0.10.
|
||||||
|
|
||||||
|
@ -657,7 +1245,7 @@ What's new in psycopg 2.0 beta 7
|
||||||
What's new in psycopg 2.0 beta 6
|
What's new in psycopg 2.0 beta 6
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* Support for named cursors (see examples/fetch.py).
|
* Support for named cursors.
|
||||||
|
|
||||||
* Safer parsing of time intervals.
|
* Safer parsing of time intervals.
|
||||||
|
|
||||||
|
@ -687,7 +1275,7 @@ What's new in psycopg 2.0 beta 5
|
||||||
* All classes have been renamed to exist in the psycopg2._psycopg module,
|
* All classes have been renamed to exist in the psycopg2._psycopg module,
|
||||||
to fix problems with automatic documentation generators like epydoc.
|
to fix problems with automatic documentation generators like epydoc.
|
||||||
|
|
||||||
* NOTIFY is correctly trapped (see examples/notify.py for example code.)
|
* NOTIFY is correctly trapped.
|
||||||
|
|
||||||
What's new in psycopg 2.0 beta 4
|
What's new in psycopg 2.0 beta 4
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -783,7 +1371,7 @@ What's new in psycopg 1.99.11
|
||||||
|
|
||||||
* changed 'tuple_factory' cursor attribute name to 'row_factory'.
|
* changed 'tuple_factory' cursor attribute name to 'row_factory'.
|
||||||
|
|
||||||
* the .cursor attribute is gone and connections and cursors are propely
|
* the .cursor attribute is gone and connections and cursors are properly
|
||||||
gc-managed.
|
gc-managed.
|
||||||
|
|
||||||
* fixes to the async core.
|
* fixes to the async core.
|
||||||
|
@ -804,8 +1392,7 @@ What's new in psycopg 1.99.10
|
||||||
What's new in psycopg 1.99.9
|
What's new in psycopg 1.99.9
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* Added simple pooling code (psycopg.pool module); see the reworked
|
* Added simple pooling code (psycopg.pool module).
|
||||||
examples/threads.py for example code.
|
|
||||||
|
|
||||||
* Added DECIMAL typecaster to convert postgresql DECIMAL and NUMERIC
|
* Added DECIMAL typecaster to convert postgresql DECIMAL and NUMERIC
|
||||||
types (i.e, all types with an OID of NUMERICOID.) Note that the
|
types (i.e, all types with an OID of NUMERICOID.) Note that the
|
||||||
|
@ -832,7 +1419,7 @@ What's new in psycopg 1.99.8
|
||||||
* now cursors support .fileno() and .isready() methods, to be used in
|
* now cursors support .fileno() and .isready() methods, to be used in
|
||||||
select() calls.
|
select() calls.
|
||||||
* .copy_from() and .copy_in() methods are back in (still using the old
|
* .copy_from() and .copy_in() methods are back in (still using the old
|
||||||
protocol, will be updated to use new one in next releasae.)
|
protocol, will be updated to use new one in next release.)
|
||||||
* fixed memory corruption bug reported on win32 platform.
|
* fixed memory corruption bug reported on win32 platform.
|
||||||
|
|
||||||
What's new in psycopg 1.99.7
|
What's new in psycopg 1.99.7
|
||||||
|
|
38
README
38
README
|
@ -1,38 +0,0 @@
|
||||||
psycopg2 - Python-PostgreSQL Database Adapter
|
|
||||||
********************************************
|
|
||||||
|
|
||||||
psycopg2 is a PostgreSQL database adapter for the Python programming
|
|
||||||
language. psycopg2 was written with the aim of being very small and fast,
|
|
||||||
and stable as a rock.
|
|
||||||
|
|
||||||
psycopg2 is different from the other database adapter because it was
|
|
||||||
designed for heavily multi-threaded applications that create and destroy
|
|
||||||
lots of cursors and make a conspicuous number of concurrent INSERTs or
|
|
||||||
UPDATEs. psycopg2 also provides full asynchronous operations and support
|
|
||||||
for coroutine libraries.
|
|
||||||
|
|
||||||
psycopg2 can compile and run on Linux, FreeBSD, Solaris, MacOS X and
|
|
||||||
Windows architecture. It supports Python versions from 2.4 onwards and
|
|
||||||
PostgreSQL versions from 7.4 onwards.
|
|
||||||
|
|
||||||
psycopg2 is free software ("free as in freedom" but I like beer too.)
|
|
||||||
It is licensed under the GNU Lesser General Public License, version 3 or
|
|
||||||
later plus an exception to allow OpenSSL (libpq) linking; see LICENSE for
|
|
||||||
more details.
|
|
||||||
|
|
||||||
Documentation
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Start by reading the INSTALL file. More information about psycopg2 extensions
|
|
||||||
to the DBAPI-2.0 is available in the files located in the doc/ direcory.
|
|
||||||
Example code can be found in the examples/ directory. If you make any changes
|
|
||||||
to the code make sure to run the unit tests localed in tests/.
|
|
||||||
|
|
||||||
Online documentation can be found at: http://initd.org/psycopg/
|
|
||||||
|
|
||||||
If you stumble upon any bugs, please tell us at: http://psycopg.lighthouseapp.com/
|
|
||||||
|
|
||||||
Contributors
|
|
||||||
------------
|
|
||||||
|
|
||||||
For a list of contributors to the project, see the AUTHORS file.
|
|
80
README.rst
Normal file
80
README.rst
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
psycopg2 - Python-PostgreSQL Database Adapter
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. Note::
|
||||||
|
|
||||||
|
The psycopg2 package is still widely used and actively maintained, but it
|
||||||
|
is not expected to receive new features.
|
||||||
|
|
||||||
|
`Psycopg 3`__ is the evolution of psycopg2 and is where `new features are
|
||||||
|
being developed`__: if you are starting a new project you should probably
|
||||||
|
start from 3!
|
||||||
|
|
||||||
|
.. __: https://pypi.org/project/psycopg/
|
||||||
|
.. __: https://www.psycopg.org/psycopg3/docs/index.html
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
:Build status: |gh-actions|
|
||||||
|
|
||||||
|
.. |gh-actions| image:: https://github.com/psycopg/psycopg2/actions/workflows/tests.yml/badge.svg
|
||||||
|
:target: https://github.com/psycopg/psycopg2/actions/workflows/tests.yml
|
||||||
|
:alt: Build status
|
8
doc/.gitignore
vendored
Normal file
8
doc/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
env
|
||||||
|
src/_build/*
|
||||||
|
html/*
|
||||||
|
psycopg2.txt
|
||||||
|
src/sqlstate_errors.rst
|
||||||
|
|
||||||
|
# Added by psycopg-website to customize published docs
|
||||||
|
src/_templates/layout.html
|
676
doc/COPYING
676
doc/COPYING
|
@ -1,676 +0,0 @@
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 29 June 2007
|
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
|
||||||
software and other kinds of works.
|
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
|
||||||
to take away your freedom to share and change the works. By contrast,
|
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
|
||||||
share and change all versions of a program--to make sure it remains free
|
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
them if you wish), that you receive source code or can get it if you
|
|
||||||
want it, that you can change the software or use pieces of it in new
|
|
||||||
free programs, and that you know you can do these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
|
||||||
or can get the source code. And you must show them these terms so they
|
|
||||||
know their rights.
|
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
|
||||||
that there is no warranty for this free software. For both users' and
|
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
|
||||||
changed, so that their problems will not be attributed erroneously to
|
|
||||||
authors of previous versions.
|
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
0. Definitions.
|
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
|
||||||
works, such as semiconductor masks.
|
|
||||||
|
|
||||||
"The Program" refers to any copyrightable work licensed under this
|
|
||||||
License. Each licensee is addressed as "you". "Licensees" and
|
|
||||||
"recipients" may be individuals or organizations.
|
|
||||||
|
|
||||||
To "modify" a work means to copy from or adapt all or part of the work
|
|
||||||
in a fashion requiring copyright permission, other than the making of an
|
|
||||||
exact copy. The resulting work is called a "modified version" of the
|
|
||||||
earlier work or a work "based on" the earlier work.
|
|
||||||
|
|
||||||
A "covered work" means either the unmodified Program or a work based
|
|
||||||
on the Program.
|
|
||||||
|
|
||||||
To "propagate" a work means to do anything with it that, without
|
|
||||||
permission, would make you directly or secondarily liable for
|
|
||||||
infringement under applicable copyright law, except executing it on a
|
|
||||||
computer or modifying a private copy. Propagation includes copying,
|
|
||||||
distribution (with or without modification), making available to the
|
|
||||||
public, and in some countries other activities as well.
|
|
||||||
|
|
||||||
To "convey" a work means any kind of propagation that enables other
|
|
||||||
parties to make or receive copies. Mere interaction with a user through
|
|
||||||
a computer network, with no transfer of a copy, is not conveying.
|
|
||||||
|
|
||||||
An interactive user interface displays "Appropriate Legal Notices"
|
|
||||||
to the extent that it includes a convenient and prominently visible
|
|
||||||
feature that (1) displays an appropriate copyright notice, and (2)
|
|
||||||
tells the user that there is no warranty for the work (except to the
|
|
||||||
extent that warranties are provided), that licensees may convey the
|
|
||||||
work under this License, and how to view a copy of this License. If
|
|
||||||
the interface presents a list of user commands or options, such as a
|
|
||||||
menu, a prominent item in the list meets this criterion.
|
|
||||||
|
|
||||||
1. Source Code.
|
|
||||||
|
|
||||||
The "source code" for a work means the preferred form of the work
|
|
||||||
for making modifications to it. "Object code" means any non-source
|
|
||||||
form of a work.
|
|
||||||
|
|
||||||
A "Standard Interface" means an interface that either is an official
|
|
||||||
standard defined by a recognized standards body, or, in the case of
|
|
||||||
interfaces specified for a particular programming language, one that
|
|
||||||
is widely used among developers working in that language.
|
|
||||||
|
|
||||||
The "System Libraries" of an executable work include anything, other
|
|
||||||
than the work as a whole, that (a) is included in the normal form of
|
|
||||||
packaging a Major Component, but which is not part of that Major
|
|
||||||
Component, and (b) serves only to enable use of the work with that
|
|
||||||
Major Component, or to implement a Standard Interface for which an
|
|
||||||
implementation is available to the public in source code form. A
|
|
||||||
"Major Component", in this context, means a major essential component
|
|
||||||
(kernel, window system, and so on) of the specific operating system
|
|
||||||
(if any) on which the executable work runs, or a compiler used to
|
|
||||||
produce the work, or an object code interpreter used to run it.
|
|
||||||
|
|
||||||
The "Corresponding Source" for a work in object code form means all
|
|
||||||
the source code needed to generate, install, and (for an executable
|
|
||||||
work) run the object code and to modify the work, including scripts to
|
|
||||||
control those activities. However, it does not include the work's
|
|
||||||
System Libraries, or general-purpose tools or generally available free
|
|
||||||
programs which are used unmodified in performing those activities but
|
|
||||||
which are not part of the work. For example, Corresponding Source
|
|
||||||
includes interface definition files associated with source files for
|
|
||||||
the work, and the source code for shared libraries and dynamically
|
|
||||||
linked subprograms that the work is specifically designed to require,
|
|
||||||
such as by intimate data communication or control flow between those
|
|
||||||
subprograms and other parts of the work.
|
|
||||||
|
|
||||||
The Corresponding Source need not include anything that users
|
|
||||||
can regenerate automatically from other parts of the Corresponding
|
|
||||||
Source.
|
|
||||||
|
|
||||||
The Corresponding Source for a work in source code form is that
|
|
||||||
same work.
|
|
||||||
|
|
||||||
2. Basic Permissions.
|
|
||||||
|
|
||||||
All rights granted under this License are granted for the term of
|
|
||||||
copyright on the Program, and are irrevocable provided the stated
|
|
||||||
conditions are met. This License explicitly affirms your unlimited
|
|
||||||
permission to run the unmodified Program. The output from running a
|
|
||||||
covered work is covered by this License only if the output, given its
|
|
||||||
content, constitutes a covered work. This License acknowledges your
|
|
||||||
rights of fair use or other equivalent, as provided by copyright law.
|
|
||||||
|
|
||||||
You may make, run and propagate covered works that you do not
|
|
||||||
convey, without conditions so long as your license otherwise remains
|
|
||||||
in force. You may convey covered works to others for the sole purpose
|
|
||||||
of having them make modifications exclusively for you, or provide you
|
|
||||||
with facilities for running those works, provided that you comply with
|
|
||||||
the terms of this License in conveying all material for which you do
|
|
||||||
not control copyright. Those thus making or running the covered works
|
|
||||||
for you must do so exclusively on your behalf, under your direction
|
|
||||||
and control, on terms that prohibit them from making any copies of
|
|
||||||
your copyrighted material outside their relationship with you.
|
|
||||||
|
|
||||||
Conveying under any other circumstances is permitted solely under
|
|
||||||
the conditions stated below. Sublicensing is not allowed; section 10
|
|
||||||
makes it unnecessary.
|
|
||||||
|
|
||||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
|
||||||
|
|
||||||
No covered work shall be deemed part of an effective technological
|
|
||||||
measure under any applicable law fulfilling obligations under article
|
|
||||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
|
||||||
similar laws prohibiting or restricting circumvention of such
|
|
||||||
measures.
|
|
||||||
|
|
||||||
When you convey a covered work, you waive any legal power to forbid
|
|
||||||
circumvention of technological measures to the extent such circumvention
|
|
||||||
is effected by exercising rights under this License with respect to
|
|
||||||
the covered work, and you disclaim any intention to limit operation or
|
|
||||||
modification of the work as a means of enforcing, against the work's
|
|
||||||
users, your or third parties' legal rights to forbid circumvention of
|
|
||||||
technological measures.
|
|
||||||
|
|
||||||
4. Conveying Verbatim Copies.
|
|
||||||
|
|
||||||
You may convey verbatim copies of the Program's source code as you
|
|
||||||
receive it, in any medium, provided that you conspicuously and
|
|
||||||
appropriately publish on each copy an appropriate copyright notice;
|
|
||||||
keep intact all notices stating that this License and any
|
|
||||||
non-permissive terms added in accord with section 7 apply to the code;
|
|
||||||
keep intact all notices of the absence of any warranty; and give all
|
|
||||||
recipients a copy of this License along with the Program.
|
|
||||||
|
|
||||||
You may charge any price or no price for each copy that you convey,
|
|
||||||
and you may offer support or warranty protection for a fee.
|
|
||||||
|
|
||||||
5. Conveying Modified Source Versions.
|
|
||||||
|
|
||||||
You may convey a work based on the Program, or the modifications to
|
|
||||||
produce it from the Program, in the form of source code under the
|
|
||||||
terms of section 4, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) The work must carry prominent notices stating that you modified
|
|
||||||
it, and giving a relevant date.
|
|
||||||
|
|
||||||
b) The work must carry prominent notices stating that it is
|
|
||||||
released under this License and any conditions added under section
|
|
||||||
7. This requirement modifies the requirement in section 4 to
|
|
||||||
"keep intact all notices".
|
|
||||||
|
|
||||||
c) You must license the entire work, as a whole, under this
|
|
||||||
License to anyone who comes into possession of a copy. This
|
|
||||||
License will therefore apply, along with any applicable section 7
|
|
||||||
additional terms, to the whole of the work, and all its parts,
|
|
||||||
regardless of how they are packaged. This License gives no
|
|
||||||
permission to license the work in any other way, but it does not
|
|
||||||
invalidate such permission if you have separately received it.
|
|
||||||
|
|
||||||
d) If the work has interactive user interfaces, each must display
|
|
||||||
Appropriate Legal Notices; however, if the Program has interactive
|
|
||||||
interfaces that do not display Appropriate Legal Notices, your
|
|
||||||
work need not make them do so.
|
|
||||||
|
|
||||||
A compilation of a covered work with other separate and independent
|
|
||||||
works, which are not by their nature extensions of the covered work,
|
|
||||||
and which are not combined with it such as to form a larger program,
|
|
||||||
in or on a volume of a storage or distribution medium, is called an
|
|
||||||
"aggregate" if the compilation and its resulting copyright are not
|
|
||||||
used to limit the access or legal rights of the compilation's users
|
|
||||||
beyond what the individual works permit. Inclusion of a covered work
|
|
||||||
in an aggregate does not cause this License to apply to the other
|
|
||||||
parts of the aggregate.
|
|
||||||
|
|
||||||
6. Conveying Non-Source Forms.
|
|
||||||
|
|
||||||
You may convey a covered work in object code form under the terms
|
|
||||||
of sections 4 and 5, provided that you also convey the
|
|
||||||
machine-readable Corresponding Source under the terms of this License,
|
|
||||||
in one of these ways:
|
|
||||||
|
|
||||||
a) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by the
|
|
||||||
Corresponding Source fixed on a durable physical medium
|
|
||||||
customarily used for software interchange.
|
|
||||||
|
|
||||||
b) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by a
|
|
||||||
written offer, valid for at least three years and valid for as
|
|
||||||
long as you offer spare parts or customer support for that product
|
|
||||||
model, to give anyone who possesses the object code either (1) a
|
|
||||||
copy of the Corresponding Source for all the software in the
|
|
||||||
product that is covered by this License, on a durable physical
|
|
||||||
medium customarily used for software interchange, for a price no
|
|
||||||
more than your reasonable cost of physically performing this
|
|
||||||
conveying of source, or (2) access to copy the
|
|
||||||
Corresponding Source from a network server at no charge.
|
|
||||||
|
|
||||||
c) Convey individual copies of the object code with a copy of the
|
|
||||||
written offer to provide the Corresponding Source. This
|
|
||||||
alternative is allowed only occasionally and noncommercially, and
|
|
||||||
only if you received the object code with such an offer, in accord
|
|
||||||
with subsection 6b.
|
|
||||||
|
|
||||||
d) Convey the object code by offering access from a designated
|
|
||||||
place (gratis or for a charge), and offer equivalent access to the
|
|
||||||
Corresponding Source in the same way through the same place at no
|
|
||||||
further charge. You need not require recipients to copy the
|
|
||||||
Corresponding Source along with the object code. If the place to
|
|
||||||
copy the object code is a network server, the Corresponding Source
|
|
||||||
may be on a different server (operated by you or a third party)
|
|
||||||
that supports equivalent copying facilities, provided you maintain
|
|
||||||
clear directions next to the object code saying where to find the
|
|
||||||
Corresponding Source. Regardless of what server hosts the
|
|
||||||
Corresponding Source, you remain obligated to ensure that it is
|
|
||||||
available for as long as needed to satisfy these requirements.
|
|
||||||
|
|
||||||
e) Convey the object code using peer-to-peer transmission, provided
|
|
||||||
you inform other peers where the object code and Corresponding
|
|
||||||
Source of the work are being offered to the general public at no
|
|
||||||
charge under subsection 6d.
|
|
||||||
|
|
||||||
A separable portion of the object code, whose source code is excluded
|
|
||||||
from the Corresponding Source as a System Library, need not be
|
|
||||||
included in conveying the object code work.
|
|
||||||
|
|
||||||
A "User Product" is either (1) a "consumer product", which means any
|
|
||||||
tangible personal property which is normally used for personal, family,
|
|
||||||
or household purposes, or (2) anything designed or sold for incorporation
|
|
||||||
into a dwelling. In determining whether a product is a consumer product,
|
|
||||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
|
||||||
product received by a particular user, "normally used" refers to a
|
|
||||||
typical or common use of that class of product, regardless of the status
|
|
||||||
of the particular user or of the way in which the particular user
|
|
||||||
actually uses, or expects or is expected to use, the product. A product
|
|
||||||
is a consumer product regardless of whether the product has substantial
|
|
||||||
commercial, industrial or non-consumer uses, unless such uses represent
|
|
||||||
the only significant mode of use of the product.
|
|
||||||
|
|
||||||
"Installation Information" for a User Product means any methods,
|
|
||||||
procedures, authorization keys, or other information required to install
|
|
||||||
and execute modified versions of a covered work in that User Product from
|
|
||||||
a modified version of its Corresponding Source. The information must
|
|
||||||
suffice to ensure that the continued functioning of the modified object
|
|
||||||
code is in no case prevented or interfered with solely because
|
|
||||||
modification has been made.
|
|
||||||
|
|
||||||
If you convey an object code work under this section in, or with, or
|
|
||||||
specifically for use in, a User Product, and the conveying occurs as
|
|
||||||
part of a transaction in which the right of possession and use of the
|
|
||||||
User Product is transferred to the recipient in perpetuity or for a
|
|
||||||
fixed term (regardless of how the transaction is characterized), the
|
|
||||||
Corresponding Source conveyed under this section must be accompanied
|
|
||||||
by the Installation Information. But this requirement does not apply
|
|
||||||
if neither you nor any third party retains the ability to install
|
|
||||||
modified object code on the User Product (for example, the work has
|
|
||||||
been installed in ROM).
|
|
||||||
|
|
||||||
The requirement to provide Installation Information does not include a
|
|
||||||
requirement to continue to provide support service, warranty, or updates
|
|
||||||
for a work that has been modified or installed by the recipient, or for
|
|
||||||
the User Product in which it has been modified or installed. Access to a
|
|
||||||
network may be denied when the modification itself materially and
|
|
||||||
adversely affects the operation of the network or violates the rules and
|
|
||||||
protocols for communication across the network.
|
|
||||||
|
|
||||||
Corresponding Source conveyed, and Installation Information provided,
|
|
||||||
in accord with this section must be in a format that is publicly
|
|
||||||
documented (and with an implementation available to the public in
|
|
||||||
source code form), and must require no special password or key for
|
|
||||||
unpacking, reading or copying.
|
|
||||||
|
|
||||||
7. Additional Terms.
|
|
||||||
|
|
||||||
"Additional permissions" are terms that supplement the terms of this
|
|
||||||
License by making exceptions from one or more of its conditions.
|
|
||||||
Additional permissions that are applicable to the entire Program shall
|
|
||||||
be treated as though they were included in this License, to the extent
|
|
||||||
that they are valid under applicable law. If additional permissions
|
|
||||||
apply only to part of the Program, that part may be used separately
|
|
||||||
under those permissions, but the entire Program remains governed by
|
|
||||||
this License without regard to the additional permissions.
|
|
||||||
|
|
||||||
When you convey a copy of a covered work, you may at your option
|
|
||||||
remove any additional permissions from that copy, or from any part of
|
|
||||||
it. (Additional permissions may be written to require their own
|
|
||||||
removal in certain cases when you modify the work.) You may place
|
|
||||||
additional permissions on material, added by you to a covered work,
|
|
||||||
for which you have or can give appropriate copyright permission.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, for material you
|
|
||||||
add to a covered work, you may (if authorized by the copyright holders of
|
|
||||||
that material) supplement the terms of this License with terms:
|
|
||||||
|
|
||||||
a) Disclaiming warranty or limiting liability differently from the
|
|
||||||
terms of sections 15 and 16 of this License; or
|
|
||||||
|
|
||||||
b) Requiring preservation of specified reasonable legal notices or
|
|
||||||
author attributions in that material or in the Appropriate Legal
|
|
||||||
Notices displayed by works containing it; or
|
|
||||||
|
|
||||||
c) Prohibiting misrepresentation of the origin of that material, or
|
|
||||||
requiring that modified versions of such material be marked in
|
|
||||||
reasonable ways as different from the original version; or
|
|
||||||
|
|
||||||
d) Limiting the use for publicity purposes of names of licensors or
|
|
||||||
authors of the material; or
|
|
||||||
|
|
||||||
e) Declining to grant rights under trademark law for use of some
|
|
||||||
trade names, trademarks, or service marks; or
|
|
||||||
|
|
||||||
f) Requiring indemnification of licensors and authors of that
|
|
||||||
material by anyone who conveys the material (or modified versions of
|
|
||||||
it) with contractual assumptions of liability to the recipient, for
|
|
||||||
any liability that these contractual assumptions directly impose on
|
|
||||||
those licensors and authors.
|
|
||||||
|
|
||||||
All other non-permissive additional terms are considered "further
|
|
||||||
restrictions" within the meaning of section 10. If the Program as you
|
|
||||||
received it, or any part of it, contains a notice stating that it is
|
|
||||||
governed by this License along with a term that is a further
|
|
||||||
restriction, you may remove that term. If a license document contains
|
|
||||||
a further restriction but permits relicensing or conveying under this
|
|
||||||
License, you may add to a covered work material governed by the terms
|
|
||||||
of that license document, provided that the further restriction does
|
|
||||||
not survive such relicensing or conveying.
|
|
||||||
|
|
||||||
If you add terms to a covered work in accord with this section, you
|
|
||||||
must place, in the relevant source files, a statement of the
|
|
||||||
additional terms that apply to those files, or a notice indicating
|
|
||||||
where to find the applicable terms.
|
|
||||||
|
|
||||||
Additional terms, permissive or non-permissive, may be stated in the
|
|
||||||
form of a separately written license, or stated as exceptions;
|
|
||||||
the above requirements apply either way.
|
|
||||||
|
|
||||||
8. Termination.
|
|
||||||
|
|
||||||
You may not propagate or modify a covered work except as expressly
|
|
||||||
provided under this License. Any attempt otherwise to propagate or
|
|
||||||
modify it is void, and will automatically terminate your rights under
|
|
||||||
this License (including any patent licenses granted under the third
|
|
||||||
paragraph of section 11).
|
|
||||||
|
|
||||||
However, if you cease all violation of this License, then your
|
|
||||||
license from a particular copyright holder is reinstated (a)
|
|
||||||
provisionally, unless and until the copyright holder explicitly and
|
|
||||||
finally terminates your license, and (b) permanently, if the copyright
|
|
||||||
holder fails to notify you of the violation by some reasonable means
|
|
||||||
prior to 60 days after the cessation.
|
|
||||||
|
|
||||||
Moreover, your license from a particular copyright holder is
|
|
||||||
reinstated permanently if the copyright holder notifies you of the
|
|
||||||
violation by some reasonable means, this is the first time you have
|
|
||||||
received notice of violation of this License (for any work) from that
|
|
||||||
copyright holder, and you cure the violation prior to 30 days after
|
|
||||||
your receipt of the notice.
|
|
||||||
|
|
||||||
Termination of your rights under this section does not terminate the
|
|
||||||
licenses of parties who have received copies or rights from you under
|
|
||||||
this License. If your rights have been terminated and not permanently
|
|
||||||
reinstated, you do not qualify to receive new licenses for the same
|
|
||||||
material under section 10.
|
|
||||||
|
|
||||||
9. Acceptance Not Required for Having Copies.
|
|
||||||
|
|
||||||
You are not required to accept this License in order to receive or
|
|
||||||
run a copy of the Program. Ancillary propagation of a covered work
|
|
||||||
occurring solely as a consequence of using peer-to-peer transmission
|
|
||||||
to receive a copy likewise does not require acceptance. However,
|
|
||||||
nothing other than this License grants you permission to propagate or
|
|
||||||
modify any covered work. These actions infringe copyright if you do
|
|
||||||
not accept this License. Therefore, by modifying or propagating a
|
|
||||||
covered work, you indicate your acceptance of this License to do so.
|
|
||||||
|
|
||||||
10. Automatic Licensing of Downstream Recipients.
|
|
||||||
|
|
||||||
Each time you convey a covered work, the recipient automatically
|
|
||||||
receives a license from the original licensors, to run, modify and
|
|
||||||
propagate that work, subject to this License. You are not responsible
|
|
||||||
for enforcing compliance by third parties with this License.
|
|
||||||
|
|
||||||
An "entity transaction" is a transaction transferring control of an
|
|
||||||
organization, or substantially all assets of one, or subdividing an
|
|
||||||
organization, or merging organizations. If propagation of a covered
|
|
||||||
work results from an entity transaction, each party to that
|
|
||||||
transaction who receives a copy of the work also receives whatever
|
|
||||||
licenses to the work the party's predecessor in interest had or could
|
|
||||||
give under the previous paragraph, plus a right to possession of the
|
|
||||||
Corresponding Source of the work from the predecessor in interest, if
|
|
||||||
the predecessor has it or can get it with reasonable efforts.
|
|
||||||
|
|
||||||
You may not impose any further restrictions on the exercise of the
|
|
||||||
rights granted or affirmed under this License. For example, you may
|
|
||||||
not impose a license fee, royalty, or other charge for exercise of
|
|
||||||
rights granted under this License, and you may not initiate litigation
|
|
||||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
|
||||||
any patent claim is infringed by making, using, selling, offering for
|
|
||||||
sale, or importing the Program or any portion of it.
|
|
||||||
|
|
||||||
11. Patents.
|
|
||||||
|
|
||||||
A "contributor" is a copyright holder who authorizes use under this
|
|
||||||
License of the Program or a work on which the Program is based. The
|
|
||||||
work thus licensed is called the contributor's "contributor version".
|
|
||||||
|
|
||||||
A contributor's "essential patent claims" are all patent claims
|
|
||||||
owned or controlled by the contributor, whether already acquired or
|
|
||||||
hereafter acquired, that would be infringed by some manner, permitted
|
|
||||||
by this License, of making, using, or selling its contributor version,
|
|
||||||
but do not include claims that would be infringed only as a
|
|
||||||
consequence of further modification of the contributor version. For
|
|
||||||
purposes of this definition, "control" includes the right to grant
|
|
||||||
patent sublicenses in a manner consistent with the requirements of
|
|
||||||
this License.
|
|
||||||
|
|
||||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
|
||||||
patent license under the contributor's essential patent claims, to
|
|
||||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
|
||||||
propagate the contents of its contributor version.
|
|
||||||
|
|
||||||
In the following three paragraphs, a "patent license" is any express
|
|
||||||
agreement or commitment, however denominated, not to enforce a patent
|
|
||||||
(such as an express permission to practice a patent or covenant not to
|
|
||||||
sue for patent infringement). To "grant" such a patent license to a
|
|
||||||
party means to make such an agreement or commitment not to enforce a
|
|
||||||
patent against the party.
|
|
||||||
|
|
||||||
If you convey a covered work, knowingly relying on a patent license,
|
|
||||||
and the Corresponding Source of the work is not available for anyone
|
|
||||||
to copy, free of charge and under the terms of this License, through a
|
|
||||||
publicly available network server or other readily accessible means,
|
|
||||||
then you must either (1) cause the Corresponding Source to be so
|
|
||||||
available, or (2) arrange to deprive yourself of the benefit of the
|
|
||||||
patent license for this particular work, or (3) arrange, in a manner
|
|
||||||
consistent with the requirements of this License, to extend the patent
|
|
||||||
license to downstream recipients. "Knowingly relying" means you have
|
|
||||||
actual knowledge that, but for the patent license, your conveying the
|
|
||||||
covered work in a country, or your recipient's use of the covered work
|
|
||||||
in a country, would infringe one or more identifiable patents in that
|
|
||||||
country that you have reason to believe are valid.
|
|
||||||
|
|
||||||
If, pursuant to or in connection with a single transaction or
|
|
||||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
|
||||||
covered work, and grant a patent license to some of the parties
|
|
||||||
receiving the covered work authorizing them to use, propagate, modify
|
|
||||||
or convey a specific copy of the covered work, then the patent license
|
|
||||||
you grant is automatically extended to all recipients of the covered
|
|
||||||
work and works based on it.
|
|
||||||
|
|
||||||
A patent license is "discriminatory" if it does not include within
|
|
||||||
the scope of its coverage, prohibits the exercise of, or is
|
|
||||||
conditioned on the non-exercise of one or more of the rights that are
|
|
||||||
specifically granted under this License. You may not convey a covered
|
|
||||||
work if you are a party to an arrangement with a third party that is
|
|
||||||
in the business of distributing software, under which you make payment
|
|
||||||
to the third party based on the extent of your activity of conveying
|
|
||||||
the work, and under which the third party grants, to any of the
|
|
||||||
parties who would receive the covered work from you, a discriminatory
|
|
||||||
patent license (a) in connection with copies of the covered work
|
|
||||||
conveyed by you (or copies made from those copies), or (b) primarily
|
|
||||||
for and in connection with specific products or compilations that
|
|
||||||
contain the covered work, unless you entered into that arrangement,
|
|
||||||
or that patent license was granted, prior to 28 March 2007.
|
|
||||||
|
|
||||||
Nothing in this License shall be construed as excluding or limiting
|
|
||||||
any implied license or other defenses to infringement that may
|
|
||||||
otherwise be available to you under applicable patent law.
|
|
||||||
|
|
||||||
12. No Surrender of Others' Freedom.
|
|
||||||
|
|
||||||
If conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot convey a
|
|
||||||
covered work so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you may
|
|
||||||
not convey it at all. For example, if you agree to terms that obligate you
|
|
||||||
to collect a royalty for further conveying from those to whom you convey
|
|
||||||
the Program, the only way you could satisfy both those terms and this
|
|
||||||
License would be to refrain entirely from conveying the Program.
|
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
|
||||||
permission to link or combine any covered work with a work licensed
|
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
|
||||||
combined work, and to convey the resulting work. The terms of this
|
|
||||||
License will continue to apply to the part which is the covered work,
|
|
||||||
but the special requirements of the GNU Affero General Public License,
|
|
||||||
section 13, concerning interaction through a network will apply to the
|
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
|
||||||
the GNU General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
|
||||||
Program specifies that a certain numbered version of the GNU General
|
|
||||||
Public License "or any later version" applies to it, you have the
|
|
||||||
option of following the terms and conditions either of that numbered
|
|
||||||
version or of any later version published by the Free Software
|
|
||||||
Foundation. If the Program does not specify a version number of the
|
|
||||||
GNU General Public License, you may choose any version ever published
|
|
||||||
by the Free Software Foundation.
|
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
|
||||||
public statement of acceptance of a version permanently authorizes you
|
|
||||||
to choose that version for the Program.
|
|
||||||
|
|
||||||
Later license versions may give you additional or different
|
|
||||||
permissions. However, no additional obligations are imposed on any
|
|
||||||
author or copyright holder as a result of your choosing to follow a
|
|
||||||
later version.
|
|
||||||
|
|
||||||
15. Disclaimer of Warranty.
|
|
||||||
|
|
||||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
|
||||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
|
||||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
|
||||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
|
||||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
|
||||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
16. Limitation of Liability.
|
|
||||||
|
|
||||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
|
||||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
|
||||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
|
||||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
|
||||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
|
||||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
|
||||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
|
||||||
SUCH DAMAGES.
|
|
||||||
|
|
||||||
17. Interpretation of Sections 15 and 16.
|
|
||||||
|
|
||||||
If the disclaimer of warranty and limitation of liability provided
|
|
||||||
above cannot be given local legal effect according to their terms,
|
|
||||||
reviewing courts shall apply local law that most closely approximates
|
|
||||||
an absolute waiver of all civil liability in connection with the
|
|
||||||
Program, unless a warranty or assumption of liability accompanies a
|
|
||||||
copy of the Program in return for a fee.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs
|
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest
|
|
||||||
possible use to the public, the best way to achieve this is to make it
|
|
||||||
free software which everyone can redistribute and change under these terms.
|
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively
|
|
||||||
state the exclusion of warranty; and each file should have at least
|
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
|
||||||
Copyright (C) <year> <name of author>
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
|
||||||
notice like this when it starts in an interactive mode:
|
|
||||||
|
|
||||||
<program> Copyright (C) <year> <name of author>
|
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type `show c' for details.
|
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, your program's commands
|
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
|
||||||
<http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
|
||||||
may consider it more useful to permit linking proprietary applications with
|
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License. But first, please read
|
|
||||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
GNU LESSER GENERAL PUBLIC LICENSE
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
of this license document, but changing it is not allowed.
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
1744
doc/ChangeLog-1.x
1744
doc/ChangeLog-1.x
File diff suppressed because it is too large
Load Diff
43
doc/HACKING
43
doc/HACKING
|
@ -1,43 +0,0 @@
|
||||||
General information
|
|
||||||
*******************
|
|
||||||
|
|
||||||
Some help to people wanting to hack on psycopg. First of all, note that
|
|
||||||
*every* function in the psycopg module source code is prefixed by one of the
|
|
||||||
following words:
|
|
||||||
|
|
||||||
psyco is used for function directly callable from python (i.e., functions
|
|
||||||
in the psycopg module itself.) the only notable exception is the
|
|
||||||
source code for the module itself, that uses "psyco" even for C-only
|
|
||||||
functions.
|
|
||||||
|
|
||||||
conn is used for functions related to connection objects.
|
|
||||||
|
|
||||||
curs is used for functions related to cursor objects.
|
|
||||||
|
|
||||||
typecast is used for typecasters and utility function related to
|
|
||||||
typecaster creation and registration.
|
|
||||||
|
|
||||||
Pythonic definition of types and functions available from python are defined
|
|
||||||
in *_type.c files. Internal functions, callable only from C are located in
|
|
||||||
*_int.c files and extensions to the DBAPI can be found in the *_ext.c files.
|
|
||||||
|
|
||||||
|
|
||||||
Patches
|
|
||||||
*******
|
|
||||||
|
|
||||||
If you submit a patch, please send a diff generated with the "-u" switch.
|
|
||||||
Also note that I don't like that much cosmetic changes (like renaming
|
|
||||||
already existing variables) and I will rewrap the patch to 78 columns
|
|
||||||
anyway, so it is much better if you do that beforehand.
|
|
||||||
|
|
||||||
|
|
||||||
The type system
|
|
||||||
***************
|
|
||||||
|
|
||||||
Simple types, like integers and strings, are converted to python base types
|
|
||||||
(the conversion functions are in typecast_base.c). Complex types are
|
|
||||||
converted to ad-hoc types, defined in the typeobj_*.{c,h} files. The
|
|
||||||
conversion function are in the other typecast_*.c files. typecast.c defines
|
|
||||||
the basic utility functions (available through the psycopg module) used when
|
|
||||||
defining new typecasters from C and python.
|
|
||||||
|
|
40
doc/Makefile
40
doc/Makefile
|
@ -1,23 +1,39 @@
|
||||||
.PHONY: help clean html text doctest
|
.PHONY: env help clean html package doctest
|
||||||
|
|
||||||
docs: html text
|
docs: html
|
||||||
|
|
||||||
check: doctest
|
check: doctest
|
||||||
|
|
||||||
help:
|
# The environment is currently required to build the documentation.
|
||||||
cd src && $(MAKE) $@
|
# It is not clean by 'make clean'
|
||||||
|
|
||||||
html:
|
PYTHON := python$(PYTHON_VERSION)
|
||||||
cd src && $(MAKE) $@
|
PYTHON_VERSION ?= $(shell $(PYTHON) -c 'import sys; print("%d.%d" % sys.version_info[:2])')
|
||||||
|
BUILD_DIR = $(shell pwd)/../build/lib.$(PYTHON_VERSION)
|
||||||
|
|
||||||
|
SPHINXBUILD ?= $$(pwd)/env/bin/sphinx-build
|
||||||
|
SPHOPTS = SPHINXBUILD=$(SPHINXBUILD)
|
||||||
|
|
||||||
|
html: package src/sqlstate_errors.rst
|
||||||
|
$(MAKE) $(SPHOPTS) -C src $@
|
||||||
cp -r src/_build/html .
|
cp -r src/_build/html .
|
||||||
|
|
||||||
text:
|
src/sqlstate_errors.rst: ../psycopg/sqlstate_errors.h $(BUILD_DIR)
|
||||||
cd src && $(MAKE) $@
|
./env/bin/python src/tools/make_sqlstate_docs.py $< > $@
|
||||||
cd src && tools/stitch_text.py index.rst _build/text > ../psycopg2.txt
|
|
||||||
|
$(BUILD_DIR):
|
||||||
|
$(MAKE) PYTHON=$(PYTHON) -C .. package
|
||||||
|
|
||||||
doctest:
|
doctest:
|
||||||
cd src && $(MAKE) $@
|
$(MAKE) PYTHON=$(PYTHON) -C .. package
|
||||||
|
$(MAKE) $(SPHOPTS) -C src $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
cd src && $(MAKE) $@
|
$(MAKE) $(SPHOPTS) -C src $@
|
||||||
rm -rf html psycopg2.txt
|
rm -rf html src/sqlstate_errors.rst
|
||||||
|
|
||||||
|
env: requirements.txt
|
||||||
|
$(PYTHON) -m venv env
|
||||||
|
./env/bin/pip install -r requirements.txt
|
||||||
|
echo "$$(pwd)/../build/lib.$(PYTHON_VERSION)" \
|
||||||
|
> env/lib/python$(PYTHON_VERSION)/site-packages/psycopg.pth
|
||||||
|
|
42
doc/README
42
doc/README
|
@ -1,42 +0,0 @@
|
||||||
How to build psycopg documentation
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
- Install Sphinx, maybe in a virtualenv. Tested with Sphinx 0.6.4::
|
|
||||||
|
|
||||||
~$ virtualenv pd
|
|
||||||
New python executable in pd/bin/python
|
|
||||||
Installing setuptools............done.
|
|
||||||
~$ cd pd
|
|
||||||
~/pd$ source bin/activate
|
|
||||||
(pd)~/pd$
|
|
||||||
|
|
||||||
- Install Sphinx in the env::
|
|
||||||
|
|
||||||
(pd)~/pd$ easy_install sphinx
|
|
||||||
Searching for sphinx
|
|
||||||
Reading http://pypi.python.org/simple/sphinx/
|
|
||||||
Reading http://sphinx.pocoo.org/
|
|
||||||
Best match: Sphinx 0.6.4
|
|
||||||
...
|
|
||||||
Finished processing dependencies for sphinx
|
|
||||||
|
|
||||||
- Build psycopg2 and ensure the package can be imported (it will be used for
|
|
||||||
reading the version number, autodocs etc.)::
|
|
||||||
|
|
||||||
(pd)~/pd/psycopg2$ python setup.py build
|
|
||||||
(pd)~/pd/psycopg2$ python setup.py install
|
|
||||||
running install
|
|
||||||
...
|
|
||||||
creating ~/pd/lib/python2.6/site-packages/psycopg2
|
|
||||||
...
|
|
||||||
|
|
||||||
- Move to the ``doc`` dir and run ``make`` from there::
|
|
||||||
|
|
||||||
(pd)~/pd/psycopg2$ cd doc/
|
|
||||||
(pd)~/pd/psycopg2/doc$ make
|
|
||||||
Running Sphinx v0.6.4
|
|
||||||
...
|
|
||||||
|
|
||||||
You should have the rendered documentation in ``./html`` and the text file
|
|
||||||
``psycopg2.txt`` now.
|
|
||||||
|
|
20
doc/README.rst
Normal file
20
doc/README.rst
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
How to build psycopg documentation
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
Building the documentation usually requires building the library too for
|
||||||
|
introspection, so you will need the same prerequisites_. The only extra
|
||||||
|
prerequisite is virtualenv_: the packages needed to build the docs will be
|
||||||
|
installed when building the env.
|
||||||
|
|
||||||
|
.. _prerequisites: https://www.psycopg.org/docs/install.html#install-from-source
|
||||||
|
.. _virtualenv: https://virtualenv.pypa.io/en/latest/
|
||||||
|
|
||||||
|
Build the env once with::
|
||||||
|
|
||||||
|
make env
|
||||||
|
|
||||||
|
Then you can build the documentation with::
|
||||||
|
|
||||||
|
make
|
||||||
|
|
||||||
|
You should find the rendered documentation in the ``html`` directory.
|
|
@ -23,7 +23,7 @@ Date: 23 Oct 2001 09:53:11 +0600
|
||||||
|
|
||||||
We use psycopg and psycopg zope adapter since fisrt public
|
We use psycopg and psycopg zope adapter since fisrt public
|
||||||
release (it seems version 0.4). Now it works on 3 our sites and in intranet
|
release (it seems version 0.4). Now it works on 3 our sites and in intranet
|
||||||
applications. We had few problems, but all problems were quckly
|
applications. We had few problems, but all problems were quickly
|
||||||
solved. The strong side of psycopg is that it's code is well organized
|
solved. The strong side of psycopg is that it's code is well organized
|
||||||
and easy to understand. When I found a problem with non-ISO datestyle in first
|
and easy to understand. When I found a problem with non-ISO datestyle in first
|
||||||
version of psycopg, it took for me 15 or 20 minutes to learn code and
|
version of psycopg, it took for me 15 or 20 minutes to learn code and
|
||||||
|
|
|
@ -255,7 +255,7 @@ Cursor Objects
|
||||||
display_size, internal_size, precision, scale,
|
display_size, internal_size, precision, scale,
|
||||||
null_ok). The first two items (name and type_code) are
|
null_ok). The first two items (name and type_code) are
|
||||||
mandatory, the other five are optional and must be set to
|
mandatory, the other five are optional and must be set to
|
||||||
None if meaningfull values are not provided.
|
None if meaningful values are not provided.
|
||||||
|
|
||||||
This attribute will be None for operations that
|
This attribute will be None for operations that
|
||||||
do not return rows or if the cursor has not had an
|
do not return rows or if the cursor has not had an
|
||||||
|
|
68
doc/release.rst
Normal file
68
doc/release.rst
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
How to make a psycopg2 release
|
||||||
|
==============================
|
||||||
|
|
||||||
|
- Edit ``setup.py`` and set a stable version release. Use PEP 440 to choose
|
||||||
|
version numbers, e.g.
|
||||||
|
|
||||||
|
- ``2.7``: a new major release, new features
|
||||||
|
- ``2.7.1``: a bugfix release
|
||||||
|
- ``2.7.1.1``: a release to fix packaging problems
|
||||||
|
- ``2.7.2.dev0``: version held during development, non-public test packages...
|
||||||
|
- ``2.8b1``: a beta for public tests
|
||||||
|
|
||||||
|
In the rest of this document we assume you have exported the version number
|
||||||
|
into an environment variable, e.g.::
|
||||||
|
|
||||||
|
$ export VERSION=2.8.4
|
||||||
|
|
||||||
|
- Push psycopg2 to master or to the maint branch. Make sure tests on `GitHub
|
||||||
|
Actions`__.
|
||||||
|
|
||||||
|
.. __: https://github.com/psycopg/psycopg2/actions/workflows/tests.yml
|
||||||
|
|
||||||
|
- Create a signed tag with the content of the relevant NEWS bit and push it.
|
||||||
|
E.g.::
|
||||||
|
|
||||||
|
# Tag name will be 2_8_4
|
||||||
|
$ git tag -a -s ${VERSION//\./_}
|
||||||
|
|
||||||
|
Psycopg 2.8.4 released
|
||||||
|
|
||||||
|
What's new in psycopg 2.8.4
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
New features:
|
||||||
|
|
||||||
|
- Fixed bug blah (:ticket:`#42`).
|
||||||
|
...
|
||||||
|
|
||||||
|
- Create the packages:
|
||||||
|
|
||||||
|
- On GitHub Actions run manually a `package build workflow`__.
|
||||||
|
|
||||||
|
.. __: https://github.com/psycopg/psycopg2/actions/workflows/packages.yml
|
||||||
|
|
||||||
|
- When the workflows have finished download the packages from the job
|
||||||
|
artifacts.
|
||||||
|
|
||||||
|
- Only for stable packages: upload the signed packages on PyPI::
|
||||||
|
|
||||||
|
$ twine upload -s wheelhouse/psycopg2-${VERSION}/*
|
||||||
|
|
||||||
|
- Create a release and release notes in the psycopg website, announce to
|
||||||
|
psycopg and pgsql-announce mailing lists.
|
||||||
|
|
||||||
|
- Edit ``setup.py`` changing the version again (e.g. go to ``2.8.5.dev0``).
|
||||||
|
|
||||||
|
|
||||||
|
Releasing test packages
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Test packages may be uploaded on the `PyPI testing site`__ using::
|
||||||
|
|
||||||
|
$ twine upload -s -r testpypi wheelhouse/psycopg2-${VERSION}/*
|
||||||
|
|
||||||
|
assuming `proper configuration`__ of ``~/.pypirc``.
|
||||||
|
|
||||||
|
.. __: https://test.pypi.org/project/psycopg2/
|
||||||
|
.. __: https://wiki.python.org/moin/TestPyPI
|
2
doc/requirements.in
Normal file
2
doc/requirements.in
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Sphinx
|
||||||
|
sphinx-better-theme
|
50
doc/requirements.txt
Normal file
50
doc/requirements.txt
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile with Python 3.10
|
||||||
|
# by the following command:
|
||||||
|
#
|
||||||
|
# pip-compile requirements.in
|
||||||
|
#
|
||||||
|
alabaster==0.7.13
|
||||||
|
# via sphinx
|
||||||
|
babel==2.12.1
|
||||||
|
# via sphinx
|
||||||
|
certifi>=2023.7.22
|
||||||
|
# via requests
|
||||||
|
charset-normalizer==3.1.0
|
||||||
|
# via requests
|
||||||
|
docutils==0.19
|
||||||
|
# via sphinx
|
||||||
|
idna==3.4
|
||||||
|
# via requests
|
||||||
|
imagesize==1.4.1
|
||||||
|
# via sphinx
|
||||||
|
jinja2==3.1.2
|
||||||
|
# via sphinx
|
||||||
|
markupsafe==2.1.2
|
||||||
|
# via jinja2
|
||||||
|
packaging==23.1
|
||||||
|
# via sphinx
|
||||||
|
pygments==2.15.0
|
||||||
|
# via sphinx
|
||||||
|
requests==2.31.0
|
||||||
|
# via sphinx
|
||||||
|
snowballstemmer==2.2.0
|
||||||
|
# via sphinx
|
||||||
|
sphinx==6.1.3
|
||||||
|
# via -r requirements.in
|
||||||
|
sphinx-better-theme==0.1.5
|
||||||
|
# via -r requirements.in
|
||||||
|
sphinxcontrib-applehelp==1.0.4
|
||||||
|
# via sphinx
|
||||||
|
sphinxcontrib-devhelp==1.0.2
|
||||||
|
# via sphinx
|
||||||
|
sphinxcontrib-htmlhelp==2.0.1
|
||||||
|
# via sphinx
|
||||||
|
sphinxcontrib-jsmath==1.0.1
|
||||||
|
# via sphinx
|
||||||
|
sphinxcontrib-qthelp==1.0.3
|
||||||
|
# via sphinx
|
||||||
|
sphinxcontrib-serializinghtml==1.1.5
|
||||||
|
# via sphinx
|
||||||
|
urllib3==1.26.17
|
||||||
|
# via requests
|
|
@ -1,5 +1,3 @@
|
||||||
@import url("default.css");
|
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
@ -14,11 +12,18 @@ div.dbapi-extension {
|
||||||
border: 1px solid #aaf;
|
border: 1px solid #aaf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code.sql,
|
||||||
tt.sql {
|
tt.sql {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a > code.sql,
|
||||||
|
a > tt.sql {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
a > code.sql:hover,
|
||||||
a > tt.sql:hover {
|
a > tt.sql:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
@ -30,3 +35,102 @@ dl.faq dt {
|
||||||
table.data-types div.line-block {
|
table.data-types div.line-block {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* better theme customisation */
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #216464;
|
||||||
|
}
|
||||||
|
|
||||||
|
header, .related, .document, footer {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
font-size: 150%;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding: 0.5rem 10px 0.5rem 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body h1, .body h2, .body h3 {
|
||||||
|
color: #074848;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 200%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 160%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 140%;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer#pagefooter {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 85%;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rellinks, #breadcrumbs {
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sphinxsidebar {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bodywrapper {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body h1, div.body h2, div.body h3 {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
border-bottom: 1px solid #d0d0d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body p.rubric {
|
||||||
|
border-bottom: 1px solid #d0d0d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body .sphinxsidebar .search {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html pre {
|
||||||
|
background-color: #efc;
|
||||||
|
border: 1px solid #ac9;
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, a:visited {
|
||||||
|
color: #0b6868;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #ede;
|
||||||
|
}
|
||||||
|
|
||||||
|
code.xref, a code {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
code.descname {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 120%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 820px) {
|
||||||
|
body {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
6
doc/src/_templates/searchbox.html
Normal file
6
doc/src/_templates/searchbox.html
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{# Add a title over the search box #}
|
||||||
|
|
||||||
|
{%- if pagename != "search" %}
|
||||||
|
<h3>Quick search</h3>
|
||||||
|
{%- include "!searchbox.html" %}
|
||||||
|
{%- endif %}
|
|
@ -12,7 +12,7 @@ More advanced topics
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
def wait(conn):
|
def wait(conn):
|
||||||
while 1:
|
while True:
|
||||||
state = conn.poll()
|
state = conn.poll()
|
||||||
if state == psycopg2.extensions.POLL_OK:
|
if state == psycopg2.extensions.POLL_OK:
|
||||||
break
|
break
|
||||||
|
@ -47,7 +47,7 @@ it is the class where query building, execution and result type-casting into
|
||||||
Python variables happens.
|
Python variables happens.
|
||||||
|
|
||||||
The `~psycopg2.extras` module contains several examples of :ref:`connection
|
The `~psycopg2.extras` module contains several examples of :ref:`connection
|
||||||
and cursor sublcasses <cursor-subclasses>`.
|
and cursor subclasses <cursor-subclasses>`.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -145,7 +145,9 @@ geometric type:
|
||||||
... self.y = y
|
... self.y = y
|
||||||
|
|
||||||
>>> def adapt_point(point):
|
>>> def adapt_point(point):
|
||||||
... return AsIs("'(%s, %s)'" % (adapt(point.x), adapt(point.y)))
|
... x = adapt(point.x).getquoted()
|
||||||
|
... y = adapt(point.y).getquoted()
|
||||||
|
... return AsIs("'(%s, %s)'" % (x, y))
|
||||||
|
|
||||||
>>> register_adapter(Point, adapt_point)
|
>>> register_adapter(Point, adapt_point)
|
||||||
|
|
||||||
|
@ -154,7 +156,7 @@ geometric type:
|
||||||
|
|
||||||
|
|
||||||
.. |point| replace:: :sql:`point`
|
.. |point| replace:: :sql:`point`
|
||||||
.. _point: http://www.postgresql.org/docs/current/static/datatype-geometric.html#DATATYPE-GEOMETRIC
|
.. _point: https://www.postgresql.org/docs/current/static/datatype-geometric.html#DATATYPE-GEOMETRIC
|
||||||
|
|
||||||
The above function call results in the SQL command::
|
The above function call results in the SQL command::
|
||||||
|
|
||||||
|
@ -224,7 +226,7 @@ read:
|
||||||
|
|
||||||
>>> cur.execute("SELECT '(10.2,20.3)'::point")
|
>>> cur.execute("SELECT '(10.2,20.3)'::point")
|
||||||
>>> point = cur.fetchone()[0]
|
>>> point = cur.fetchone()[0]
|
||||||
>>> print type(point), point.x, point.y
|
>>> print(type(point), point.x, point.y)
|
||||||
<class 'Point'> 10.2 20.3
|
<class 'Point'> 10.2 20.3
|
||||||
|
|
||||||
A typecaster created by `!new_type()` can be also used with
|
A typecaster created by `!new_type()` can be also used with
|
||||||
|
@ -257,9 +259,9 @@ documentation), you should keep the connection in `~connection.autocommit`
|
||||||
mode if you wish to receive or send notifications in a timely manner.
|
mode if you wish to receive or send notifications in a timely manner.
|
||||||
|
|
||||||
.. |LISTEN| replace:: :sql:`LISTEN`
|
.. |LISTEN| replace:: :sql:`LISTEN`
|
||||||
.. _LISTEN: http://www.postgresql.org/docs/current/static/sql-listen.html
|
.. _LISTEN: https://www.postgresql.org/docs/current/static/sql-listen.html
|
||||||
.. |NOTIFY| replace:: :sql:`NOTIFY`
|
.. |NOTIFY| replace:: :sql:`NOTIFY`
|
||||||
.. _NOTIFY: http://www.postgresql.org/docs/current/static/sql-notify.html
|
.. _NOTIFY: https://www.postgresql.org/docs/current/static/sql-notify.html
|
||||||
|
|
||||||
Notifications are received after every query execution. If the user is
|
Notifications are received after every query execution. If the user is
|
||||||
interested in receiving notifications but not in performing any query, the
|
interested in receiving notifications but not in performing any query, the
|
||||||
|
@ -268,7 +270,7 @@ wasting resources.
|
||||||
|
|
||||||
A simple application could poll the connection from time to time to check if
|
A simple application could poll the connection from time to time to check if
|
||||||
something new has arrived. A better strategy is to use some I/O completion
|
something new has arrived. A better strategy is to use some I/O completion
|
||||||
function such as :py:func:`~select.select` to sleep until awaken from the kernel when there is
|
function such as :py:func:`~select.select` to sleep until awakened by the kernel when there is
|
||||||
some data to read on the connection, thereby using no CPU unless there is
|
some data to read on the connection, thereby using no CPU unless there is
|
||||||
something to read::
|
something to read::
|
||||||
|
|
||||||
|
@ -282,18 +284,20 @@ something to read::
|
||||||
curs = conn.cursor()
|
curs = conn.cursor()
|
||||||
curs.execute("LISTEN test;")
|
curs.execute("LISTEN test;")
|
||||||
|
|
||||||
print "Waiting for notifications on channel 'test'"
|
print("Waiting for notifications on channel 'test'")
|
||||||
while 1:
|
while True:
|
||||||
if select.select([conn],[],[],5) == ([],[],[]):
|
if select.select([conn],[],[],5) == ([],[],[]):
|
||||||
print "Timeout"
|
print("Timeout")
|
||||||
else:
|
else:
|
||||||
conn.poll()
|
conn.poll()
|
||||||
while conn.notifies:
|
while conn.notifies:
|
||||||
notify = conn.notifies.pop()
|
notify = conn.notifies.pop(0)
|
||||||
print "Got NOTIFY:", notify.pid, notify.channel, notify.payload
|
print("Got NOTIFY:", notify.pid, notify.channel, notify.payload)
|
||||||
|
|
||||||
Running the script and executing a command such as :sql:`NOTIFY test, 'hello'`
|
Running the script and executing a command such as :sql:`NOTIFY test, 'hello'`
|
||||||
in a separate :program:`psql` shell, the output may look similar to::
|
in a separate :program:`psql` shell, the output may look similar to:
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
Waiting for notifications on channel 'test'
|
Waiting for notifications on channel 'test'
|
||||||
Timeout
|
Timeout
|
||||||
|
@ -310,6 +314,10 @@ received from a previous version server will have the
|
||||||
Added `~psycopg2.extensions.Notify` object and handling notification
|
Added `~psycopg2.extensions.Notify` object and handling notification
|
||||||
payload.
|
payload.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
The `~connection.notifies` attribute is writable: it is possible to
|
||||||
|
replace it with any object exposing an `!append()` method. An useful
|
||||||
|
example would be to use a `~collections.deque` object.
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
@ -320,7 +328,7 @@ received from a previous version server will have the
|
||||||
Asynchronous support
|
Asynchronous support
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
.. versionadded:: 2.2.0
|
.. versionadded:: 2.2
|
||||||
|
|
||||||
Psycopg can issue asynchronous queries to a PostgreSQL database. An asynchronous
|
Psycopg can issue asynchronous queries to a PostgreSQL database. An asynchronous
|
||||||
communication style is established passing the parameter *async*\=1 to the
|
communication style is established passing the parameter *async*\=1 to the
|
||||||
|
@ -339,7 +347,7 @@ together with the Python :py:func:`~select.select` function in order to carry on
|
||||||
asynchronous operations with Psycopg::
|
asynchronous operations with Psycopg::
|
||||||
|
|
||||||
def wait(conn):
|
def wait(conn):
|
||||||
while 1:
|
while True:
|
||||||
state = conn.poll()
|
state = conn.poll()
|
||||||
if state == psycopg2.extensions.POLL_OK:
|
if state == psycopg2.extensions.POLL_OK:
|
||||||
break
|
break
|
||||||
|
@ -367,7 +375,7 @@ completely non-blocking connection attempt: see the libpq documentation for
|
||||||
|PQconnectStart|_.
|
|PQconnectStart|_.
|
||||||
|
|
||||||
.. |PQconnectStart| replace:: `!PQconnectStart()`
|
.. |PQconnectStart| replace:: `!PQconnectStart()`
|
||||||
.. _PQconnectStart: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNECTSTARTPARAMS
|
.. _PQconnectStart: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNECTSTARTPARAMS
|
||||||
|
|
||||||
The same loop should be also used to perform nonblocking queries: after
|
The same loop should be also used to perform nonblocking queries: after
|
||||||
sending a query via `~cursor.execute()` or `~cursor.callproc()`, call
|
sending a query via `~cursor.execute()` or `~cursor.callproc()`, call
|
||||||
|
@ -417,7 +425,7 @@ this will be probably implemented in a future release.
|
||||||
Support for coroutine libraries
|
Support for coroutine libraries
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
.. versionadded:: 2.2.0
|
.. versionadded:: 2.2
|
||||||
|
|
||||||
Psycopg can be used together with coroutine_\-based libraries and participate
|
Psycopg can be used together with coroutine_\-based libraries and participate
|
||||||
in cooperative multithreading.
|
in cooperative multithreading.
|
||||||
|
@ -460,7 +468,7 @@ example callback (using `!select()` to block) is provided as
|
||||||
`psycopg2.extras.wait_select()`: it boils down to something similar to::
|
`psycopg2.extras.wait_select()`: it boils down to something similar to::
|
||||||
|
|
||||||
def wait_select(conn):
|
def wait_select(conn):
|
||||||
while 1:
|
while True:
|
||||||
state = conn.poll()
|
state = conn.poll()
|
||||||
if state == extensions.POLL_OK:
|
if state == extensions.POLL_OK:
|
||||||
break
|
break
|
||||||
|
@ -476,14 +484,14 @@ psycopg2 scope, as the callback can be tied to the libraries' implementation
|
||||||
details. You can check the `psycogreen`_ project for further informations and
|
details. You can check the `psycogreen`_ project for further informations and
|
||||||
resources about the topic.
|
resources about the topic.
|
||||||
|
|
||||||
.. _coroutine: http://en.wikipedia.org/wiki/Coroutine
|
.. _coroutine: https://en.wikipedia.org/wiki/Coroutine
|
||||||
.. _greenlet: http://pypi.python.org/pypi/greenlet
|
.. _greenlet: https://pypi.org/project/greenlet/
|
||||||
.. _green threads: http://en.wikipedia.org/wiki/Green_threads
|
.. _green threads: https://en.wikipedia.org/wiki/Green_threads
|
||||||
.. _Eventlet: http://eventlet.net/
|
.. _Eventlet: https://eventlet.net/
|
||||||
.. _gevent: http://www.gevent.org/
|
.. _gevent: http://www.gevent.org/
|
||||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
||||||
.. _psycogreen: http://bitbucket.org/dvarrazzo/psycogreen/
|
.. _psycogreen: https://github.com/psycopg/psycogreen/
|
||||||
.. __: http://www.postgresql.org/docs/current/static/libpq-async.html
|
.. __: https://www.postgresql.org/docs/current/static/libpq-async.html
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
@ -503,3 +511,89 @@ resources about the topic.
|
||||||
conn.commit()
|
conn.commit()
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
single: Replication
|
||||||
|
|
||||||
|
.. _replication-support:
|
||||||
|
|
||||||
|
Replication protocol support
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
|
||||||
|
Modern PostgreSQL servers (version 9.0 and above) support replication. The
|
||||||
|
replication protocol is built on top of the client-server protocol and can be
|
||||||
|
operated using ``libpq``, as such it can be also operated by ``psycopg2``.
|
||||||
|
The replication protocol can be operated on both synchronous and
|
||||||
|
:ref:`asynchronous <async-support>` connections.
|
||||||
|
|
||||||
|
Server version 9.4 adds a new feature called *Logical Replication*.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
- PostgreSQL `Streaming Replication Protocol`__
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/protocol-replication.html
|
||||||
|
|
||||||
|
|
||||||
|
Logical replication Quick-Start
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
You must be using PostgreSQL server version 9.4 or above to run this quick
|
||||||
|
start.
|
||||||
|
|
||||||
|
Make sure that replication connections are permitted for user ``postgres`` in
|
||||||
|
``pg_hba.conf`` and reload the server configuration. You also need to set
|
||||||
|
``wal_level=logical`` and ``max_wal_senders``, ``max_replication_slots`` to
|
||||||
|
value greater than zero in ``postgresql.conf`` (these changes require a server
|
||||||
|
restart). Create a database ``psycopg2_test``.
|
||||||
|
|
||||||
|
Then run the following code to quickly try the replication support out. This
|
||||||
|
is not production code -- it's only intended as a simple demo of logical
|
||||||
|
replication::
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
import sys
|
||||||
|
import psycopg2
|
||||||
|
import psycopg2.extras
|
||||||
|
|
||||||
|
conn = psycopg2.connect('dbname=psycopg2_test user=postgres',
|
||||||
|
connection_factory=psycopg2.extras.LogicalReplicationConnection)
|
||||||
|
cur = conn.cursor()
|
||||||
|
try:
|
||||||
|
# test_decoding produces textual output
|
||||||
|
cur.start_replication(slot_name='pytest', decode=True)
|
||||||
|
except psycopg2.ProgrammingError:
|
||||||
|
cur.create_replication_slot('pytest', output_plugin='test_decoding')
|
||||||
|
cur.start_replication(slot_name='pytest', decode=True)
|
||||||
|
|
||||||
|
class DemoConsumer(object):
|
||||||
|
def __call__(self, msg):
|
||||||
|
print(msg.payload)
|
||||||
|
msg.cursor.send_feedback(flush_lsn=msg.data_start)
|
||||||
|
|
||||||
|
democonsumer = DemoConsumer()
|
||||||
|
|
||||||
|
print("Starting streaming, press Control-C to end...", file=sys.stderr)
|
||||||
|
try:
|
||||||
|
cur.consume_stream(democonsumer)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
print("The slot 'pytest' still exists. Drop it with "
|
||||||
|
"SELECT pg_drop_replication_slot('pytest'); if no longer needed.",
|
||||||
|
file=sys.stderr)
|
||||||
|
print("WARNING: Transaction logs will accumulate in pg_xlog "
|
||||||
|
"until the slot is dropped.", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
You can now make changes to the ``psycopg2_test`` database using a normal
|
||||||
|
psycopg2 session, ``psql``, etc. and see the logical decoding stream printed
|
||||||
|
by this demo client.
|
||||||
|
|
||||||
|
This will continue running until terminated with ``Control-C``.
|
||||||
|
|
||||||
|
For the details see :ref:`replication-objects`.
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# Psycopg documentation build configuration file, created by
|
# Psycopg documentation build configuration file, created by
|
||||||
# sphinx-quickstart on Sun Feb 7 13:48:41 2010.
|
# sphinx-quickstart on Sun Feb 7 13:48:41 2010.
|
||||||
|
@ -11,7 +10,9 @@
|
||||||
# All configuration values have a default; values that are commented out
|
# All configuration values have a default; values that are commented out
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
|
|
||||||
import sys, os
|
import os
|
||||||
|
import sys
|
||||||
|
from better import better_theme_path
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
@ -22,8 +23,13 @@ sys.path.append(os.path.abspath('tools/lib'))
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.ifconfig',
|
extensions = [
|
||||||
'sphinx.ext.doctest', 'sphinx.ext.intersphinx' ]
|
'sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.todo',
|
||||||
|
'sphinx.ext.ifconfig',
|
||||||
|
'sphinx.ext.doctest',
|
||||||
|
'sphinx.ext.intersphinx',
|
||||||
|
]
|
||||||
|
|
||||||
# Specific extensions for Psycopg documentation.
|
# Specific extensions for Psycopg documentation.
|
||||||
extensions += ['dbapi_extension', 'sql_role', 'ticket_role']
|
extensions += ['dbapi_extension', 'sql_role', 'ticket_role']
|
||||||
|
@ -41,8 +47,10 @@ source_suffix = '.rst'
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'Psycopg'
|
project = 'Psycopg'
|
||||||
copyright = u'2001-2013, Federico Di Gregorio. Documentation by Daniele Varrazzo'
|
copyright = (
|
||||||
|
'2001-2021, Federico Di Gregorio, Daniele Varrazzo, The Psycopg Team'
|
||||||
|
)
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
@ -54,19 +62,19 @@ version = '2.0'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
try:
|
try:
|
||||||
import psycopg2
|
import psycopg2
|
||||||
|
except ImportError:
|
||||||
|
print("WARNING: couldn't import psycopg to read version.")
|
||||||
|
release = version
|
||||||
|
else:
|
||||||
release = psycopg2.__version__.split()[0]
|
release = psycopg2.__version__.split()[0]
|
||||||
version = '.'.join(release.split('.')[:2])
|
version = '.'.join(release.split('.')[:2])
|
||||||
except ImportError:
|
|
||||||
print "WARNING: couldn't import psycopg to read version."
|
|
||||||
release = version
|
|
||||||
|
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {'py': ('https://docs.python.org/3', None)}
|
||||||
'py': ('http://docs.python.org/', None),
|
|
||||||
'py3': ('http://docs.python.org/3.2', None),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Pattern to generate links to the bug tracker
|
# Pattern to generate links to the bug tracker
|
||||||
ticket_url = 'http://psycopg.lighthouseapp.com/projects/62710/tickets/%s'
|
ticket_url = 'https://github.com/psycopg/psycopg2/issues/%s'
|
||||||
|
ticket_remap_until = 25
|
||||||
|
ticket_remap_offset = 230
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
@ -99,6 +107,10 @@ default_role = 'obj'
|
||||||
# output. They are ignored by default.
|
# output. They are ignored by default.
|
||||||
# show_authors = False
|
# show_authors = False
|
||||||
|
|
||||||
|
# Using 'python' instead of the default gives warnings if parsing an example
|
||||||
|
# fails, instead of defaulting to none
|
||||||
|
highlight_language = 'python'
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = 'sphinx'
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
@ -111,12 +123,10 @@ todo_include_todos = False
|
||||||
rst_epilog = """
|
rst_epilog = """
|
||||||
.. |DBAPI| replace:: DB API 2.0
|
.. |DBAPI| replace:: DB API 2.0
|
||||||
|
|
||||||
.. _DBAPI: http://www.python.org/dev/peps/pep-0249/
|
.. _DBAPI: https://www.python.org/dev/peps/pep-0249/
|
||||||
|
|
||||||
.. _transaction isolation level:
|
.. _transaction isolation level:
|
||||||
http://www.postgresql.org/docs/current/static/transaction-iso.html
|
https://www.postgresql.org/docs/current/static/transaction-iso.html
|
||||||
|
|
||||||
.. _mx.DateTime: http://www.egenix.com/products/python/mxBase/mxDateTime/
|
|
||||||
|
|
||||||
.. |MVCC| replace:: :abbr:`MVCC (Multiversion concurrency control)`
|
.. |MVCC| replace:: :abbr:`MVCC (Multiversion concurrency control)`
|
||||||
"""
|
"""
|
||||||
|
@ -125,26 +135,32 @@ rst_epilog = """
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||||
html_theme = 'default'
|
html_theme = 'better'
|
||||||
|
|
||||||
# The stylesheet to use with HTML output: this will include the original one
|
# The stylesheet to use with HTML output: this will include the original one
|
||||||
# adding a few classes.
|
# adding a few classes.
|
||||||
html_style = 'psycopg.css'
|
# html_style = 'psycopg.css'
|
||||||
|
|
||||||
|
# Hide the sphinx footer
|
||||||
|
html_show_sphinx = False
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
#html_theme_options = {}
|
html_theme_options = {
|
||||||
|
'linktotheme': False,
|
||||||
|
'cssfiles': ['_static/psycopg.css'],
|
||||||
|
}
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
#html_theme_path = []
|
html_theme_path = [better_theme_path]
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
# "<project> v<release> documentation".
|
# "<project> v<release> documentation".
|
||||||
# html_title = None
|
# html_title = None
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
#html_short_title = None
|
html_short_title = 'Home'
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
# of the sidebar.
|
# of the sidebar.
|
||||||
|
@ -169,7 +185,10 @@ html_static_path = ['_static']
|
||||||
# html_use_smartypants = True
|
# html_use_smartypants = True
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
# Custom sidebar templates, maps document names to template names.
|
||||||
#html_sidebars = {}
|
# no need for the prev/next topic link using better theme: they are on top
|
||||||
|
html_sidebars = {
|
||||||
|
'**': ['localtoc.html', 'searchbox.html'],
|
||||||
|
}
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
# template names.
|
# template names.
|
||||||
|
@ -210,8 +229,13 @@ htmlhelp_basename = 'psycopgdoc'
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'psycopg.tex', u'Psycopg Documentation',
|
(
|
||||||
u'Federico Di Gregorio', 'manual'),
|
'index',
|
||||||
|
'psycopg.tex',
|
||||||
|
'Psycopg Documentation',
|
||||||
|
'Federico Di Gregorio',
|
||||||
|
'manual',
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
@ -231,6 +255,7 @@ latex_documents = [
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
# latex_use_modindex = True
|
# latex_use_modindex = True
|
||||||
|
|
||||||
|
toc_object_entries = False
|
||||||
|
|
||||||
doctest_global_setup = """
|
doctest_global_setup = """
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,28 @@ The ``connection`` class
|
||||||
Connections are thread safe and can be shared among many threads. See
|
Connections are thread safe and can be shared among many threads. See
|
||||||
:ref:`thread-safety` for details.
|
:ref:`thread-safety` for details.
|
||||||
|
|
||||||
|
Connections can be used as context managers. Note that a context wraps a
|
||||||
|
transaction: if the context exits with success the transaction is
|
||||||
|
committed, if it exits with an exception the transaction is rolled back.
|
||||||
|
Note that the connection is not closed by the context and it can be used
|
||||||
|
for several contexts.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
conn = psycopg2.connect(DSN)
|
||||||
|
|
||||||
|
with conn:
|
||||||
|
with conn.cursor() as curs:
|
||||||
|
curs.execute(SQL1)
|
||||||
|
|
||||||
|
with conn:
|
||||||
|
with conn.cursor() as curs:
|
||||||
|
curs.execute(SQL2)
|
||||||
|
|
||||||
|
# leaving contexts doesn't close the connection
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
.. method:: cursor(name=None, cursor_factory=None, scrollable=None, withhold=False)
|
.. method:: cursor(name=None, cursor_factory=None, scrollable=None, withhold=False)
|
||||||
|
|
||||||
Return a new `cursor` object using the connection.
|
Return a new `cursor` object using the connection.
|
||||||
|
@ -41,11 +63,6 @@ The ``connection`` class
|
||||||
previously only valid PostgreSQL identifiers were accepted as
|
previously only valid PostgreSQL identifiers were accepted as
|
||||||
cursor name.
|
cursor name.
|
||||||
|
|
||||||
.. warning::
|
|
||||||
It is unsafe to expose the *name* to an untrusted source, for
|
|
||||||
instance you shouldn't allow *name* to be read from a HTML form.
|
|
||||||
Consider it as part of the query, not as a query parameter.
|
|
||||||
|
|
||||||
The *cursor_factory* argument can be used to create non-standard
|
The *cursor_factory* argument can be used to create non-standard
|
||||||
cursors. The class returned must be a subclass of
|
cursors. The class returned must be a subclass of
|
||||||
`psycopg2.extensions.cursor`. See :ref:`subclassing-cursor` for
|
`psycopg2.extensions.cursor`. See :ref:`subclassing-cursor` for
|
||||||
|
@ -122,7 +139,7 @@ The ``connection`` class
|
||||||
with a `~connection.commit()`/`~connection.rollback()` before
|
with a `~connection.commit()`/`~connection.rollback()` before
|
||||||
closing.
|
closing.
|
||||||
|
|
||||||
.. _PgBouncer: http://pgbouncer.projects.postgresql.org/
|
.. _PgBouncer: http://www.pgbouncer.org/
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
@ -203,7 +220,7 @@ The ``connection`` class
|
||||||
.. seealso:: the |PREPARE TRANSACTION|_ PostgreSQL command.
|
.. seealso:: the |PREPARE TRANSACTION|_ PostgreSQL command.
|
||||||
|
|
||||||
.. |PREPARE TRANSACTION| replace:: :sql:`PREPARE TRANSACTION`
|
.. |PREPARE TRANSACTION| replace:: :sql:`PREPARE TRANSACTION`
|
||||||
.. _PREPARE TRANSACTION: http://www.postgresql.org/docs/current/static/sql-prepare-transaction.html
|
.. _PREPARE TRANSACTION: https://www.postgresql.org/docs/current/static/sql-prepare-transaction.html
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
@ -229,7 +246,7 @@ The ``connection`` class
|
||||||
.. seealso:: the |COMMIT PREPARED|_ PostgreSQL command.
|
.. seealso:: the |COMMIT PREPARED|_ PostgreSQL command.
|
||||||
|
|
||||||
.. |COMMIT PREPARED| replace:: :sql:`COMMIT PREPARED`
|
.. |COMMIT PREPARED| replace:: :sql:`COMMIT PREPARED`
|
||||||
.. _COMMIT PREPARED: http://www.postgresql.org/docs/current/static/sql-commit-prepared.html
|
.. _COMMIT PREPARED: https://www.postgresql.org/docs/current/static/sql-commit-prepared.html
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
@ -251,7 +268,7 @@ The ``connection`` class
|
||||||
.. seealso:: the |ROLLBACK PREPARED|_ PostgreSQL command.
|
.. seealso:: the |ROLLBACK PREPARED|_ PostgreSQL command.
|
||||||
|
|
||||||
.. |ROLLBACK PREPARED| replace:: :sql:`ROLLBACK PREPARED`
|
.. |ROLLBACK PREPARED| replace:: :sql:`ROLLBACK PREPARED`
|
||||||
.. _ROLLBACK PREPARED: http://www.postgresql.org/docs/current/static/sql-rollback-prepared.html
|
.. _ROLLBACK PREPARED: https://www.postgresql.org/docs/current/static/sql-rollback-prepared.html
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
@ -272,7 +289,7 @@ The ``connection`` class
|
||||||
transactions initiated by a program using such driver should be
|
transactions initiated by a program using such driver should be
|
||||||
unpacked correctly.
|
unpacked correctly.
|
||||||
|
|
||||||
.. __: http://jdbc.postgresql.org/
|
.. __: https://jdbc.postgresql.org/
|
||||||
|
|
||||||
Xids returned by `!tpc_recover()` also have extra attributes
|
Xids returned by `!tpc_recover()` also have extra attributes
|
||||||
`~psycopg2.extensions.Xid.prepared`, `~psycopg2.extensions.Xid.owner`,
|
`~psycopg2.extensions.Xid.prepared`, `~psycopg2.extensions.Xid.owner`,
|
||||||
|
@ -282,7 +299,7 @@ The ``connection`` class
|
||||||
.. seealso:: the |pg_prepared_xacts|_ system view.
|
.. seealso:: the |pg_prepared_xacts|_ system view.
|
||||||
|
|
||||||
.. |pg_prepared_xacts| replace:: `pg_prepared_xacts`
|
.. |pg_prepared_xacts| replace:: `pg_prepared_xacts`
|
||||||
.. _pg_prepared_xacts: http://www.postgresql.org/docs/current/static/view-pg-prepared-xacts.html
|
.. _pg_prepared_xacts: https://www.postgresql.org/docs/current/static/view-pg-prepared-xacts.html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -295,8 +312,8 @@ The ``connection`` class
|
||||||
|
|
||||||
.. attribute:: closed
|
.. attribute:: closed
|
||||||
|
|
||||||
Read-only attribute reporting whether the database connection is open
|
Read-only integer attribute: 0 if the connection is open, nonzero if
|
||||||
(0) or closed (1).
|
it is closed or broken.
|
||||||
|
|
||||||
|
|
||||||
.. method:: cancel
|
.. method:: cancel
|
||||||
|
@ -314,7 +331,7 @@ The ``connection`` class
|
||||||
|PQcancel|_.
|
|PQcancel|_.
|
||||||
|
|
||||||
.. |PQcancel| replace:: `!PQcancel()`
|
.. |PQcancel| replace:: `!PQcancel()`
|
||||||
.. _PQcancel: http://www.postgresql.org/docs/current/static/libpq-cancel.html#LIBPQ-PQCANCEL
|
.. _PQcancel: https://www.postgresql.org/docs/current/static/libpq-cancel.html#LIBPQ-PQCANCEL
|
||||||
|
|
||||||
.. versionadded:: 2.3
|
.. versionadded:: 2.3
|
||||||
|
|
||||||
|
@ -330,10 +347,10 @@ The ``connection`` class
|
||||||
available for recover.
|
available for recover.
|
||||||
|
|
||||||
.. |RESET| replace:: :sql:`RESET`
|
.. |RESET| replace:: :sql:`RESET`
|
||||||
.. _RESET: http://www.postgresql.org/docs/current/static/sql-reset.html
|
.. _RESET: https://www.postgresql.org/docs/current/static/sql-reset.html
|
||||||
|
|
||||||
.. |SET SESSION AUTHORIZATION| replace:: :sql:`SET SESSION AUTHORIZATION`
|
.. |SET SESSION AUTHORIZATION| replace:: :sql:`SET SESSION AUTHORIZATION`
|
||||||
.. __: http://www.postgresql.org/docs/current/static/sql-set-session-authorization.html
|
.. __: https://www.postgresql.org/docs/current/static/sql-set-session-authorization.html
|
||||||
|
|
||||||
.. versionadded:: 2.0.12
|
.. versionadded:: 2.0.12
|
||||||
|
|
||||||
|
@ -343,25 +360,28 @@ The ``connection`` class
|
||||||
Read-only string containing the connection string used by the
|
Read-only string containing the connection string used by the
|
||||||
connection.
|
connection.
|
||||||
|
|
||||||
|
If a password was specified in the connection string it will be
|
||||||
|
obscured.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. rubric:: Transaction control methods and attributes.
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
pair: Transaction; Autocommit
|
pair: Transaction; Autocommit
|
||||||
pair: Transaction; Isolation level
|
pair: Transaction; Isolation level
|
||||||
|
|
||||||
.. method:: set_session([isolation_level,] [readonly,] [deferrable,] [autocommit])
|
.. method:: set_session(isolation_level=None, readonly=None, deferrable=None, autocommit=None)
|
||||||
|
|
||||||
Set one or more parameters for the next transactions or statements in
|
Set one or more parameters for the next transactions or statements in
|
||||||
the current session. See |SET TRANSACTION|_ for further details.
|
the current session.
|
||||||
|
|
||||||
.. |SET TRANSACTION| replace:: :sql:`SET TRANSACTION`
|
|
||||||
.. _SET TRANSACTION: http://www.postgresql.org/docs/current/static/sql-set-transaction.html
|
|
||||||
|
|
||||||
:param isolation_level: set the `isolation level`_ for the next
|
:param isolation_level: set the `isolation level`_ for the next
|
||||||
transactions/statements. The value can be one of the
|
transactions/statements. The value can be one of the literal
|
||||||
:ref:`constants <isolation-level-constants>` defined in the
|
values ``READ UNCOMMITTED``, ``READ COMMITTED``, ``REPEATABLE
|
||||||
`~psycopg2.extensions` module or one of the literal values
|
READ``, ``SERIALIZABLE`` or the equivalent :ref:`constant
|
||||||
``READ UNCOMMITTED``, ``READ COMMITTED``, ``REPEATABLE READ``,
|
<isolation-level-constants>` defined in the `~psycopg2.extensions`
|
||||||
``SERIALIZABLE``.
|
module.
|
||||||
:param readonly: if `!True`, set the connection to read only;
|
:param readonly: if `!True`, set the connection to read only;
|
||||||
read/write if `!False`.
|
read/write if `!False`.
|
||||||
:param deferrable: if `!True`, set the connection to deferrable;
|
:param deferrable: if `!True`, set the connection to deferrable;
|
||||||
|
@ -370,35 +390,59 @@ The ``connection`` class
|
||||||
PostgreSQL session setting but an alias for setting the
|
PostgreSQL session setting but an alias for setting the
|
||||||
`autocommit` attribute.
|
`autocommit` attribute.
|
||||||
|
|
||||||
|
.. _isolation level:
|
||||||
|
https://www.postgresql.org/docs/current/static/transaction-iso.html
|
||||||
|
|
||||||
|
Arguments set to `!None` (the default for all) will not be changed.
|
||||||
The parameters *isolation_level*, *readonly* and *deferrable* also
|
The parameters *isolation_level*, *readonly* and *deferrable* also
|
||||||
accept the string ``DEFAULT`` as a value: the effect is to reset the
|
accept the string ``DEFAULT`` as a value: the effect is to reset the
|
||||||
parameter to the server default.
|
parameter to the server default. Defaults are defined by the server
|
||||||
|
configuration: see values for |default_transaction_isolation|__,
|
||||||
.. _isolation level:
|
|
||||||
http://www.postgresql.org/docs/current/static/transaction-iso.html
|
|
||||||
|
|
||||||
The function must be invoked with no transaction in progress. At every
|
|
||||||
function invocation, only the specified parameters are changed.
|
|
||||||
|
|
||||||
The default for the values are defined by the server configuration:
|
|
||||||
see values for |default_transaction_isolation|__,
|
|
||||||
|default_transaction_read_only|__, |default_transaction_deferrable|__.
|
|default_transaction_read_only|__, |default_transaction_deferrable|__.
|
||||||
|
|
||||||
.. |default_transaction_isolation| replace:: :sql:`default_transaction_isolation`
|
.. |default_transaction_isolation| replace:: :sql:`default_transaction_isolation`
|
||||||
.. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-ISOLATION
|
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-ISOLATION
|
||||||
.. |default_transaction_read_only| replace:: :sql:`default_transaction_read_only`
|
.. |default_transaction_read_only| replace:: :sql:`default_transaction_read_only`
|
||||||
.. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-READ-ONLY
|
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-READ-ONLY
|
||||||
.. |default_transaction_deferrable| replace:: :sql:`default_transaction_deferrable`
|
.. |default_transaction_deferrable| replace:: :sql:`default_transaction_deferrable`
|
||||||
.. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-DEFERRABLE
|
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-DEFERRABLE
|
||||||
|
|
||||||
.. note::
|
The function must be invoked with no transaction in progress.
|
||||||
|
|
||||||
There is currently no builtin method to read the current value for
|
.. seealso:: |SET TRANSACTION|_ for further details about the behaviour
|
||||||
the parameters: use :sql:`SHOW default_transaction_...` to read
|
of the transaction parameters in the server.
|
||||||
the values from the backend.
|
|
||||||
|
.. |SET TRANSACTION| replace:: :sql:`SET TRANSACTION`
|
||||||
|
.. _SET TRANSACTION: https://www.postgresql.org/docs/current/static/sql-set-transaction.html
|
||||||
|
|
||||||
.. versionadded:: 2.4.2
|
.. versionadded:: 2.4.2
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
Before this version, the function would have set
|
||||||
|
:sql:`default_transaction_*` attribute in the current session;
|
||||||
|
this implementation has the problem of not playing well with
|
||||||
|
external connection pooling working at transaction level and not
|
||||||
|
resetting the state of the session: changing the default
|
||||||
|
transaction would pollute the connections in the pool and create
|
||||||
|
problems to other applications using the same pool.
|
||||||
|
|
||||||
|
Starting from 2.7, if the connection is not autocommit, the
|
||||||
|
transaction characteristics are issued together with :sql:`BEGIN`
|
||||||
|
and will leave the :sql:`default_transaction_*` settings untouched.
|
||||||
|
For example::
|
||||||
|
|
||||||
|
conn.set_session(readonly=True)
|
||||||
|
|
||||||
|
will not change :sql:`default_transaction_read_only`, but
|
||||||
|
following transaction will start with a :sql:`BEGIN READ ONLY`.
|
||||||
|
Conversely, using::
|
||||||
|
|
||||||
|
conn.set_session(readonly=True, autocommit=True)
|
||||||
|
|
||||||
|
will set :sql:`default_transaction_read_only` to :sql:`on` and
|
||||||
|
rely on the server to apply the read only state to whatever
|
||||||
|
transaction, implicit or explicit, is executed in the connection.
|
||||||
|
|
||||||
|
|
||||||
.. attribute:: autocommit
|
.. attribute:: autocommit
|
||||||
|
|
||||||
|
@ -418,8 +462,8 @@ The ``connection`` class
|
||||||
|
|
||||||
By default, any query execution, including a simple :sql:`SELECT`
|
By default, any query execution, including a simple :sql:`SELECT`
|
||||||
will start a transaction: for long-running programs, if no further
|
will start a transaction: for long-running programs, if no further
|
||||||
action is taken, the session will remain "idle in transaction", a
|
action is taken, the session will remain "idle in transaction", an
|
||||||
condition non desiderable for several reasons (locks are held by
|
undesirable condition for several reasons (locks are held by
|
||||||
the session, tables bloat...). For long lived scripts, either
|
the session, tables bloat...). For long lived scripts, either
|
||||||
ensure to terminate a transaction as soon as possible or use an
|
ensure to terminate a transaction as soon as possible or use an
|
||||||
autocommit connection.
|
autocommit connection.
|
||||||
|
@ -428,31 +472,83 @@ The ``connection`` class
|
||||||
|
|
||||||
|
|
||||||
.. attribute:: isolation_level
|
.. attribute:: isolation_level
|
||||||
|
|
||||||
|
Return or set the `transaction isolation level`_ for the current
|
||||||
|
session. The value is one of the :ref:`isolation-level-constants`
|
||||||
|
defined in the `psycopg2.extensions` module. On set it is also
|
||||||
|
possible to use one of the literal values ``READ UNCOMMITTED``, ``READ
|
||||||
|
COMMITTED``, ``REPEATABLE READ``, ``SERIALIZABLE``, ``DEFAULT``.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
|
||||||
|
the property is writable.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
|
||||||
|
the default value for `!isolation_level` is
|
||||||
|
`~psycopg2.extensions.ISOLATION_LEVEL_DEFAULT`; previously the
|
||||||
|
property would have queried the server and returned the real value
|
||||||
|
applied. To know this value you can run a query such as :sql:`show
|
||||||
|
transaction_isolation`. Usually the default value is `READ
|
||||||
|
COMMITTED`, but this may be changed in the server configuration.
|
||||||
|
|
||||||
|
This value is now entirely separate from the `autocommit`
|
||||||
|
property: in previous version, if `!autocommit` was set to `!True`
|
||||||
|
this property would have returned
|
||||||
|
`~psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT`; it will now
|
||||||
|
return the server isolation level.
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: readonly
|
||||||
|
|
||||||
|
Return or set the read-only status for the current session. Available
|
||||||
|
values are `!True` (new transactions will be in read-only mode),
|
||||||
|
`!False` (new transactions will be writable), `!None` (use the default
|
||||||
|
configured for the server by :sql:`default_transaction_read_only`).
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: deferrable
|
||||||
|
|
||||||
|
Return or set the `deferrable status`__ for the current session.
|
||||||
|
Available values are `!True` (new transactions will be in deferrable
|
||||||
|
mode), `!False` (new transactions will be in non deferrable mode),
|
||||||
|
`!None` (use the default configured for the server by
|
||||||
|
:sql:`default_transaction_deferrable`).
|
||||||
|
|
||||||
|
.. __: `SET TRANSACTION`_
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
|
||||||
|
|
||||||
.. method:: set_isolation_level(level)
|
.. method:: set_isolation_level(level)
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
From version 2.4.2, `set_session()` and `autocommit`, offer
|
This is a legacy method mixing `~conn.isolation_level` and
|
||||||
finer control on the transaction characteristics.
|
`~conn.autocommit`. Using the respective properties is a better
|
||||||
|
option.
|
||||||
|
|
||||||
Read or set the `transaction isolation level`_ for the current session.
|
Set the `transaction isolation level`_ for the current session.
|
||||||
The level defines the different phenomena that can happen in the
|
The level defines the different phenomena that can happen in the
|
||||||
database between concurrent transactions.
|
database between concurrent transactions.
|
||||||
|
|
||||||
The value set or read is an integer: symbolic constants are defined in
|
The value set is an integer: symbolic constants are defined in
|
||||||
the module `psycopg2.extensions`: see
|
the module `psycopg2.extensions`: see
|
||||||
:ref:`isolation-level-constants` for the available values.
|
:ref:`isolation-level-constants` for the available values.
|
||||||
|
|
||||||
The default level is :sql:`READ COMMITTED`: at this level a
|
The default level is `~psycopg2.extensions.ISOLATION_LEVEL_DEFAULT`:
|
||||||
transaction is automatically started the first time a database command
|
at this level a transaction is automatically started the first time a
|
||||||
is executed. If you want an *autocommit* mode, switch to
|
database command is executed. If you want an *autocommit* mode,
|
||||||
`~psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT` before
|
switch to `~psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT` before
|
||||||
executing any command::
|
executing any command::
|
||||||
|
|
||||||
>>> conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
|
>>> conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
|
||||||
|
|
||||||
See also :ref:`transactions-control`.
|
See also :ref:`transactions-control`.
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
pair: Client; Encoding
|
pair: Client; Encoding
|
||||||
|
|
||||||
|
@ -463,7 +559,7 @@ The ``connection`` class
|
||||||
is the encoding defined by the database. It should be one of the
|
is the encoding defined by the database. It should be one of the
|
||||||
`characters set supported by PostgreSQL`__
|
`characters set supported by PostgreSQL`__
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/multibyte.html
|
.. __: https://www.postgresql.org/docs/current/static/multibyte.html
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
@ -475,21 +571,29 @@ The ``connection`` class
|
||||||
the session.
|
the session.
|
||||||
|
|
||||||
.. doctest::
|
.. doctest::
|
||||||
:options: NORMALIZE_WHITESPACE
|
:options: +NORMALIZE_WHITESPACE
|
||||||
|
|
||||||
>>> cur.execute("CREATE TABLE foo (id serial PRIMARY KEY);")
|
>>> cur.execute("CREATE TABLE foo (id serial PRIMARY KEY);")
|
||||||
>>> pprint(conn.notices)
|
>>> pprint(conn.notices)
|
||||||
['NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"\n',
|
['NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"\n',
|
||||||
'NOTICE: CREATE TABLE will create implicit sequence "foo_id_seq" for serial column "foo.id"\n']
|
'NOTICE: CREATE TABLE will create implicit sequence "foo_id_seq" for serial column "foo.id"\n']
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
The `!notices` attribute is writable: the user may replace it
|
||||||
|
with any Python object exposing an `!append()` method. If
|
||||||
|
appending raises an exception the notice is silently
|
||||||
|
dropped.
|
||||||
|
|
||||||
To avoid a leak in case excessive notices are generated, only the last
|
To avoid a leak in case excessive notices are generated, only the last
|
||||||
50 messages are kept.
|
50 messages are kept. This check is only in place if the `!notices`
|
||||||
|
attribute is a list: if any other object is used it will be up to the
|
||||||
|
user to guard from leakage.
|
||||||
|
|
||||||
You can configure what messages to receive using `PostgreSQL logging
|
You can configure what messages to receive using `PostgreSQL logging
|
||||||
configuration parameters`__ such as ``log_statement``,
|
configuration parameters`__ such as ``log_statement``,
|
||||||
``client_min_messages``, ``log_min_duration_statement`` etc.
|
``client_min_messages``, ``log_min_duration_statement`` etc.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/runtime-config-logging.html
|
.. __: https://www.postgresql.org/docs/current/static/runtime-config-logging.html
|
||||||
|
|
||||||
|
|
||||||
.. attribute:: notifies
|
.. attribute:: notifies
|
||||||
|
@ -505,6 +609,12 @@ The ``connection`` class
|
||||||
the payload was not accessible. To keep backward compatibility,
|
the payload was not accessible. To keep backward compatibility,
|
||||||
`!Notify` objects can still be accessed as 2 items tuples.
|
`!Notify` objects can still be accessed as 2 items tuples.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
The `!notifies` attribute is writable: the user may replace it
|
||||||
|
with any Python object exposing an `!append()` method. If
|
||||||
|
appending raises an exception the notification is silently
|
||||||
|
dropped.
|
||||||
|
|
||||||
|
|
||||||
.. attribute:: cursor_factory
|
.. attribute:: cursor_factory
|
||||||
|
|
||||||
|
@ -515,92 +625,14 @@ The ``connection`` class
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
pair: Backend; PID
|
pair: Connection; Info
|
||||||
|
|
||||||
.. method:: get_backend_pid()
|
.. attribute:: info
|
||||||
|
|
||||||
Returns the process ID (PID) of the backend server process handling
|
A `~psycopg2.extensions.ConnectionInfo` object exposing information
|
||||||
this connection.
|
about the native libpq connection.
|
||||||
|
|
||||||
Note that the PID belongs to a process executing on the database
|
.. versionadded:: 2.8
|
||||||
server host, not the local host!
|
|
||||||
|
|
||||||
.. seealso:: libpq docs for `PQbackendPID()`__ for details.
|
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQBACKENDPID
|
|
||||||
|
|
||||||
.. versionadded:: 2.0.8
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
|
||||||
pair: Server; Parameters
|
|
||||||
|
|
||||||
.. method:: get_parameter_status(parameter)
|
|
||||||
|
|
||||||
Look up a current parameter setting of the server.
|
|
||||||
|
|
||||||
Potential values for ``parameter`` are: ``server_version``,
|
|
||||||
``server_encoding``, ``client_encoding``, ``is_superuser``,
|
|
||||||
``session_authorization``, ``DateStyle``, ``TimeZone``,
|
|
||||||
``integer_datetimes``, and ``standard_conforming_strings``.
|
|
||||||
|
|
||||||
If server did not report requested parameter, return `!None`.
|
|
||||||
|
|
||||||
.. seealso:: libpq docs for `PQparameterStatus()`__ for details.
|
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPARAMETERSTATUS
|
|
||||||
|
|
||||||
.. versionadded:: 2.0.12
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
|
||||||
pair: Transaction; Status
|
|
||||||
|
|
||||||
.. method:: get_transaction_status()
|
|
||||||
|
|
||||||
Return the current session transaction status as an integer. Symbolic
|
|
||||||
constants for the values are defined in the module
|
|
||||||
`psycopg2.extensions`: see :ref:`transaction-status-constants`
|
|
||||||
for the available values.
|
|
||||||
|
|
||||||
.. seealso:: libpq docs for `PQtransactionStatus()`__ for details.
|
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQTRANSACTIONSTATUS
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
|
||||||
pair: Protocol; Version
|
|
||||||
|
|
||||||
.. attribute:: protocol_version
|
|
||||||
|
|
||||||
A read-only integer representing frontend/backend protocol being used.
|
|
||||||
Currently Psycopg supports only protocol 3, which allows connection
|
|
||||||
to PostgreSQL server from version 7.4. Psycopg versions previous than
|
|
||||||
2.3 support both protocols 2 and 3.
|
|
||||||
|
|
||||||
.. seealso:: libpq docs for `PQprotocolVersion()`__ for details.
|
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPROTOCOLVERSION
|
|
||||||
|
|
||||||
.. versionadded:: 2.0.12
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
|
||||||
pair: Server; Version
|
|
||||||
|
|
||||||
.. attribute:: server_version
|
|
||||||
|
|
||||||
A read-only integer representing the backend version.
|
|
||||||
|
|
||||||
The number is formed by converting the major, minor, and revision
|
|
||||||
numbers into two-decimal-digit numbers and appending them together.
|
|
||||||
For example, version 8.1.5 will be returned as ``80105``.
|
|
||||||
|
|
||||||
.. seealso:: libpq docs for `PQserverVersion()`__ for details.
|
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQSERVERVERSION
|
|
||||||
|
|
||||||
.. versionadded:: 2.0.12
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
@ -613,6 +645,8 @@ The ``connection`` class
|
||||||
`psycopg2.extensions`: see :ref:`connection-status-constants`
|
`psycopg2.extensions`: see :ref:`connection-status-constants`
|
||||||
for the available values.
|
for the available values.
|
||||||
|
|
||||||
|
The status is undefined for `closed` connections.
|
||||||
|
|
||||||
|
|
||||||
.. method:: lobject([oid [, mode [, new_oid [, new_file [, lobject_factory]]]]])
|
.. method:: lobject([oid [, mode [, new_oid [, new_file [, lobject_factory]]]]])
|
||||||
|
|
||||||
|
@ -627,13 +661,13 @@ The ``connection`` class
|
||||||
:param new_oid: Create a new object using the specified OID. The
|
:param new_oid: Create a new object using the specified OID. The
|
||||||
function raises `~psycopg2.OperationalError` if the OID is already
|
function raises `~psycopg2.OperationalError` if the OID is already
|
||||||
in use. Default is 0, meaning assign a new one automatically.
|
in use. Default is 0, meaning assign a new one automatically.
|
||||||
:param new_file: The name of a file to be imported in the the database
|
:param new_file: The name of a file to be imported in the database
|
||||||
(using the |lo_import|_ function)
|
(using the |lo_import|_ function)
|
||||||
:param lobject_factory: Subclass of
|
:param lobject_factory: Subclass of
|
||||||
`~psycopg2.extensions.lobject` to be instantiated.
|
`~psycopg2.extensions.lobject` to be instantiated.
|
||||||
|
|
||||||
.. |lo_import| replace:: `!lo_import()`
|
.. |lo_import| replace:: `!lo_import()`
|
||||||
.. _lo_import: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-IMPORT
|
.. _lo_import: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-IMPORT
|
||||||
|
|
||||||
Available values for *mode* are:
|
Available values for *mode* are:
|
||||||
|
|
||||||
|
@ -658,17 +692,21 @@ The ``connection`` class
|
||||||
support.
|
support.
|
||||||
|
|
||||||
|
|
||||||
.. rubric:: Methods related to asynchronous support.
|
.. rubric:: Methods related to asynchronous support
|
||||||
|
|
||||||
.. versionadded:: 2.2.0
|
.. versionadded:: 2.2
|
||||||
|
|
||||||
.. seealso:: :ref:`async-support` and :ref:`green-support`.
|
.. seealso:: :ref:`async-support` and :ref:`green-support`.
|
||||||
|
|
||||||
|
|
||||||
.. attribute:: async
|
.. attribute:: async
|
||||||
|
async_
|
||||||
|
|
||||||
Read only attribute: 1 if the connection is asynchronous, 0 otherwise.
|
Read only attribute: 1 if the connection is asynchronous, 0 otherwise.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7 added the `!async_` alias for Python versions
|
||||||
|
where `!async` is a keyword.
|
||||||
|
|
||||||
|
|
||||||
.. method:: poll()
|
.. method:: poll()
|
||||||
|
|
||||||
|
@ -678,7 +716,7 @@ The ``connection`` class
|
||||||
|
|
||||||
Return one of the constants defined in :ref:`poll-constants`. If it
|
Return one of the constants defined in :ref:`poll-constants`. If it
|
||||||
returns `~psycopg2.extensions.POLL_OK` then the connection has been
|
returns `~psycopg2.extensions.POLL_OK` then the connection has been
|
||||||
estabilished or the query results are available on the client.
|
established or the query results are available on the client.
|
||||||
Otherwise wait until the file descriptor returned by `fileno()` is
|
Otherwise wait until the file descriptor returned by `fileno()` is
|
||||||
ready to read or to write, as explained in :ref:`async-support`.
|
ready to read or to write, as explained in :ref:`async-support`.
|
||||||
`poll()` should be also used by the function installed by
|
`poll()` should be also used by the function installed by
|
||||||
|
@ -700,6 +738,178 @@ The ``connection`` class
|
||||||
Return `!True` if the connection is executing an asynchronous operation.
|
Return `!True` if the connection is executing an asynchronous operation.
|
||||||
|
|
||||||
|
|
||||||
|
.. rubric:: Interoperation with other C API modules
|
||||||
|
|
||||||
|
.. attribute:: pgconn_ptr
|
||||||
|
|
||||||
|
Return the internal `!PGconn*` as integer. Useful to pass the libpq
|
||||||
|
raw connection structure to C functions, e.g. via `ctypes`::
|
||||||
|
|
||||||
|
>>> import ctypes
|
||||||
|
>>> import ctypes.util
|
||||||
|
>>> libpq = ctypes.pydll.LoadLibrary(ctypes.util.find_library('pq'))
|
||||||
|
>>> libpq.PQserverVersion.argtypes = [ctypes.c_void_p]
|
||||||
|
>>> libpq.PQserverVersion.restype = ctypes.c_int
|
||||||
|
>>> libpq.PQserverVersion(conn.pgconn_ptr)
|
||||||
|
90611
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: get_native_connection()
|
||||||
|
|
||||||
|
Return the internal `!PGconn*` wrapped in a PyCapsule object. This is
|
||||||
|
only useful for passing the `libpq` raw connection associated to this
|
||||||
|
connection object to other C-level modules that may have a use for it.
|
||||||
|
|
||||||
|
.. seealso:: Python C API `Capsules`__ docs.
|
||||||
|
|
||||||
|
.. __: https://docs.python.org/3.1/c-api/capsule.html
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. rubric:: informative methods of the native connection
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
These methods are better accessed using the `~connection.info`
|
||||||
|
attributes and may be dropped in future versions.
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
pair: Transaction; Status
|
||||||
|
|
||||||
|
.. method:: get_transaction_status()
|
||||||
|
|
||||||
|
Also available as `~connection.info`\ `!.`\
|
||||||
|
`~psycopg2.extensions.ConnectionInfo.transaction_status`.
|
||||||
|
|
||||||
|
Return the current session transaction status as an integer. Symbolic
|
||||||
|
constants for the values are defined in the module
|
||||||
|
`psycopg2.extensions`: see :ref:`transaction-status-constants`
|
||||||
|
for the available values.
|
||||||
|
|
||||||
|
.. seealso:: libpq docs for `PQtransactionStatus()`__ for details.
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQTRANSACTIONSTATUS
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
pair: Protocol; Version
|
||||||
|
|
||||||
|
.. attribute:: protocol_version
|
||||||
|
|
||||||
|
Also available as `~connection.info`\ `!.`\
|
||||||
|
`~psycopg2.extensions.ConnectionInfo.protocol_version`.
|
||||||
|
|
||||||
|
A read-only integer representing frontend/backend protocol being used.
|
||||||
|
Currently Psycopg supports only protocol 3, which allows connection
|
||||||
|
to PostgreSQL server from version 7.4. Psycopg versions previous than
|
||||||
|
2.3 support both protocols 2 and 3.
|
||||||
|
|
||||||
|
.. seealso:: libpq docs for `PQprotocolVersion()`__ for details.
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPROTOCOLVERSION
|
||||||
|
|
||||||
|
.. versionadded:: 2.0.12
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
pair: Server; Version
|
||||||
|
|
||||||
|
.. attribute:: server_version
|
||||||
|
|
||||||
|
Also available as `~connection.info`\ `!.`\
|
||||||
|
`~psycopg2.extensions.ConnectionInfo.server_version`.
|
||||||
|
|
||||||
|
A read-only integer representing the backend version.
|
||||||
|
|
||||||
|
The number is formed by converting the major, minor, and revision
|
||||||
|
numbers into two-decimal-digit numbers and appending them together.
|
||||||
|
For example, version 8.1.5 will be returned as ``80105``.
|
||||||
|
|
||||||
|
.. seealso:: libpq docs for `PQserverVersion()`__ for details.
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQSERVERVERSION
|
||||||
|
|
||||||
|
.. versionadded:: 2.0.12
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
pair: Backend; PID
|
||||||
|
|
||||||
|
.. method:: get_backend_pid()
|
||||||
|
|
||||||
|
Also available as `~connection.info`\ `!.`\
|
||||||
|
`~psycopg2.extensions.ConnectionInfo.backend_pid`.
|
||||||
|
|
||||||
|
Returns the process ID (PID) of the backend server process *you
|
||||||
|
connected to*. Note that if you use a connection pool service such as
|
||||||
|
PgBouncer_ this value will not be updated if your connection is
|
||||||
|
switched to a different backend.
|
||||||
|
|
||||||
|
Note that the PID belongs to a process executing on the database
|
||||||
|
server host, not the local host!
|
||||||
|
|
||||||
|
.. seealso:: libpq docs for `PQbackendPID()`__ for details.
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQBACKENDPID
|
||||||
|
|
||||||
|
.. versionadded:: 2.0.8
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
pair: Server; Parameters
|
||||||
|
|
||||||
|
.. method:: get_parameter_status(parameter)
|
||||||
|
|
||||||
|
Also available as `~connection.info`\ `!.`\
|
||||||
|
`~psycopg2.extensions.ConnectionInfo.parameter_status()`.
|
||||||
|
|
||||||
|
Look up a current parameter setting of the server.
|
||||||
|
|
||||||
|
Potential values for ``parameter`` are: ``server_version``,
|
||||||
|
``server_encoding``, ``client_encoding``, ``is_superuser``,
|
||||||
|
``session_authorization``, ``DateStyle``, ``TimeZone``,
|
||||||
|
``integer_datetimes``, and ``standard_conforming_strings``.
|
||||||
|
|
||||||
|
If server did not report requested parameter, return `!None`.
|
||||||
|
|
||||||
|
.. seealso:: libpq docs for `PQparameterStatus()`__ for details.
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPARAMETERSTATUS
|
||||||
|
|
||||||
|
.. versionadded:: 2.0.12
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
pair: Connection; Parameters
|
||||||
|
|
||||||
|
.. method:: get_dsn_parameters()
|
||||||
|
|
||||||
|
Also available as `~connection.info`\ `!.`\
|
||||||
|
`~psycopg2.extensions.ConnectionInfo.dsn_parameters`.
|
||||||
|
|
||||||
|
Get the effective dsn parameters for the connection as a dictionary.
|
||||||
|
|
||||||
|
The *password* parameter is removed from the result.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> conn.get_dsn_parameters()
|
||||||
|
{'dbname': 'test', 'user': 'postgres', 'port': '5432', 'sslmode': 'prefer'}
|
||||||
|
|
||||||
|
Requires libpq >= 9.3.
|
||||||
|
|
||||||
|
.. seealso:: libpq docs for `PQconninfo()`__ for details.
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNINFO
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
|
||||||
|
|
||||||
.. testcode::
|
.. testcode::
|
||||||
:hide:
|
:hide:
|
||||||
|
|
||||||
|
|
|
@ -34,49 +34,62 @@ The ``cursor`` class
|
||||||
many cursors from the same connection and should use each cursor from
|
many cursors from the same connection and should use each cursor from
|
||||||
a single thread. See :ref:`thread-safety` for details.
|
a single thread. See :ref:`thread-safety` for details.
|
||||||
|
|
||||||
|
Cursors can be used as context managers: leaving the context will close
|
||||||
|
the cursor.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
with conn.cursor() as curs:
|
||||||
|
curs.execute(SQL)
|
||||||
|
|
||||||
|
# the cursor is now closed
|
||||||
|
|
||||||
|
|
||||||
.. attribute:: description
|
.. attribute:: description
|
||||||
|
|
||||||
This read-only attribute is a sequence of 7-item sequences.
|
Read-only attribute describing the result of a query. It is a
|
||||||
|
sequence of `~psycopg2.extensions.Column` instances, each one
|
||||||
|
describing one result column in order. The attribute is `!None` for
|
||||||
|
operations that do not return rows or if the cursor has not had an
|
||||||
|
operation invoked via the |execute*|_ methods yet.
|
||||||
|
|
||||||
Each of these sequences is a named tuple (a regular tuple if
|
For compatibility with the DB-API, every object can be unpacked as a
|
||||||
:func:`collections.namedtuple` is not available) containing information
|
7-items sequence: the attributes retuned this way are the following.
|
||||||
describing one result column:
|
For further details and other attributes available check the
|
||||||
|
`~psycopg2.extensions.Column` documentation.
|
||||||
|
|
||||||
0. `!name`: the name of the column returned.
|
0. `~psycopg2.extensions.Column.name`: the name of the column returned.
|
||||||
1. `!type_code`: the PostgreSQL OID of the column. You can use the
|
|
||||||
|pg_type|_ system table to get more informations about the type.
|
|
||||||
This is the value used by Psycopg to decide what Python type use
|
|
||||||
to represent the value. See also
|
|
||||||
:ref:`type-casting-from-sql-to-python`.
|
|
||||||
2. `!display_size`: the actual length of the column in bytes.
|
|
||||||
Obtaining this value is computationally intensive, so it is
|
|
||||||
always `!None` unless the :envvar:`PSYCOPG_DISPLAY_SIZE` parameter
|
|
||||||
is set at compile time. See also PQgetlength_.
|
|
||||||
3. `!internal_size`: the size in bytes of the column associated to
|
|
||||||
this column on the server. Set to a negative value for
|
|
||||||
variable-size types See also PQfsize_.
|
|
||||||
4. `!precision`: total number of significant digits in columns of
|
|
||||||
type |NUMERIC|_. `!None` for other types.
|
|
||||||
5. `!scale`: count of decimal digits in the fractional part in
|
|
||||||
columns of type |NUMERIC|. `!None` for other types.
|
|
||||||
6. `!null_ok`: always `!None` as not easy to retrieve from the libpq.
|
|
||||||
|
|
||||||
This attribute will be `!None` for operations that do not return rows
|
1. `~psycopg2.extensions.Column.type_code`: the PostgreSQL OID of the
|
||||||
or if the cursor has not had an operation invoked via the
|
column.
|
||||||
|execute*|_ methods yet.
|
|
||||||
|
|
||||||
.. |pg_type| replace:: :sql:`pg_type`
|
2. `~psycopg2.extensions.Column.display_size`: the actual length of
|
||||||
.. _pg_type: http://www.postgresql.org/docs/current/static/catalog-pg-type.html
|
the column in bytes.
|
||||||
.. _PQgetlength: http://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQGETLENGTH
|
|
||||||
.. _PQfsize: http://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQFSIZE
|
3. `~psycopg2.extensions.Column.internal_size`: the size in bytes of
|
||||||
.. _NUMERIC: http://www.postgresql.org/docs/current/static/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL
|
the column associated to this column on the server.
|
||||||
.. |NUMERIC| replace:: :sql:`NUMERIC`
|
|
||||||
|
4. `~psycopg2.extensions.Column.precision`: total number of
|
||||||
|
significant digits in columns of type |NUMERIC|. `!None`
|
||||||
|
for other types.
|
||||||
|
|
||||||
|
5. `~psycopg2.extensions.Column.scale`: count of decimal digits in
|
||||||
|
the fractional part in columns of type |NUMERIC|. `!None`
|
||||||
|
for other types.
|
||||||
|
|
||||||
|
6. `~psycopg2.extensions.Column.null_ok`: always `!None` as not easy
|
||||||
|
to retrieve from the libpq.
|
||||||
|
|
||||||
.. versionchanged:: 2.4
|
.. versionchanged:: 2.4
|
||||||
if possible, columns descriptions are named tuple instead of
|
if possible, columns descriptions are named tuple instead of
|
||||||
regular tuples.
|
regular tuples.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.8
|
||||||
|
columns descriptions are instances of `!Column`, exposing extra
|
||||||
|
attributes.
|
||||||
|
|
||||||
|
.. |NUMERIC| replace:: :sql:`NUMERIC`
|
||||||
|
|
||||||
.. method:: close()
|
.. method:: close()
|
||||||
|
|
||||||
Close the cursor now (rather than whenever `del` is executed).
|
Close the cursor now (rather than whenever `del` is executed).
|
||||||
|
@ -111,7 +124,7 @@ The ``cursor`` class
|
||||||
.. attribute:: name
|
.. attribute:: name
|
||||||
|
|
||||||
Read-only attribute containing the name of the cursor if it was
|
Read-only attribute containing the name of the cursor if it was
|
||||||
creates as named cursor by `connection.cursor()`, or `!None` if
|
created as named cursor by `connection.cursor()`, or `!None` if
|
||||||
it is a client side cursor. See :ref:`server-side-cursors`.
|
it is a client side cursor. See :ref:`server-side-cursors`.
|
||||||
|
|
||||||
.. extension::
|
.. extension::
|
||||||
|
@ -129,7 +142,7 @@ The ``cursor`` class
|
||||||
backward scroll (see the |declare-notes|__).
|
backward scroll (see the |declare-notes|__).
|
||||||
|
|
||||||
.. |declare-notes| replace:: :sql:`DECLARE` notes
|
.. |declare-notes| replace:: :sql:`DECLARE` notes
|
||||||
.. __: http://www.postgresql.org/docs/current/static/sql-declare.html#SQL-DECLARE-NOTES
|
.. __: https://www.postgresql.org/docs/current/static/sql-declare.html#SQL-DECLARE-NOTES
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -171,9 +184,9 @@ The ``cursor`` class
|
||||||
.. rubric:: Commands execution methods
|
.. rubric:: Commands execution methods
|
||||||
|
|
||||||
|
|
||||||
.. method:: execute(operation [, parameters])
|
.. method:: execute(query, vars=None)
|
||||||
|
|
||||||
Prepare and execute a database operation (query or command).
|
Execute a database operation (query or command).
|
||||||
|
|
||||||
Parameters may be provided as sequence or mapping and will be bound to
|
Parameters may be provided as sequence or mapping and will be bound to
|
||||||
variables in the operation. Variables are specified either with
|
variables in the operation. Variables are specified either with
|
||||||
|
@ -184,11 +197,10 @@ The ``cursor`` class
|
||||||
values can be retrieved using |fetch*|_ methods.
|
values can be retrieved using |fetch*|_ methods.
|
||||||
|
|
||||||
|
|
||||||
.. method:: executemany(operation, seq_of_parameters)
|
.. method:: executemany(query, vars_list)
|
||||||
|
|
||||||
Prepare a database operation (query or command) and then execute it
|
Execute a database operation (query or command) against all parameter
|
||||||
against all parameter tuples or mappings found in the sequence
|
tuples or mappings found in the sequence *vars_list*.
|
||||||
`seq_of_parameters`.
|
|
||||||
|
|
||||||
The function is mostly useful for commands that update the database:
|
The function is mostly useful for commands that update the database:
|
||||||
any result set returned by the query is discarded.
|
any result set returned by the query is discarded.
|
||||||
|
@ -196,18 +208,47 @@ The ``cursor`` class
|
||||||
Parameters are bounded to the query using the same rules described in
|
Parameters are bounded to the query using the same rules described in
|
||||||
the `~cursor.execute()` method.
|
the `~cursor.execute()` method.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
>>> nums = ((1,), (5,), (10,))
|
||||||
|
>>> cur.executemany("INSERT INTO test (num) VALUES (%s)", nums)
|
||||||
|
|
||||||
|
>>> tuples = ((123, "foo"), (42, "bar"), (23, "baz"))
|
||||||
|
>>> cur.executemany("INSERT INTO test (num, data) VALUES (%s, %s)", tuples)
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
In its current implementation this method is not faster than
|
||||||
|
executing `~cursor.execute()` in a loop. For better performance
|
||||||
|
you can use the functions described in :ref:`fast-exec`.
|
||||||
|
|
||||||
|
|
||||||
.. method:: callproc(procname [, parameters])
|
.. method:: callproc(procname [, parameters])
|
||||||
|
|
||||||
Call a stored database procedure with the given name. The sequence of
|
Call a stored database procedure with the given name. The sequence of
|
||||||
parameters must contain one entry for each argument that the procedure
|
parameters must contain one entry for each argument that the procedure
|
||||||
expects. The result of the call is returned as modified copy of the
|
expects. Overloaded procedures are supported. Named parameters can be
|
||||||
input sequence. Input parameters are left untouched, output and
|
used by supplying the parameters as a dictionary.
|
||||||
input/output parameters replaced with possibly new values.
|
|
||||||
|
|
||||||
The procedure may also provide a result set as output. This must then
|
This function is, at present, not DBAPI-compliant. The return value is
|
||||||
be made available through the standard |fetch*|_ methods.
|
supposed to consist of the sequence of parameters with modified output
|
||||||
|
and input/output parameters. In future versions, the DBAPI-compliant
|
||||||
|
return value may be implemented, but for now the function returns None.
|
||||||
|
|
||||||
|
The procedure may provide a result set as output. This is then made
|
||||||
|
available through the standard |fetch*|_ methods.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
added support for named arguments.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
`!callproc()` can only be used with PostgreSQL functions__, not
|
||||||
|
with the procedures__ introduced in PostgreSQL 11, which require
|
||||||
|
the :sql:`CALL` statement to run. Please use a normal
|
||||||
|
`execute()` to run them.
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/sql-createfunction.html
|
||||||
|
.. __: https://www.postgresql.org/docs/current/sql-createprocedure.html
|
||||||
|
|
||||||
.. method:: mogrify(operation [, parameters])
|
.. method:: mogrify(operation [, parameters])
|
||||||
|
|
||||||
|
@ -215,6 +256,8 @@ The ``cursor`` class
|
||||||
exactly the one that would be sent to the database running the
|
exactly the one that would be sent to the database running the
|
||||||
`~cursor.execute()` method or similar.
|
`~cursor.execute()` method or similar.
|
||||||
|
|
||||||
|
The returned string is always a bytes string.
|
||||||
|
|
||||||
>>> cur.mogrify("INSERT INTO test (num, data) VALUES (%s, %s)", (42, 'bar'))
|
>>> cur.mogrify("INSERT INTO test (num, data) VALUES (%s, %s)", (42, 'bar'))
|
||||||
"INSERT INTO test (num, data) VALUES (42, E'bar')"
|
"INSERT INTO test (num, data) VALUES (42, E'bar')"
|
||||||
|
|
||||||
|
@ -249,7 +292,7 @@ The ``cursor`` class
|
||||||
|
|
||||||
>>> cur.execute("SELECT * FROM test;")
|
>>> cur.execute("SELECT * FROM test;")
|
||||||
>>> for record in cur:
|
>>> for record in cur:
|
||||||
... print record
|
... print(record)
|
||||||
...
|
...
|
||||||
(1, 100, "abc'def")
|
(1, 100, "abc'def")
|
||||||
(2, None, 'dada')
|
(2, None, 'dada')
|
||||||
|
@ -332,10 +375,6 @@ The ``cursor`` class
|
||||||
`~psycopg2.ProgrammingError` is raised and the cursor position is
|
`~psycopg2.ProgrammingError` is raised and the cursor position is
|
||||||
not changed.
|
not changed.
|
||||||
|
|
||||||
The method can be used both for client-side cursors and
|
|
||||||
:ref:`server-side cursors <server-side-cursors>`. Server-side cursors
|
|
||||||
can usually scroll backwards only if declared `~cursor.scrollable`.
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
According to the |DBAPI|_, the exception raised for a cursor out
|
According to the |DBAPI|_, the exception raised for a cursor out
|
||||||
|
@ -347,6 +386,13 @@ The ``cursor`` class
|
||||||
except (ProgrammingError, IndexError), exc:
|
except (ProgrammingError, IndexError), exc:
|
||||||
deal_with_it(exc)
|
deal_with_it(exc)
|
||||||
|
|
||||||
|
The method can be used both for client-side cursors and
|
||||||
|
:ref:`server-side cursors <server-side-cursors>`. Server-side cursors
|
||||||
|
can usually scroll backwards only if declared `~cursor.scrollable`.
|
||||||
|
Moving out-of-bound in a server-side cursor doesn't result in an
|
||||||
|
exception, if the backend doesn't raise any (Postgres doesn't tell us
|
||||||
|
in a reliable way if we went out of bound).
|
||||||
|
|
||||||
|
|
||||||
.. attribute:: arraysize
|
.. attribute:: arraysize
|
||||||
|
|
||||||
|
@ -415,17 +461,17 @@ The ``cursor`` class
|
||||||
more flexibility.
|
more flexibility.
|
||||||
|
|
||||||
.. |CREATE-TABLE| replace:: :sql:`CREATE TABLE`
|
.. |CREATE-TABLE| replace:: :sql:`CREATE TABLE`
|
||||||
.. __: http://www.postgresql.org/docs/current/static/sql-createtable.html
|
.. __: https://www.postgresql.org/docs/current/static/sql-createtable.html
|
||||||
|
|
||||||
.. |INSERT-RETURNING| replace:: :sql:`INSERT ... RETURNING`
|
.. |INSERT-RETURNING| replace:: :sql:`INSERT ... RETURNING`
|
||||||
.. __: http://www.postgresql.org/docs/current/static/sql-insert.html
|
.. __: https://www.postgresql.org/docs/current/static/sql-insert.html
|
||||||
|
|
||||||
|
|
||||||
.. attribute:: query
|
.. attribute:: query
|
||||||
|
|
||||||
Read-only attribute containing the body of the last query sent to the
|
Read-only attribute containing the body of the last query sent to the
|
||||||
backend (including bound arguments). `!None` if no query has been
|
backend (including bound arguments) as bytes string. `!None` if no
|
||||||
executed yet:
|
query has been executed yet:
|
||||||
|
|
||||||
>>> cur.execute("INSERT INTO test (num, data) VALUES (%s, %s)", (42, 'bar'))
|
>>> cur.execute("INSERT INTO test (num, data) VALUES (%s, %s)", (42, 'bar'))
|
||||||
>>> cur.query
|
>>> cur.query
|
||||||
|
@ -470,8 +516,10 @@ The ``cursor`` class
|
||||||
|
|
||||||
The time zone factory used to handle data types such as
|
The time zone factory used to handle data types such as
|
||||||
:sql:`TIMESTAMP WITH TIME ZONE`. It should be a `~datetime.tzinfo`
|
:sql:`TIMESTAMP WITH TIME ZONE`. It should be a `~datetime.tzinfo`
|
||||||
object. A few implementations are available in the `psycopg2.tz`
|
object. Default is `datetime.timezone`.
|
||||||
module.
|
|
||||||
|
.. versionchanged:: 2.9
|
||||||
|
previosly the default factory was `psycopg2.tz.FixedOffsetTimezone`.
|
||||||
|
|
||||||
|
|
||||||
.. method:: nextset()
|
.. method:: nextset()
|
||||||
|
@ -489,6 +537,9 @@ The ``cursor`` class
|
||||||
|
|
||||||
.. rubric:: COPY-related methods
|
.. rubric:: COPY-related methods
|
||||||
|
|
||||||
|
Efficiently copy data from file-like objects to the database and back. See
|
||||||
|
:ref:`copy` for an overview.
|
||||||
|
|
||||||
.. extension::
|
.. extension::
|
||||||
|
|
||||||
The :sql:`COPY` command is a PostgreSQL extension to the SQL standard.
|
The :sql:`COPY` command is a PostgreSQL extension to the SQL standard.
|
||||||
|
@ -497,7 +548,7 @@ The ``cursor`` class
|
||||||
.. method:: copy_from(file, table, sep='\\t', null='\\\\N', size=8192, columns=None)
|
.. method:: copy_from(file, table, sep='\\t', null='\\\\N', size=8192, columns=None)
|
||||||
|
|
||||||
Read data *from* the file-like object *file* appending them to
|
Read data *from* the file-like object *file* appending them to
|
||||||
the table named *table*. See :ref:`copy` for an overview.
|
the table named *table*.
|
||||||
|
|
||||||
:param file: file-like object to read data from. It must have both
|
:param file: file-like object to read data from. It must have both
|
||||||
`!read()` and `!readline()` methods.
|
`!read()` and `!readline()` methods.
|
||||||
|
@ -527,6 +578,11 @@ The ``cursor`` class
|
||||||
are encoded in the connection `~connection.encoding` when sent to
|
are encoded in the connection `~connection.encoding` when sent to
|
||||||
the backend.
|
the backend.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.9
|
||||||
|
the table and fields names are now quoted. If you need to specify
|
||||||
|
a schema-qualified table please use `copy_expert()`.
|
||||||
|
|
||||||
|
|
||||||
.. method:: copy_to(file, table, sep='\\t', null='\\\\N', columns=None)
|
.. method:: copy_to(file, table, sep='\\t', null='\\\\N', columns=None)
|
||||||
|
|
||||||
Write the content of the table named *table* *to* the file-like
|
Write the content of the table named *table* *to* the file-like
|
||||||
|
@ -556,6 +612,10 @@ The ``cursor`` class
|
||||||
are decoded in the connection `~connection.encoding` when read
|
are decoded in the connection `~connection.encoding` when read
|
||||||
from the backend.
|
from the backend.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.9
|
||||||
|
the table and fields names are now quoted. If you need to specify
|
||||||
|
a schema-qualified table please use `copy_expert()`.
|
||||||
|
|
||||||
|
|
||||||
.. method:: copy_expert(sql, file, size=8192)
|
.. method:: copy_expert(sql, file, size=8192)
|
||||||
|
|
||||||
|
@ -570,7 +630,10 @@ The ``cursor`` class
|
||||||
The *sql* statement should be in the form :samp:`COPY {table} TO
|
The *sql* statement should be in the form :samp:`COPY {table} TO
|
||||||
STDOUT` to export :samp:`{table}` to the *file* object passed as
|
STDOUT` to export :samp:`{table}` to the *file* object passed as
|
||||||
argument or :samp:`COPY {table} FROM STDIN` to import the content of
|
argument or :samp:`COPY {table} FROM STDIN` to import the content of
|
||||||
the *file* object into :samp:`{table}`.
|
the *file* object into :samp:`{table}`. If you need to compose a
|
||||||
|
:sql:`COPY` statement dynamically (because table, fields, or query
|
||||||
|
parameters are in Python variables) you may use the objects provided
|
||||||
|
by the `psycopg2.sql` module.
|
||||||
|
|
||||||
*file* must be a readable file-like object (as required by
|
*file* must be a readable file-like object (as required by
|
||||||
`~cursor.copy_from()`) for *sql* statement :sql:`COPY ... FROM STDIN`
|
`~cursor.copy_from()`) for *sql* statement :sql:`COPY ... FROM STDIN`
|
||||||
|
@ -586,7 +649,7 @@ The ``cursor`` class
|
||||||
...
|
...
|
||||||
|
|
||||||
.. |COPY| replace:: :sql:`COPY`
|
.. |COPY| replace:: :sql:`COPY`
|
||||||
.. __: http://www.postgresql.org/docs/current/static/sql-copy.html
|
.. __: https://www.postgresql.org/docs/current/static/sql-copy.html
|
||||||
|
|
||||||
.. versionadded:: 2.0.6
|
.. versionadded:: 2.0.6
|
||||||
|
|
||||||
|
@ -595,6 +658,24 @@ The ``cursor`` class
|
||||||
using Unicode data instead of bytes.
|
using Unicode data instead of bytes.
|
||||||
|
|
||||||
|
|
||||||
|
.. rubric:: Interoperation with other C API modules
|
||||||
|
|
||||||
|
.. attribute:: pgresult_ptr
|
||||||
|
|
||||||
|
Return the cursor's internal `!PGresult*` as integer. Useful to pass
|
||||||
|
the libpq raw result structure to C functions, e.g. via `ctypes`::
|
||||||
|
|
||||||
|
>>> import ctypes
|
||||||
|
>>> libpq = ctypes.pydll.LoadLibrary(ctypes.util.find_library('pq'))
|
||||||
|
>>> libpq.PQcmdStatus.argtypes = [ctypes.c_void_p]
|
||||||
|
>>> libpq.PQcmdStatus.restype = ctypes.c_char_p
|
||||||
|
|
||||||
|
>>> curs.execute("select 'x'")
|
||||||
|
>>> libpq.PQcmdStatus(curs.pgresult_ptr)
|
||||||
|
b'SELECT 1'
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
|
||||||
.. testcode::
|
.. testcode::
|
||||||
:hide:
|
:hide:
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ From PostgreSQL documentation:
|
||||||
|
|
||||||
.. seealso:: `PostgreSQL Error Codes table`__
|
.. seealso:: `PostgreSQL Error Codes table`__
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/errcodes-appendix.html#ERRCODES-TABLE
|
.. __: https://www.postgresql.org/docs/current/static/errcodes-appendix.html#ERRCODES-TABLE
|
||||||
|
|
||||||
|
|
||||||
An example of the available constants defined in the module:
|
An example of the available constants defined in the module:
|
||||||
|
@ -49,8 +49,8 @@ An example of the available constants defined in the module:
|
||||||
>>> errorcodes.UNDEFINED_TABLE
|
>>> errorcodes.UNDEFINED_TABLE
|
||||||
'42P01'
|
'42P01'
|
||||||
|
|
||||||
Constants representing all the error values documented by PostgreSQL versions
|
Constants representing all the error values defined by PostgreSQL versions
|
||||||
between 8.1 and 9.2 are included in the module.
|
between 8.1 and 15 are included in the module.
|
||||||
|
|
||||||
|
|
||||||
.. autofunction:: lookup(code)
|
.. autofunction:: lookup(code)
|
||||||
|
@ -59,7 +59,7 @@ between 8.1 and 9.2 are included in the module.
|
||||||
|
|
||||||
>>> try:
|
>>> try:
|
||||||
... cur.execute("SELECT ouch FROM aargh;")
|
... cur.execute("SELECT ouch FROM aargh;")
|
||||||
... except Exception, e:
|
... except Exception as e:
|
||||||
... pass
|
... pass
|
||||||
...
|
...
|
||||||
>>> errorcodes.lookup(e.pgcode[:2])
|
>>> errorcodes.lookup(e.pgcode[:2])
|
||||||
|
|
89
doc/src/errors.rst
Normal file
89
doc/src/errors.rst
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
`psycopg2.errors` -- Exception classes mapping PostgreSQL errors
|
||||||
|
================================================================
|
||||||
|
|
||||||
|
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
single: Error; Class
|
||||||
|
|
||||||
|
.. module:: psycopg2.errors
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
|
||||||
|
.. versionchanged:: 2.8.4 added errors introduced in PostgreSQL 12
|
||||||
|
|
||||||
|
.. versionchanged:: 2.8.6 added errors introduced in PostgreSQL 13
|
||||||
|
|
||||||
|
.. versionchanged:: 2.9.2 added errors introduced in PostgreSQL 14
|
||||||
|
|
||||||
|
.. versionchanged:: 2.9.4 added errors introduced in PostgreSQL 15
|
||||||
|
|
||||||
|
.. versionchanged:: 2.9.10 added errors introduced in PostgreSQL 17
|
||||||
|
|
||||||
|
This module exposes the classes psycopg raises upon receiving an error from
|
||||||
|
the database with a :sql:`SQLSTATE` value attached (available in the
|
||||||
|
`~psycopg2.Error.pgcode` attribute). The content of the module is generated
|
||||||
|
from the PostgreSQL source code and includes classes for every error defined
|
||||||
|
by PostgreSQL in versions between 9.1 and 15.
|
||||||
|
|
||||||
|
Every class in the module is named after what referred as "condition name" `in
|
||||||
|
the documentation`__, converted to CamelCase: e.g. the error 22012,
|
||||||
|
``division_by_zero`` is exposed by this module as the class `!DivisionByZero`.
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/errcodes-appendix.html#ERRCODES-TABLE
|
||||||
|
|
||||||
|
Every exception class is a subclass of one of the :ref:`standard DB-API
|
||||||
|
exception <dbapi-exceptions>` and expose the `~psycopg2.Error` interface.
|
||||||
|
Each class' superclass is what used to be raised by psycopg in versions before
|
||||||
|
the introduction of this module, so everything should be compatible with
|
||||||
|
previously written code catching one the DB-API class: if your code used to
|
||||||
|
catch `!IntegrityError` to detect a duplicate entry, it will keep on working
|
||||||
|
even if a more specialised subclass such as `UniqueViolation` is raised.
|
||||||
|
|
||||||
|
The new classes allow a more idiomatic way to check and process a specific
|
||||||
|
error among the many the database may return. For instance, in order to check
|
||||||
|
that a table is locked, the following code could have been used previously:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
try:
|
||||||
|
cur.execute("LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT")
|
||||||
|
except psycopg2.OperationalError as e:
|
||||||
|
if e.pgcode == psycopg2.errorcodes.LOCK_NOT_AVAILABLE:
|
||||||
|
locked = True
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
While this method is still available, the specialised class allows for a more
|
||||||
|
idiomatic error handler:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
try:
|
||||||
|
cur.execute("LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT")
|
||||||
|
except psycopg2.errors.LockNotAvailable:
|
||||||
|
locked = True
|
||||||
|
|
||||||
|
|
||||||
|
.. autofunction:: lookup
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
try:
|
||||||
|
cur.execute("LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT")
|
||||||
|
except psycopg2.errors.lookup("55P03"):
|
||||||
|
locked = True
|
||||||
|
|
||||||
|
|
||||||
|
SQLSTATE exception classes
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
The following table contains the list of all the SQLSTATE classes exposed by
|
||||||
|
the module.
|
||||||
|
|
||||||
|
Note that, for completeness, the module also exposes all the
|
||||||
|
:ref:`DB-API-defined exceptions <dbapi-exceptions>` and :ref:`a few
|
||||||
|
psycopg-specific ones <extension-exceptions>` exposed by the `!extensions`
|
||||||
|
module, which are not listed here.
|
||||||
|
|
||||||
|
.. include:: sqlstate_errors.rst
|
|
@ -12,6 +12,12 @@
|
||||||
The module contains a few objects and function extending the minimum set of
|
The module contains a few objects and function extending the minimum set of
|
||||||
functionalities defined by the |DBAPI|_.
|
functionalities defined by the |DBAPI|_.
|
||||||
|
|
||||||
|
Classes definitions
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Instances of these classes are usually returned by factory functions or
|
||||||
|
attributes. Their definitions are exposed here to allow subclassing,
|
||||||
|
introspection etc.
|
||||||
|
|
||||||
.. class:: connection(dsn, async=False)
|
.. class:: connection(dsn, async=False)
|
||||||
|
|
||||||
|
@ -23,9 +29,12 @@ functionalities defined by the |DBAPI|_.
|
||||||
|
|
||||||
For a complete description of the class, see `connection`.
|
For a complete description of the class, see `connection`.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
*async_* can be used as alias for *async*.
|
||||||
|
|
||||||
.. class:: cursor(conn, name=None)
|
.. class:: cursor(conn, name=None)
|
||||||
|
|
||||||
It is the class usually returnded by the `connection.cursor()`
|
It is the class usually returned by the `connection.cursor()`
|
||||||
method. It is exposed by the `extensions` module in order to allow
|
method. It is exposed by the `extensions` module in order to allow
|
||||||
subclassing to extend its behaviour: the subclass should be passed to the
|
subclassing to extend its behaviour: the subclass should be passed to the
|
||||||
`!cursor()` method using the `cursor_factory` parameter. See
|
`!cursor()` method using the `cursor_factory` parameter. See
|
||||||
|
@ -33,6 +42,7 @@ functionalities defined by the |DBAPI|_.
|
||||||
|
|
||||||
For a complete description of the class, see `cursor`.
|
For a complete description of the class, see `cursor`.
|
||||||
|
|
||||||
|
|
||||||
.. class:: lobject(conn [, oid [, mode [, new_oid [, new_file ]]]])
|
.. class:: lobject(conn [, oid [, mode [, new_oid [, new_file ]]]])
|
||||||
|
|
||||||
Wrapper for a PostgreSQL large object. See :ref:`large-objects` for an
|
Wrapper for a PostgreSQL large object. See :ref:`large-objects` for an
|
||||||
|
@ -47,11 +57,13 @@ functionalities defined by the |DBAPI|_.
|
||||||
|
|
||||||
Database OID of the object.
|
Database OID of the object.
|
||||||
|
|
||||||
|
|
||||||
.. attribute:: mode
|
.. attribute:: mode
|
||||||
|
|
||||||
The mode the database was open. See `connection.lobject()` for a
|
The mode the database was open. See `connection.lobject()` for a
|
||||||
description of the available modes.
|
description of the available modes.
|
||||||
|
|
||||||
|
|
||||||
.. method:: read(bytes=-1)
|
.. method:: read(bytes=-1)
|
||||||
|
|
||||||
Read a chunk of data from the current file position. If -1 (default)
|
Read a chunk of data from the current file position. If -1 (default)
|
||||||
|
@ -64,6 +76,7 @@ functionalities defined by the |DBAPI|_.
|
||||||
.. versionchanged:: 2.4
|
.. versionchanged:: 2.4
|
||||||
added Unicode support.
|
added Unicode support.
|
||||||
|
|
||||||
|
|
||||||
.. method:: write(str)
|
.. method:: write(str)
|
||||||
|
|
||||||
Write a string to the large object. Return the number of bytes
|
Write a string to the large object. Return the number of bytes
|
||||||
|
@ -73,6 +86,7 @@ functionalities defined by the |DBAPI|_.
|
||||||
.. versionchanged:: 2.4
|
.. versionchanged:: 2.4
|
||||||
added Unicode support.
|
added Unicode support.
|
||||||
|
|
||||||
|
|
||||||
.. method:: export(file_name)
|
.. method:: export(file_name)
|
||||||
|
|
||||||
Export the large object content to the file system.
|
Export the large object content to the file system.
|
||||||
|
@ -80,35 +94,52 @@ functionalities defined by the |DBAPI|_.
|
||||||
The method uses the efficient |lo_export|_ libpq function.
|
The method uses the efficient |lo_export|_ libpq function.
|
||||||
|
|
||||||
.. |lo_export| replace:: `!lo_export()`
|
.. |lo_export| replace:: `!lo_export()`
|
||||||
.. _lo_export: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT
|
.. _lo_export: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT
|
||||||
|
|
||||||
|
|
||||||
.. method:: seek(offset, whence=0)
|
.. method:: seek(offset, whence=0)
|
||||||
|
|
||||||
Set the lobject current position.
|
Set the lobject current position.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.6
|
||||||
|
added support for *offset* > 2GB.
|
||||||
|
|
||||||
|
|
||||||
.. method:: tell()
|
.. method:: tell()
|
||||||
|
|
||||||
Return the lobject current position.
|
Return the lobject current position.
|
||||||
|
|
||||||
.. method:: truncate(len=0)
|
.. versionadded:: 2.2
|
||||||
|
|
||||||
.. versionadded:: 2.2.0
|
.. versionchanged:: 2.6
|
||||||
|
added support for return value > 2GB.
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: truncate(len=0)
|
||||||
|
|
||||||
Truncate the lobject to the given size.
|
Truncate the lobject to the given size.
|
||||||
|
|
||||||
The method will only be available if Psycopg has been built against libpq
|
The method will only be available if Psycopg has been built against
|
||||||
from PostgreSQL 8.3 or later and can only be used with PostgreSQL servers
|
libpq from PostgreSQL 8.3 or later and can only be used with
|
||||||
running these versions. It uses the |lo_truncate|_ libpq function.
|
PostgreSQL servers running these versions. It uses the |lo_truncate|_
|
||||||
|
libpq function.
|
||||||
|
|
||||||
.. |lo_truncate| replace:: `!lo_truncate()`
|
.. |lo_truncate| replace:: `!lo_truncate()`
|
||||||
.. _lo_truncate: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-TRUNCATE
|
.. _lo_truncate: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-TRUNCATE
|
||||||
|
|
||||||
|
.. versionadded:: 2.2
|
||||||
|
|
||||||
|
.. versionchanged:: 2.6
|
||||||
|
added support for *len* > 2GB.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
If Psycopg is built with |lo_truncate| support (i.e. if the
|
If Psycopg is built with |lo_truncate| support or with the 64 bits API
|
||||||
:program:`pg_config` used during setup is version >= 8.3), but at
|
support (resp. from PostgreSQL versions 8.3 and 9.3) but at runtime an
|
||||||
runtime an older libpq is found, Psycopg will fail to import. See
|
older version of the dynamic library is found, the ``psycopg2`` module
|
||||||
:ref:`the lo_truncate FAQ <faq-lo_truncate>` about the problem.
|
will fail to import. See :ref:`the lo_truncate FAQ <faq-lo_truncate>`
|
||||||
|
about the problem.
|
||||||
|
|
||||||
|
|
||||||
.. method:: close()
|
.. method:: close()
|
||||||
|
|
||||||
|
@ -123,6 +154,130 @@ functionalities defined by the |DBAPI|_.
|
||||||
Close the object and remove it from the database.
|
Close the object and remove it from the database.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. autoclass:: ConnectionInfo(connection)
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
|
||||||
|
.. autoattribute:: dbname
|
||||||
|
.. autoattribute:: user
|
||||||
|
.. autoattribute:: password
|
||||||
|
.. autoattribute:: host
|
||||||
|
.. autoattribute:: port
|
||||||
|
.. autoattribute:: options
|
||||||
|
.. autoattribute:: dsn_parameters
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> conn.info.dsn_parameters
|
||||||
|
{'dbname': 'test', 'user': 'postgres', 'port': '5432', 'sslmode': 'prefer'}
|
||||||
|
|
||||||
|
Requires libpq >= 9.3.
|
||||||
|
|
||||||
|
.. autoattribute:: status
|
||||||
|
.. autoattribute:: transaction_status
|
||||||
|
.. automethod:: parameter_status(name)
|
||||||
|
|
||||||
|
.. autoattribute:: protocol_version
|
||||||
|
|
||||||
|
Currently Psycopg supports only protocol 3, which allows connection
|
||||||
|
to PostgreSQL server from version 7.4. Psycopg versions previous than
|
||||||
|
2.3 support both protocols 2 and 3.
|
||||||
|
|
||||||
|
.. autoattribute:: server_version
|
||||||
|
|
||||||
|
The number is formed by converting the major, minor, and revision
|
||||||
|
numbers into two-decimal-digit numbers and appending them together.
|
||||||
|
After PostgreSQL 10 the minor version was dropped, so the second group
|
||||||
|
of digits is always ``00``. For example, version 9.3.5 will be
|
||||||
|
returned as ``90305``, version 10.2 as ``100002``.
|
||||||
|
|
||||||
|
.. autoattribute:: error_message
|
||||||
|
.. autoattribute:: socket
|
||||||
|
.. autoattribute:: backend_pid
|
||||||
|
.. autoattribute:: needs_password
|
||||||
|
.. autoattribute:: used_password
|
||||||
|
.. autoattribute:: ssl_in_use
|
||||||
|
.. automethod:: ssl_attribute(name)
|
||||||
|
.. autoattribute:: ssl_attribute_names
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: Column(\*args, \*\*kwargs)
|
||||||
|
|
||||||
|
Description of one result column, exposed as items of the
|
||||||
|
`cursor.description` sequence.
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
|
||||||
|
in previous version the `!description` attribute was a sequence of
|
||||||
|
simple tuples or namedtuples.
|
||||||
|
|
||||||
|
.. attribute:: name
|
||||||
|
|
||||||
|
The name of the column returned.
|
||||||
|
|
||||||
|
.. attribute:: type_code
|
||||||
|
|
||||||
|
The PostgreSQL OID of the column. You can use the |pg_type|_ system
|
||||||
|
table to get more informations about the type. This is the value used
|
||||||
|
by Psycopg to decide what Python type use to represent the value. See
|
||||||
|
also :ref:`type-casting-from-sql-to-python`.
|
||||||
|
|
||||||
|
.. attribute:: display_size
|
||||||
|
|
||||||
|
Supposed to be the actual length of the column in bytes. Obtaining
|
||||||
|
this value is computationally intensive, so it is always `!None`.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.8
|
||||||
|
It was previously possible to obtain this value using a compiler
|
||||||
|
flag at builtin.
|
||||||
|
|
||||||
|
.. attribute:: internal_size
|
||||||
|
|
||||||
|
The size in bytes of the column associated to this column on the
|
||||||
|
server. Set to a negative value for variable-size types See also
|
||||||
|
PQfsize_.
|
||||||
|
|
||||||
|
.. attribute:: precision
|
||||||
|
|
||||||
|
Total number of significant digits in columns of type |NUMERIC|_.
|
||||||
|
`!None` for other types.
|
||||||
|
|
||||||
|
.. attribute:: scale
|
||||||
|
|
||||||
|
Count of decimal digits in the fractional part in columns of type
|
||||||
|
|NUMERIC|. `!None` for other types.
|
||||||
|
|
||||||
|
.. attribute:: null_ok
|
||||||
|
|
||||||
|
Always `!None` as not easy to retrieve from the libpq.
|
||||||
|
|
||||||
|
.. attribute:: table_oid
|
||||||
|
|
||||||
|
The oid of the table from which the column was fetched (matching
|
||||||
|
:sql:`pg_class.oid`). `!None` if the column is not a simple reference
|
||||||
|
to a table column. See also PQftable_.
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
|
||||||
|
.. attribute:: table_column
|
||||||
|
|
||||||
|
The number of the column (within its table) making up the result
|
||||||
|
(matching :sql:`pg_attribute.attnum`, so it will start from 1).
|
||||||
|
`!None` if the column is not a simple reference to a table column. See
|
||||||
|
also PQftablecol_.
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
|
||||||
|
.. |pg_type| replace:: :sql:`pg_type`
|
||||||
|
.. _pg_type: https://www.postgresql.org/docs/current/static/catalog-pg-type.html
|
||||||
|
.. _PQgetlength: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQGETLENGTH
|
||||||
|
.. _PQfsize: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQFSIZE
|
||||||
|
.. _PQftable: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQFTABLE
|
||||||
|
.. _PQftablecol: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQFTABLECOL
|
||||||
|
.. _NUMERIC: https://www.postgresql.org/docs/current/static/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL
|
||||||
|
.. |NUMERIC| replace:: :sql:`NUMERIC`
|
||||||
|
|
||||||
.. autoclass:: Notify(pid, channel, payload='')
|
.. autoclass:: Notify(pid, channel, payload='')
|
||||||
:members: pid, channel, payload
|
:members: pid, channel, payload
|
||||||
|
|
||||||
|
@ -155,6 +310,7 @@ functionalities defined by the |DBAPI|_.
|
||||||
message_primary
|
message_primary
|
||||||
schema_name
|
schema_name
|
||||||
severity
|
severity
|
||||||
|
severity_nonlocalized
|
||||||
source_file
|
source_file
|
||||||
source_function
|
source_function
|
||||||
source_line
|
source_line
|
||||||
|
@ -167,14 +323,9 @@ functionalities defined by the |DBAPI|_.
|
||||||
not all the fields are available for all the errors and for all the
|
not all the fields are available for all the errors and for all the
|
||||||
server versions.
|
server versions.
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
The `!severity_nonlocalized` attribute.
|
||||||
|
|
||||||
.. autofunction:: set_wait_callback(f)
|
|
||||||
|
|
||||||
.. versionadded:: 2.2.0
|
|
||||||
|
|
||||||
.. autofunction:: get_wait_callback()
|
|
||||||
|
|
||||||
.. versionadded:: 2.2.0
|
|
||||||
|
|
||||||
|
|
||||||
.. _sql-adaptation-objects:
|
.. _sql-adaptation-objects:
|
||||||
|
@ -189,7 +340,7 @@ deal with Python objects adaptation:
|
||||||
|
|
||||||
.. function:: adapt(obj)
|
.. function:: adapt(obj)
|
||||||
|
|
||||||
Return the SQL representation of *obj* as a string. Raise a
|
Return the SQL representation of *obj* as an `ISQLQuote`. Raise a
|
||||||
`~psycopg2.ProgrammingError` if how to adapt the object is unknown.
|
`~psycopg2.ProgrammingError` if how to adapt the object is unknown.
|
||||||
In order to allow new objects to be adapted, register a new adapter for it
|
In order to allow new objects to be adapted, register a new adapter for it
|
||||||
using the `register_adapter()` function.
|
using the `register_adapter()` function.
|
||||||
|
@ -203,7 +354,7 @@ deal with Python objects adaptation:
|
||||||
Register a new adapter for the objects of class *class*.
|
Register a new adapter for the objects of class *class*.
|
||||||
|
|
||||||
*adapter* should be a function taking a single argument (the object
|
*adapter* should be a function taking a single argument (the object
|
||||||
to adapt) and returning an object conforming the `ISQLQuote`
|
to adapt) and returning an object conforming to the `ISQLQuote`
|
||||||
protocol (e.g. exposing a `!getquoted()` method). The `AsIs` is
|
protocol (e.g. exposing a `!getquoted()` method). The `AsIs` is
|
||||||
often useful for this task.
|
often useful for this task.
|
||||||
|
|
||||||
|
@ -262,9 +413,9 @@ deal with Python objects adaptation:
|
||||||
|
|
||||||
.. method:: getquoted()
|
.. method:: getquoted()
|
||||||
|
|
||||||
Return the string enclosed in single quotes. Any single quote
|
Return the string enclosed in single quotes. Any single quote appearing
|
||||||
appearing in the the string is escaped by doubling it according to SQL
|
in the string is escaped by doubling it according to SQL string
|
||||||
string constants syntax. Backslashes are escaped too.
|
constants syntax. Backslashes are escaped too.
|
||||||
|
|
||||||
>>> QuotedString(r"O'Reilly").getquoted()
|
>>> QuotedString(r"O'Reilly").getquoted()
|
||||||
"'O''Reilly'"
|
"'O''Reilly'"
|
||||||
|
@ -302,13 +453,6 @@ deal with Python objects adaptation:
|
||||||
|
|
||||||
Specialized adapters for Python datetime objects.
|
Specialized adapters for Python datetime objects.
|
||||||
|
|
||||||
.. class:: DateFromMx
|
|
||||||
TimeFromMx
|
|
||||||
TimestampFromMx
|
|
||||||
IntervalFromMx
|
|
||||||
|
|
||||||
Specialized adapters for `mx.DateTime`_ objects.
|
|
||||||
|
|
||||||
.. data:: adapters
|
.. data:: adapters
|
||||||
|
|
||||||
Dictionary of the currently registered object adapters. Use
|
Dictionary of the currently registered object adapters. Use
|
||||||
|
@ -352,8 +496,8 @@ details.
|
||||||
`register_type()` to be used.
|
`register_type()` to be used.
|
||||||
|
|
||||||
:param oids: tuple of OIDs of the PostgreSQL type to convert. It should
|
:param oids: tuple of OIDs of the PostgreSQL type to convert. It should
|
||||||
probably be the oid of the array type (e.g. the ``typarray`` field in
|
probably contain the oid of the array type (e.g. the ``typarray``
|
||||||
the ``pg_type`` table.
|
field in the ``pg_type`` table).
|
||||||
:param name: the name of the new type adapter.
|
:param name: the name of the new type adapter.
|
||||||
:param base_caster: a Psycopg typecaster, e.g. created using the
|
:param base_caster: a Psycopg typecaster, e.g. created using the
|
||||||
`new_type()` function. The caster should be able to parse a single
|
`new_type()` function. The caster should be able to parse a single
|
||||||
|
@ -366,11 +510,12 @@ details.
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The function can be used to create a generic array typecaster,
|
The function can be used to create a generic array typecaster,
|
||||||
returning a list of strings: just use the `~psycopg2.STRING` as base
|
returning a list of strings: just use `psycopg2.STRING` as base
|
||||||
typecaster. For instance, if you want to receive from the database an
|
typecaster. For instance, if you want to receive an array of
|
||||||
array of :sql:`macaddr`, each address represented by string, you can
|
:sql:`macaddr` from the database, each address represented by string,
|
||||||
use::
|
you can use::
|
||||||
|
|
||||||
|
# select typarray from pg_type where typname = 'macaddr' -> 1040
|
||||||
psycopg2.extensions.register_type(
|
psycopg2.extensions.register_type(
|
||||||
psycopg2.extensions.new_array_type(
|
psycopg2.extensions.new_array_type(
|
||||||
(1040,), 'MACADDR[]', psycopg2.STRING))
|
(1040,), 'MACADDR[]', psycopg2.STRING))
|
||||||
|
@ -395,24 +540,31 @@ details.
|
||||||
|
|
||||||
.. data:: encodings
|
.. data:: encodings
|
||||||
|
|
||||||
Mapping from `PostgreSQL encoding`__ names to `Python codec`__ names.
|
Mapping from `PostgreSQL encoding`__ to `Python encoding`__ names.
|
||||||
Used by Psycopg when adapting or casting unicode strings. See
|
Used by Psycopg when adapting or casting unicode strings. See
|
||||||
:ref:`unicode-handling`.
|
:ref:`unicode-handling`.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/multibyte.html
|
.. __: https://www.postgresql.org/docs/current/static/multibyte.html
|
||||||
.. __: http://docs.python.org/library/codecs.html#standard-encodings
|
.. __: https://docs.python.org/library/codecs.html#standard-encodings
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
single: Exceptions; Additional
|
single: Exceptions; Additional
|
||||||
|
|
||||||
|
.. _extension-exceptions:
|
||||||
|
|
||||||
Additional exceptions
|
Additional exceptions
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
The module exports a few exceptions in addition to the :ref:`standard ones
|
The module exports a few exceptions in addition to the :ref:`standard ones
|
||||||
<dbapi-exceptions>` defined by the |DBAPI|_.
|
<dbapi-exceptions>` defined by the |DBAPI|_.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
From psycopg 2.8 these error classes are also exposed by the
|
||||||
|
`psycopg2.errors` module.
|
||||||
|
|
||||||
|
|
||||||
.. exception:: QueryCanceledError
|
.. exception:: QueryCanceledError
|
||||||
|
|
||||||
(subclasses `~psycopg2.OperationalError`)
|
(subclasses `~psycopg2.OperationalError`)
|
||||||
|
@ -427,13 +579,143 @@ The module exports a few exceptions in addition to the :ref:`standard ones
|
||||||
|
|
||||||
(subclasses `~psycopg2.OperationalError`)
|
(subclasses `~psycopg2.OperationalError`)
|
||||||
|
|
||||||
Error causing transaction rollback (deadlocks, serialisation failures,
|
Error causing transaction rollback (deadlocks, serialization failures,
|
||||||
etc). It can be trapped specifically to detect a deadlock.
|
etc). It can be trapped specifically to detect a deadlock.
|
||||||
|
|
||||||
.. versionadded:: 2.0.7
|
.. versionadded:: 2.0.7
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _coroutines-functions:
|
||||||
|
|
||||||
|
Coroutines support functions
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
These functions are used to set and retrieve the callback function for
|
||||||
|
:ref:`cooperation with coroutine libraries <green-support>`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.2
|
||||||
|
|
||||||
|
.. autofunction:: set_wait_callback(f)
|
||||||
|
|
||||||
|
.. autofunction:: get_wait_callback()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Other functions
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. function:: libpq_version()
|
||||||
|
|
||||||
|
Return the version number of the ``libpq`` dynamic library loaded as an
|
||||||
|
integer, in the same format of `~connection.server_version`.
|
||||||
|
|
||||||
|
Raise `~psycopg2.NotSupportedError` if the ``psycopg2`` module was
|
||||||
|
compiled with a ``libpq`` version lesser than 9.1 (which can be detected
|
||||||
|
by the `~psycopg2.__libpq_version__` constant).
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
|
||||||
|
.. seealso:: libpq docs for `PQlibVersion()`__.
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/libpq-misc.html#LIBPQ-PQLIBVERSION
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: make_dsn(dsn=None, \*\*kwargs)
|
||||||
|
|
||||||
|
Create a valid connection string from arguments.
|
||||||
|
|
||||||
|
Put together the arguments in *kwargs* into a connection string. If *dsn*
|
||||||
|
is specified too, merge the arguments coming from both the sources. If the
|
||||||
|
same argument name is specified in both the sources, the *kwargs* value
|
||||||
|
overrides the *dsn* value.
|
||||||
|
|
||||||
|
The input arguments are validated: the output should always be a valid
|
||||||
|
connection string (as far as `parse_dsn()` is concerned). If not raise
|
||||||
|
`~psycopg2.ProgrammingError`.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> from psycopg2.extensions import make_dsn
|
||||||
|
>>> make_dsn('dbname=foo host=example.com', password="s3cr3t")
|
||||||
|
'host=example.com password=s3cr3t dbname=foo'
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: parse_dsn(dsn)
|
||||||
|
|
||||||
|
Parse connection string into a dictionary of keywords and values.
|
||||||
|
|
||||||
|
Parsing is delegated to the libpq: different versions of the client
|
||||||
|
library may support different formats or parameters (for example,
|
||||||
|
`connection URIs`__ are only supported from libpq 9.2). Raise
|
||||||
|
`~psycopg2.ProgrammingError` if the *dsn* is not valid.
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> from psycopg2.extensions import parse_dsn
|
||||||
|
>>> parse_dsn('dbname=test user=postgres password=secret')
|
||||||
|
{'password': 'secret', 'user': 'postgres', 'dbname': 'test'}
|
||||||
|
>>> parse_dsn("postgresql://someone@example.com/somedb?connect_timeout=10")
|
||||||
|
{'host': 'example.com', 'user': 'someone', 'dbname': 'somedb', 'connect_timeout': '10'}
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
|
||||||
|
.. seealso:: libpq docs for `PQconninfoParse()`__.
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNINFOPARSE
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: quote_ident(str, scope)
|
||||||
|
|
||||||
|
Return quoted identifier according to PostgreSQL quoting rules.
|
||||||
|
|
||||||
|
The *scope* must be a `connection` or a `cursor`, the underlying
|
||||||
|
connection encoding is used for any necessary character conversion.
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
|
||||||
|
.. seealso:: libpq docs for `PQescapeIdentifier()`__
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQESCAPEIDENTIFIER
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: encrypt_password(password, user, scope=None, algorithm=None)
|
||||||
|
|
||||||
|
Return the encrypted form of a PostgreSQL password.
|
||||||
|
|
||||||
|
:param password: the cleartext password to encrypt
|
||||||
|
:param user: the name of the user to use the password for
|
||||||
|
:param scope: the scope to encrypt the password into; if *algorithm* is
|
||||||
|
``md5`` it can be `!None`
|
||||||
|
:type scope: `connection` or `cursor`
|
||||||
|
:param algorithm: the password encryption algorithm to use
|
||||||
|
|
||||||
|
The *algorithm* ``md5`` is always supported. Other algorithms are only
|
||||||
|
supported if the client libpq version is at least 10 and may require a
|
||||||
|
compatible server version: check the `PostgreSQL encryption
|
||||||
|
documentation`__ to know the algorithms supported by your server.
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/encryption-options.html
|
||||||
|
|
||||||
|
Using `!None` as *algorithm* will result in querying the server to know the
|
||||||
|
current server password encryption setting, which is a blocking operation:
|
||||||
|
query the server separately and specify a value for *algorithm* if you
|
||||||
|
want to maintain a non-blocking behaviour.
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
|
||||||
|
.. seealso:: PostgreSQL docs for the `password_encryption`__ setting, libpq `PQencryptPasswordConn()`__, `PQencryptPassword()`__ functions.
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/runtime-config-connection.html#GUC-PASSWORD-ENCRYPTION
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/libpq-misc.html#LIBPQ-PQENCRYPTPASSWORDCONN
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/libpq-misc.html#LIBPQ-PQENCRYPTPASSWORD
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
pair: Isolation level; Constants
|
pair: Isolation level; Constants
|
||||||
|
|
||||||
|
@ -443,15 +725,16 @@ Isolation level constants
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
Psycopg2 `connection` objects hold informations about the PostgreSQL
|
Psycopg2 `connection` objects hold informations about the PostgreSQL
|
||||||
`transaction isolation level`_. The current transaction level can be read
|
`transaction isolation level`_. By default Psycopg doesn't change the default
|
||||||
from the `~connection.isolation_level` attribute. The default isolation
|
configuration of the server (`ISOLATION_LEVEL_DEFAULT`); the default for
|
||||||
level is :sql:`READ COMMITTED`. A different isolation level con be set
|
PostgreSQL servers is typically :sql:`READ COMMITTED`, but this may be changed
|
||||||
through the `~connection.set_isolation_level()` method. The level can be
|
in the server configuration files. A different isolation level can be set
|
||||||
set to one of the following constants:
|
through the `~connection.set_isolation_level()` or `~connection.set_session()`
|
||||||
|
methods. The level can be set to one of the following constants:
|
||||||
|
|
||||||
.. data:: ISOLATION_LEVEL_AUTOCOMMIT
|
.. data:: ISOLATION_LEVEL_AUTOCOMMIT
|
||||||
|
|
||||||
No transaction is started when command are issued and no
|
No transaction is started when commands are executed and no
|
||||||
`~connection.commit()` or `~connection.rollback()` is required.
|
`~connection.commit()` or `~connection.rollback()` is required.
|
||||||
Some PostgreSQL command such as :sql:`CREATE DATABASE` or :sql:`VACUUM`
|
Some PostgreSQL command such as :sql:`CREATE DATABASE` or :sql:`VACUUM`
|
||||||
can't run into a transaction: to run such command use::
|
can't run into a transaction: to run such command use::
|
||||||
|
@ -468,8 +751,8 @@ set to one of the following constants:
|
||||||
|
|
||||||
.. data:: ISOLATION_LEVEL_READ_COMMITTED
|
.. data:: ISOLATION_LEVEL_READ_COMMITTED
|
||||||
|
|
||||||
This is usually the the default PostgreSQL value, but a different default
|
This is usually the default PostgreSQL value, but a different default may
|
||||||
may be set in the database configuration.
|
be set in the database configuration.
|
||||||
|
|
||||||
A new transaction is started at the first `~cursor.execute()` command on a
|
A new transaction is started at the first `~cursor.execute()` command on a
|
||||||
cursor and at each new `!execute()` after a `~connection.commit()` or a
|
cursor and at each new `!execute()` after a `~connection.commit()` or a
|
||||||
|
@ -482,7 +765,7 @@ set to one of the following constants:
|
||||||
.. seealso:: `Read Committed Isolation Level`__ in PostgreSQL
|
.. seealso:: `Read Committed Isolation Level`__ in PostgreSQL
|
||||||
documentation.
|
documentation.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-READ-COMMITTED
|
.. __: https://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-READ-COMMITTED
|
||||||
|
|
||||||
.. data:: ISOLATION_LEVEL_REPEATABLE_READ
|
.. data:: ISOLATION_LEVEL_REPEATABLE_READ
|
||||||
|
|
||||||
|
@ -506,7 +789,7 @@ set to one of the following constants:
|
||||||
.. seealso:: `Repeatable Read Isolation Level`__ in PostgreSQL
|
.. seealso:: `Repeatable Read Isolation Level`__ in PostgreSQL
|
||||||
documentation.
|
documentation.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-REPEATABLE-READ
|
.. __: https://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-REPEATABLE-READ
|
||||||
|
|
||||||
.. data:: ISOLATION_LEVEL_SERIALIZABLE
|
.. data:: ISOLATION_LEVEL_SERIALIZABLE
|
||||||
|
|
||||||
|
@ -515,7 +798,7 @@ set to one of the following constants:
|
||||||
:sql:`SERIALIZABLE` isolation level. This is the strictest transactions
|
:sql:`SERIALIZABLE` isolation level. This is the strictest transactions
|
||||||
isolation level, equivalent to having the transactions executed serially
|
isolation level, equivalent to having the transactions executed serially
|
||||||
rather than concurrently. However applications using this level must be
|
rather than concurrently. However applications using this level must be
|
||||||
prepared to retry reansactions due to serialization failures.
|
prepared to retry transactions due to serialization failures.
|
||||||
|
|
||||||
Starting from PostgreSQL 9.1, this mode monitors for conditions which
|
Starting from PostgreSQL 9.1, this mode monitors for conditions which
|
||||||
could make execution of a concurrent set of serializable transactions
|
could make execution of a concurrent set of serializable transactions
|
||||||
|
@ -525,8 +808,18 @@ set to one of the following constants:
|
||||||
|
|
||||||
.. seealso:: `Serializable Isolation Level`__ in PostgreSQL documentation.
|
.. seealso:: `Serializable Isolation Level`__ in PostgreSQL documentation.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-SERIALIZABLE
|
.. __: https://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-SERIALIZABLE
|
||||||
|
|
||||||
|
.. data:: ISOLATION_LEVEL_DEFAULT
|
||||||
|
|
||||||
|
A new transaction is started at the first `~cursor.execute()` command, but
|
||||||
|
the isolation level is not explicitly selected by Psycopg: the server will
|
||||||
|
use whatever level is defined in its configuration or by statements
|
||||||
|
executed within the session outside Pyscopg control. If you want to know
|
||||||
|
what the value is you can use a query such as :sql:`show
|
||||||
|
transaction_isolation`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
@ -538,7 +831,7 @@ Transaction status constants
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
These values represent the possible status of a transaction: the current value
|
These values represent the possible status of a transaction: the current value
|
||||||
can be read using the `connection.get_transaction_status()` method.
|
can be read using the `connection.info.transaction_status` property.
|
||||||
|
|
||||||
.. data:: TRANSACTION_STATUS_IDLE
|
.. data:: TRANSACTION_STATUS_IDLE
|
||||||
|
|
||||||
|
@ -609,7 +902,7 @@ internal usage and Python code should not rely on them.
|
||||||
Poll constants
|
Poll constants
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
.. versionadded:: 2.2.0
|
.. versionadded:: 2.2
|
||||||
|
|
||||||
These values can be returned by `connection.poll()` during asynchronous
|
These values can be returned by `connection.poll()` during asynchronous
|
||||||
connection and communication. They match the values in the libpq enum
|
connection and communication. They match the values in the libpq enum
|
||||||
|
@ -658,6 +951,7 @@ Python objects. All the typecasters are automatically registered, except
|
||||||
from the database. See :ref:`unicode-handling` for details.
|
from the database. See :ref:`unicode-handling` for details.
|
||||||
|
|
||||||
.. data:: BOOLEAN
|
.. data:: BOOLEAN
|
||||||
|
BYTES
|
||||||
DATE
|
DATE
|
||||||
DECIMAL
|
DECIMAL
|
||||||
FLOAT
|
FLOAT
|
||||||
|
@ -674,6 +968,7 @@ from the database. See :ref:`unicode-handling` for details.
|
||||||
|
|
||||||
.. data:: BINARYARRAY
|
.. data:: BINARYARRAY
|
||||||
BOOLEANARRAY
|
BOOLEANARRAY
|
||||||
|
BYTESARRAY
|
||||||
DATEARRAY
|
DATEARRAY
|
||||||
DATETIMEARRAY
|
DATETIMEARRAY
|
||||||
DECIMALARRAY
|
DECIMALARRAY
|
||||||
|
@ -690,31 +985,26 @@ from the database. See :ref:`unicode-handling` for details.
|
||||||
|
|
||||||
.. data:: PYDATE
|
.. data:: PYDATE
|
||||||
PYDATETIME
|
PYDATETIME
|
||||||
|
PYDATETIMETZ
|
||||||
PYINTERVAL
|
PYINTERVAL
|
||||||
PYTIME
|
PYTIME
|
||||||
PYDATEARRAY
|
PYDATEARRAY
|
||||||
PYDATETIMEARRAY
|
PYDATETIMEARRAY
|
||||||
|
PYDATETIMETZARRAY
|
||||||
PYINTERVALARRAY
|
PYINTERVALARRAY
|
||||||
PYTIMEARRAY
|
PYTIMEARRAY
|
||||||
|
|
||||||
Typecasters to convert time-related data types to Python `!datetime`
|
Typecasters to convert time-related data types to Python `!datetime`
|
||||||
objects.
|
objects.
|
||||||
|
|
||||||
.. data:: MXDATE
|
.. versionchanged:: 2.2
|
||||||
MXDATETIME
|
|
||||||
MXINTERVAL
|
|
||||||
MXTIME
|
|
||||||
MXDATEARRAY
|
|
||||||
MXDATETIMEARRAY
|
|
||||||
MXINTERVALARRAY
|
|
||||||
MXTIMEARRAY
|
|
||||||
|
|
||||||
Typecasters to convert time-related data types to `mx.DateTime`_ objects.
|
|
||||||
Only available if Psycopg was compiled with `!mx` support.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.2.0
|
|
||||||
previously the `DECIMAL` typecaster and the specific time-related
|
previously the `DECIMAL` typecaster and the specific time-related
|
||||||
typecasters (`!PY*` and `!MX*`) were not exposed by the `extensions`
|
typecasters (`!PY*` and `!MX*`) were not exposed by the `extensions`
|
||||||
module. In older versions they can be imported from the implementation
|
module. In older versions they can be imported from the implementation
|
||||||
module `!psycopg2._psycopg`.
|
module `!psycopg2._psycopg`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.7.2
|
||||||
|
the `!*DATETIMETZ*` objects.
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
the `!BYTES` and `BYTESARRAY` objects.
|
||||||
|
|
|
@ -41,8 +41,8 @@ If you want to use a `!connection` subclass you can pass it as the
|
||||||
Dictionary-like cursor
|
Dictionary-like cursor
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The dict cursors allow to access to the retrieved records using an iterface
|
The dict cursors allow to access to the attributes of retrieved records
|
||||||
similar to the Python dictionaries instead of the tuples.
|
using an interface similar to the Python dictionaries instead of the tuples.
|
||||||
|
|
||||||
>>> dict_cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
>>> dict_cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
||||||
>>> dict_cur.execute("INSERT INTO test (num, data) VALUES(%s, %s)",
|
>>> dict_cur.execute("INSERT INTO test (num, data) VALUES(%s, %s)",
|
||||||
|
@ -99,20 +99,6 @@ Real dictionary cursor
|
||||||
|
|
||||||
.. versionadded:: 2.3
|
.. versionadded:: 2.3
|
||||||
|
|
||||||
These objects require :py:func:`collections.namedtuple` to be found, so it is
|
|
||||||
available out-of-the-box only from Python 2.6. Anyway, the namedtuple
|
|
||||||
implementation is compatible with previous Python versions, so all you
|
|
||||||
have to do is to `download it`__ and make it available where we
|
|
||||||
expect it to be... ::
|
|
||||||
|
|
||||||
from somewhere import namedtuple
|
|
||||||
import collections
|
|
||||||
collections.namedtuple = namedtuple
|
|
||||||
from psycopg.extras import NamedTupleConnection
|
|
||||||
# ...
|
|
||||||
|
|
||||||
.. __: http://code.activestate.com/recipes/500261-named-tuples/
|
|
||||||
|
|
||||||
.. autoclass:: NamedTupleCursor
|
.. autoclass:: NamedTupleCursor
|
||||||
|
|
||||||
.. autoclass:: NamedTupleConnection
|
.. autoclass:: NamedTupleConnection
|
||||||
|
@ -136,6 +122,11 @@ Logging cursor
|
||||||
.. autoclass:: LoggingCursor
|
.. autoclass:: LoggingCursor
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Queries that are executed with `cursor.executemany()` are not logged.
|
||||||
|
|
||||||
|
|
||||||
.. autoclass:: MinTimeLoggingConnection
|
.. autoclass:: MinTimeLoggingConnection
|
||||||
:members: initialize,filter
|
:members: initialize,filter
|
||||||
|
|
||||||
|
@ -143,6 +134,417 @@ Logging cursor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _replication-objects:
|
||||||
|
|
||||||
|
Replication support objects
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
See :ref:`replication-support` for an introduction to the topic.
|
||||||
|
|
||||||
|
|
||||||
|
The following replication types are defined:
|
||||||
|
|
||||||
|
.. data:: REPLICATION_LOGICAL
|
||||||
|
.. data:: REPLICATION_PHYSICAL
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
pair: Connection; replication
|
||||||
|
|
||||||
|
.. autoclass:: LogicalReplicationConnection
|
||||||
|
|
||||||
|
This connection factory class can be used to open a special type of
|
||||||
|
connection that is used for logical replication.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
from psycopg2.extras import LogicalReplicationConnection
|
||||||
|
log_conn = psycopg2.connect(dsn, connection_factory=LogicalReplicationConnection)
|
||||||
|
log_cur = log_conn.cursor()
|
||||||
|
|
||||||
|
|
||||||
|
.. autoclass:: PhysicalReplicationConnection
|
||||||
|
|
||||||
|
This connection factory class can be used to open a special type of
|
||||||
|
connection that is used for physical replication.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
from psycopg2.extras import PhysicalReplicationConnection
|
||||||
|
phys_conn = psycopg2.connect(dsn, connection_factory=PhysicalReplicationConnection)
|
||||||
|
phys_cur = phys_conn.cursor()
|
||||||
|
|
||||||
|
Both `LogicalReplicationConnection` and `PhysicalReplicationConnection` use
|
||||||
|
`ReplicationCursor` for actual communication with the server.
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
pair: Message; replication
|
||||||
|
|
||||||
|
The individual messages in the replication stream are represented by
|
||||||
|
`ReplicationMessage` objects (both logical and physical type):
|
||||||
|
|
||||||
|
.. autoclass:: ReplicationMessage
|
||||||
|
|
||||||
|
.. attribute:: payload
|
||||||
|
|
||||||
|
The actual data received from the server.
|
||||||
|
|
||||||
|
An instance of either `bytes()` or `unicode()`, depending on the value
|
||||||
|
of `decode` option passed to `~ReplicationCursor.start_replication()`
|
||||||
|
on the connection. See `~ReplicationCursor.read_message()` for
|
||||||
|
details.
|
||||||
|
|
||||||
|
.. attribute:: data_size
|
||||||
|
|
||||||
|
The raw size of the message payload (before possible unicode
|
||||||
|
conversion).
|
||||||
|
|
||||||
|
.. attribute:: data_start
|
||||||
|
|
||||||
|
LSN position of the start of the message.
|
||||||
|
|
||||||
|
.. attribute:: wal_end
|
||||||
|
|
||||||
|
LSN position of the current end of WAL on the server.
|
||||||
|
|
||||||
|
.. attribute:: send_time
|
||||||
|
|
||||||
|
A `~datetime` object representing the server timestamp at the moment
|
||||||
|
when the message was sent.
|
||||||
|
|
||||||
|
.. attribute:: cursor
|
||||||
|
|
||||||
|
A reference to the corresponding `ReplicationCursor` object.
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
pair: Cursor; replication
|
||||||
|
|
||||||
|
.. autoclass:: ReplicationCursor
|
||||||
|
|
||||||
|
.. method:: create_replication_slot(slot_name, slot_type=None, output_plugin=None)
|
||||||
|
|
||||||
|
Create streaming replication slot.
|
||||||
|
|
||||||
|
:param slot_name: name of the replication slot to be created
|
||||||
|
:param slot_type: type of replication: should be either
|
||||||
|
`REPLICATION_LOGICAL` or `REPLICATION_PHYSICAL`
|
||||||
|
:param output_plugin: name of the logical decoding output plugin to be
|
||||||
|
used by the slot; required for logical
|
||||||
|
replication connections, disallowed for physical
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
log_cur.create_replication_slot("logical1", "test_decoding")
|
||||||
|
phys_cur.create_replication_slot("physical1")
|
||||||
|
|
||||||
|
# either logical or physical replication connection
|
||||||
|
cur.create_replication_slot("slot1", slot_type=REPLICATION_LOGICAL)
|
||||||
|
|
||||||
|
When creating a slot on a logical replication connection, a logical
|
||||||
|
replication slot is created by default. Logical replication requires
|
||||||
|
name of the logical decoding output plugin to be specified.
|
||||||
|
|
||||||
|
When creating a slot on a physical replication connection, a physical
|
||||||
|
replication slot is created by default. No output plugin parameter is
|
||||||
|
required or allowed when creating a physical replication slot.
|
||||||
|
|
||||||
|
In either case the type of slot being created can be specified
|
||||||
|
explicitly using *slot_type* parameter.
|
||||||
|
|
||||||
|
Replication slots are a feature of PostgreSQL server starting with
|
||||||
|
version 9.4.
|
||||||
|
|
||||||
|
.. method:: drop_replication_slot(slot_name)
|
||||||
|
|
||||||
|
Drop streaming replication slot.
|
||||||
|
|
||||||
|
:param slot_name: name of the replication slot to drop
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
# either logical or physical replication connection
|
||||||
|
cur.drop_replication_slot("slot1")
|
||||||
|
|
||||||
|
Replication slots are a feature of PostgreSQL server starting with
|
||||||
|
version 9.4.
|
||||||
|
|
||||||
|
.. method:: start_replication(slot_name=None, slot_type=None, start_lsn=0, timeline=0, options=None, decode=False, status_interval=10)
|
||||||
|
|
||||||
|
Start replication on the connection.
|
||||||
|
|
||||||
|
:param slot_name: name of the replication slot to use; required for
|
||||||
|
logical replication, physical replication can work
|
||||||
|
with or without a slot
|
||||||
|
:param slot_type: type of replication: should be either
|
||||||
|
`REPLICATION_LOGICAL` or `REPLICATION_PHYSICAL`
|
||||||
|
:param start_lsn: the optional LSN position to start replicating from,
|
||||||
|
can be an integer or a string of hexadecimal digits
|
||||||
|
in the form ``XXX/XXX``
|
||||||
|
:param timeline: WAL history timeline to start streaming from (optional,
|
||||||
|
can only be used with physical replication)
|
||||||
|
:param options: a dictionary of options to pass to logical replication
|
||||||
|
slot (not allowed with physical replication)
|
||||||
|
:param decode: a flag indicating that unicode conversion should be
|
||||||
|
performed on messages received from the server
|
||||||
|
:param status_interval: time between feedback packets sent to the server
|
||||||
|
|
||||||
|
If a *slot_name* is specified, the slot must exist on the server and
|
||||||
|
its type must match the replication type used.
|
||||||
|
|
||||||
|
If not specified using *slot_type* parameter, the type of replication
|
||||||
|
is defined by the type of replication connection. Logical replication
|
||||||
|
is only allowed on logical replication connection, but physical
|
||||||
|
replication can be used with both types of connection.
|
||||||
|
|
||||||
|
On the other hand, physical replication doesn't require a named
|
||||||
|
replication slot to be used, only logical replication does. In any
|
||||||
|
case logical replication and replication slots are a feature of
|
||||||
|
PostgreSQL server starting with version 9.4. Physical replication can
|
||||||
|
be used starting with 9.0.
|
||||||
|
|
||||||
|
If *start_lsn* is specified, the requested stream will start from that
|
||||||
|
LSN. The default is `!None` which passes the LSN ``0/0`` causing
|
||||||
|
replay to begin at the last point for which the server got flush
|
||||||
|
confirmation from the client, or the oldest available point for a new
|
||||||
|
slot.
|
||||||
|
|
||||||
|
The server might produce an error if a WAL file for the given LSN has
|
||||||
|
already been recycled or it may silently start streaming from a later
|
||||||
|
position: the client can verify the actual position using information
|
||||||
|
provided by the `ReplicationMessage` attributes. The exact server
|
||||||
|
behavior depends on the type of replication and use of slots.
|
||||||
|
|
||||||
|
The *timeline* parameter can only be specified with physical
|
||||||
|
replication and only starting with server version 9.3.
|
||||||
|
|
||||||
|
A dictionary of *options* may be passed to the logical decoding plugin
|
||||||
|
on a logical replication slot. The set of supported options depends
|
||||||
|
on the output plugin that was used to create the slot. Must be
|
||||||
|
`!None` for physical replication.
|
||||||
|
|
||||||
|
If *decode* is set to `!True` the messages received from the server
|
||||||
|
would be converted according to the connection `~connection.encoding`.
|
||||||
|
*This parameter should not be set with physical replication or with
|
||||||
|
logical replication plugins that produce binary output.*
|
||||||
|
|
||||||
|
Replication stream should periodically send feedback to the database
|
||||||
|
to prevent disconnect via timeout. Feedback is automatically sent when
|
||||||
|
`read_message()` is called or during run of the `consume_stream()`.
|
||||||
|
To specify the feedback interval use *status_interval* parameter.
|
||||||
|
The value of this parameter must be set to at least 1 second, but
|
||||||
|
it can have a fractional part.
|
||||||
|
|
||||||
|
|
||||||
|
This function constructs a |START_REPLICATION|_ command and calls
|
||||||
|
`start_replication_expert()` internally.
|
||||||
|
|
||||||
|
After starting the replication, to actually consume the incoming
|
||||||
|
server messages use `consume_stream()` or implement a loop around
|
||||||
|
`read_message()` in case of :ref:`asynchronous connection
|
||||||
|
<async-support>`.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.8.3
|
||||||
|
added the *status_interval* parameter.
|
||||||
|
|
||||||
|
.. |START_REPLICATION| replace:: :sql:`START_REPLICATION`
|
||||||
|
.. _START_REPLICATION: https://www.postgresql.org/docs/current/static/protocol-replication.html
|
||||||
|
|
||||||
|
.. method:: start_replication_expert(command, decode=False, status_interval=10)
|
||||||
|
|
||||||
|
Start replication on the connection using provided
|
||||||
|
|START_REPLICATION|_ command.
|
||||||
|
|
||||||
|
:param command: The full replication command. It can be a string or a
|
||||||
|
`~psycopg2.sql.Composable` instance for dynamic generation.
|
||||||
|
:param decode: a flag indicating that unicode conversion should be
|
||||||
|
performed on messages received from the server.
|
||||||
|
:param status_interval: time between feedback packets sent to the server
|
||||||
|
|
||||||
|
.. versionchanged:: 2.8.3
|
||||||
|
added the *status_interval* parameter.
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: consume_stream(consume, keepalive_interval=None)
|
||||||
|
|
||||||
|
:param consume: a callable object with signature :samp:`consume({msg})`
|
||||||
|
:param keepalive_interval: interval (in seconds) to send keepalive
|
||||||
|
messages to the server
|
||||||
|
|
||||||
|
This method can only be used with synchronous connection. For
|
||||||
|
asynchronous connections see `read_message()`.
|
||||||
|
|
||||||
|
Before using this method to consume the stream call
|
||||||
|
`start_replication()` first.
|
||||||
|
|
||||||
|
This method enters an endless loop reading messages from the server
|
||||||
|
and passing them to ``consume()`` one at a time, then waiting for more
|
||||||
|
messages from the server. In order to make this method break out of
|
||||||
|
the loop and return, ``consume()`` can throw a `StopReplication`
|
||||||
|
exception. Any unhandled exception will make it break out of the loop
|
||||||
|
as well.
|
||||||
|
|
||||||
|
The *msg* object passed to ``consume()`` is an instance of
|
||||||
|
`ReplicationMessage` class. See `read_message()` for details about
|
||||||
|
message decoding.
|
||||||
|
|
||||||
|
This method also sends feedback messages to the server every
|
||||||
|
*keepalive_interval* (in seconds). The value of this parameter must
|
||||||
|
be set to at least 1 second, but it can have a fractional part.
|
||||||
|
If the *keepalive_interval* is not specified, the value of
|
||||||
|
*status_interval* specified in the `start_replication()` or
|
||||||
|
`start_replication_expert()` will be used.
|
||||||
|
|
||||||
|
The client must confirm every processed message by calling
|
||||||
|
`send_feedback()` method on the corresponding replication cursor. A
|
||||||
|
reference to the cursor is provided in the `ReplicationMessage` as an
|
||||||
|
attribute.
|
||||||
|
|
||||||
|
The following example is a sketch implementation of ``consume()``
|
||||||
|
callable for logical replication::
|
||||||
|
|
||||||
|
class LogicalStreamConsumer(object):
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
def __call__(self, msg):
|
||||||
|
self.process_message(msg.payload)
|
||||||
|
msg.cursor.send_feedback(flush_lsn=msg.data_start)
|
||||||
|
|
||||||
|
consumer = LogicalStreamConsumer()
|
||||||
|
cur.consume_stream(consumer)
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
When using replication with slots, failure to constantly consume
|
||||||
|
*and* report success to the server appropriately can eventually
|
||||||
|
lead to "disk full" condition on the server, because the server
|
||||||
|
retains all the WAL segments that might be needed to stream the
|
||||||
|
changes via all of the currently open replication slots.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.8.3
|
||||||
|
changed the default value of the *keepalive_interval* parameter to `!None`.
|
||||||
|
|
||||||
|
.. method:: send_feedback(write_lsn=0, flush_lsn=0, apply_lsn=0, reply=False, force=False)
|
||||||
|
|
||||||
|
:param write_lsn: a LSN position up to which the client has written the data locally
|
||||||
|
:param flush_lsn: a LSN position up to which the client has processed the
|
||||||
|
data reliably (the server is allowed to discard all
|
||||||
|
and every data that predates this LSN)
|
||||||
|
:param apply_lsn: a LSN position up to which the warm standby server
|
||||||
|
has applied the changes (physical replication
|
||||||
|
master-slave protocol only)
|
||||||
|
:param reply: request the server to send back a keepalive message immediately
|
||||||
|
:param force: force sending a feedback message regardless of status_interval timeout
|
||||||
|
|
||||||
|
Use this method to report to the server that all messages up to a
|
||||||
|
certain LSN position have been processed on the client and may be
|
||||||
|
discarded on the server.
|
||||||
|
|
||||||
|
If the *reply* or *force* parameters are not set, this method will
|
||||||
|
just update internal structures without sending the feedback message
|
||||||
|
to the server. The library sends feedback message automatically
|
||||||
|
when *status_interval* timeout is reached. For this to work, you must
|
||||||
|
call `send_feedback()` on the same Cursor that you called `start_replication()`
|
||||||
|
on (the one in `message.cursor`) or your feedback will be lost.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.8.3
|
||||||
|
added the *force* parameter.
|
||||||
|
|
||||||
|
Low-level replication cursor methods for :ref:`asynchronous connection
|
||||||
|
<async-support>` operation.
|
||||||
|
|
||||||
|
With the synchronous connection a call to `consume_stream()` handles all
|
||||||
|
the complexity of handling the incoming messages and sending keepalive
|
||||||
|
replies, but at times it might be beneficial to use low-level interface
|
||||||
|
for better control, in particular to `~select` on multiple sockets. The
|
||||||
|
following methods are provided for asynchronous operation:
|
||||||
|
|
||||||
|
.. method:: read_message()
|
||||||
|
|
||||||
|
Try to read the next message from the server without blocking and
|
||||||
|
return an instance of `ReplicationMessage` or `!None`, in case there
|
||||||
|
are no more data messages from the server at the moment.
|
||||||
|
|
||||||
|
This method should be used in a loop with asynchronous connections
|
||||||
|
(after calling `start_replication()` once). For synchronous
|
||||||
|
connections see `consume_stream()`.
|
||||||
|
|
||||||
|
The returned message's `~ReplicationMessage.payload` is an instance of
|
||||||
|
`!unicode` decoded according to connection `~connection.encoding`
|
||||||
|
*iff* *decode* was set to `!True` in the initial call to
|
||||||
|
`start_replication()` on this connection, otherwise it is an instance
|
||||||
|
of `!bytes` with no decoding.
|
||||||
|
|
||||||
|
It is expected that the calling code will call this method repeatedly
|
||||||
|
in order to consume all of the messages that might have been buffered
|
||||||
|
until `!None` is returned. After receiving `!None` from this method
|
||||||
|
the caller should use `~select.select()` or `~select.poll()` on the
|
||||||
|
corresponding connection to block the process until there is more data
|
||||||
|
from the server.
|
||||||
|
|
||||||
|
Last, but not least, this method sends feedback messages when
|
||||||
|
*status_interval* timeout is reached or when keepalive message with
|
||||||
|
reply request arrived from the server.
|
||||||
|
|
||||||
|
.. method:: fileno()
|
||||||
|
|
||||||
|
Call the corresponding connection's `~connection.fileno()` method and
|
||||||
|
return the result.
|
||||||
|
|
||||||
|
This is a convenience method which allows replication cursor to be
|
||||||
|
used directly in `~select.select()` or `~select.poll()` calls.
|
||||||
|
|
||||||
|
.. attribute:: io_timestamp
|
||||||
|
|
||||||
|
A `~datetime` object representing the timestamp at the moment of last
|
||||||
|
communication with the server (a data or keepalive message in either
|
||||||
|
direction).
|
||||||
|
|
||||||
|
.. attribute:: feedback_timestamp
|
||||||
|
|
||||||
|
A `~datetime` object representing the timestamp at the moment when
|
||||||
|
the last feedback message sent to the server.
|
||||||
|
|
||||||
|
.. versionadded:: 2.8.3
|
||||||
|
|
||||||
|
.. attribute:: wal_end
|
||||||
|
|
||||||
|
LSN position of the current end of WAL on the server at the
|
||||||
|
moment of last data or keepalive message received from the
|
||||||
|
server.
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
|
||||||
|
An actual example of asynchronous operation might look like this::
|
||||||
|
|
||||||
|
from select import select
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def consume(msg):
|
||||||
|
# ...
|
||||||
|
msg.cursor.send_feedback(flush_lsn=msg.data_start)
|
||||||
|
|
||||||
|
status_interval = 10.0
|
||||||
|
while True:
|
||||||
|
msg = cur.read_message()
|
||||||
|
if msg:
|
||||||
|
consume(msg)
|
||||||
|
else:
|
||||||
|
now = datetime.now()
|
||||||
|
timeout = status_interval - (now - cur.feedback_timestamp).total_seconds()
|
||||||
|
try:
|
||||||
|
sel = select([cur], [], [], max(0, timeout))
|
||||||
|
except InterruptedError:
|
||||||
|
pass # recalculate timeout and continue
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
pair: Cursor; Replication
|
||||||
|
|
||||||
|
.. autoclass:: StopReplication
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
single: Data types; Additional
|
single: Data types; Additional
|
||||||
|
|
||||||
|
@ -160,25 +562,26 @@ JSON_ adaptation
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
.. versionadded:: 2.5
|
.. versionadded:: 2.5
|
||||||
|
.. versionchanged:: 2.5.4
|
||||||
|
added |jsonb| support. In previous versions |jsonb| values are returned
|
||||||
|
as strings. See :ref:`the FAQ <faq-jsonb-adapt>` for a workaround.
|
||||||
|
|
||||||
Psycopg can adapt Python objects to and from the PostgreSQL |pgjson|_ type.
|
Psycopg can adapt Python objects to and from the PostgreSQL |jsons|_
|
||||||
With PostgreSQL 9.2 adaptation is available out-of-the-box. To use JSON data
|
types. With PostgreSQL 9.2 and following versions adaptation is
|
||||||
with previous database versions (either with the `9.1 json extension`__, but
|
available out-of-the-box. To use JSON data with previous database versions
|
||||||
even if you want to convert text fields to JSON) you can use
|
(either with the `9.1 json extension`__, but even if you want to convert text
|
||||||
`register_json()`.
|
fields to JSON) you can use the `register_json()` function.
|
||||||
|
|
||||||
.. __: http://people.planetpostgresql.org/andrew/index.php?/archives/255-JSON-for-PG-9.2-...-and-now-for-9.1!.html
|
.. __: http://people.planetpostgresql.org/andrew/index.php?/archives/255-JSON-for-PG-9.2-...-and-now-for-9.1!.html
|
||||||
|
|
||||||
The Python library used to convert Python objects to JSON depends on the
|
The Python :py:mod:`json` module is used by default to convert Python objects
|
||||||
language version: with Python 2.6 and following the :py:mod:`json` module from
|
to JSON and to parse data from the database.
|
||||||
the standard library is used; with previous versions the `simplejson`_ module
|
|
||||||
is used if available. Note that the last `!simplejson` version supporting
|
|
||||||
Python 2.4 is the 2.0.9.
|
|
||||||
|
|
||||||
.. _JSON: http://www.json.org/
|
.. _JSON: https://www.json.org/
|
||||||
.. |pgjson| replace:: :sql:`json`
|
.. |json| replace:: :sql:`json`
|
||||||
.. _pgjson: http://www.postgresql.org/docs/current/static/datatype-json.html
|
.. |jsonb| replace:: :sql:`jsonb`
|
||||||
.. _simplejson: http://pypi.python.org/pypi/simplejson/
|
.. |jsons| replace:: |json| and |jsonb|
|
||||||
|
.. _jsons: https://www.postgresql.org/docs/current/static/datatype-json.html
|
||||||
|
|
||||||
In order to pass a Python object to the database as query argument you can use
|
In order to pass a Python object to the database as query argument you can use
|
||||||
the `Json` adapter::
|
the `Json` adapter::
|
||||||
|
@ -186,8 +589,22 @@ the `Json` adapter::
|
||||||
curs.execute("insert into mytable (jsondata) values (%s)",
|
curs.execute("insert into mytable (jsondata) values (%s)",
|
||||||
[Json({'a': 100})])
|
[Json({'a': 100})])
|
||||||
|
|
||||||
Reading from the database, |pgjson| values will be automatically converted to
|
Reading from the database, |json| and |jsonb| values will be automatically
|
||||||
Python objects.
|
converted to Python objects.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If you are using the PostgreSQL :sql:`json` data type but you want to read
|
||||||
|
it as string in Python instead of having it parsed, your can either cast
|
||||||
|
the column to :sql:`text` in the query (it is an efficient operation, that
|
||||||
|
doesn't involve a copy)::
|
||||||
|
|
||||||
|
cur.execute("select jsondata::text from mytable")
|
||||||
|
|
||||||
|
or you can register a no-op `!loads()` function with
|
||||||
|
`register_default_json()`::
|
||||||
|
|
||||||
|
psycopg2.extras.register_default_json(loads=lambda x: x)
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -204,7 +621,7 @@ Python objects.
|
||||||
effects.
|
effects.
|
||||||
|
|
||||||
If you want to customize the adaptation from Python to PostgreSQL you can
|
If you want to customize the adaptation from Python to PostgreSQL you can
|
||||||
either provide a custom `!dumps()` function to `!Json`::
|
either provide a custom `!dumps()` function to `Json`::
|
||||||
|
|
||||||
curs.execute("insert into mytable (jsondata) values (%s)",
|
curs.execute("insert into mytable (jsondata) values (%s)",
|
||||||
[Json({'a': 100}, dumps=simplejson.dumps)])
|
[Json({'a': 100}, dumps=simplejson.dumps)])
|
||||||
|
@ -219,13 +636,22 @@ or you can subclass it overriding the `~Json.dumps()` method::
|
||||||
[MyJson({'a': 100})])
|
[MyJson({'a': 100})])
|
||||||
|
|
||||||
Customizing the conversion from PostgreSQL to Python can be done passing a
|
Customizing the conversion from PostgreSQL to Python can be done passing a
|
||||||
custom `!loads()` function to `register_json()` (or `register_default_json()`
|
custom `!loads()` function to `register_json()`. For the builtin data types
|
||||||
for PostgreSQL 9.2). For example, if you want to convert the float values
|
(|json| from PostgreSQL 9.2, |jsonb| from PostgreSQL 9.4) use
|
||||||
from :sql:`json` into :py:class:`~decimal.Decimal` you can use::
|
`register_default_json()` and `register_default_jsonb()`. For example, if you
|
||||||
|
want to convert the float values from :sql:`json` into
|
||||||
|
:py:class:`~decimal.Decimal` you can use::
|
||||||
|
|
||||||
loads = lambda x: json.loads(x, parse_float=Decimal)
|
loads = lambda x: json.loads(x, parse_float=Decimal)
|
||||||
psycopg2.extras.register_json(conn, loads=loads)
|
psycopg2.extras.register_json(conn, loads=loads)
|
||||||
|
|
||||||
|
Or, if you want to use an alternative JSON module implementation, such as the
|
||||||
|
faster UltraJSON_, you can use::
|
||||||
|
|
||||||
|
psycopg2.extras.register_default_json(loads=ujson.loads, globally=True)
|
||||||
|
psycopg2.extras.register_default_jsonb(loads=ujson.loads, globally=True)
|
||||||
|
|
||||||
|
.. _UltraJSON: https://pypi.org/project/ujson/
|
||||||
|
|
||||||
|
|
||||||
.. autoclass:: Json
|
.. autoclass:: Json
|
||||||
|
@ -234,8 +660,15 @@ from :sql:`json` into :py:class:`~decimal.Decimal` you can use::
|
||||||
|
|
||||||
.. autofunction:: register_json
|
.. autofunction:: register_json
|
||||||
|
|
||||||
|
.. versionchanged:: 2.5.4
|
||||||
|
added the *name* parameter to enable :sql:`jsonb` support.
|
||||||
|
|
||||||
.. autofunction:: register_default_json
|
.. autofunction:: register_default_json
|
||||||
|
|
||||||
|
.. autofunction:: register_default_jsonb
|
||||||
|
|
||||||
|
.. versionadded:: 2.5.4
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
@ -274,7 +707,7 @@ can be enabled using the `register_hstore()` function.
|
||||||
|
|
||||||
|
|
||||||
.. |hstore| replace:: :sql:`hstore`
|
.. |hstore| replace:: :sql:`hstore`
|
||||||
.. _hstore: http://www.postgresql.org/docs/current/static/hstore.html
|
.. _hstore: https://www.postgresql.org/docs/current/static/hstore.html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -296,7 +729,7 @@ after a table row type) into a Python named tuple, or into a regular tuple if
|
||||||
:py:func:`collections.namedtuple` is not found.
|
:py:func:`collections.namedtuple` is not found.
|
||||||
|
|
||||||
.. |CREATE TYPE| replace:: :sql:`CREATE TYPE`
|
.. |CREATE TYPE| replace:: :sql:`CREATE TYPE`
|
||||||
.. _CREATE TYPE: http://www.postgresql.org/docs/current/static/sql-createtype.html
|
.. _CREATE TYPE: https://www.postgresql.org/docs/current/static/sql-createtype.html
|
||||||
|
|
||||||
.. doctest::
|
.. doctest::
|
||||||
|
|
||||||
|
@ -410,7 +843,7 @@ PostgreSQL |range|_ types. Builtin |range| types are supported out-of-the-box;
|
||||||
user-defined |range| types can be adapted using `register_range()`.
|
user-defined |range| types can be adapted using `register_range()`.
|
||||||
|
|
||||||
.. |range| replace:: :sql:`range`
|
.. |range| replace:: :sql:`range`
|
||||||
.. _range: http://www.postgresql.org/docs/current/static/rangetypes.html
|
.. _range: https://www.postgresql.org/docs/current/static/rangetypes.html
|
||||||
|
|
||||||
.. autoclass:: Range
|
.. autoclass:: Range
|
||||||
|
|
||||||
|
@ -419,12 +852,20 @@ user-defined |range| types can be adapted using `register_range()`.
|
||||||
features: it doesn't perform normalization and doesn't implement all the
|
features: it doesn't perform normalization and doesn't implement all the
|
||||||
operators__ supported by the database.
|
operators__ supported by the database.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/functions-range.html#RANGE-OPERATORS-TABLE
|
.. __: https://www.postgresql.org/docs/current/static/functions-range.html#RANGE-OPERATORS-TABLE
|
||||||
|
|
||||||
`!Range` objects are immutable, hashable, and support the ``in`` operator
|
`!Range` objects are immutable, hashable, and support the ``in`` operator
|
||||||
(checking if an element is within the range). They can be tested for
|
(checking if an element is within the range). They can be tested for
|
||||||
equivalence but not for ordering. Empty ranges evaluate to `!False` in
|
equivalence. Empty ranges evaluate to `!False` in boolean context,
|
||||||
boolean context, nonempty evaluate to `!True`.
|
nonempty evaluate to `!True`.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.5.3
|
||||||
|
|
||||||
|
`!Range` objects can be sorted although, as on the server-side, this
|
||||||
|
ordering is not particularly meangingful. It is only meant to be used
|
||||||
|
by programs assuming objects using `!Range` as primary key can be
|
||||||
|
sorted on them. In previous versions comparing `!Range`\s raises
|
||||||
|
`!TypeError`.
|
||||||
|
|
||||||
Although it is possible to instantiate `!Range` objects, the class doesn't
|
Although it is possible to instantiate `!Range` objects, the class doesn't
|
||||||
have an adapter registered, so you cannot normally pass these instances as
|
have an adapter registered, so you cannot normally pass these instances as
|
||||||
|
@ -453,6 +894,17 @@ automatically casted into instances of these classes.
|
||||||
.. autoclass:: DateTimeRange
|
.. autoclass:: DateTimeRange
|
||||||
.. autoclass:: DateTimeTZRange
|
.. autoclass:: DateTimeTZRange
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Python lacks a representation for :sql:`infinity` date so Psycopg converts
|
||||||
|
the value to `date.max` and such. When written into the database these
|
||||||
|
dates will assume their literal value (e.g. :sql:`9999-12-31` instead of
|
||||||
|
:sql:`infinity`). Check :ref:`infinite-dates-handling` for an example of
|
||||||
|
an alternative adapter to map `date.max` to :sql:`infinity`. An
|
||||||
|
alternative dates adapter will be used automatically by the `DateRange`
|
||||||
|
adapter and so on.
|
||||||
|
|
||||||
|
|
||||||
Custom |range| types (created with |CREATE TYPE|_ :sql:`... AS RANGE`) can be
|
Custom |range| types (created with |CREATE TYPE|_ :sql:`... AS RANGE`) can be
|
||||||
adapted to a custom `Range` subclass:
|
adapted to a custom `Range` subclass:
|
||||||
|
|
||||||
|
@ -516,12 +968,29 @@ UUID data type
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
pair: INET; Data types
|
pair: INET; Data types
|
||||||
|
pair: CIDR; Data types
|
||||||
|
pair: MACADDR; Data types
|
||||||
|
|
||||||
:sql:`inet` data type
|
.. _adapt-network:
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. versionadded:: 2.0.9
|
Networking data types
|
||||||
.. versionchanged:: 2.4.5 added inet array support.
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
By default Psycopg casts the PostgreSQL networking data types (:sql:`inet`,
|
||||||
|
:sql:`cidr`, :sql:`macaddr`) into ordinary strings; array of such types are
|
||||||
|
converted into lists of strings.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
in previous version array of networking types were not treated as arrays.
|
||||||
|
|
||||||
|
.. autofunction:: register_ipaddress
|
||||||
|
|
||||||
|
|
||||||
|
.. autofunction:: register_inet
|
||||||
|
|
||||||
|
.. deprecated:: 2.7
|
||||||
|
this function will not receive further development and may disappear in
|
||||||
|
future versions.
|
||||||
|
|
||||||
.. doctest::
|
.. doctest::
|
||||||
|
|
||||||
|
@ -536,24 +1005,78 @@ UUID data type
|
||||||
'192.168.0.1/24'
|
'192.168.0.1/24'
|
||||||
|
|
||||||
|
|
||||||
.. autofunction:: register_inet
|
|
||||||
|
|
||||||
.. autoclass:: Inet
|
.. autoclass:: Inet
|
||||||
|
|
||||||
|
.. deprecated:: 2.7
|
||||||
|
this object will not receive further development and may disappear in
|
||||||
|
future versions.
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
|
||||||
single: Time zones; Fractional
|
|
||||||
|
|
||||||
Fractional time zones
|
.. _fast-exec:
|
||||||
---------------------
|
|
||||||
|
|
||||||
.. autofunction:: register_tstz_w_secs
|
Fast execution helpers
|
||||||
|
----------------------
|
||||||
|
|
||||||
.. versionadded:: 2.0.9
|
The current implementation of `~cursor.executemany()` is (using an extremely
|
||||||
|
charitable understatement) not particularly performing. These functions can
|
||||||
|
be used to speed up the repeated execution of a statement against a set of
|
||||||
|
parameters. By reducing the number of server roundtrips the performance can be
|
||||||
|
`orders of magnitude better`__ than using `!executemany()`.
|
||||||
|
|
||||||
|
.. __: https://github.com/psycopg/psycopg2/issues/491#issuecomment-276551038
|
||||||
|
|
||||||
|
|
||||||
|
.. autofunction:: execute_batch
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
>>> nums = ((1,), (5,), (10,))
|
||||||
|
>>> execute_batch(cur, "INSERT INTO test (num) VALUES (%s)", nums)
|
||||||
|
|
||||||
|
>>> tuples = ((123, "foo"), (42, "bar"), (23, "baz"))
|
||||||
|
>>> execute_batch(cur, "INSERT INTO test (num, data) VALUES (%s, %s)", tuples)
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
`!execute_batch()` can be also used in conjunction with PostgreSQL
|
||||||
|
prepared statements using |PREPARE|_, |EXECUTE|_, |DEALLOCATE|_.
|
||||||
|
Instead of executing::
|
||||||
|
|
||||||
|
execute_batch(cur,
|
||||||
|
"big and complex SQL with %s %s params",
|
||||||
|
params_list)
|
||||||
|
|
||||||
|
it is possible to execute something like::
|
||||||
|
|
||||||
|
cur.execute("PREPARE stmt AS big and complex SQL with $1 $2 params")
|
||||||
|
execute_batch(cur, "EXECUTE stmt (%s, %s)", params_list)
|
||||||
|
cur.execute("DEALLOCATE stmt")
|
||||||
|
|
||||||
|
which may bring further performance benefits: if the operation to perform
|
||||||
|
is complex, every single execution will be faster as the query plan is
|
||||||
|
already cached; furthermore the amount of data to send on the server will
|
||||||
|
be lesser (one |EXECUTE| per param set instead of the whole, likely
|
||||||
|
longer, statement).
|
||||||
|
|
||||||
|
.. |PREPARE| replace:: :sql:`PREPARE`
|
||||||
|
.. _PREPARE: https://www.postgresql.org/docs/current/static/sql-prepare.html
|
||||||
|
|
||||||
|
.. |EXECUTE| replace:: :sql:`EXECUTE`
|
||||||
|
.. _EXECUTE: https://www.postgresql.org/docs/current/static/sql-execute.html
|
||||||
|
|
||||||
|
.. |DEALLOCATE| replace:: :sql:`DEALLOCATE`
|
||||||
|
.. _DEALLOCATE: https://www.postgresql.org/docs/current/static/sql-deallocate.html
|
||||||
|
|
||||||
|
|
||||||
|
.. autofunction:: execute_values
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
.. versionchanged:: 2.8
|
||||||
|
added the *fetch* parameter.
|
||||||
|
|
||||||
.. versionchanged:: 2.2.2
|
|
||||||
function is no-op: see :ref:`tz-handling`.
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
pair: Example; Coroutine;
|
pair: Example; Coroutine;
|
||||||
|
@ -565,3 +1088,6 @@ Coroutine support
|
||||||
|
|
||||||
.. autofunction:: wait_select(conn)
|
.. autofunction:: wait_select(conn)
|
||||||
|
|
||||||
|
.. versionchanged:: 2.6.2
|
||||||
|
allow to cancel a query using :kbd:`Ctrl-C`, see
|
||||||
|
:ref:`the FAQ <faq-interrupt-query>` for an example.
|
||||||
|
|
163
doc/src/faq.rst
163
doc/src/faq.rst
|
@ -7,6 +7,30 @@ Here are a few gotchas you may encounter using `psycopg2`. Feel free to
|
||||||
suggest new entries!
|
suggest new entries!
|
||||||
|
|
||||||
|
|
||||||
|
Meta
|
||||||
|
----
|
||||||
|
|
||||||
|
.. _faq-question:
|
||||||
|
.. cssclass:: faq
|
||||||
|
|
||||||
|
How do I ask a question?
|
||||||
|
- Have you first checked if your question is answered already in the
|
||||||
|
documentation?
|
||||||
|
|
||||||
|
- If your question is about installing psycopg, have you checked the
|
||||||
|
:ref:`install FAQ <faq-compile>` and the :ref:`install docs
|
||||||
|
<installation>`?
|
||||||
|
|
||||||
|
- Have you googled for your error message?
|
||||||
|
|
||||||
|
- If you haven't found an answer yet, please write to the `Mailing List`_.
|
||||||
|
|
||||||
|
- If you haven't found a bug, DO NOT write to the bug tracker to ask
|
||||||
|
questions. You will only get piro grumpy.
|
||||||
|
|
||||||
|
.. _mailing list: https://www.postgresql.org/list/psycopg/
|
||||||
|
|
||||||
|
|
||||||
.. _faq-transactions:
|
.. _faq-transactions:
|
||||||
|
|
||||||
Problems with transactions handling
|
Problems with transactions handling
|
||||||
|
@ -40,7 +64,7 @@ I receive the error *current transaction is aborted, commands ignored until end
|
||||||
PostgreSQL supports nested transactions using the |SAVEPOINT|_ command).
|
PostgreSQL supports nested transactions using the |SAVEPOINT|_ command).
|
||||||
|
|
||||||
.. |SAVEPOINT| replace:: :sql:`SAVEPOINT`
|
.. |SAVEPOINT| replace:: :sql:`SAVEPOINT`
|
||||||
.. _SAVEPOINT: http://www.postgresql.org/docs/current/static/sql-savepoint.html
|
.. _SAVEPOINT: https://www.postgresql.org/docs/current/static/sql-savepoint.html
|
||||||
|
|
||||||
|
|
||||||
.. _faq-transaction-aborted-multiprocess:
|
.. _faq-transaction-aborted-multiprocess:
|
||||||
|
@ -73,7 +97,7 @@ Why does `!cursor.execute()` raise the exception *can't adapt*?
|
||||||
|
|
||||||
I can't pass an integer or a float parameter to my query: it says *a number is required*, but *it is* a number!
|
I can't pass an integer or a float parameter to my query: it says *a number is required*, but *it is* a number!
|
||||||
In your query string, you always have to use ``%s`` placeholders,
|
In your query string, you always have to use ``%s`` placeholders,
|
||||||
event when passing a number. All Python objects are converted by Psycopg
|
even when passing a number. All Python objects are converted by Psycopg
|
||||||
in their SQL representation, so they get passed to the query as strings.
|
in their SQL representation, so they get passed to the query as strings.
|
||||||
See :ref:`query-parameters`. ::
|
See :ref:`query-parameters`. ::
|
||||||
|
|
||||||
|
@ -108,6 +132,19 @@ My database is Unicode, but I receive all the strings as UTF-8 `!str`. Can I rec
|
||||||
See :ref:`unicode-handling` for the gory details.
|
See :ref:`unicode-handling` for the gory details.
|
||||||
|
|
||||||
|
|
||||||
|
.. _faq-bytes:
|
||||||
|
.. cssclass:: faq
|
||||||
|
|
||||||
|
My database is in mixed encoding. My program was working on Python 2 but Python 3 fails decoding the strings. How do I avoid decoding?
|
||||||
|
From psycopg 2.8 you can use the following adapters to always return bytes
|
||||||
|
from strings::
|
||||||
|
|
||||||
|
psycopg2.extensions.register_type(psycopg2.extensions.BYTES)
|
||||||
|
psycopg2.extensions.register_type(psycopg2.extensions.BYTESARRAY)
|
||||||
|
|
||||||
|
See :ref:`unicode-handling` for an example.
|
||||||
|
|
||||||
|
|
||||||
.. _faq-float:
|
.. _faq-float:
|
||||||
.. cssclass:: faq
|
.. cssclass:: faq
|
||||||
|
|
||||||
|
@ -121,10 +158,54 @@ Psycopg converts :sql:`decimal`\/\ :sql:`numeric` database types into Python `!D
|
||||||
psycopg2.extensions.register_type(DEC2FLOAT)
|
psycopg2.extensions.register_type(DEC2FLOAT)
|
||||||
|
|
||||||
See :ref:`type-casting-from-sql-to-python` to read the relevant
|
See :ref:`type-casting-from-sql-to-python` to read the relevant
|
||||||
documentation. If you find `!psycopg2.extensions.DECIMAL` not avalable, use
|
documentation. If you find `!psycopg2.extensions.DECIMAL` not available, use
|
||||||
`!psycopg2._psycopg.DECIMAL` instead.
|
`!psycopg2._psycopg.DECIMAL` instead.
|
||||||
|
|
||||||
|
|
||||||
|
.. _faq-json-adapt:
|
||||||
|
.. cssclass:: faq
|
||||||
|
|
||||||
|
Psycopg automatically converts PostgreSQL :sql:`json` data into Python objects. How can I receive strings instead?
|
||||||
|
The easiest way to avoid JSON parsing is to register a no-op function with
|
||||||
|
`~psycopg2.extras.register_default_json()`::
|
||||||
|
|
||||||
|
psycopg2.extras.register_default_json(loads=lambda x: x)
|
||||||
|
|
||||||
|
See :ref:`adapt-json` for further details.
|
||||||
|
|
||||||
|
|
||||||
|
.. _faq-jsonb-adapt:
|
||||||
|
.. cssclass:: faq
|
||||||
|
|
||||||
|
Psycopg converts :sql:`json` values into Python objects but :sql:`jsonb` values are returned as strings. Can :sql:`jsonb` be converted automatically?
|
||||||
|
Automatic conversion of :sql:`jsonb` values is supported from Psycopg
|
||||||
|
release 2.5.4. For previous versions you can register the :sql:`json`
|
||||||
|
typecaster on the :sql:`jsonb` oids (which are known and not supposed to
|
||||||
|
change in future PostgreSQL versions)::
|
||||||
|
|
||||||
|
psycopg2.extras.register_json(oid=3802, array_oid=3807, globally=True)
|
||||||
|
|
||||||
|
See :ref:`adapt-json` for further details.
|
||||||
|
|
||||||
|
|
||||||
|
.. _faq-identifier:
|
||||||
|
.. cssclass:: faq
|
||||||
|
|
||||||
|
How can I pass field/table names to a query?
|
||||||
|
The arguments in the `~cursor.execute()` methods can only represent data
|
||||||
|
to pass to the query: they cannot represent a table or field name::
|
||||||
|
|
||||||
|
# This doesn't work
|
||||||
|
cur.execute("insert into %s values (%s)", ["my_table", 42])
|
||||||
|
|
||||||
|
If you want to build a query dynamically you can use the objects exposed
|
||||||
|
by the `psycopg2.sql` module::
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
sql.SQL("insert into %s values (%%s)") % [sql.Identifier("my_table")],
|
||||||
|
[42])
|
||||||
|
|
||||||
|
|
||||||
.. _faq-bytea-9.0:
|
.. _faq-bytea-9.0:
|
||||||
.. cssclass:: faq
|
.. cssclass:: faq
|
||||||
|
|
||||||
|
@ -140,8 +221,8 @@ Transferring binary data from PostgreSQL 9.0 doesn't work.
|
||||||
session before reading binary data;
|
session before reading binary data;
|
||||||
- upgrade the libpq library on the client to at least 9.0.
|
- upgrade the libpq library on the client to at least 9.0.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/datatype-binary.html
|
.. __: https://www.postgresql.org/docs/current/static/datatype-binary.html
|
||||||
.. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-BYTEA-OUTPUT
|
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-BYTEA-OUTPUT
|
||||||
|
|
||||||
|
|
||||||
.. _faq-array:
|
.. _faq-array:
|
||||||
|
@ -190,24 +271,64 @@ When should I save and re-use a connection as opposed to creating a new one as n
|
||||||
|
|
||||||
What are the advantages or disadvantages of using named cursors?
|
What are the advantages or disadvantages of using named cursors?
|
||||||
The only disadvantages is that they use up resources on the server and
|
The only disadvantages is that they use up resources on the server and
|
||||||
that there is a little overhead because a at least two queries (one to
|
that there is a little overhead because at least two queries (one to
|
||||||
create the cursor and one to fetch the initial result set) are issued to
|
create the cursor and one to fetch the initial result set) are issued to
|
||||||
the backend. The advantage is that data is fetched one chunk at a time:
|
the backend. The advantage is that data is fetched one chunk at a time:
|
||||||
using small `~cursor.fetchmany()` values it is possible to use very
|
using small `~cursor.fetchmany()` values it is possible to use very
|
||||||
little memory on the client and to skip or discard parts of the result set.
|
little memory on the client and to skip or discard parts of the result set.
|
||||||
|
|
||||||
|
|
||||||
|
.. _faq-interrupt-query:
|
||||||
|
.. cssclass:: faq
|
||||||
|
|
||||||
|
How do I interrupt a long-running query in an interactive shell?
|
||||||
|
Normally the interactive shell becomes unresponsive to :kbd:`Ctrl-C` when
|
||||||
|
running a query. Using a connection in green mode allows Python to
|
||||||
|
receive and handle the interrupt, although it may leave the connection
|
||||||
|
broken, if the async callback doesn't handle the `!KeyboardInterrupt`
|
||||||
|
correctly.
|
||||||
|
|
||||||
|
Starting from psycopg 2.6.2, the `~psycopg2.extras.wait_select` callback
|
||||||
|
can handle a :kbd:`Ctrl-C` correctly. For previous versions, you can use
|
||||||
|
`this implementation`__.
|
||||||
|
|
||||||
|
.. __: https://www.psycopg.org/articles/2014/07/20/cancelling-postgresql-statements-python/
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> psycopg2.extensions.set_wait_callback(psycopg2.extras.wait_select)
|
||||||
|
>>> cnn = psycopg2.connect('')
|
||||||
|
>>> cur = cnn.cursor()
|
||||||
|
>>> cur.execute("select pg_sleep(10)")
|
||||||
|
^C
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "<stdin>", line 1, in <module>
|
||||||
|
QueryCanceledError: canceling statement due to user request
|
||||||
|
|
||||||
|
>>> cnn.rollback()
|
||||||
|
>>> # You can use the connection and cursor again from here
|
||||||
|
|
||||||
|
|
||||||
.. _faq-compile:
|
.. _faq-compile:
|
||||||
|
|
||||||
Problems compiling and deploying psycopg2
|
Problems compiling and installing psycopg2
|
||||||
-----------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
|
.. _faq-wheels:
|
||||||
|
.. cssclass:: faq
|
||||||
|
|
||||||
|
Psycopg 2.8 fails to install, Psycopg 2.7 was working fine.
|
||||||
|
With Psycopg 2.7 you were installing binary packages, but they have proven
|
||||||
|
unreliable so now you have to install them explicitly using the
|
||||||
|
``psycopg2-binary`` package. See :ref:`binary-packages` for all the
|
||||||
|
details.
|
||||||
|
|
||||||
.. _faq-python-h:
|
.. _faq-python-h:
|
||||||
.. cssclass:: faq
|
.. cssclass:: faq
|
||||||
|
|
||||||
I can't compile `!psycopg2`: the compiler says *error: Python.h: No such file or directory*. What am I missing?
|
I can't compile `!psycopg2`: the compiler says *error: Python.h: No such file or directory*. What am I missing?
|
||||||
You need to install a Python development package: it is usually called
|
You need to install a Python development package: it is usually called
|
||||||
``python-dev``.
|
``python-dev`` or ``python3-dev`` according to your Python version.
|
||||||
|
|
||||||
|
|
||||||
.. _faq-libpq-fe-h:
|
.. _faq-libpq-fe-h:
|
||||||
|
@ -222,19 +343,28 @@ I can't compile `!psycopg2`: the compiler says *error: libpq-fe.h: No such file
|
||||||
.. cssclass:: faq
|
.. cssclass:: faq
|
||||||
|
|
||||||
`!psycopg2` raises `!ImportError` with message *_psycopg.so: undefined symbol: lo_truncate* when imported.
|
`!psycopg2` raises `!ImportError` with message *_psycopg.so: undefined symbol: lo_truncate* when imported.
|
||||||
This means that Psycopg has been compiled with |lo_truncate|_ support,
|
This means that Psycopg was compiled with |lo_truncate|_ support (*i.e.*
|
||||||
which means that the libpq used at compile time was version >= 8.3, but at
|
the libpq used at compile time was version >= 8.3) but at runtime an older
|
||||||
runtime an older libpq library is found. You can use::
|
libpq dynamic library is found.
|
||||||
|
|
||||||
|
Fast-forward several years, if the message reports *undefined symbol:
|
||||||
|
lo_truncate64* it means that Psycopg was built with large objects 64 bits
|
||||||
|
API support (*i.e.* the libpq used at compile time was at least 9.3) but
|
||||||
|
at runtime an older libpq dynamic library is found.
|
||||||
|
|
||||||
|
You can use:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
$ ldd /path/to/packages/psycopg2/_psycopg.so | grep libpq
|
$ ldd /path/to/packages/psycopg2/_psycopg.so | grep libpq
|
||||||
|
|
||||||
to find what is the version used at runtime.
|
to find what is the libpq dynamic library used at runtime.
|
||||||
|
|
||||||
You can avoid the problem by using the same version of the
|
You can avoid the problem by using the same version of the
|
||||||
:program:`pg_config` at install time and the libpq at runtime.
|
:program:`pg_config` at install time and the libpq at runtime.
|
||||||
|
|
||||||
.. |lo_truncate| replace:: `!lo_truncate()`
|
.. |lo_truncate| replace:: `!lo_truncate()`
|
||||||
.. _lo_truncate: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-TRUNCATE
|
.. _lo_truncate: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-TRUNCATE
|
||||||
|
|
||||||
|
|
||||||
.. _faq-import-mod_wsgi:
|
.. _faq-import-mod_wsgi:
|
||||||
|
@ -248,6 +378,5 @@ Psycopg raises *ImportError: cannot import name tz* on import in mod_wsgi / ASP,
|
||||||
use the WSGIPythonEggs__ directive.
|
use the WSGIPythonEggs__ directive.
|
||||||
|
|
||||||
.. _egg: http://peak.telecommunity.com/DevCenter/PythonEggs
|
.. _egg: http://peak.telecommunity.com/DevCenter/PythonEggs
|
||||||
.. __: http://stackoverflow.com/questions/2192323/what-is-the-python-egg-cache-python-egg-cache
|
.. __: https://stackoverflow.com/questions/2192323/what-is-the-python-egg-cache-python-egg-cache
|
||||||
.. __: http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPythonEggs
|
.. __: https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIPythonEggs.html
|
||||||
|
|
||||||
|
|
|
@ -14,27 +14,19 @@ of concurrent :sql:`INSERT`\s or :sql:`UPDATE`\s.
|
||||||
Psycopg 2 is mostly implemented in C as a libpq_ wrapper, resulting in being
|
Psycopg 2 is mostly implemented in C as a libpq_ wrapper, resulting in being
|
||||||
both efficient and secure. It features client-side and :ref:`server-side
|
both efficient and secure. It features client-side and :ref:`server-side
|
||||||
<server-side-cursors>` cursors, :ref:`asynchronous communication
|
<server-side-cursors>` cursors, :ref:`asynchronous communication
|
||||||
<async-support>` and :ref:`notifications <async-notify>`, |COPY-TO-FROM|__
|
<async-support>` and :ref:`notifications <async-notify>`, :ref:`COPY <copy>`
|
||||||
support, and a flexible :ref:`objects adaptation system
|
support. Many Python types are supported out-of-the-box and :ref:`adapted to
|
||||||
<python-types-adaptation>`. Many basic Python types are supported
|
matching PostgreSQL data types <python-types-adaptation>`; adaptation can be
|
||||||
out-of-the-box and mapped to matching PostgreSQL data types, such as strings
|
extended and customized thanks to a flexible :ref:`objects adaptation system
|
||||||
(both byte strings and Unicode), numbers (ints, longs, floats, decimals),
|
<adapting-new-types>`.
|
||||||
booleans and date/time objects (both built-in and `mx.DateTime`_), several
|
|
||||||
types of :ref:`binary objects <adapt-binary>`. Also available are mappings
|
|
||||||
between lists and PostgreSQL arrays of any supported type, between
|
|
||||||
:ref:`dictionaries and PostgreSQL hstore <adapt-hstore>`, between
|
|
||||||
:ref:`tuples/namedtuples and PostgreSQL composite types <adapt-composite>`,
|
|
||||||
and between Python objects and :ref:`JSON <adapt-json>`.
|
|
||||||
|
|
||||||
Psycopg 2 is both Unicode and Python 3 friendly.
|
Psycopg 2 is both Unicode and Python 3 friendly.
|
||||||
|
|
||||||
|
|
||||||
.. _Psycopg: http://initd.org/psycopg/
|
.. _Psycopg: https://psycopg.org/
|
||||||
.. _PostgreSQL: http://www.postgresql.org/
|
.. _PostgreSQL: https://www.postgresql.org/
|
||||||
.. _Python: http://www.python.org/
|
.. _Python: https://www.python.org/
|
||||||
.. _libpq: http://www.postgresql.org/docs/current/static/libpq.html
|
.. _libpq: https://www.postgresql.org/docs/current/static/libpq.html
|
||||||
.. |COPY-TO-FROM| replace:: :sql:`COPY TO/COPY FROM`
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/sql-copy.html
|
|
||||||
|
|
||||||
|
|
||||||
.. rubric:: Contents
|
.. rubric:: Contents
|
||||||
|
@ -49,12 +41,15 @@ Psycopg 2 is both Unicode and Python 3 friendly.
|
||||||
cursor
|
cursor
|
||||||
advanced
|
advanced
|
||||||
extensions
|
extensions
|
||||||
|
extras
|
||||||
|
errors
|
||||||
|
sql
|
||||||
tz
|
tz
|
||||||
pool
|
pool
|
||||||
extras
|
|
||||||
errorcodes
|
errorcodes
|
||||||
faq
|
faq
|
||||||
news
|
news
|
||||||
|
license
|
||||||
|
|
||||||
|
|
||||||
.. ifconfig:: builder != 'text'
|
.. ifconfig:: builder != 'text'
|
||||||
|
@ -62,6 +57,7 @@ Psycopg 2 is both Unicode and Python 3 friendly.
|
||||||
.. rubric:: Indices and tables
|
.. rubric:: Indices and tables
|
||||||
|
|
||||||
* :ref:`genindex`
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
* :ref:`search`
|
* :ref:`search`
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,4 +68,3 @@ Psycopg 2 is both Unicode and Python 3 friendly.
|
||||||
**To Do items in the documentation**
|
**To Do items in the documentation**
|
||||||
|
|
||||||
.. todolist::
|
.. todolist::
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
Introduction
|
.. _installation:
|
||||||
|
|
||||||
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||||
|
@ -6,120 +8,161 @@ Introduction
|
||||||
Psycopg is a PostgreSQL_ adapter for the Python_ programming language. It is a
|
Psycopg is a PostgreSQL_ adapter for the Python_ programming language. It is a
|
||||||
wrapper for the libpq_, the official PostgreSQL client library.
|
wrapper for the libpq_, the official PostgreSQL client library.
|
||||||
|
|
||||||
The `psycopg2` package is the current mature implementation of the adapter: it
|
.. _PostgreSQL: https://www.postgresql.org/
|
||||||
is a C extension and as such it is only compatible with CPython_. If you want
|
.. _Python: https://www.python.org/
|
||||||
to use Psycopg on a different Python implementation (PyPy, Jython, IronPython)
|
|
||||||
there is an experimental `porting of Psycopg for Ctypes`__, but it is not as
|
|
||||||
mature as the C implementation yet.
|
.. index::
|
||||||
|
single: Install; from PyPI
|
||||||
|
single: Install; wheel
|
||||||
|
single: Wheel
|
||||||
|
|
||||||
|
.. _binary-packages:
|
||||||
|
|
||||||
|
Quick Install
|
||||||
|
-------------
|
||||||
|
|
||||||
|
For most operating systems, the quickest way to install Psycopg is using the
|
||||||
|
wheel_ package available on PyPI_:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ pip install psycopg2-binary
|
||||||
|
|
||||||
|
This will install a pre-compiled binary version of the module which does not
|
||||||
|
require the build or runtime prerequisites described below. Make sure to use
|
||||||
|
an up-to-date version of :program:`pip` (you can upgrade it using something
|
||||||
|
like ``pip install -U pip``).
|
||||||
|
|
||||||
|
You may then import the ``psycopg2`` package, as usual:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
|
||||||
|
# Connect to your postgres DB
|
||||||
|
conn = psycopg2.connect("dbname=test user=postgres")
|
||||||
|
|
||||||
|
# Open a cursor to perform database operations
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# Execute a query
|
||||||
|
cur.execute("SELECT * FROM my_data")
|
||||||
|
|
||||||
|
# Retrieve query results
|
||||||
|
records = cur.fetchall()
|
||||||
|
|
||||||
|
.. _PyPI: https://pypi.org/project/psycopg2-binary/
|
||||||
|
.. _wheel: https://pythonwheels.com/
|
||||||
|
|
||||||
|
|
||||||
|
psycopg vs psycopg-binary
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The ``psycopg2-binary`` package is meant for beginners to start playing
|
||||||
|
with Python and PostgreSQL without the need to meet the build
|
||||||
|
requirements.
|
||||||
|
|
||||||
|
If you are the maintainer of a published package depending on `!psycopg2`
|
||||||
|
you shouldn't use ``psycopg2-binary`` as a module dependency. **For
|
||||||
|
production use you are advised to use the source distribution.**
|
||||||
|
|
||||||
|
The binary packages come with their own versions of a few C libraries,
|
||||||
|
among which ``libpq`` and ``libssl``, which will be used regardless of other
|
||||||
|
libraries available on the client: upgrading the system libraries will not
|
||||||
|
upgrade the libraries used by `!psycopg2`. Please build `!psycopg2` from
|
||||||
|
source if you want to maintain binary upgradeability.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
The `!psycopg2` wheel package comes packaged, among the others, with its
|
||||||
|
own ``libssl`` binary. This may create conflicts with other extension
|
||||||
|
modules binding with ``libssl`` as well, for instance with the Python
|
||||||
|
`ssl` module: in some cases, under concurrency, the interaction between
|
||||||
|
the two libraries may result in a segfault. In case of doubts you are
|
||||||
|
advised to use a package built from source.
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
single: Install; disable wheel
|
||||||
|
single: Wheel; disable
|
||||||
|
|
||||||
|
.. _disable-wheel:
|
||||||
|
|
||||||
|
Change in binary packages between Psycopg 2.7 and 2.8
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In version 2.7.x, :command:`pip install psycopg2` would have tried to install
|
||||||
|
automatically the binary package of Psycopg. Because of concurrency problems
|
||||||
|
binary packages have displayed, ``psycopg2-binary`` has become a separate
|
||||||
|
package, and from 2.8 it has become the only way to install the binary
|
||||||
|
package.
|
||||||
|
|
||||||
|
If you are using Psycopg 2.7 and you want to disable the use of wheel binary
|
||||||
|
packages, relying on the system libraries available on your client, you
|
||||||
|
can use the :command:`pip` |--no-binary option|__, e.g.:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ pip install --no-binary :all: psycopg2
|
||||||
|
|
||||||
|
.. |--no-binary option| replace:: ``--no-binary`` option
|
||||||
|
.. __: https://pip.pypa.io/en/stable/reference/pip_install/#install-no-binary
|
||||||
|
|
||||||
|
which can be specified in your :file:`requirements.txt` files too, e.g. use:
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
psycopg2>=2.7,<2.8 --no-binary psycopg2
|
||||||
|
|
||||||
|
to use the last bugfix release of the `!psycopg2` 2.7 package, specifying to
|
||||||
|
always compile it from source. Of course in this case you will have to meet
|
||||||
|
the :ref:`build prerequisites <build-prerequisites>`.
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
single: Prerequisites
|
||||||
|
|
||||||
|
Prerequisites
|
||||||
|
-------------
|
||||||
|
|
||||||
The current `!psycopg2` implementation supports:
|
The current `!psycopg2` implementation supports:
|
||||||
|
|
||||||
- Python 2 versions from 2.5 to 2.7
|
..
|
||||||
- Python 3 versions from 3.1 to 3.3
|
NOTE: keep consistent with setup.py and the /features/ page.
|
||||||
- PostgreSQL versions from 7.4 to 9.2
|
|
||||||
|
|
||||||
.. _PostgreSQL: http://www.postgresql.org/
|
|
||||||
.. _Python: http://www.python.org/
|
|
||||||
.. _libpq: http://www.postgresql.org/docs/current/static/libpq.html
|
|
||||||
.. _CPython: http://en.wikipedia.org/wiki/CPython
|
|
||||||
.. _Ctypes: http://docs.python.org/library/ctypes.html
|
|
||||||
.. __: https://github.com/mvantellingen/psycopg2-ctypes
|
|
||||||
|
|
||||||
|
- Python versions from 3.8 to 3.13
|
||||||
|
- PostgreSQL server versions from 7.4 to 17
|
||||||
|
- PostgreSQL client library version from 9.1
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
`!psycopg2` usually depends at runtime on the libpq dynamic library.
|
Not all the psycopg2 versions support all the supported Python versions.
|
||||||
However it can connect to PostgreSQL servers of any supported version,
|
|
||||||
independently of the version of the libpq used: just install the most
|
Please see the :ref:`release notes <news>` to verify when the support for
|
||||||
recent libpq version or the most practical, without trying to match it to
|
a new Python version was added and when the support for an old Python
|
||||||
the version of the PostgreSQL server you will have to connect to.
|
version was removed.
|
||||||
|
|
||||||
|
|
||||||
Installation
|
.. _build-prerequisites:
|
||||||
============
|
|
||||||
|
|
||||||
If possible, and usually it is, please :ref:`install Psycopg from a package
|
Build prerequisites
|
||||||
<install-from-package>` available for your distribution or operating system.
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Compiling from source is a very easy task, however `!psycopg2` is a C
|
The build prerequisites are to be met in order to install Psycopg from source
|
||||||
extension module and as such it requires a few more things in place respect to
|
code, from a source distribution package, GitHub_ or from PyPI.
|
||||||
a pure Python module. So, if you don't have experience compiling Python
|
|
||||||
extension packages, *above all if you are a Windows or a Mac OS user*, please
|
|
||||||
use a pre-compiled package and go straight to the :ref:`module usage <usage>`
|
|
||||||
avoid bothering with the gory details.
|
|
||||||
|
|
||||||
|
.. _GitHub: https://github.com/psycopg/psycopg2
|
||||||
|
|
||||||
|
Psycopg is a C wrapper around the libpq_ PostgreSQL client library. To install
|
||||||
.. _install-from-package:
|
it from sources you will need:
|
||||||
|
|
||||||
Install from a package
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
.. index::
|
|
||||||
pair: Install; Linux
|
|
||||||
|
|
||||||
**Linux**
|
|
||||||
Psycopg is available already packaged in many Linux distributions: look
|
|
||||||
for a package such as ``python-psycopg2`` using the package manager of
|
|
||||||
your choice.
|
|
||||||
|
|
||||||
On Debian, Ubuntu and other deb-based distributions you should just need::
|
|
||||||
|
|
||||||
sudo apt-get install python-psycopg2
|
|
||||||
|
|
||||||
to install the package with all its dependencies.
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
|
||||||
pair: Install; Mac OS X
|
|
||||||
|
|
||||||
**Mac OS X**
|
|
||||||
Psycopg is available as a `fink package`__ in the *unstable* tree: you may
|
|
||||||
install it with::
|
|
||||||
|
|
||||||
fink install psycopg2-py27
|
|
||||||
|
|
||||||
.. __: http://pdb.finkproject.org/pdb/package.php/psycopg2-py27
|
|
||||||
|
|
||||||
The library is also available on `MacPorts`__ try::
|
|
||||||
|
|
||||||
sudo port install py27-psycopg2
|
|
||||||
|
|
||||||
.. __: http://www.macports.org/
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
|
||||||
pair: Install; Windows
|
|
||||||
|
|
||||||
**Microsoft Windows**
|
|
||||||
Jason Erickson maintains a packaged `Windows port of Psycopg`__ with
|
|
||||||
installation executable. Download. Double click. Done.
|
|
||||||
|
|
||||||
.. __: http://www.stickpeople.com/projects/python/win-psycopg/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
|
||||||
single: Install; from source
|
|
||||||
|
|
||||||
.. _install-from-source:
|
|
||||||
|
|
||||||
Install from source
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
These notes illustrate how to compile Psycopg on Linux. If you want to compile
|
|
||||||
Psycopg on other platforms you may have to adjust some details accordingly.
|
|
||||||
|
|
||||||
.. _requirements:
|
|
||||||
|
|
||||||
Psycopg is a C wrapper to the libpq PostgreSQL client library. To install it
|
|
||||||
from sources you will need:
|
|
||||||
|
|
||||||
- A C compiler.
|
- A C compiler.
|
||||||
|
|
||||||
- The Python header files. They are usually installed in a package such as
|
- The Python header files. They are usually installed in a package such as
|
||||||
**python-dev**. A message such as *error: Python.h: No such file or
|
**python-dev** or **python3-dev**. A message such as *error: Python.h: No
|
||||||
directory* is an indication that the Python headers are missing.
|
such file or directory* is an indication that the Python headers are
|
||||||
|
missing.
|
||||||
|
|
||||||
- The libpq header files. They are usually installed in a package such as
|
- The libpq header files. They are usually installed in a package such as
|
||||||
**libpq-dev**. If you get an *error: libpq-fe.h: No such file or directory*
|
**libpq-dev**. If you get an *error: libpq-fe.h: No such file or directory*
|
||||||
|
@ -131,12 +174,40 @@ from sources you will need:
|
||||||
running ``pg_config --version``: if it returns an error or an unexpected
|
running ``pg_config --version``: if it returns an error or an unexpected
|
||||||
version number then locate the directory containing the :program:`pg_config`
|
version number then locate the directory containing the :program:`pg_config`
|
||||||
shipped with the right libpq version (usually
|
shipped with the right libpq version (usually
|
||||||
``/usr/lib/postgresql/X.Y/bin/``) and add it to the :envvar:`PATH`::
|
``/usr/lib/postgresql/X.Y/bin/``) and add it to the :envvar:`PATH`:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
$ export PATH=/usr/lib/postgresql/X.Y/bin/:$PATH
|
$ export PATH=/usr/lib/postgresql/X.Y/bin/:$PATH
|
||||||
|
|
||||||
You only need it to compile and install `!psycopg2`, not for its regular
|
You only need :program:`pg_config` to compile `!psycopg2`, not for its
|
||||||
usage.
|
regular usage.
|
||||||
|
|
||||||
|
Once everything is in place it's just a matter of running the standard:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ pip install psycopg2
|
||||||
|
|
||||||
|
or, from the directory containing the source code:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ python setup.py build
|
||||||
|
$ python setup.py install
|
||||||
|
|
||||||
|
|
||||||
|
Runtime requirements
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Unless you compile `!psycopg2` as a static library, or you install it from a
|
||||||
|
self-contained wheel package, it will need the libpq_ library at runtime
|
||||||
|
(usually distributed in a ``libpq.so`` or ``libpq.dll`` file). `!psycopg2`
|
||||||
|
relies on the host OS to find the library if the library is installed in a
|
||||||
|
standard location there is usually no problem; if the library is in a
|
||||||
|
non-standard location you will have to tell Psycopg how to find it,
|
||||||
|
which is OS-dependent (for instance setting a suitable
|
||||||
|
:envvar:`LD_LIBRARY_PATH` on Linux).
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -146,61 +217,40 @@ from sources you will need:
|
||||||
:program:`ldd`) if the module ``psycopg2/_psycopg.so`` is linked to the
|
:program:`ldd`) if the module ``psycopg2/_psycopg.so`` is linked to the
|
||||||
right ``libpq.so``.
|
right ``libpq.so``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Whatever version of libpq `!psycopg2` is compiled with, it will be
|
||||||
.. index::
|
possible to connect to PostgreSQL servers of any supported version: just
|
||||||
single: Install; from PyPI
|
install the most recent libpq version or the most practical, without
|
||||||
|
trying to match it to the version of the PostgreSQL server you will have
|
||||||
.. _package-manager:
|
to connect to.
|
||||||
|
|
||||||
Use a Python package manager
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
If the above requirements are satisfied, you can use :program:`easy_install`,
|
|
||||||
:program:`pip` or whatever the Python package manager of the week::
|
|
||||||
|
|
||||||
$ pip install psycopg2
|
|
||||||
|
|
||||||
Please refer to your package manager documentation about performing a local or
|
|
||||||
global installation, :program:`virtualenv` (fully supported by recent Psycopg
|
|
||||||
versions), using different Python versions and other nuances.
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
single: setup.py
|
single: setup.py
|
||||||
single: setup.cfg
|
single: setup.cfg
|
||||||
|
|
||||||
.. _source-package:
|
Non-standard builds
|
||||||
|
-------------------
|
||||||
Use the source package
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
You can download a copy of Psycopg source files from the `Psycopg download
|
|
||||||
page`__. Once unpackaged, to compile and install the package you can run::
|
|
||||||
|
|
||||||
$ python setup.py build
|
|
||||||
$ sudo python setup.py install
|
|
||||||
|
|
||||||
If you have less standard requirements such as:
|
If you have less standard requirements such as:
|
||||||
|
|
||||||
- creating a :ref:`debug build <debug-build>`,
|
- creating a :ref:`debug build <debug-build>`,
|
||||||
- using :program:`pg_config` not in the :envvar:`PATH`,
|
- using :program:`pg_config` not in the :envvar:`PATH`,
|
||||||
- supporting ``mx.DateTime``,
|
|
||||||
|
|
||||||
then take a look at the ``setup.cfg`` file.
|
then take a look at the ``setup.cfg`` file.
|
||||||
|
|
||||||
Some of the options available in ``setup.cfg`` are also available as command
|
Some of the options available in ``setup.cfg`` are also available as command
|
||||||
line arguments of the ``build_ext`` sub-command. For instance you can specify
|
line arguments of the ``build_ext`` sub-command. For instance you can specify
|
||||||
an alternate :program:`pg_config` version using::
|
an alternate :program:`pg_config` location using:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
$ python setup.py build_ext --pg-config /path/to/pg_config build
|
$ python setup.py build_ext --pg-config /path/to/pg_config build
|
||||||
|
|
||||||
Use ``python setup.py build_ext --help`` to get a list of the options
|
Use ``python setup.py build_ext --help`` to get a list of the options
|
||||||
supported.
|
supported.
|
||||||
|
|
||||||
.. __: http://initd.org/psycopg/download/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
single: debug
|
single: debug
|
||||||
|
@ -209,30 +259,83 @@ supported.
|
||||||
.. _debug-build:
|
.. _debug-build:
|
||||||
|
|
||||||
Creating a debug build
|
Creating a debug build
|
||||||
----------------------
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
In case of problems, Psycopg can be configured to emit detailed debug
|
In case of problems, Psycopg can be configured to emit detailed debug
|
||||||
messages, which can be very useful for diagnostics and to report a bug. In
|
messages, which can be very useful for diagnostics and to report a bug. In
|
||||||
order to create a debug package:
|
order to create a debug package:
|
||||||
|
|
||||||
- `Download`__ and unpack the Psycopg source package.
|
- `Download`__ and unpack the Psycopg *source package* (the ``.tar.gz``
|
||||||
|
package).
|
||||||
|
|
||||||
- Edit the ``setup.cfg`` file adding the ``PSYCOPG_DEBUG`` flag to the
|
- Edit the ``setup.cfg`` file adding the ``PSYCOPG_DEBUG`` flag to the
|
||||||
``define`` option.
|
``define`` option.
|
||||||
|
|
||||||
- :ref:`Compile and install <source-package>` the package.
|
- :ref:`Compile and install <build-prerequisites>` the package.
|
||||||
|
|
||||||
- Set the :envvar:`PSYCOPG_DEBUG` variable::
|
- Set the :envvar:`PSYCOPG_DEBUG` environment variable:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
$ export PSYCOPG_DEBUG=1
|
$ export PSYCOPG_DEBUG=1
|
||||||
|
|
||||||
- Run your program (making sure that the `!psycopg2` package imported is the
|
- Run your program (making sure that the `!psycopg2` package imported is the
|
||||||
one you just compiled and not e.g. the system one): you will have a copious
|
one you just compiled and not e.g. the system one): you will have a copious
|
||||||
stream of informations printed on stdout.
|
stream of informations printed on stderr.
|
||||||
|
|
||||||
.. __: http://initd.org/psycopg/download/
|
.. __: https://pypi.org/project/psycopg2/#files
|
||||||
|
|
||||||
|
|
||||||
|
Non-standard Python Implementation
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The `psycopg2` package is the current mature implementation of the adapter: it
|
||||||
|
is a C extension and as such it is only compatible with CPython_. If you want
|
||||||
|
to use Psycopg on a different Python implementation (PyPy, Jython, IronPython)
|
||||||
|
there is a couple of alternative:
|
||||||
|
|
||||||
|
- a `Ctypes port`__, but it is not as mature as the C implementation yet
|
||||||
|
and it is not as feature-complete;
|
||||||
|
|
||||||
|
- a `CFFI port`__ which is currently more used and reported more efficient on
|
||||||
|
PyPy, but please be careful of its version numbers because they are not
|
||||||
|
aligned to the official psycopg2 ones and some features may differ.
|
||||||
|
|
||||||
|
.. _PostgreSQL: https://www.postgresql.org/
|
||||||
|
.. _Python: https://www.python.org/
|
||||||
|
.. _libpq: https://www.postgresql.org/docs/current/static/libpq.html
|
||||||
|
.. _CPython: https://en.wikipedia.org/wiki/CPython
|
||||||
|
.. _Ctypes: https://docs.python.org/library/ctypes.html
|
||||||
|
.. __: https://github.com/mvantellingen/psycopg2-ctypes
|
||||||
|
.. __: https://github.com/chtd/psycopg2cffi
|
||||||
|
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
single: tests
|
||||||
|
|
||||||
|
.. _test-suite:
|
||||||
|
|
||||||
|
Running the test suite
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Once `!psycopg2` is installed you can run the test suite to verify it is
|
||||||
|
working correctly. From the source directory, you can run:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ python -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')" --verbose
|
||||||
|
|
||||||
|
The tests run against a database called ``psycopg2_test`` on UNIX socket and
|
||||||
|
the standard port. You can configure a different database to run the test by
|
||||||
|
setting the environment variables:
|
||||||
|
|
||||||
|
- :envvar:`PSYCOPG2_TESTDB`
|
||||||
|
- :envvar:`PSYCOPG2_TESTDB_HOST`
|
||||||
|
- :envvar:`PSYCOPG2_TESTDB_PORT`
|
||||||
|
- :envvar:`PSYCOPG2_TESTDB_USER`
|
||||||
|
|
||||||
|
The database should already exist before running the tests.
|
||||||
|
|
||||||
|
|
||||||
.. _other-problems:
|
.. _other-problems:
|
||||||
|
|
||||||
|
@ -241,18 +344,21 @@ If you still have problems
|
||||||
|
|
||||||
Try the following. *In order:*
|
Try the following. *In order:*
|
||||||
|
|
||||||
- Read again the :ref:`requirements <requirements>`.
|
- Read again the :ref:`build-prerequisites`.
|
||||||
|
|
||||||
- Read the :ref:`FAQ <faq-compile>`.
|
- Read the :ref:`FAQ <faq-compile>`.
|
||||||
|
|
||||||
- Google for `!psycopg2` *your error message*. Especially useful the week
|
- Google for `!psycopg2` *your error message*. Especially useful the week
|
||||||
after the release of a new OS X version.
|
after the release of a new OS X version.
|
||||||
|
|
||||||
- Write to the `Mailing List`__.
|
- Write to the `Mailing List`_.
|
||||||
|
|
||||||
|
- If you think that you have discovered a bug, test failure or missing feature
|
||||||
|
please raise a ticket in the `bug tracker`_.
|
||||||
|
|
||||||
- Complain on your blog or on Twitter that `!psycopg2` is the worst package
|
- Complain on your blog or on Twitter that `!psycopg2` is the worst package
|
||||||
ever and about the quality time you have wasted figuring out the correct
|
ever and about the quality time you have wasted figuring out the correct
|
||||||
:envvar:`ARCHFLAGS`. Especially useful from the Starbucks near you.
|
:envvar:`ARCHFLAGS`. Especially useful from the Starbucks near you.
|
||||||
|
|
||||||
.. __: http://mail.postgresql.org/mj/mj_wwwusr/domain=postgresql.org?func=lists-long-full&extra=psycopg
|
.. _mailing list: https://www.postgresql.org/list/psycopg/
|
||||||
|
.. _bug tracker: https://github.com/psycopg/psycopg2/issues
|
||||||
|
|
7
doc/src/license.rst
Normal file
7
doc/src/license.rst
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.. index::
|
||||||
|
single: License
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. include:: ../../LICENSE
|
|
@ -17,47 +17,44 @@ The module interface respects the standard defined in the |DBAPI|_.
|
||||||
single: DSN (Database Source Name)
|
single: DSN (Database Source Name)
|
||||||
|
|
||||||
.. function::
|
.. function::
|
||||||
connect(dsn, connection_factory=None, cursor_factory=None, async=False)
|
connect(dsn=None, connection_factory=None, cursor_factory=None, async=False, \*\*kwargs)
|
||||||
connect(\*\*kwargs, connection_factory=None, cursor_factory=None, async=False)
|
|
||||||
|
|
||||||
Create a new database session and return a new `connection` object.
|
Create a new database session and return a new `connection` object.
|
||||||
|
|
||||||
The connection parameters can be specified either as a `libpq connection
|
The connection parameters can be specified as a `libpq connection
|
||||||
string`__ using the *dsn* parameter::
|
string`__ using the *dsn* parameter::
|
||||||
|
|
||||||
conn = psycopg2.connect("dbname=test user=postgres password=secret")
|
conn = psycopg2.connect("dbname=test user=postgres password=secret")
|
||||||
|
|
||||||
or using a set of keyword arguments::
|
or using a set of keyword arguments::
|
||||||
|
|
||||||
conn = psycopg2.connect(database="test", user="postgres", password="secret")
|
conn = psycopg2.connect(dbname="test", user="postgres", password="secret")
|
||||||
|
|
||||||
The two call styles are mutually exclusive: you cannot specify connection
|
or using a mix of both: if the same parameter name is specified in both
|
||||||
parameters as keyword arguments together with a connection string; only
|
sources, the *kwargs* value will have precedence over the *dsn* value.
|
||||||
the parameters not needed for the database connection (*i.e.*
|
Note that either the *dsn* or at least one connection-related keyword
|
||||||
*connection_factory*, *cursor_factory*, and *async*) are supported
|
argument is required.
|
||||||
together with the *dsn* argument.
|
|
||||||
|
|
||||||
The basic connection parameters are:
|
The basic connection parameters are:
|
||||||
|
|
||||||
- `!dbname` -- the database name (only in the *dsn* string)
|
- `!dbname` -- the database name (`!database` is a deprecated alias)
|
||||||
- `!database` -- the database name (only as keyword argument)
|
|
||||||
- `!user` -- user name used to authenticate
|
- `!user` -- user name used to authenticate
|
||||||
- `!password` -- password used to authenticate
|
- `!password` -- password used to authenticate
|
||||||
- `!host` -- database host address (defaults to UNIX socket if not provided)
|
- `!host` -- database host address (defaults to UNIX socket if not provided)
|
||||||
- `!port` -- connection port number (defaults to 5432 if not provided)
|
- `!port` -- connection port number (defaults to 5432 if not provided)
|
||||||
|
|
||||||
Any other connection parameter supported by the client library/server can
|
Any other connection parameter supported by the client library/server can
|
||||||
be passed either in the connection string or as keywords. The PostgreSQL
|
be passed either in the connection string or as a keyword. The PostgreSQL
|
||||||
documentation contains the complete list of the `supported parameters`__.
|
documentation contains the complete list of the `supported parameters`__.
|
||||||
Also note that the same parameters can be passed to the client library
|
Also note that the same parameters can be passed to the client library
|
||||||
using `environment variables`__.
|
using `environment variables`__.
|
||||||
|
|
||||||
.. __:
|
.. __:
|
||||||
.. _connstring: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
|
.. _connstring: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
|
||||||
.. __:
|
.. __:
|
||||||
.. _connparams: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PARAMKEYWORDS
|
.. _connparams: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PARAMKEYWORDS
|
||||||
.. __:
|
.. __:
|
||||||
.. _connenvvars: http://www.postgresql.org/docs/current/static/libpq-envars.html
|
.. _connenvvars: https://www.postgresql.org/docs/current/static/libpq-envars.html
|
||||||
|
|
||||||
Using the *connection_factory* parameter a different class or
|
Using the *connection_factory* parameter a different class or
|
||||||
connections factory can be specified. It should be a callable object
|
connections factory can be specified. It should be a callable object
|
||||||
|
@ -67,7 +64,8 @@ The module interface respects the standard defined in the |DBAPI|_.
|
||||||
cursors you can use this parameter instead of subclassing a connection.
|
cursors you can use this parameter instead of subclassing a connection.
|
||||||
|
|
||||||
Using *async*\=\ `!True` an asynchronous connection will be created: see
|
Using *async*\=\ `!True` an asynchronous connection will be created: see
|
||||||
:ref:`async-support` to know about advantages and limitations.
|
:ref:`async-support` to know about advantages and limitations. *async_* is
|
||||||
|
a valid alias for the Python version where ``async`` is a keyword.
|
||||||
|
|
||||||
.. versionchanged:: 2.4.3
|
.. versionchanged:: 2.4.3
|
||||||
any keyword argument is passed to the connection. Previously only the
|
any keyword argument is passed to the connection. Previously only the
|
||||||
|
@ -76,8 +74,15 @@ The module interface respects the standard defined in the |DBAPI|_.
|
||||||
.. versionchanged:: 2.5
|
.. versionchanged:: 2.5
|
||||||
added the *cursor_factory* parameter.
|
added the *cursor_factory* parameter.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
both *dsn* and keyword arguments can be specified.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
added *async_* alias.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
|
- `~psycopg2.extensions.parse_dsn`
|
||||||
- libpq `connection string syntax`__
|
- libpq `connection string syntax`__
|
||||||
- libpq supported `connection parameters`__
|
- libpq supported `connection parameters`__
|
||||||
- libpq supported `environment variables`__
|
- libpq supported `environment variables`__
|
||||||
|
@ -88,9 +93,8 @@ The module interface respects the standard defined in the |DBAPI|_.
|
||||||
|
|
||||||
.. extension::
|
.. extension::
|
||||||
|
|
||||||
The parameters *connection_factory* and *async* are Psycopg extensions
|
The non-connection-related keyword parameters are Psycopg extensions
|
||||||
to the |DBAPI|.
|
to the |DBAPI|_.
|
||||||
|
|
||||||
|
|
||||||
.. data:: apilevel
|
.. data:: apilevel
|
||||||
|
|
||||||
|
@ -109,6 +113,14 @@ The module interface respects the standard defined in the |DBAPI|_.
|
||||||
by the interface. For `psycopg2` is ``pyformat``. See also
|
by the interface. For `psycopg2` is ``pyformat``. See also
|
||||||
:ref:`query-parameters`.
|
:ref:`query-parameters`.
|
||||||
|
|
||||||
|
.. data:: __libpq_version__
|
||||||
|
|
||||||
|
Integer constant reporting the version of the ``libpq`` library this
|
||||||
|
``psycopg2`` module was compiled with (in the same format of
|
||||||
|
`~psycopg2.extensions.ConnectionInfo.server_version`). If this value is
|
||||||
|
greater or equal than ``90100`` then you may query the version of the
|
||||||
|
actually loaded library using the `~psycopg2.extensions.libpq_version()`
|
||||||
|
function.
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
@ -125,14 +137,15 @@ available through the following exceptions:
|
||||||
.. exception:: Warning
|
.. exception:: Warning
|
||||||
|
|
||||||
Exception raised for important warnings like data truncations while
|
Exception raised for important warnings like data truncations while
|
||||||
inserting, etc. It is a subclass of the Python `~exceptions.StandardError`.
|
inserting, etc. It is a subclass of the Python `StandardError`
|
||||||
|
(`Exception` on Python 3).
|
||||||
|
|
||||||
.. exception:: Error
|
.. exception:: Error
|
||||||
|
|
||||||
Exception that is the base class of all other error exceptions. You can
|
Exception that is the base class of all other error exceptions. You can
|
||||||
use this to catch all errors with one single `!except` statement. Warnings
|
use this to catch all errors with one single `!except` statement. Warnings
|
||||||
are not considered errors and thus not use this class as base. It
|
are not considered errors and thus not use this class as base. It
|
||||||
is a subclass of the Python `!StandardError`.
|
is a subclass of the Python `StandardError` (`Exception` on Python 3).
|
||||||
|
|
||||||
.. attribute:: pgerror
|
.. attribute:: pgerror
|
||||||
|
|
||||||
|
@ -150,15 +163,16 @@ available through the following exceptions:
|
||||||
|
|
||||||
>>> try:
|
>>> try:
|
||||||
... cur.execute("SELECT * FROM barf")
|
... cur.execute("SELECT * FROM barf")
|
||||||
... except Exception, e:
|
... except psycopg2.Error as e:
|
||||||
... pass
|
... pass
|
||||||
|
|
||||||
>>> e.pgcode
|
>>> e.pgcode
|
||||||
'42P01'
|
'42P01'
|
||||||
>>> print e.pgerror
|
>>> print(e.pgerror)
|
||||||
ERROR: relation "barf" does not exist
|
ERROR: relation "barf" does not exist
|
||||||
LINE 1: SELECT * FROM barf
|
LINE 1: SELECT * FROM barf
|
||||||
^
|
^
|
||||||
|
|
||||||
.. attribute:: cursor
|
.. attribute:: cursor
|
||||||
|
|
||||||
The cursor the exception was raised from; `None` if not applicable.
|
The cursor the exception was raised from; `None` if not applicable.
|
||||||
|
@ -170,7 +184,7 @@ available through the following exceptions:
|
||||||
|
|
||||||
>>> try:
|
>>> try:
|
||||||
... cur.execute("SELECT * FROM barf")
|
... cur.execute("SELECT * FROM barf")
|
||||||
... except Exception, e:
|
... except psycopg2.Error as e:
|
||||||
... pass
|
... pass
|
||||||
|
|
||||||
>>> e.diag.severity
|
>>> e.diag.severity
|
||||||
|
@ -238,13 +252,14 @@ available through the following exceptions:
|
||||||
|
|
||||||
.. extension::
|
.. extension::
|
||||||
|
|
||||||
Psycopg may raise a few other, more specialized, exceptions: currently
|
Psycopg actually raises a different exception for each :sql:`SQLSTATE`
|
||||||
`~psycopg2.extensions.QueryCanceledError` and
|
error returned by the database: the classes are available in the
|
||||||
`~psycopg2.extensions.TransactionRollbackError` are defined. These
|
`psycopg2.errors` module. Every exception class is a subclass of one of
|
||||||
exceptions are not exposed by the main `!psycopg2` module but are
|
the exception classes defined here though, so they don't need to be
|
||||||
made available by the `~psycopg2.extensions` module. All the
|
trapped specifically: trapping `!Error` or `!DatabaseError` is usually
|
||||||
additional exceptions are subclasses of standard |DBAPI| exceptions, so
|
what needed to write a generic error handler; trapping a specific error
|
||||||
trapping them specifically is not required.
|
such as `!NotNullViolation` can be useful to write specific exception
|
||||||
|
handlers.
|
||||||
|
|
||||||
|
|
||||||
This is the exception inheritance layout:
|
This is the exception inheritance layout:
|
||||||
|
@ -258,8 +273,6 @@ This is the exception inheritance layout:
|
||||||
\|__ `DatabaseError`
|
\|__ `DatabaseError`
|
||||||
\|__ `DataError`
|
\|__ `DataError`
|
||||||
\|__ `OperationalError`
|
\|__ `OperationalError`
|
||||||
\| \|__ `psycopg2.extensions.QueryCanceledError`
|
|
||||||
\| \|__ `psycopg2.extensions.TransactionRollbackError`
|
|
||||||
\|__ `IntegrityError`
|
\|__ `IntegrityError`
|
||||||
\|__ `InternalError`
|
\|__ `InternalError`
|
||||||
\|__ `ProgrammingError`
|
\|__ `ProgrammingError`
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
.. index::
|
||||||
|
single: Release notes
|
||||||
|
single: News
|
||||||
|
|
||||||
|
.. _news:
|
||||||
|
|
||||||
Release notes
|
Release notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -24,13 +24,18 @@ directly in the client application.
|
||||||
|
|
||||||
.. method:: getconn(key=None)
|
.. method:: getconn(key=None)
|
||||||
|
|
||||||
Get a free connection and assign it to *key* if not `!None`.
|
Get a free connection from the pool.
|
||||||
|
|
||||||
|
The *key* parameter is optional: if used, the connection will be
|
||||||
|
associated to the key and calling `!getconn()` with the same key again
|
||||||
|
will return the same connection.
|
||||||
|
|
||||||
.. method:: putconn(conn, key=None, close=False)
|
.. method:: putconn(conn, key=None, close=False)
|
||||||
|
|
||||||
Put away a connection.
|
Put away a connection.
|
||||||
|
|
||||||
If *close* is `!True`, discard the connection from the pool.
|
If *close* is `!True`, discard the connection from the pool.
|
||||||
|
*key* should be used consistently with `getconn()`.
|
||||||
|
|
||||||
.. method:: closeall
|
.. method:: closeall
|
||||||
|
|
||||||
|
@ -53,12 +58,3 @@ be used.
|
||||||
.. autoclass:: ThreadedConnectionPool
|
.. autoclass:: ThreadedConnectionPool
|
||||||
|
|
||||||
.. note:: This pool class can be safely used in multi-threaded applications.
|
.. note:: This pool class can be safely used in multi-threaded applications.
|
||||||
|
|
||||||
|
|
||||||
.. autoclass:: PersistentConnectionPool
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
This pool class is mostly designed to interact with Zope and probably
|
|
||||||
not useful in generic applications.
|
|
||||||
|
|
||||||
|
|
147
doc/src/sql.rst
Normal file
147
doc/src/sql.rst
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
`psycopg2.sql` -- SQL string composition
|
||||||
|
========================================
|
||||||
|
|
||||||
|
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||||
|
|
||||||
|
.. module:: psycopg2.sql
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
|
||||||
|
The module contains objects and functions useful to generate SQL dynamically,
|
||||||
|
in a convenient and safe way. SQL identifiers (e.g. names of tables and
|
||||||
|
fields) cannot be passed to the `~cursor.execute()` method like query
|
||||||
|
arguments::
|
||||||
|
|
||||||
|
# This will not work
|
||||||
|
table_name = 'my_table'
|
||||||
|
cur.execute("insert into %s values (%s, %s)", [table_name, 10, 20])
|
||||||
|
|
||||||
|
The SQL query should be composed before the arguments are merged, for
|
||||||
|
instance::
|
||||||
|
|
||||||
|
# This works, but it is not optimal
|
||||||
|
table_name = 'my_table'
|
||||||
|
cur.execute(
|
||||||
|
"insert into %s values (%%s, %%s)" % table_name,
|
||||||
|
[10, 20])
|
||||||
|
|
||||||
|
This sort of works, but it is an accident waiting to happen: the table name
|
||||||
|
may be an invalid SQL literal and need quoting; even more serious is the
|
||||||
|
security problem in case the table name comes from an untrusted source. The
|
||||||
|
name should be escaped using `~psycopg2.extensions.quote_ident()`::
|
||||||
|
|
||||||
|
# This works, but it is not optimal
|
||||||
|
table_name = 'my_table'
|
||||||
|
cur.execute(
|
||||||
|
"insert into %s values (%%s, %%s)" % ext.quote_ident(table_name, cur),
|
||||||
|
[10, 20])
|
||||||
|
|
||||||
|
This is now safe, but it somewhat ad-hoc. In case, for some reason, it is
|
||||||
|
necessary to include a value in the query string (as opposite as in a value)
|
||||||
|
the merging rule is still different (`~psycopg2.extensions.adapt()` should be
|
||||||
|
used...). It is also still relatively dangerous: if `!quote_ident()` is
|
||||||
|
forgotten somewhere, the program will usually work, but will eventually crash
|
||||||
|
in the presence of a table or field name with containing characters to escape,
|
||||||
|
or will present a potentially exploitable weakness.
|
||||||
|
|
||||||
|
The objects exposed by the `!psycopg2.sql` module allow generating SQL
|
||||||
|
statements on the fly, separating clearly the variable parts of the statement
|
||||||
|
from the query parameters::
|
||||||
|
|
||||||
|
from psycopg2 import sql
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
sql.SQL("insert into {} values (%s, %s)")
|
||||||
|
.format(sql.Identifier('my_table')),
|
||||||
|
[10, 20])
|
||||||
|
|
||||||
|
|
||||||
|
Module usage
|
||||||
|
------------
|
||||||
|
|
||||||
|
Usually you should express the template of your query as an `SQL` instance
|
||||||
|
with `{}`\-style placeholders and use `~SQL.format()` to merge the variable
|
||||||
|
parts into them, all of which must be `Composable` subclasses. You can still
|
||||||
|
have `%s`\ -style placeholders in your query and pass values to
|
||||||
|
`~cursor.execute()`: such value placeholders will be untouched by
|
||||||
|
`!format()`::
|
||||||
|
|
||||||
|
query = sql.SQL("select {field} from {table} where {pkey} = %s").format(
|
||||||
|
field=sql.Identifier('my_name'),
|
||||||
|
table=sql.Identifier('some_table'),
|
||||||
|
pkey=sql.Identifier('id'))
|
||||||
|
|
||||||
|
The resulting object is meant to be passed directly to cursor methods such as
|
||||||
|
`~cursor.execute()`, `~cursor.executemany()`, `~cursor.copy_expert()`, but can
|
||||||
|
also be used to compose a query as a Python string, using the
|
||||||
|
`~Composable.as_string()` method::
|
||||||
|
|
||||||
|
cur.execute(query, (42,))
|
||||||
|
|
||||||
|
If part of your query is a variable sequence of arguments, such as a
|
||||||
|
comma-separated list of field names, you can use the `SQL.join()` method to
|
||||||
|
pass them to the query::
|
||||||
|
|
||||||
|
query = sql.SQL("select {fields} from {table}").format(
|
||||||
|
fields=sql.SQL(',').join([
|
||||||
|
sql.Identifier('field1'),
|
||||||
|
sql.Identifier('field2'),
|
||||||
|
sql.Identifier('field3'),
|
||||||
|
]),
|
||||||
|
table=sql.Identifier('some_table'))
|
||||||
|
|
||||||
|
|
||||||
|
`!sql` objects
|
||||||
|
--------------
|
||||||
|
|
||||||
|
The `!sql` objects are in the following inheritance hierarchy:
|
||||||
|
|
||||||
|
| `Composable`: the base class exposing the common interface
|
||||||
|
| ``|__`` `SQL`: a literal snippet of an SQL query
|
||||||
|
| ``|__`` `Identifier`: a PostgreSQL identifier or dot-separated sequence of identifiers
|
||||||
|
| ``|__`` `Literal`: a value hardcoded into a query
|
||||||
|
| ``|__`` `Placeholder`: a `%s`\ -style placeholder whose value will be added later e.g. by `~cursor.execute()`
|
||||||
|
| ``|__`` `Composed`: a sequence of `!Composable` instances.
|
||||||
|
|
||||||
|
|
||||||
|
.. autoclass:: Composable
|
||||||
|
|
||||||
|
.. automethod:: as_string
|
||||||
|
|
||||||
|
|
||||||
|
.. autoclass:: SQL
|
||||||
|
|
||||||
|
.. autoattribute:: string
|
||||||
|
|
||||||
|
.. automethod:: format
|
||||||
|
|
||||||
|
.. automethod:: join
|
||||||
|
|
||||||
|
|
||||||
|
.. autoclass:: Identifier
|
||||||
|
|
||||||
|
.. versionchanged:: 2.8
|
||||||
|
added support for multiple strings.
|
||||||
|
|
||||||
|
.. autoattribute:: strings
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
previous verions only had a `!string` attribute. The attribute
|
||||||
|
still exists but is deprecate and will only work if the
|
||||||
|
`!Identifier` wraps a single string.
|
||||||
|
|
||||||
|
.. autoclass:: Literal
|
||||||
|
|
||||||
|
.. autoattribute:: wrapped
|
||||||
|
|
||||||
|
|
||||||
|
.. autoclass:: Placeholder
|
||||||
|
|
||||||
|
.. autoattribute:: name
|
||||||
|
|
||||||
|
|
||||||
|
.. autoclass:: Composed
|
||||||
|
|
||||||
|
.. autoattribute:: seq
|
||||||
|
|
||||||
|
.. automethod:: join
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
extension
|
extension
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
@ -12,7 +11,7 @@
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
|
|
||||||
from sphinx.locale import _
|
from sphinx.locale import _
|
||||||
from sphinx.util.compat import Directive, make_admonition
|
from docutils.parsers.rst import Directive
|
||||||
|
|
||||||
class extension_node(nodes.Admonition, nodes.Element): pass
|
class extension_node(nodes.Admonition, nodes.Element): pass
|
||||||
|
|
||||||
|
@ -29,12 +28,11 @@ class Extension(Directive):
|
||||||
option_spec = {}
|
option_spec = {}
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
nodes = make_admonition(extension_node,
|
node = extension_node('\n'.join(self.content))
|
||||||
self.name, [_('DB API extension')], self.options,
|
node += nodes.title(_('DB API extension'), _('DB API extension'))
|
||||||
self.content, self.lineno, self.content_offset,
|
self.state.nested_parse(self.content, self.content_offset, node)
|
||||||
self.block_text, self.state, self.state_machine)
|
node['classes'].append('dbapi-extension')
|
||||||
nodes[0]['classes'].append('dbapi-extension')
|
return [node]
|
||||||
return nodes
|
|
||||||
|
|
||||||
|
|
||||||
def visit_extension_node(self, node):
|
def visit_extension_node(self, node):
|
||||||
|
@ -50,4 +48,3 @@ def setup(app):
|
||||||
text=(visit_extension_node, depart_extension_node))
|
text=(visit_extension_node, depart_extension_node))
|
||||||
|
|
||||||
app.add_directive('extension', Extension)
|
app.add_directive('extension', Extension)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
sql role
|
sql role
|
||||||
~~~~~~~~
|
~~~~~~~~
|
||||||
|
@ -18,4 +17,3 @@ def sql_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
roles.register_local_role('sql', sql_role)
|
roles.register_local_role('sql', sql_role)
|
||||||
|
|
||||||
|
|
|
@ -1,39 +1,57 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
ticket role
|
ticket role
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
An interpreted text role to link docs to lighthouse issues.
|
An interpreted text role to link docs to tickets issues.
|
||||||
|
|
||||||
:copyright: Copyright 2013 by Daniele Varrazzo.
|
:copyright: Copyright 2013 by Daniele Varrazzo.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
from docutils import nodes, utils
|
from docutils import nodes, utils
|
||||||
from docutils.parsers.rst import roles
|
from docutils.parsers.rst import roles
|
||||||
|
|
||||||
def ticket_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
def ticket_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||||
try:
|
cfg = inliner.document.settings.env.app.config
|
||||||
num = int(text.replace('#', ''))
|
if cfg.ticket_url is None:
|
||||||
except ValueError:
|
|
||||||
msg = inliner.reporter.error(
|
|
||||||
"ticket number must be... a number, got '%s'" % text)
|
|
||||||
prb = inliner.problematic(rawtext, rawtext, msg)
|
|
||||||
return [prb], [msg]
|
|
||||||
|
|
||||||
url_pattern = inliner.document.settings.env.app.config.ticket_url
|
|
||||||
if url_pattern is None:
|
|
||||||
msg = inliner.reporter.warning(
|
msg = inliner.reporter.warning(
|
||||||
"ticket not configured: please configure ticket_url in conf.py")
|
"ticket not configured: please configure ticket_url in conf.py")
|
||||||
prb = inliner.problematic(rawtext, rawtext, msg)
|
prb = inliner.problematic(rawtext, rawtext, msg)
|
||||||
return [prb], [msg]
|
return [prb], [msg]
|
||||||
|
|
||||||
url = url_pattern % num
|
rv = [nodes.Text(name + ' ')]
|
||||||
|
tokens = re.findall(r'(#?\d+)|([^\d#]+)', text)
|
||||||
|
for ticket, noise in tokens:
|
||||||
|
if ticket:
|
||||||
|
num = int(ticket.replace('#', ''))
|
||||||
|
|
||||||
|
# Push numbers of the oldel tickets ahead.
|
||||||
|
# We moved the tickets from a different tracker to GitHub and the
|
||||||
|
# latter already had a few ticket numbers taken (as merge
|
||||||
|
# requests).
|
||||||
|
remap_until = cfg.ticket_remap_until
|
||||||
|
remap_offset = cfg.ticket_remap_offset
|
||||||
|
if remap_until and remap_offset:
|
||||||
|
if num <= remap_until:
|
||||||
|
num += remap_offset
|
||||||
|
|
||||||
|
url = cfg.ticket_url % num
|
||||||
roles.set_classes(options)
|
roles.set_classes(options)
|
||||||
node = nodes.reference(rawtext, 'ticket ' + utils.unescape(text),
|
node = nodes.reference(ticket, utils.unescape(ticket),
|
||||||
refuri=url, **options)
|
refuri=url, **options)
|
||||||
return [node], []
|
|
||||||
|
rv.append(node)
|
||||||
|
|
||||||
|
else:
|
||||||
|
assert noise
|
||||||
|
rv.append(nodes.Text(noise))
|
||||||
|
|
||||||
|
return rv, []
|
||||||
|
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
app.add_config_value('ticket_url', None, 'env')
|
app.add_config_value('ticket_url', None, 'env')
|
||||||
|
app.add_config_value('ticket_remap_until', None, 'env')
|
||||||
|
app.add_config_value('ticket_remap_offset', None, 'env')
|
||||||
app.add_role('ticket', ticket_role)
|
app.add_role('ticket', ticket_role)
|
||||||
|
app.add_role('tickets', ticket_role)
|
||||||
|
|
57
doc/src/tools/make_sqlstate_docs.py
Normal file
57
doc/src/tools/make_sqlstate_docs.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""Create the docs table of the sqlstate errors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from psycopg2._psycopg import sqlstate_errors
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
sqlclasses = {}
|
||||||
|
clsfile = sys.argv[1]
|
||||||
|
with open(clsfile) as f:
|
||||||
|
for l in f:
|
||||||
|
m = re.match(r'/\* Class (..) - (.+) \*/', l)
|
||||||
|
if m is not None:
|
||||||
|
sqlclasses[m.group(1)] = m.group(2)
|
||||||
|
|
||||||
|
Line = namedtuple('Line', 'colstate colexc colbase sqlstate')
|
||||||
|
|
||||||
|
lines = [Line('SQLSTATE', 'Exception', 'Base exception', None)]
|
||||||
|
for k in sorted(sqlstate_errors):
|
||||||
|
exc = sqlstate_errors[k]
|
||||||
|
lines.append(Line(
|
||||||
|
f"``{k}``", f"`!{exc.__name__}`",
|
||||||
|
f"`!{get_base_exception(exc).__name__}`", k))
|
||||||
|
|
||||||
|
widths = [max(len(l[c]) for l in lines) for c in range(3)]
|
||||||
|
h = Line(*(['=' * w for w in widths] + [None]))
|
||||||
|
lines.insert(0, h)
|
||||||
|
lines.insert(2, h)
|
||||||
|
lines.append(h)
|
||||||
|
|
||||||
|
h1 = '-' * (sum(widths) + len(widths) - 1)
|
||||||
|
sqlclass = None
|
||||||
|
for l in lines:
|
||||||
|
cls = l.sqlstate[:2] if l.sqlstate else None
|
||||||
|
if cls and cls != sqlclass:
|
||||||
|
print(f"**Class {cls}**: {sqlclasses[cls]}")
|
||||||
|
print(h1)
|
||||||
|
sqlclass = cls
|
||||||
|
|
||||||
|
print("%-*s %-*s %-*s" % (
|
||||||
|
widths[0], l.colstate, widths[1], l.colexc, widths[2], l.colbase))
|
||||||
|
|
||||||
|
|
||||||
|
def get_base_exception(exc):
|
||||||
|
for cls in exc.__mro__:
|
||||||
|
if cls.__module__ == 'psycopg2':
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
|
@ -1,63 +0,0 @@
|
||||||
#! /usr/bin/env python
|
|
||||||
"""A script to stitch together the generated text files in the correct order.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 3:
|
|
||||||
sys.stderr.write("usage: %s index.rst text-dir\n")
|
|
||||||
return 2
|
|
||||||
|
|
||||||
_, index, txt_dir = sys.argv
|
|
||||||
|
|
||||||
for fb in iter_file_base(index):
|
|
||||||
emit(fb, txt_dir)
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def iter_file_base(fn):
|
|
||||||
f = open(fn)
|
|
||||||
if sys.version_info[0] >= 3:
|
|
||||||
have_line = iter(f).__next__
|
|
||||||
else:
|
|
||||||
have_line = iter(f).next
|
|
||||||
|
|
||||||
while not have_line().startswith('.. toctree'):
|
|
||||||
pass
|
|
||||||
while have_line().strip().startswith(':'):
|
|
||||||
pass
|
|
||||||
|
|
||||||
yield os.path.splitext(os.path.basename(fn))[0]
|
|
||||||
|
|
||||||
n = 0
|
|
||||||
while True:
|
|
||||||
line = have_line()
|
|
||||||
if line.isspace():
|
|
||||||
continue
|
|
||||||
if line.startswith(".."):
|
|
||||||
break
|
|
||||||
n += 1
|
|
||||||
yield line.strip()
|
|
||||||
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
if n < 5:
|
|
||||||
# maybe format changed?
|
|
||||||
raise Exception("Not enough files found. Format change in index.rst?")
|
|
||||||
|
|
||||||
def emit(basename, txt_dir):
|
|
||||||
f = open(os.path.join(txt_dir, basename + ".txt"))
|
|
||||||
for line in f:
|
|
||||||
line = line.replace("``", "'")
|
|
||||||
sys.stdout.write(line)
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
# some space between sections
|
|
||||||
sys.stdout.write("\n\n")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main())
|
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
|
|
||||||
.. module:: psycopg2.tz
|
.. module:: psycopg2.tz
|
||||||
|
|
||||||
|
.. deprecated:: 2.9
|
||||||
|
The module will be dropped in psycopg 2.10. Use `datetime.timezone`
|
||||||
|
instead.
|
||||||
|
|
||||||
This module holds two different tzinfo implementations that can be used as the
|
This module holds two different tzinfo implementations that can be used as the
|
||||||
`tzinfo` argument to `~datetime.datetime` constructors, directly passed to
|
`tzinfo` argument to `~datetime.datetime` constructors, directly passed to
|
||||||
Psycopg functions or used to set the `cursor.tzinfo_factory` attribute in
|
Psycopg functions or used to set the `cursor.tzinfo_factory` attribute in
|
||||||
|
@ -13,4 +17,3 @@ cursors.
|
||||||
.. autoclass:: psycopg2.tz.FixedOffsetTimezone
|
.. autoclass:: psycopg2.tz.FixedOffsetTimezone
|
||||||
|
|
||||||
.. autoclass:: psycopg2.tz.LocalTimezone
|
.. autoclass:: psycopg2.tz.LocalTimezone
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ The main entry points of Psycopg are:
|
||||||
|
|
||||||
- The class `connection` encapsulates a database session. It allows to:
|
- The class `connection` encapsulates a database session. It allows to:
|
||||||
|
|
||||||
- create new `cursor`\s using the `~connection.cursor()` method to
|
- create new `cursor` instances using the `~connection.cursor()` method to
|
||||||
execute database commands and queries,
|
execute database commands and queries,
|
||||||
|
|
||||||
- terminate transactions using the methods `~connection.commit()` or
|
- terminate transactions using the methods `~connection.commit()` or
|
||||||
|
@ -73,68 +73,97 @@ The main entry points of Psycopg are:
|
||||||
Passing parameters to SQL queries
|
Passing parameters to SQL queries
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
Psycopg casts Python variables to SQL literals by type. Many standard Python types
|
Psycopg converts Python variables to SQL values using their types: the Python
|
||||||
are already `adapted to the correct SQL representation`__.
|
type determines the function used to convert the object into a string
|
||||||
|
representation suitable for PostgreSQL. Many standard Python types are
|
||||||
|
already `adapted to the correct SQL representation`__.
|
||||||
|
|
||||||
.. __: python-types-adaptation_
|
.. __: python-types-adaptation_
|
||||||
|
|
||||||
Example: the Python function call::
|
Passing parameters to an SQL statement happens in functions such as
|
||||||
|
`cursor.execute()` by using ``%s`` placeholders in the SQL statement, and
|
||||||
|
passing a sequence of values as the second argument of the function. For
|
||||||
|
example the Python function call::
|
||||||
|
|
||||||
>>> cur.execute(
|
>>> cur.execute("""
|
||||||
... """INSERT INTO some_table (an_int, a_date, a_string)
|
... INSERT INTO some_table (an_int, a_date, a_string)
|
||||||
... VALUES (%s, %s, %s);""",
|
... VALUES (%s, %s, %s);
|
||||||
|
... """,
|
||||||
... (10, datetime.date(2005, 11, 18), "O'Reilly"))
|
... (10, datetime.date(2005, 11, 18), "O'Reilly"))
|
||||||
|
|
||||||
is converted into the SQL command::
|
is converted into a SQL command similar to:
|
||||||
|
|
||||||
|
.. code-block:: sql
|
||||||
|
|
||||||
INSERT INTO some_table (an_int, a_date, a_string)
|
INSERT INTO some_table (an_int, a_date, a_string)
|
||||||
VALUES (10, '2005-11-18', 'O''Reilly');
|
VALUES (10, '2005-11-18', 'O''Reilly');
|
||||||
|
|
||||||
Named arguments are supported too using :samp:`%({name})s` placeholders.
|
Named arguments are supported too using :samp:`%({name})s` placeholders in the
|
||||||
Using named arguments the values can be passed to the query in any order and
|
query and specifying the values into a mapping. Using named arguments allows
|
||||||
many placeholders can use the same values::
|
to specify the values in any order and to repeat the same value in several
|
||||||
|
places in the query::
|
||||||
|
|
||||||
>>> cur.execute(
|
>>> cur.execute("""
|
||||||
... """INSERT INTO some_table (an_int, a_date, another_date, a_string)
|
... INSERT INTO some_table (an_int, a_date, another_date, a_string)
|
||||||
... VALUES (%(int)s, %(date)s, %(date)s, %(str)s);""",
|
... VALUES (%(int)s, %(date)s, %(date)s, %(str)s);
|
||||||
|
... """,
|
||||||
... {'int': 10, 'str': "O'Reilly", 'date': datetime.date(2005, 11, 18)})
|
... {'int': 10, 'str': "O'Reilly", 'date': datetime.date(2005, 11, 18)})
|
||||||
|
|
||||||
|
Using characters ``%``, ``(``, ``)`` in the argument names is not supported.
|
||||||
|
|
||||||
When parameters are used, in order to include a literal ``%`` in the query you
|
When parameters are used, in order to include a literal ``%`` in the query you
|
||||||
can use the ``%%`` string.
|
can use the ``%%`` string::
|
||||||
|
|
||||||
|
>>> cur.execute("SELECT (%s % 2) = 0 AS even", (10,)) # WRONG
|
||||||
|
>>> cur.execute("SELECT (%s %% 2) = 0 AS even", (10,)) # correct
|
||||||
|
|
||||||
While the mechanism resembles regular Python strings manipulation, there are a
|
While the mechanism resembles regular Python strings manipulation, there are a
|
||||||
few subtle differences you should care about when passing parameters to a
|
few subtle differences you should care about when passing parameters to a
|
||||||
query:
|
query.
|
||||||
|
|
||||||
- The Python string operator ``%`` is not used: the `~cursor.execute()`
|
- The Python string operator ``%`` *must not be used*: the `~cursor.execute()`
|
||||||
method accepts a tuple or dictionary of values as second parameter.
|
method accepts a tuple or dictionary of values as second parameter.
|
||||||
|sql-warn|__.
|
|sql-warn|__:
|
||||||
|
|
||||||
.. |sql-warn| replace:: **Never** use ``%`` or ``+`` to merge values
|
.. |sql-warn| replace:: **Never** use ``%`` or ``+`` to merge values
|
||||||
into queries
|
into queries
|
||||||
|
|
||||||
.. __: sql-injection_
|
.. __: sql-injection_
|
||||||
|
|
||||||
- The variables placeholder must *always be a* ``%s``, even if a different
|
>>> cur.execute("INSERT INTO numbers VALUES (%s, %s)" % (10, 20)) # WRONG
|
||||||
placeholder (such as a ``%d`` for integers or ``%f`` for floats) may look
|
>>> cur.execute("INSERT INTO numbers VALUES (%s, %s)", (10, 20)) # correct
|
||||||
more appropriate::
|
|
||||||
|
|
||||||
>>> cur.execute("INSERT INTO numbers VALUES (%d)", (42,)) # WRONG
|
|
||||||
>>> cur.execute("INSERT INTO numbers VALUES (%s)", (42,)) # correct
|
|
||||||
|
|
||||||
- For positional variables binding, *the second argument must always be a
|
- For positional variables binding, *the second argument must always be a
|
||||||
sequence*, even if it contains a single variable. And remember that Python
|
sequence*, even if it contains a single variable (remember that Python
|
||||||
requires a comma to create a single element tuple::
|
requires a comma to create a single element tuple)::
|
||||||
|
|
||||||
>>> cur.execute("INSERT INTO foo VALUES (%s)", "bar") # WRONG
|
>>> cur.execute("INSERT INTO foo VALUES (%s)", "bar") # WRONG
|
||||||
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar")) # WRONG
|
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar")) # WRONG
|
||||||
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar",)) # correct
|
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar",)) # correct
|
||||||
>>> cur.execute("INSERT INTO foo VALUES (%s)", ["bar"]) # correct
|
>>> cur.execute("INSERT INTO foo VALUES (%s)", ["bar"]) # correct
|
||||||
|
|
||||||
- Only variable values should be bound via this method: it shouldn't be used
|
- The placeholder *must not be quoted*. Psycopg will add quotes where needed::
|
||||||
to set table or field names. For these elements, ordinary string formatting
|
|
||||||
should be used before running `~cursor.execute()`.
|
|
||||||
|
|
||||||
|
>>> cur.execute("INSERT INTO numbers VALUES ('%s')", (10,)) # WRONG
|
||||||
|
>>> cur.execute("INSERT INTO numbers VALUES (%s)", (10,)) # correct
|
||||||
|
|
||||||
|
- The variables placeholder *must always be a* ``%s``, even if a different
|
||||||
|
placeholder (such as a ``%d`` for integers or ``%f`` for floats) may look
|
||||||
|
more appropriate::
|
||||||
|
|
||||||
|
>>> cur.execute("INSERT INTO numbers VALUES (%d)", (10,)) # WRONG
|
||||||
|
>>> cur.execute("INSERT INTO numbers VALUES (%s)", (10,)) # correct
|
||||||
|
|
||||||
|
- Only query values should be bound via this method: it shouldn't be used to
|
||||||
|
merge table or field names to the query (Psycopg will try quoting the table
|
||||||
|
name as a string value, generating invalid SQL). If you need to generate
|
||||||
|
dynamically SQL queries (for instance choosing dynamically a table name)
|
||||||
|
you can use the facilities provided by the `psycopg2.sql` module::
|
||||||
|
|
||||||
|
>>> cur.execute("INSERT INTO %s VALUES (%s)", ('numbers', 10)) # WRONG
|
||||||
|
>>> cur.execute( # correct
|
||||||
|
... SQL("INSERT INTO {} VALUES (%s)").format(Identifier('numbers')),
|
||||||
|
... (10,))
|
||||||
|
|
||||||
|
|
||||||
.. index:: Security, SQL injection
|
.. index:: Security, SQL injection
|
||||||
|
@ -144,13 +173,15 @@ query:
|
||||||
The problem with the query parameters
|
The problem with the query parameters
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The SQL representation for many data types is often not the same of the Python
|
The SQL representation of many data types is often different from their Python
|
||||||
string representation. The classic example is with single quotes in
|
string representation. The typical example is with single quotes in strings:
|
||||||
strings: SQL uses them as string constants bounds and requires them to be
|
in SQL single quotes are used as string literal delimiters, so the ones
|
||||||
escaped, whereas in Python single quotes can be left unescaped in strings
|
appearing inside the string itself must be escaped, whereas in Python single
|
||||||
bounded by double quotes. For this reason a naïve approach to the composition
|
quotes can be left unescaped if the string is delimited by double quotes.
|
||||||
of query strings, e.g. using string concatenation, is a recipe for terrible
|
|
||||||
problems::
|
Because of the difference, sometime subtle, between the data types
|
||||||
|
representations, a naïve approach to query strings composition, such as using
|
||||||
|
Python strings concatenation, is a recipe for *terrible* problems::
|
||||||
|
|
||||||
>>> SQL = "INSERT INTO authors (name) VALUES ('%s');" # NEVER DO THIS
|
>>> SQL = "INSERT INTO authors (name) VALUES ('%s');" # NEVER DO THIS
|
||||||
>>> data = ("O'Reilly", )
|
>>> data = ("O'Reilly", )
|
||||||
|
@ -159,16 +190,16 @@ problems::
|
||||||
LINE 1: INSERT INTO authors (name) VALUES ('O'Reilly')
|
LINE 1: INSERT INTO authors (name) VALUES ('O'Reilly')
|
||||||
^
|
^
|
||||||
|
|
||||||
If the variable containing the data to be sent to the database comes from an
|
If the variables containing the data to send to the database come from an
|
||||||
untrusted source (e.g. a form published on a web site) an attacker could
|
untrusted source (such as a form published on a web site) an attacker could
|
||||||
easily craft a malformed string, either gaining access to unauthorized data or
|
easily craft a malformed string, either gaining access to unauthorized data or
|
||||||
performing destructive operations on the database. This form of attack is
|
performing destructive operations on the database. This form of attack is
|
||||||
called `SQL injection`_ and is known to be one of the most widespread forms of
|
called `SQL injection`_ and is known to be one of the most widespread forms of
|
||||||
attack to servers. Before continuing, please print `this page`__ as a memo and
|
attack to database servers. Before continuing, please print `this page`__ as a
|
||||||
hang it onto your desk.
|
memo and hang it onto your desk.
|
||||||
|
|
||||||
.. _SQL injection: http://en.wikipedia.org/wiki/SQL_injection
|
.. _SQL injection: https://en.wikipedia.org/wiki/SQL_injection
|
||||||
.. __: http://xkcd.com/327/
|
.. __: https://xkcd.com/327/
|
||||||
|
|
||||||
Psycopg can `automatically convert Python objects to and from SQL
|
Psycopg can `automatically convert Python objects to and from SQL
|
||||||
literals`__: using this feature your code will be more robust and
|
literals`__: using this feature your code will be more robust and
|
||||||
|
@ -190,6 +221,27 @@ argument of the `~cursor.execute()` method::
|
||||||
>>> cur.execute(SQL, data) # Note: no % operator
|
>>> cur.execute(SQL, data) # Note: no % operator
|
||||||
|
|
||||||
|
|
||||||
|
Values containing backslashes and LIKE
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Unlike in Python, the backslash (`\\`) is not used as an escape
|
||||||
|
character *except* in patterns used with `LIKE` and `ILIKE` where they
|
||||||
|
are needed to escape the `%` and `_` characters.
|
||||||
|
|
||||||
|
This can lead to confusing situations::
|
||||||
|
|
||||||
|
>>> path = r'C:\Users\Bobby.Tables'
|
||||||
|
>>> cur.execute('INSERT INTO mytable(path) VALUES (%s)', (path,))
|
||||||
|
>>> cur.execute('SELECT * FROM mytable WHERE path LIKE %s', (path,))
|
||||||
|
>>> cur.fetchall()
|
||||||
|
[]
|
||||||
|
|
||||||
|
The solution is to specify an `ESCAPE` character of `''` (empty string)
|
||||||
|
in your `LIKE` query::
|
||||||
|
|
||||||
|
>>> cur.execute("SELECT * FROM mytable WHERE path LIKE %s ESCAPE ''", (path,))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
single: Adaptation
|
single: Adaptation
|
||||||
|
@ -242,7 +294,8 @@ types:
|
||||||
+--------------------+-------------------------+--------------------------+
|
+--------------------+-------------------------+--------------------------+
|
||||||
| `!date` | :sql:`date` | :ref:`adapt-date` |
|
| `!date` | :sql:`date` | :ref:`adapt-date` |
|
||||||
+--------------------+-------------------------+ |
|
+--------------------+-------------------------+ |
|
||||||
| `!time` | :sql:`time` | |
|
| `!time` | | :sql:`time` | |
|
||||||
|
| | | :sql:`timetz` | |
|
||||||
+--------------------+-------------------------+ |
|
+--------------------+-------------------------+ |
|
||||||
| `!datetime` | | :sql:`timestamp` | |
|
| `!datetime` | | :sql:`timestamp` | |
|
||||||
| | | :sql:`timestamptz` | |
|
| | | :sql:`timestamptz` | |
|
||||||
|
@ -260,7 +313,10 @@ types:
|
||||||
+--------------------+-------------------------+--------------------------+
|
+--------------------+-------------------------+--------------------------+
|
||||||
| Anything\ |tm| | :sql:`json` | :ref:`adapt-json` |
|
| Anything\ |tm| | :sql:`json` | :ref:`adapt-json` |
|
||||||
+--------------------+-------------------------+--------------------------+
|
+--------------------+-------------------------+--------------------------+
|
||||||
| `uuid` | :sql:`uuid` | :ref:`adapt-uuid` |
|
| `~uuid.UUID` | :sql:`uuid` | :ref:`adapt-uuid` |
|
||||||
|
+--------------------+-------------------------+--------------------------+
|
||||||
|
| `ipaddress` | | :sql:`inet` | :ref:`adapt-network` |
|
||||||
|
| objects | | :sql:`cidr` | |
|
||||||
+--------------------+-------------------------+--------------------------+
|
+--------------------+-------------------------+--------------------------+
|
||||||
|
|
||||||
.. |tm| unicode:: U+2122
|
.. |tm| unicode:: U+2122
|
||||||
|
@ -298,8 +354,8 @@ proper SQL literals::
|
||||||
Numbers adaptation
|
Numbers adaptation
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Numeric objects: `int`, `long`, `float`, `~decimal.Decimal` are converted in
|
Python numeric objects `int`, `long`, `float`, `~decimal.Decimal` are
|
||||||
the PostgreSQL numerical representation::
|
converted into a PostgreSQL numerical representation::
|
||||||
|
|
||||||
>>> cur.mogrify("SELECT %s, %s, %s, %s;", (10, 10L, 10.0, Decimal("10.00")))
|
>>> cur.mogrify("SELECT %s, %s, %s, %s;", (10, 10L, 10.0, Decimal("10.00")))
|
||||||
'SELECT 10, 10, 10.0, 10.00;'
|
'SELECT 10, 10, 10.0, 10.00;'
|
||||||
|
@ -311,12 +367,12 @@ converted into `!Decimal`.
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Sometimes you may prefer to receive :sql:`numeric` data as `!float`
|
Sometimes you may prefer to receive :sql:`numeric` data as `!float`
|
||||||
insted, for performance reason or ease of manipulation: you can configure
|
instead, for performance reason or ease of manipulation: you can configure
|
||||||
an adapter to :ref:`cast PostgreSQL numeric to Python float <faq-float>`.
|
an adapter to :ref:`cast PostgreSQL numeric to Python float <faq-float>`.
|
||||||
This of course may imply a loss of precision.
|
This of course may imply a loss of precision.
|
||||||
|
|
||||||
.. seealso:: `PostgreSQL numeric types
|
.. seealso:: `PostgreSQL numeric types
|
||||||
<http://www.postgresql.org/docs/current/static/datatype-numeric.html>`__
|
<https://www.postgresql.org/docs/current/static/datatype-numeric.html>`__
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
@ -348,33 +404,33 @@ Unicode handling
|
||||||
Psycopg can exchange Unicode data with a PostgreSQL database. Python
|
Psycopg can exchange Unicode data with a PostgreSQL database. Python
|
||||||
`!unicode` objects are automatically *encoded* in the client encoding
|
`!unicode` objects are automatically *encoded* in the client encoding
|
||||||
defined on the database connection (the `PostgreSQL encoding`__, available in
|
defined on the database connection (the `PostgreSQL encoding`__, available in
|
||||||
`connection.encoding`, is translated into a `Python codec`__ using the
|
`connection.encoding`, is translated into a `Python encoding`__ using the
|
||||||
`~psycopg2.extensions.encodings` mapping)::
|
`~psycopg2.extensions.encodings` mapping)::
|
||||||
|
|
||||||
>>> print u, type(u)
|
>>> print(u, type(u))
|
||||||
àèìòù€ <type 'unicode'>
|
àèìòù€ <type 'unicode'>
|
||||||
|
|
||||||
>>> cur.execute("INSERT INTO test (num, data) VALUES (%s,%s);", (74, u))
|
>>> cur.execute("INSERT INTO test (num, data) VALUES (%s,%s);", (74, u))
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/multibyte.html
|
.. __: https://www.postgresql.org/docs/current/static/multibyte.html
|
||||||
.. __: http://docs.python.org/library/codecs.html#standard-encodings
|
.. __: https://docs.python.org/library/codecs.html
|
||||||
|
|
||||||
When reading data from the database, in Python 2 the strings returned are
|
When reading data from the database, in Python 2 the strings returned are
|
||||||
usually 8 bit `!str` objects encoded in the database client encoding::
|
usually 8 bit `!str` objects encoded in the database client encoding::
|
||||||
|
|
||||||
>>> print conn.encoding
|
>>> print(conn.encoding)
|
||||||
UTF8
|
UTF8
|
||||||
|
|
||||||
>>> cur.execute("SELECT data FROM test WHERE num = 74")
|
>>> cur.execute("SELECT data FROM test WHERE num = 74")
|
||||||
>>> x = cur.fetchone()[0]
|
>>> x = cur.fetchone()[0]
|
||||||
>>> print x, type(x), repr(x)
|
>>> print(x, type(x), repr(x))
|
||||||
àèìòù€ <type 'str'> '\xc3\xa0\xc3\xa8\xc3\xac\xc3\xb2\xc3\xb9\xe2\x82\xac'
|
àèìòù€ <type 'str'> '\xc3\xa0\xc3\xa8\xc3\xac\xc3\xb2\xc3\xb9\xe2\x82\xac'
|
||||||
|
|
||||||
>>> conn.set_client_encoding('LATIN9')
|
>>> conn.set_client_encoding('LATIN9')
|
||||||
|
|
||||||
>>> cur.execute("SELECT data FROM test WHERE num = 74")
|
>>> cur.execute("SELECT data FROM test WHERE num = 74")
|
||||||
>>> x = cur.fetchone()[0]
|
>>> x = cur.fetchone()[0]
|
||||||
>>> print type(x), repr(x)
|
>>> print(type(x), repr(x))
|
||||||
<type 'str'> '\xe0\xe8\xec\xf2\xf9\xa4'
|
<type 'str'> '\xe0\xe8\xec\xf2\xf9\xa4'
|
||||||
|
|
||||||
In Python 3 instead the strings are automatically *decoded* in the connection
|
In Python 3 instead the strings are automatically *decoded* in the connection
|
||||||
|
@ -386,7 +442,7 @@ In Python 2 you must register a :ref:`typecaster
|
||||||
|
|
||||||
>>> cur.execute("SELECT data FROM test WHERE num = 74")
|
>>> cur.execute("SELECT data FROM test WHERE num = 74")
|
||||||
>>> x = cur.fetchone()[0]
|
>>> x = cur.fetchone()[0]
|
||||||
>>> print x, type(x), repr(x)
|
>>> print(x, type(x), repr(x))
|
||||||
àèìòù€ <type 'unicode'> u'\xe0\xe8\xec\xf2\xf9\u20ac'
|
àèìòù€ <type 'unicode'> u'\xe0\xe8\xec\xf2\xf9\u20ac'
|
||||||
|
|
||||||
In the above example, the `~psycopg2.extensions.UNICODE` typecaster is
|
In the above example, the `~psycopg2.extensions.UNICODE` typecaster is
|
||||||
|
@ -401,13 +457,29 @@ the connection or globally: see the function
|
||||||
Unicode, you can register the related typecasters globally as soon as
|
Unicode, you can register the related typecasters globally as soon as
|
||||||
Psycopg is imported::
|
Psycopg is imported::
|
||||||
|
|
||||||
import psycopg2
|
|
||||||
import psycopg2.extensions
|
import psycopg2.extensions
|
||||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
||||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)
|
psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)
|
||||||
|
|
||||||
and forget about this story.
|
and forget about this story.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
In some cases, on Python 3, you may want to receive `!bytes` instead of
|
||||||
|
`!str`, without undergoing to any decoding. This is especially the case if
|
||||||
|
the data in the database is in mixed encoding. The
|
||||||
|
`~psycopg2.extensions.BYTES` caster is what you neeed::
|
||||||
|
|
||||||
|
import psycopg2.extensions
|
||||||
|
psycopg2.extensions.register_type(psycopg2.extensions.BYTES, conn)
|
||||||
|
psycopg2.extensions.register_type(psycopg2.extensions.BYTESARRAY, conn)
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("select %s::text", (u"€",))
|
||||||
|
cur.fetchone()[0]
|
||||||
|
b'\xe2\x82\xac'
|
||||||
|
|
||||||
|
.. versionadded: 2.8
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
single: Buffer; Adaptation
|
single: Buffer; Adaptation
|
||||||
|
@ -422,17 +494,15 @@ the connection or globally: see the function
|
||||||
Binary adaptation
|
Binary adaptation
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Binary types: Python types representing binary objects are converted into
|
Python types representing binary objects are converted into PostgreSQL binary
|
||||||
PostgreSQL binary string syntax, suitable for :sql:`bytea` fields. Such
|
string syntax, suitable for :sql:`bytea` fields. Such types are `buffer`
|
||||||
types are `buffer` (only available in Python 2), `memoryview` (available
|
(only available in Python 2), `memoryview`, `bytearray`, and `bytes` (only in
|
||||||
from Python 2.7), `bytearray` (available from Python 2.6) and `bytes`
|
Python 3: the name is available in Python 2 but it's only an alias for the
|
||||||
(only from Python 3: the name is available from Python 2.6 but it's only an
|
type `!str`). Any object implementing the `Revised Buffer Protocol`__ should
|
||||||
alias for the type `!str`). Any object implementing the `Revised Buffer
|
be usable as binary type. Received data is returned as `!buffer` (in Python 2)
|
||||||
Protocol`__ should be usable as binary type where the protocol is supported
|
|
||||||
(i.e. from Python 2.6). Received data is returned as `!buffer` (in Python 2)
|
|
||||||
or `!memoryview` (in Python 3).
|
or `!memoryview` (in Python 3).
|
||||||
|
|
||||||
.. __: http://www.python.org/dev/peps/pep-3118/
|
.. __: https://www.python.org/dev/peps/pep-3118/
|
||||||
|
|
||||||
.. versionchanged:: 2.4
|
.. versionchanged:: 2.4
|
||||||
only strings were supported before.
|
only strings were supported before.
|
||||||
|
@ -461,8 +531,8 @@ or `!memoryview` (in Python 3).
|
||||||
server configuration file or in the client session (using a query such as
|
server configuration file or in the client session (using a query such as
|
||||||
``SET bytea_output TO escape;``) before receiving binary data.
|
``SET bytea_output TO escape;``) before receiving binary data.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/datatype-binary.html
|
.. __: https://www.postgresql.org/docs/current/static/datatype-binary.html
|
||||||
.. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-BYTEA-OUTPUT
|
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-BYTEA-OUTPUT
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
@ -470,18 +540,16 @@ or `!memoryview` (in Python 3).
|
||||||
single: Date objects; Adaptation
|
single: Date objects; Adaptation
|
||||||
single: Time objects; Adaptation
|
single: Time objects; Adaptation
|
||||||
single: Interval objects; Adaptation
|
single: Interval objects; Adaptation
|
||||||
single: mx.DateTime; Adaptation
|
|
||||||
|
|
||||||
.. _adapt-date:
|
.. _adapt-date:
|
||||||
|
|
||||||
Date/Time objects adaptation
|
Date/Time objects adaptation
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Date and time objects: builtin `~datetime.datetime`, `~datetime.date`,
|
Python builtin `~datetime.datetime`, `~datetime.date`,
|
||||||
`~datetime.time`, `~datetime.timedelta` are converted into PostgreSQL's
|
`~datetime.time`, `~datetime.timedelta` are converted into PostgreSQL's
|
||||||
:sql:`timestamp[tz]`, :sql:`date`, :sql:`time`, :sql:`interval` data types.
|
:sql:`timestamp[tz]`, :sql:`date`, :sql:`time[tz]`, :sql:`interval` data types.
|
||||||
Time zones are supported too. The Egenix `mx.DateTime`_ objects are adapted
|
Time zones are supported too.
|
||||||
the same way::
|
|
||||||
|
|
||||||
>>> dt = datetime.datetime.now()
|
>>> dt = datetime.datetime.now()
|
||||||
>>> dt
|
>>> dt
|
||||||
|
@ -494,7 +562,8 @@ the same way::
|
||||||
"SELECT '38 days 6027.425337 seconds';"
|
"SELECT '38 days 6027.425337 seconds';"
|
||||||
|
|
||||||
.. seealso:: `PostgreSQL date/time types
|
.. seealso:: `PostgreSQL date/time types
|
||||||
<http://www.postgresql.org/docs/current/static/datatype-datetime.html>`__
|
<https://www.postgresql.org/docs/current/static/datatype-datetime.html>`__
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
single: Time Zones
|
single: Time Zones
|
||||||
|
@ -505,29 +574,95 @@ Time zones handling
|
||||||
'''''''''''''''''''
|
'''''''''''''''''''
|
||||||
|
|
||||||
The PostgreSQL type :sql:`timestamp with time zone` (a.k.a.
|
The PostgreSQL type :sql:`timestamp with time zone` (a.k.a.
|
||||||
:sql:`timestamptz`) is converted into Python `~datetime.datetime` objects with
|
:sql:`timestamptz`) is converted into Python `~datetime.datetime` objects.
|
||||||
a `~datetime.datetime.tzinfo` attribute set to a
|
|
||||||
`~psycopg2.tz.FixedOffsetTimezone` instance.
|
|
||||||
|
|
||||||
>>> cur.execute("SET TIME ZONE 'Europe/Rome';") # UTC + 1 hour
|
>>> cur.execute("SET TIME ZONE 'Europe/Rome'") # UTC + 1 hour
|
||||||
>>> cur.execute("SELECT '2010-01-01 10:30:45'::timestamptz;")
|
>>> cur.execute("SELECT '2010-01-01 10:30:45'::timestamptz")
|
||||||
|
>>> cur.fetchone()[0]
|
||||||
|
datetime.datetime(2010, 1, 1, 10, 30, 45,
|
||||||
|
tzinfo=datetime.timezone(datetime.timedelta(seconds=3600)))
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Before Python 3.7, the `datetime` module only supported timezones with an
|
||||||
|
integer number of minutes. A few historical time zones had seconds in the
|
||||||
|
UTC offset: these time zones will have the offset rounded to the nearest
|
||||||
|
minute, with an error of up to 30 seconds, on Python versions before 3.7.
|
||||||
|
|
||||||
|
>>> cur.execute("SET TIME ZONE 'Asia/Calcutta'") # offset was +5:21:10
|
||||||
|
>>> cur.execute("SELECT '1900-01-01 10:30:45'::timestamptz")
|
||||||
>>> cur.fetchone()[0].tzinfo
|
>>> cur.fetchone()[0].tzinfo
|
||||||
psycopg2.tz.FixedOffsetTimezone(offset=60, name=None)
|
# On Python 3.6: 5h, 21m
|
||||||
|
datetime.timezone(datetime.timedelta(0, 19260))
|
||||||
Note that only time zones with an integer number of minutes are supported:
|
# On Python 3.7 and following: 5h, 21m, 10s
|
||||||
this is a limitation of the Python `datetime` module. A few historical time
|
datetime.timezone(datetime.timedelta(seconds=19270))
|
||||||
zones had seconds in the UTC offset: these time zones will have the offset
|
|
||||||
rounded to the nearest minute, with an error of up to 30 seconds.
|
|
||||||
|
|
||||||
>>> cur.execute("SET TIME ZONE 'Asia/Calcutta';") # offset was +5:53:20
|
|
||||||
>>> cur.execute("SELECT '1930-01-01 10:30:45'::timestamptz;")
|
|
||||||
>>> cur.fetchone()[0].tzinfo
|
|
||||||
psycopg2.tz.FixedOffsetTimezone(offset=353, name=None)
|
|
||||||
|
|
||||||
.. versionchanged:: 2.2.2
|
.. versionchanged:: 2.2.2
|
||||||
timezones with seconds are supported (with rounding). Previously such
|
timezones with seconds are supported (with rounding). Previously such
|
||||||
timezones raised an error. In order to deal with them in previous
|
timezones raised an error.
|
||||||
versions use `psycopg2.extras.register_tstz_w_secs()`.
|
|
||||||
|
.. versionchanged:: 2.9
|
||||||
|
timezones with seconds are supported without rounding.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.9
|
||||||
|
use `datetime.timezone` as default tzinfo object instead of
|
||||||
|
`~psycopg2.tz.FixedOffsetTimezone`.
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
double: Date objects; Infinite
|
||||||
|
|
||||||
|
.. _infinite-dates-handling:
|
||||||
|
|
||||||
|
Infinite dates handling
|
||||||
|
'''''''''''''''''''''''
|
||||||
|
|
||||||
|
PostgreSQL can store the representation of an "infinite" date, timestamp, or
|
||||||
|
interval. Infinite dates are not available to Python, so these objects are
|
||||||
|
mapped to `!date.max`, `!datetime.max`, `!interval.max`. Unfortunately the
|
||||||
|
mapping cannot be bidirectional so these dates will be stored back into the
|
||||||
|
database with their values, such as :sql:`9999-12-31`.
|
||||||
|
|
||||||
|
It is possible to create an alternative adapter for dates and other objects
|
||||||
|
to map `date.max` to :sql:`infinity`, for instance::
|
||||||
|
|
||||||
|
class InfDateAdapter:
|
||||||
|
def __init__(self, wrapped):
|
||||||
|
self.wrapped = wrapped
|
||||||
|
def getquoted(self):
|
||||||
|
if self.wrapped == datetime.date.max:
|
||||||
|
return b"'infinity'::date"
|
||||||
|
elif self.wrapped == datetime.date.min:
|
||||||
|
return b"'-infinity'::date"
|
||||||
|
else:
|
||||||
|
return psycopg2.extensions.DateFromPy(self.wrapped).getquoted()
|
||||||
|
|
||||||
|
psycopg2.extensions.register_adapter(datetime.date, InfDateAdapter)
|
||||||
|
|
||||||
|
Of course it will not be possible to write the value of `date.max` in the
|
||||||
|
database anymore: :sql:`infinity` will be stored instead.
|
||||||
|
|
||||||
|
|
||||||
|
.. _time-handling:
|
||||||
|
|
||||||
|
Time handling
|
||||||
|
'''''''''''''
|
||||||
|
|
||||||
|
The PostgreSQL :sql:`time` and Python `~datetime.time` types are not
|
||||||
|
fully bidirectional.
|
||||||
|
|
||||||
|
Within PostgreSQL, the :sql:`time` type's maximum value of ``24:00:00`` is
|
||||||
|
treated as 24-hours later than the minimum value of ``00:00:00``.
|
||||||
|
|
||||||
|
>>> cur.execute("SELECT '24:00:00'::time - '00:00:00'::time")
|
||||||
|
>>> cur.fetchone()[0]
|
||||||
|
datetime.timedelta(days=1)
|
||||||
|
|
||||||
|
However, Python's `!time` only supports times until ``23:59:59``.
|
||||||
|
Retrieving a value of ``24:00:00`` results in a `!time` of ``00:00:00``.
|
||||||
|
|
||||||
|
>>> cur.execute("SELECT '24:00:00'::time, '00:00:00'::time")
|
||||||
|
>>> cur.fetchone()
|
||||||
|
(datetime.time(0, 0), datetime.time(0, 0))
|
||||||
|
|
||||||
|
|
||||||
.. _adapt-list:
|
.. _adapt-list:
|
||||||
|
@ -555,12 +690,12 @@ Python lists are converted into PostgreSQL :sql:`ARRAY`\ s::
|
||||||
Furthermore :sql:`ANY` can also work with empty lists, whereas :sql:`IN ()`
|
Furthermore :sql:`ANY` can also work with empty lists, whereas :sql:`IN ()`
|
||||||
is a SQL syntax error.
|
is a SQL syntax error.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/functions-subquery.html#FUNCTIONS-SUBQUERY-ANY-SOME
|
.. __: https://www.postgresql.org/docs/current/static/functions-subquery.html#FUNCTIONS-SUBQUERY-ANY-SOME
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Reading back from PostgreSQL, arrays are converted to lists of Python
|
Reading back from PostgreSQL, arrays are converted to lists of Python
|
||||||
objects as expected, but only if the items are of a known known type.
|
objects as expected, but only if the items are of a known type.
|
||||||
Arrays of unknown types are returned as represented by the database (e.g.
|
Arrays of unknown types are returned as represented by the database (e.g.
|
||||||
``{a,b,c}``). If you want to convert the items into Python objects you can
|
``{a,b,c}``). If you want to convert the items into Python objects you can
|
||||||
easily create a typecaster for :ref:`array of unknown types
|
easily create a typecaster for :ref:`array of unknown types
|
||||||
|
@ -576,7 +711,7 @@ Tuples adaptation
|
||||||
double: Tuple; Adaptation
|
double: Tuple; Adaptation
|
||||||
single: IN operator
|
single: IN operator
|
||||||
|
|
||||||
Python tuples are converted in a syntax suitable for the SQL :sql:`IN`
|
Python tuples are converted into a syntax suitable for the SQL :sql:`IN`
|
||||||
operator and to represent a composite type::
|
operator and to represent a composite type::
|
||||||
|
|
||||||
>>> cur.mogrify("SELECT %s IN %s;", (10, (10, 20, 30)))
|
>>> cur.mogrify("SELECT %s IN %s;", (10, (10, 20, 30)))
|
||||||
|
@ -623,24 +758,30 @@ until a call to the `~connection.rollback()` method.
|
||||||
|
|
||||||
The connection is responsible for terminating its transaction, calling either
|
The connection is responsible for terminating its transaction, calling either
|
||||||
the `~connection.commit()` or `~connection.rollback()` method. Committed
|
the `~connection.commit()` or `~connection.rollback()` method. Committed
|
||||||
changes are immediately made persistent into the database. Closing the
|
changes are immediately made persistent in the database. If the connection
|
||||||
connection using the `~connection.close()` method or destroying the
|
is closed (using the `~connection.close()` method) or destroyed (using `!del`
|
||||||
connection object (using `!del` or letting it fall out of scope)
|
or by letting it fall out of scope) while a transaction is in progress, the
|
||||||
will result in an implicit rollback.
|
server will discard the transaction. However doing so is not advisable:
|
||||||
|
middleware such as PgBouncer_ may see the connection closed uncleanly and
|
||||||
|
dispose of it.
|
||||||
|
|
||||||
|
.. _PgBouncer: http://www.pgbouncer.org/
|
||||||
|
|
||||||
It is possible to set the connection in *autocommit* mode: this way all the
|
It is possible to set the connection in *autocommit* mode: this way all the
|
||||||
commands executed will be immediately committed and no rollback is possible. A
|
commands executed will be immediately committed and no rollback is possible. A
|
||||||
few commands (e.g. :sql:`CREATE DATABASE`, :sql:`VACUUM`...) require to be run
|
few commands (e.g. :sql:`CREATE DATABASE`, :sql:`VACUUM`, :sql:`CALL` on
|
||||||
|
`stored procedures`__ using transaction control...) require to be run
|
||||||
outside any transaction: in order to be able to run these commands from
|
outside any transaction: in order to be able to run these commands from
|
||||||
Psycopg, the connection must be in autocommit mode: you can use the
|
Psycopg, the connection must be in autocommit mode: you can use the
|
||||||
`~connection.autocommit` property (`~connection.set_isolation_level()` in
|
`~connection.autocommit` property.
|
||||||
older versions).
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/xproc.html
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
By default even a simple :sql:`SELECT` will start a transaction: in
|
By default even a simple :sql:`SELECT` will start a transaction: in
|
||||||
long-running programs, if no further action is taken, the session will
|
long-running programs, if no further action is taken, the session will
|
||||||
remain "idle in transaction", a condition non desiderable for several
|
remain "idle in transaction", an undesirable condition for several
|
||||||
reasons (locks are held by the session, tables bloat...). For long lived
|
reasons (locks are held by the session, tables bloat...). For long lived
|
||||||
scripts, either make sure to terminate a transaction as soon as possible or
|
scripts, either make sure to terminate a transaction as soon as possible or
|
||||||
use an autocommit connection.
|
use an autocommit connection.
|
||||||
|
@ -654,6 +795,8 @@ the details.
|
||||||
.. index::
|
.. index::
|
||||||
single: with statement
|
single: with statement
|
||||||
|
|
||||||
|
.. _with:
|
||||||
|
|
||||||
``with`` statement
|
``with`` statement
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -666,13 +809,41 @@ managers* and can be used with the ``with`` statement::
|
||||||
|
|
||||||
When a connection exits the ``with`` block, if no exception has been raised by
|
When a connection exits the ``with`` block, if no exception has been raised by
|
||||||
the block, the transaction is committed. In case of exception the transaction
|
the block, the transaction is committed. In case of exception the transaction
|
||||||
is rolled back. In no case the connection is closed: a connection can be used
|
is rolled back.
|
||||||
in more than a ``with`` statement and each ``with`` block is effectively
|
|
||||||
wrapped in a transaction.
|
|
||||||
|
|
||||||
When a cursor exits the ``with`` block it is closed, releasing any resource
|
When a cursor exits the ``with`` block it is closed, releasing any resource
|
||||||
eventually associated with it. The state of the transaction is not affected.
|
eventually associated with it. The state of the transaction is not affected.
|
||||||
|
|
||||||
|
A connection can be used in more than one ``with`` statement
|
||||||
|
and each ``with`` block is effectively wrapped in a separate transaction::
|
||||||
|
|
||||||
|
conn = psycopg2.connect(DSN)
|
||||||
|
|
||||||
|
with conn:
|
||||||
|
with conn.cursor() as curs:
|
||||||
|
curs.execute(SQL1)
|
||||||
|
|
||||||
|
with conn:
|
||||||
|
with conn.cursor() as curs:
|
||||||
|
curs.execute(SQL2)
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Unlike file objects or other resources, exiting the connection's
|
||||||
|
``with`` block **doesn't close the connection**, but only the transaction
|
||||||
|
associated to it. If you want to make sure the connection is closed after
|
||||||
|
a certain point, you should still use a try-catch block::
|
||||||
|
|
||||||
|
conn = psycopg2.connect(DSN)
|
||||||
|
try:
|
||||||
|
# connection usage
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
.. versionchanged:: 2.9
|
||||||
|
``with connection`` starts a transaction also on autocommit connections.
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
|
@ -689,7 +860,7 @@ Server side cursors
|
||||||
|
|
||||||
When a database query is executed, the Psycopg `cursor` usually fetches
|
When a database query is executed, the Psycopg `cursor` usually fetches
|
||||||
all the records returned by the backend, transferring them to the client
|
all the records returned by the backend, transferring them to the client
|
||||||
process. If the query returned an huge amount of data, a proportionally large
|
process. If the query returns a huge amount of data, a proportionally large
|
||||||
amount of memory will be allocated by the client.
|
amount of memory will be allocated by the client.
|
||||||
|
|
||||||
If the dataset is too large to be practically handled on the client side, it is
|
If the dataset is too large to be practically handled on the client side, it is
|
||||||
|
@ -719,8 +890,8 @@ you may decrease this value if you are dealing with huge records.
|
||||||
|
|
||||||
Named cursors are usually created :sql:`WITHOUT HOLD`, meaning they live only
|
Named cursors are usually created :sql:`WITHOUT HOLD`, meaning they live only
|
||||||
as long as the current transaction. Trying to fetch from a named cursor after
|
as long as the current transaction. Trying to fetch from a named cursor after
|
||||||
a `~connection.commit()` or to create a named cursor when the `connection`
|
a `~connection.commit()` or to create a named cursor when the connection
|
||||||
transaction isolation level is set to `AUTOCOMMIT` will result in an exception.
|
is in `~connection.autocommit` mode will result in an exception.
|
||||||
It is possible to create a :sql:`WITH HOLD` cursor by specifying a `!True`
|
It is possible to create a :sql:`WITH HOLD` cursor by specifying a `!True`
|
||||||
value for the `withhold` parameter to `~connection.cursor()` or by setting the
|
value for the `withhold` parameter to `~connection.cursor()` or by setting the
|
||||||
`~cursor.withhold` attribute to `!True` before calling `~cursor.execute()` on
|
`~cursor.withhold` attribute to `!True` before calling `~cursor.execute()` on
|
||||||
|
@ -735,7 +906,9 @@ lifetime extends well after `~connection.commit()`, calling
|
||||||
It is also possible to use a named cursor to consume a cursor created
|
It is also possible to use a named cursor to consume a cursor created
|
||||||
in some other way than using the |DECLARE| executed by
|
in some other way than using the |DECLARE| executed by
|
||||||
`~cursor.execute()`. For example, you may have a PL/pgSQL function
|
`~cursor.execute()`. For example, you may have a PL/pgSQL function
|
||||||
returning a cursor::
|
returning a cursor:
|
||||||
|
|
||||||
|
.. code-block:: postgres
|
||||||
|
|
||||||
CREATE FUNCTION reffunc(refcursor) RETURNS refcursor AS $$
|
CREATE FUNCTION reffunc(refcursor) RETURNS refcursor AS $$
|
||||||
BEGIN
|
BEGIN
|
||||||
|
@ -763,7 +936,7 @@ lifetime extends well after `~connection.commit()`, calling
|
||||||
|
|
||||||
|
|
||||||
.. |DECLARE| replace:: :sql:`DECLARE`
|
.. |DECLARE| replace:: :sql:`DECLARE`
|
||||||
.. _DECLARE: http://www.postgresql.org/docs/current/static/sql-declare.html
|
.. _DECLARE: https://www.postgresql.org/docs/current/static/sql-declare.html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -793,7 +966,7 @@ forked processes`__, so when using a module such as `multiprocessing` or a
|
||||||
forking web deploy method such as FastCGI make sure to create the connections
|
forking web deploy method such as FastCGI make sure to create the connections
|
||||||
*after* the fork.
|
*after* the fork.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNECT
|
.. __: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNECT
|
||||||
|
|
||||||
Connections shouldn't be shared either by different green threads: see
|
Connections shouldn't be shared either by different green threads: see
|
||||||
:ref:`green-support` for further details.
|
:ref:`green-support` for further details.
|
||||||
|
@ -810,11 +983,19 @@ Using COPY TO and COPY FROM
|
||||||
|
|
||||||
Psycopg `cursor` objects provide an interface to the efficient
|
Psycopg `cursor` objects provide an interface to the efficient
|
||||||
PostgreSQL |COPY|__ command to move data from files to tables and back.
|
PostgreSQL |COPY|__ command to move data from files to tables and back.
|
||||||
|
|
||||||
|
Currently no adaptation is provided between Python and PostgreSQL types on
|
||||||
|
|COPY|: the file can be any Python file-like object but its format must be in
|
||||||
|
the format accepted by `PostgreSQL COPY command`__ (data format, escaped
|
||||||
|
characters, etc).
|
||||||
|
|
||||||
|
.. __: COPY_
|
||||||
|
|
||||||
The methods exposed are:
|
The methods exposed are:
|
||||||
|
|
||||||
`~cursor.copy_from()`
|
`~cursor.copy_from()`
|
||||||
Reads data *from* a file-like object appending them to a database table
|
Reads data *from* a file-like object appending them to a database table
|
||||||
(:sql:`COPY table FROM file` syntax). The source file must have both
|
(:sql:`COPY table FROM file` syntax). The source file must provide both
|
||||||
`!read()` and `!readline()` method.
|
`!read()` and `!readline()` method.
|
||||||
|
|
||||||
`~cursor.copy_to()`
|
`~cursor.copy_to()`
|
||||||
|
@ -829,7 +1010,7 @@ Please refer to the documentation of the single methods for details and
|
||||||
examples.
|
examples.
|
||||||
|
|
||||||
.. |COPY| replace:: :sql:`COPY`
|
.. |COPY| replace:: :sql:`COPY`
|
||||||
.. __: http://www.postgresql.org/docs/current/static/sql-copy.html
|
.. __: https://www.postgresql.org/docs/current/static/sql-copy.html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -846,7 +1027,7 @@ access to user data that is stored in a special large-object structure. They
|
||||||
are useful with data values too large to be manipulated conveniently as a
|
are useful with data values too large to be manipulated conveniently as a
|
||||||
whole.
|
whole.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/current/static/largeobjects.html
|
.. __: https://www.postgresql.org/docs/current/static/largeobjects.html
|
||||||
|
|
||||||
Psycopg allows access to the large object using the
|
Psycopg allows access to the large object using the
|
||||||
`~psycopg2.extensions.lobject` class. Objects are generated using the
|
`~psycopg2.extensions.lobject` class. Objects are generated using the
|
||||||
|
@ -857,9 +1038,23 @@ Psycopg large object support efficient import/export with file system files
|
||||||
using the |lo_import|_ and |lo_export|_ libpq functions.
|
using the |lo_import|_ and |lo_export|_ libpq functions.
|
||||||
|
|
||||||
.. |lo_import| replace:: `!lo_import()`
|
.. |lo_import| replace:: `!lo_import()`
|
||||||
.. _lo_import: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-IMPORT
|
.. _lo_import: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-IMPORT
|
||||||
.. |lo_export| replace:: `!lo_export()`
|
.. |lo_export| replace:: `!lo_export()`
|
||||||
.. _lo_export: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT
|
.. _lo_export: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT
|
||||||
|
|
||||||
|
.. versionchanged:: 2.6
|
||||||
|
added support for large objects greater than 2GB. Note that the support is
|
||||||
|
enabled only if all the following conditions are verified:
|
||||||
|
|
||||||
|
- the Python build is 64 bits;
|
||||||
|
- the extension was built against at least libpq 9.3;
|
||||||
|
- the server version is at least PostgreSQL 9.3
|
||||||
|
(`~connection.server_version` must be >= ``90300``).
|
||||||
|
|
||||||
|
If Psycopg was built with 64 bits large objects support (i.e. the first
|
||||||
|
two conditions above are verified), the `psycopg2.__version__` constant
|
||||||
|
will contain the ``lo64`` flag. If any of the condition is not met
|
||||||
|
several `!lobject` methods will fail if the arguments exceed 2GB.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -909,6 +1104,5 @@ transactions produced by a Java program.
|
||||||
|
|
||||||
For further details see the documentation for the above methods.
|
For further details see the documentation for the above methods.
|
||||||
|
|
||||||
.. __: http://www.opengroup.org/bookstore/catalog/c193.htm
|
.. __: https://publications.opengroup.org/c193
|
||||||
.. __: http://jdbc.postgresql.org/
|
.. __: https://jdbc.postgresql.org/
|
||||||
|
|
||||||
|
|
|
@ -1,89 +0,0 @@
|
||||||
# binary.py - working with binary data
|
|
||||||
#
|
|
||||||
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import psycopg2
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
print "Encoding for this connection is", conn.encoding
|
|
||||||
|
|
||||||
curs = conn.cursor()
|
|
||||||
try:
|
|
||||||
curs.execute("CREATE TABLE test_binary (id int4, name text, img bytea)")
|
|
||||||
except:
|
|
||||||
conn.rollback()
|
|
||||||
curs.execute("DROP TABLE test_binary")
|
|
||||||
curs.execute("CREATE TABLE test_binary (id int4, name text, img bytea)")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# first we try two inserts, one with an explicit Binary call and the other
|
|
||||||
# using a buffer on a file object.
|
|
||||||
|
|
||||||
data1 = {'id':1, 'name':'somehackers.jpg',
|
|
||||||
'img':psycopg2.Binary(open('somehackers.jpg').read())}
|
|
||||||
data2 = {'id':2, 'name':'whereareyou.jpg',
|
|
||||||
'img':buffer(open('whereareyou.jpg').read())}
|
|
||||||
|
|
||||||
curs.execute("""INSERT INTO test_binary
|
|
||||||
VALUES (%(id)s, %(name)s, %(img)s)""", data1)
|
|
||||||
curs.execute("""INSERT INTO test_binary
|
|
||||||
VALUES (%(id)s, %(name)s, %(img)s)""", data2)
|
|
||||||
|
|
||||||
# now we try to extract the images as simple text strings
|
|
||||||
|
|
||||||
print "Extracting the images as strings..."
|
|
||||||
curs.execute("SELECT * FROM test_binary")
|
|
||||||
|
|
||||||
for row in curs.fetchall():
|
|
||||||
name, ext = row[1].split('.')
|
|
||||||
new_name = name + '_S.' + ext
|
|
||||||
print " writing %s to %s ..." % (name+'.'+ext, new_name),
|
|
||||||
open(new_name, 'wb').write(row[2])
|
|
||||||
print "done"
|
|
||||||
print " python type of image data is", type(row[2])
|
|
||||||
|
|
||||||
# extract exactly the same data but using a binary cursor
|
|
||||||
|
|
||||||
print "Extracting the images using a binary cursor:"
|
|
||||||
|
|
||||||
curs.execute("""DECLARE zot CURSOR FOR
|
|
||||||
SELECT img, name FROM test_binary FOR READ ONLY""")
|
|
||||||
curs.execute("""FETCH ALL FROM zot""")
|
|
||||||
|
|
||||||
for row in curs.fetchall():
|
|
||||||
name, ext = row[1].split('.')
|
|
||||||
new_name = name + '_B.' + ext
|
|
||||||
print " writing %s to %s ..." % (name+'.'+ext, new_name),
|
|
||||||
open(new_name, 'wb').write(row[0])
|
|
||||||
print "done"
|
|
||||||
print " python type of image data is", type(row[0])
|
|
||||||
|
|
||||||
# this rollback is required because we can't drop a table with a binary cusor
|
|
||||||
# declared and still open
|
|
||||||
conn.rollback()
|
|
||||||
|
|
||||||
curs.execute("DROP TABLE test_binary")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
print "\nNow try to load the new images, to check it worked!"
|
|
|
@ -1,177 +0,0 @@
|
||||||
# copy_from.py -- example about copy_from
|
|
||||||
#
|
|
||||||
# Copyright (C) 2002 Tom Jenkins <tjenkins@devis.com>
|
|
||||||
# Copyright (C) 2005 Federico Di Gregorio <fog@initd.org>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by the
|
|
||||||
# Free Software Foundation; either version 2, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful, but
|
|
||||||
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
|
||||||
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
||||||
# for more details.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import StringIO
|
|
||||||
import psycopg2
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
print "Encoding for this connection is", conn.encoding
|
|
||||||
|
|
||||||
curs = conn.cursor()
|
|
||||||
try:
|
|
||||||
curs.execute("CREATE TABLE test_copy (fld1 text, fld2 text, fld3 int4)")
|
|
||||||
except:
|
|
||||||
conn.rollback()
|
|
||||||
curs.execute("DROP TABLE test_copy")
|
|
||||||
curs.execute("CREATE TABLE test_copy (fld1 text, fld2 text, fld3 int4)")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# copy_from with default arguments, from open file
|
|
||||||
|
|
||||||
io = open('copy_from.txt', 'wr')
|
|
||||||
data = ['Tom\tJenkins\t37\n',
|
|
||||||
'Madonna\t\\N\t45\n',
|
|
||||||
'Federico\tDi Gregorio\t\\N\n']
|
|
||||||
io.writelines(data)
|
|
||||||
io.close()
|
|
||||||
|
|
||||||
io = open('copy_from.txt', 'r')
|
|
||||||
curs.copy_from(io, 'test_copy')
|
|
||||||
print "1) Copy %d records from file object " % len(data) + \
|
|
||||||
"using defaults (sep: \\t and null = \\N)"
|
|
||||||
io.close()
|
|
||||||
|
|
||||||
curs.execute("SELECT * FROM test_copy")
|
|
||||||
rows = curs.fetchall()
|
|
||||||
print " Select returned %d rows" % len(rows)
|
|
||||||
|
|
||||||
for r in rows:
|
|
||||||
print " %s %s\t%s" % (r[0], r[1], r[2])
|
|
||||||
curs.execute("delete from test_copy")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# copy_from using custom separator, from open file
|
|
||||||
|
|
||||||
io = open('copy_from.txt', 'wr')
|
|
||||||
data = ['Tom:Jenkins:37\n',
|
|
||||||
'Madonna:\N:45\n',
|
|
||||||
'Federico:Di Gregorio:\N\n']
|
|
||||||
io.writelines(data)
|
|
||||||
io.close()
|
|
||||||
|
|
||||||
io = open('copy_from.txt', 'r')
|
|
||||||
curs.copy_from(io, 'test_copy', ':')
|
|
||||||
print "2) Copy %d records from file object using sep = :" % len(data)
|
|
||||||
io.close()
|
|
||||||
|
|
||||||
curs.execute("SELECT * FROM test_copy")
|
|
||||||
rows = curs.fetchall()
|
|
||||||
print " Select returned %d rows" % len(rows)
|
|
||||||
|
|
||||||
for r in rows:
|
|
||||||
print " %s %s\t%s" % (r[0], r[1], r[2])
|
|
||||||
curs.execute("delete from test_copy")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# copy_from using custom null identifier, from open file
|
|
||||||
|
|
||||||
io = open('copy_from.txt', 'wr')
|
|
||||||
data = ['Tom\tJenkins\t37\n',
|
|
||||||
'Madonna\tNULL\t45\n',
|
|
||||||
'Federico\tDi Gregorio\tNULL\n']
|
|
||||||
io.writelines(data)
|
|
||||||
io.close()
|
|
||||||
|
|
||||||
io = open('copy_from.txt', 'r')
|
|
||||||
curs.copy_from(io, 'test_copy', null='NULL')
|
|
||||||
print "3) Copy %d records from file object using null = NULL" % len(data)
|
|
||||||
io.close()
|
|
||||||
|
|
||||||
curs.execute("SELECT * FROM test_copy")
|
|
||||||
rows = curs.fetchall()
|
|
||||||
print " Select using cursor returned %d rows" % len(rows)
|
|
||||||
|
|
||||||
for r in rows:
|
|
||||||
print " %s %s\t%s" % (r[0], r[1], r[2])
|
|
||||||
curs.execute("delete from test_copy")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# copy_from using custom separator and null identifier
|
|
||||||
|
|
||||||
io = open('copy_from.txt', 'wr')
|
|
||||||
data = ['Tom:Jenkins:37\n', 'Madonna:NULL:45\n', 'Federico:Di Gregorio:NULL\n']
|
|
||||||
io.writelines(data)
|
|
||||||
io.close()
|
|
||||||
|
|
||||||
io = open('copy_from.txt', 'r')
|
|
||||||
curs.copy_from(io, 'test_copy', ':', 'NULL')
|
|
||||||
print "4) Copy %d records from file object " % len(data) + \
|
|
||||||
"using sep = : and null = NULL"
|
|
||||||
io.close()
|
|
||||||
|
|
||||||
curs.execute("SELECT * FROM test_copy")
|
|
||||||
rows = curs.fetchall()
|
|
||||||
print " Select using cursor returned %d rows" % len(rows)
|
|
||||||
|
|
||||||
for r in rows:
|
|
||||||
print " %s %s\t%s" % (r[0], r[1], r[2])
|
|
||||||
curs.execute("delete from test_copy")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# anything can be used as a file if it has .read() and .readline() methods
|
|
||||||
|
|
||||||
data = StringIO.StringIO()
|
|
||||||
data.write('\n'.join(['Tom\tJenkins\t37',
|
|
||||||
'Madonna\t\N\t45',
|
|
||||||
'Federico\tDi Gregorio\t\N']))
|
|
||||||
data.seek(0)
|
|
||||||
|
|
||||||
curs.copy_from(data, 'test_copy')
|
|
||||||
print "5) Copy 3 records from StringIO object using defaults"
|
|
||||||
|
|
||||||
curs.execute("SELECT * FROM test_copy")
|
|
||||||
rows = curs.fetchall()
|
|
||||||
print " Select using cursor returned %d rows" % len(rows)
|
|
||||||
|
|
||||||
for r in rows:
|
|
||||||
print " %s %s\t%s" % (r[0], r[1], r[2])
|
|
||||||
curs.execute("delete from test_copy")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# simple error test
|
|
||||||
|
|
||||||
print "6) About to raise an error"
|
|
||||||
data = StringIO.StringIO()
|
|
||||||
data.write('\n'.join(['Tom\tJenkins\t37',
|
|
||||||
'Madonna\t\N\t45',
|
|
||||||
'Federico\tDi Gregorio\taaa']))
|
|
||||||
data.seek(0)
|
|
||||||
|
|
||||||
try:
|
|
||||||
curs.copy_from(data, 'test_copy')
|
|
||||||
except StandardError, err:
|
|
||||||
conn.rollback()
|
|
||||||
print " Caught error (as expected):\n", err
|
|
||||||
|
|
||||||
conn.rollback()
|
|
||||||
|
|
||||||
curs.execute("DROP TABLE test_copy")
|
|
||||||
os.unlink('copy_from.txt')
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,103 +0,0 @@
|
||||||
# copy_to.py -- example about copy_to
|
|
||||||
#
|
|
||||||
# Copyright (C) 2002 Tom Jenkins <tjenkins@devis.com>
|
|
||||||
# Copyright (C) 2005 Federico Di Gregorio <fog@initd.org>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by the
|
|
||||||
# Free Software Foundation; either version 2, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful, but
|
|
||||||
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
|
||||||
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
||||||
# for more details.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import StringIO
|
|
||||||
import psycopg2
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
print "Encoding for this connection is", conn.encoding
|
|
||||||
|
|
||||||
curs = conn.cursor()
|
|
||||||
try:
|
|
||||||
curs.execute("CREATE TABLE test_copy (fld1 text, fld2 text, fld3 int4)")
|
|
||||||
except:
|
|
||||||
conn.rollback()
|
|
||||||
curs.execute("DROP TABLE test_copy")
|
|
||||||
curs.execute("CREATE TABLE test_copy (fld1 text, fld2 text, fld3 int4)")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# demostrate copy_to functionality
|
|
||||||
data = [('Tom', 'Jenkins', '37'),
|
|
||||||
('Madonna', None, '45'),
|
|
||||||
('Federico', 'Di Gregorio', None)]
|
|
||||||
query = "INSERT INTO test_copy VALUES (%s, %s, %s)"
|
|
||||||
curs.executemany(query, data)
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# copy_to using defaults
|
|
||||||
io = open('copy_to.txt', 'w')
|
|
||||||
curs.copy_to(io, 'test_copy')
|
|
||||||
print "1) Copy %d records into file object using defaults: " % len (data) + \
|
|
||||||
"sep = \\t and null = \\N"
|
|
||||||
io.close()
|
|
||||||
|
|
||||||
rows = open('copy_to.txt', 'r').readlines()
|
|
||||||
print " File has %d rows:" % len(rows)
|
|
||||||
|
|
||||||
for r in rows:
|
|
||||||
print " ", r,
|
|
||||||
|
|
||||||
# copy_to using custom separator
|
|
||||||
io = open('copy_to.txt', 'w')
|
|
||||||
curs.copy_to(io, 'test_copy', ':')
|
|
||||||
print "2) Copy %d records into file object using sep = :" % len(data)
|
|
||||||
io.close()
|
|
||||||
|
|
||||||
rows = open('copy_to.txt', 'r').readlines()
|
|
||||||
print " File has %d rows:" % len(rows)
|
|
||||||
|
|
||||||
for r in rows:
|
|
||||||
print " ", r,
|
|
||||||
|
|
||||||
# copy_to using custom null identifier
|
|
||||||
io = open('copy_to.txt', 'w')
|
|
||||||
curs.copy_to(io, 'test_copy', null='NULL')
|
|
||||||
print "3) Copy %d records into file object using null = NULL" % len(data)
|
|
||||||
io.close()
|
|
||||||
|
|
||||||
rows = open('copy_to.txt', 'r').readlines()
|
|
||||||
print " File has %d rows:" % len(rows)
|
|
||||||
|
|
||||||
for r in rows:
|
|
||||||
print " ", r,
|
|
||||||
|
|
||||||
# copy_to using custom separator and null identifier
|
|
||||||
io = open('copy_to.txt', 'w')
|
|
||||||
curs.copy_to(io, 'test_copy', ':', 'NULL')
|
|
||||||
print "4) Copy %d records into file object using sep = : and null ) NULL" % \
|
|
||||||
len(data)
|
|
||||||
io.close()
|
|
||||||
|
|
||||||
rows = open('copy_to.txt', 'r').readlines()
|
|
||||||
print " File has %d rows:" % len(rows)
|
|
||||||
|
|
||||||
for r in rows:
|
|
||||||
print " ", r,
|
|
||||||
|
|
||||||
curs.execute("DROP TABLE test_copy")
|
|
||||||
os.unlink('copy_to.txt')
|
|
||||||
conn.commit()
|
|
|
@ -1,63 +0,0 @@
|
||||||
# cursor.py - how to subclass the cursor type
|
|
||||||
#
|
|
||||||
# Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import psycopg2
|
|
||||||
import psycopg2.extensions
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dsn:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
print "Encoding for this connection is", conn.encoding
|
|
||||||
|
|
||||||
|
|
||||||
class NoDataError(psycopg2.ProgrammingError):
|
|
||||||
"""Exception that will be raised by our cursor."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Cursor(psycopg2.extensions.cursor):
|
|
||||||
"""A custom cursor."""
|
|
||||||
|
|
||||||
def fetchone(self):
|
|
||||||
"""Like fetchone but raise an exception if no data is available.
|
|
||||||
|
|
||||||
Note that to have .fetchmany() and .fetchall() to raise the same
|
|
||||||
exception we'll have to override them too; even if internally psycopg
|
|
||||||
uses the same function to fetch rows, the code path from Python is
|
|
||||||
different.
|
|
||||||
"""
|
|
||||||
d = psycopg2.extensions.cursor.fetchone(self)
|
|
||||||
if d is None:
|
|
||||||
raise NoDataError("no more data")
|
|
||||||
return d
|
|
||||||
|
|
||||||
curs = conn.cursor(cursor_factory=Cursor)
|
|
||||||
curs.execute("SELECT 1 AS foo")
|
|
||||||
print "Result of fetchone():", curs.fetchone()
|
|
||||||
|
|
||||||
# now let's raise the exception
|
|
||||||
try:
|
|
||||||
curs.fetchone()
|
|
||||||
except NoDataError, err:
|
|
||||||
print "Exception caught:", err
|
|
||||||
|
|
||||||
conn.rollback()
|
|
|
@ -1,144 +0,0 @@
|
||||||
"""
|
|
||||||
This example/recipe has been contributed by Valentino Volonghi (dialtone)
|
|
||||||
|
|
||||||
Mapping arbitrary objects to a PostgreSQL database with psycopg2
|
|
||||||
|
|
||||||
- Problem
|
|
||||||
|
|
||||||
You need to store arbitrary objects in a PostgreSQL database without being
|
|
||||||
intrusive for your classes (don't want inheritance from an 'Item' or
|
|
||||||
'Persistent' object).
|
|
||||||
|
|
||||||
- Solution
|
|
||||||
"""
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import psycopg2
|
|
||||||
from psycopg2.extensions import adapt, register_adapter
|
|
||||||
|
|
||||||
try:
|
|
||||||
sorted()
|
|
||||||
except:
|
|
||||||
def sorted(seq):
|
|
||||||
seq.sort()
|
|
||||||
return seq
|
|
||||||
|
|
||||||
# Here is the adapter for every object that we may ever need to
|
|
||||||
# insert in the database. It receives the original object and does
|
|
||||||
# its job on that instance
|
|
||||||
|
|
||||||
class ObjectMapper(object):
|
|
||||||
def __init__(self, orig, curs=None):
|
|
||||||
self.orig = orig
|
|
||||||
self.tmp = {}
|
|
||||||
self.items, self.fields = self._gatherState()
|
|
||||||
|
|
||||||
def _gatherState(self):
|
|
||||||
adaptee_name = self.orig.__class__.__name__
|
|
||||||
fields = sorted([(field, getattr(self.orig, field))
|
|
||||||
for field in persistent_fields[adaptee_name]])
|
|
||||||
items = []
|
|
||||||
for item, value in fields:
|
|
||||||
items.append(item)
|
|
||||||
return items, fields
|
|
||||||
|
|
||||||
def getTableName(self):
|
|
||||||
return self.orig.__class__.__name__
|
|
||||||
|
|
||||||
def getMappedValues(self):
|
|
||||||
tmp = []
|
|
||||||
for i in self.items:
|
|
||||||
tmp.append("%%(%s)s"%i)
|
|
||||||
return ", ".join(tmp)
|
|
||||||
|
|
||||||
def getValuesDict(self):
|
|
||||||
return dict(self.fields)
|
|
||||||
|
|
||||||
def getFields(self):
|
|
||||||
return self.items
|
|
||||||
|
|
||||||
def generateInsert(self):
|
|
||||||
qry = "INSERT INTO"
|
|
||||||
qry += " " + self.getTableName() + " ("
|
|
||||||
qry += ", ".join(self.getFields()) + ") VALUES ("
|
|
||||||
qry += self.getMappedValues() + ")"
|
|
||||||
return qry, self.getValuesDict()
|
|
||||||
|
|
||||||
# Here are the objects
|
|
||||||
class Album(object):
|
|
||||||
id = 0
|
|
||||||
def __init__(self):
|
|
||||||
self.creation_time = datetime.now()
|
|
||||||
self.album_id = self.id
|
|
||||||
Album.id = Album.id + 1
|
|
||||||
self.binary_data = buffer('12312312312121')
|
|
||||||
|
|
||||||
class Order(object):
|
|
||||||
id = 0
|
|
||||||
def __init__(self):
|
|
||||||
self.items = ['rice','chocolate']
|
|
||||||
self.price = 34
|
|
||||||
self.order_id = self.id
|
|
||||||
Order.id = Order.id + 1
|
|
||||||
|
|
||||||
register_adapter(Album, ObjectMapper)
|
|
||||||
register_adapter(Order, ObjectMapper)
|
|
||||||
|
|
||||||
# Describe what is needed to save on each object
|
|
||||||
# This is actually just configuration, you can use xml with a parser if you
|
|
||||||
# like to have plenty of wasted CPU cycles ;P.
|
|
||||||
|
|
||||||
persistent_fields = {'Album': ['album_id', 'creation_time', 'binary_data'],
|
|
||||||
'Order': ['order_id', 'items', 'price']
|
|
||||||
}
|
|
||||||
|
|
||||||
print adapt(Album()).generateInsert()
|
|
||||||
print adapt(Album()).generateInsert()
|
|
||||||
print adapt(Album()).generateInsert()
|
|
||||||
print adapt(Order()).generateInsert()
|
|
||||||
print adapt(Order()).generateInsert()
|
|
||||||
print adapt(Order()).generateInsert()
|
|
||||||
|
|
||||||
"""
|
|
||||||
- Discussion
|
|
||||||
|
|
||||||
Psycopg 2 has a great new feature: adaptation. The big thing about
|
|
||||||
adaptation is that it enables the programmer to glue most of the
|
|
||||||
code out there without many difficulties.
|
|
||||||
|
|
||||||
This recipe tries to focus attention on a way to generate SQL queries to
|
|
||||||
insert completely new objects inside a database. As you can see objects do
|
|
||||||
not know anything about the code that is handling them. We specify all the
|
|
||||||
fields that we need for each object through the persistent_fields dict.
|
|
||||||
|
|
||||||
The most important lines of this recipe are:
|
|
||||||
register_adapter(Album, ObjectMapper)
|
|
||||||
register_adapter(Order, ObjectMapper)
|
|
||||||
|
|
||||||
In these lines we notify the system that when we call adapt with an Album instance
|
|
||||||
as an argument we want it to istantiate ObjectMapper passing the Album instance
|
|
||||||
as argument (self.orig in the ObjectMapper class).
|
|
||||||
|
|
||||||
The output is something like this (for each call to generateInsert):
|
|
||||||
|
|
||||||
('INSERT INTO Album (album_id, binary_data, creation_time) VALUES
|
|
||||||
(%(album_id)s, %(binary_data)s, %(creation_time)s)',
|
|
||||||
|
|
||||||
{'binary_data': <read-only buffer for 0x402de070, ...>,
|
|
||||||
'creation_time': datetime.datetime(2004, 9, 10, 20, 48, 29, 633728),
|
|
||||||
'album_id': 1}
|
|
||||||
)
|
|
||||||
|
|
||||||
This is a tuple of {SQL_QUERY, FILLING_DICT}, and all the quoting/converting
|
|
||||||
stuff (from python's datetime to postgres s and from python's buffer to
|
|
||||||
postgres' blob) is handled with the same adaptation process hunder the hood
|
|
||||||
by psycopg2.
|
|
||||||
|
|
||||||
At last, just notice that ObjectMapper is working for both Album and Order
|
|
||||||
instances without any glitches at all, and both classes could have easily been
|
|
||||||
coming from closed source libraries or C coded ones (which are not easily
|
|
||||||
modified), whereas a common pattern in todays ORMs or OODBs is to provide
|
|
||||||
a basic 'Persistent' object that already knows how to store itself in the
|
|
||||||
database.
|
|
||||||
"""
|
|
|
@ -1,65 +0,0 @@
|
||||||
# dict.py - using DictCUrsor/DictRow
|
|
||||||
#
|
|
||||||
# Copyright (C) 2005-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import psycopg2
|
|
||||||
import psycopg2.extras
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dsn:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
print "Encoding for this connection is", conn.encoding
|
|
||||||
|
|
||||||
|
|
||||||
curs = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
|
||||||
curs.execute("SELECT 1 AS foo, 'cip' AS bar, date(now()) as zot")
|
|
||||||
print "Cursor's row factory is", curs.row_factory
|
|
||||||
|
|
||||||
data = curs.fetchone()
|
|
||||||
print "The type of the data row is", type(data)
|
|
||||||
print "Some data accessed both as tuple and dict:"
|
|
||||||
print " ", data['foo'], data['bar'], data['zot']
|
|
||||||
print " ", data[0], data[1], data[2]
|
|
||||||
|
|
||||||
# execute another query and demostrate we can still access the row
|
|
||||||
curs.execute("SELECT 2 AS foo")
|
|
||||||
print "The type of the data row is", type(data)
|
|
||||||
print "Some more data accessed both as tuple and dict:"
|
|
||||||
print " ", data['foo'], data['bar'], data['zot']
|
|
||||||
print " ", data[0], data[1], data[2]
|
|
||||||
|
|
||||||
curs = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
|
||||||
curs.execute("SELECT 1 AS foo, 'cip' AS bar, date(now()) as zot")
|
|
||||||
print "Cursor's row factory is", curs.row_factory
|
|
||||||
|
|
||||||
data = curs.fetchone()
|
|
||||||
print "The type of the data row is", type(data)
|
|
||||||
print "Some data accessed both as tuple and dict:"
|
|
||||||
print " ", data['foo'], data['bar'], data['zot']
|
|
||||||
print " ", "No access using indices: this is a specialized cursor."
|
|
||||||
|
|
||||||
# execute another query and demostrate we can still access the row
|
|
||||||
curs.execute("SELECT 2 AS foo")
|
|
||||||
print "The type of the data row is", type(data)
|
|
||||||
print "Some more data accessed both as tuple and dict:"
|
|
||||||
print " ", data['foo'], data['bar'], data['zot']
|
|
||||||
print " ", "No access using indices: this is a specialized cursor."
|
|
|
@ -1,99 +0,0 @@
|
||||||
# datetime.py - example of using date and time types
|
|
||||||
#
|
|
||||||
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import psycopg2
|
|
||||||
import mx.DateTime
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from psycopg2.extensions import adapt
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
curs = conn.cursor()
|
|
||||||
|
|
||||||
try:
|
|
||||||
curs.execute("""CREATE TABLE test_dt (
|
|
||||||
k int4, d date, t time, dt timestamp, z interval)""")
|
|
||||||
except:
|
|
||||||
conn.rollback()
|
|
||||||
curs.execute("DROP TABLE test_dt")
|
|
||||||
curs.execute("""CREATE TABLE test_dt (
|
|
||||||
k int4, d date, t time, dt timestamp, z interval)""")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# build and insert some data using mx.DateTime
|
|
||||||
mx1 = (
|
|
||||||
1,
|
|
||||||
mx.DateTime.Date(2004, 10, 19),
|
|
||||||
mx.DateTime.Time(0, 11, 17.015),
|
|
||||||
mx.DateTime.Timestamp(2004, 10, 19, 0, 11, 17.5),
|
|
||||||
mx.DateTime.DateTimeDelta(13, 15, 17, 59.9))
|
|
||||||
|
|
||||||
from psycopg2.extensions import adapt
|
|
||||||
import psycopg2.extras
|
|
||||||
print adapt(mx1)
|
|
||||||
|
|
||||||
print "Inserting mx.DateTime values..."
|
|
||||||
curs.execute("INSERT INTO test_dt VALUES (%s, %s, %s, %s, %s)", mx1)
|
|
||||||
|
|
||||||
# build and insert some values using the datetime adapters
|
|
||||||
dt1 = (
|
|
||||||
2,
|
|
||||||
datetime.date(2004, 10, 19),
|
|
||||||
datetime.time(0, 11, 17, 15000),
|
|
||||||
datetime.datetime(2004, 10, 19, 0, 11, 17, 500000),
|
|
||||||
datetime.timedelta(13, 15*3600+17*60+59, 900000))
|
|
||||||
|
|
||||||
print "Inserting Python datetime values..."
|
|
||||||
curs.execute("INSERT INTO test_dt VALUES (%s, %s, %s, %s, %s)", dt1)
|
|
||||||
|
|
||||||
# now extract the row from database and print them
|
|
||||||
print "Extracting values inserted with mx.DateTime wrappers:"
|
|
||||||
curs.execute("SELECT d, t, dt, z FROM test_dt WHERE k = 1")
|
|
||||||
for n, x in zip(mx1[1:], curs.fetchone()):
|
|
||||||
try:
|
|
||||||
# this will work only if psycopg has been compiled with datetime
|
|
||||||
# as the default typecaster for date/time values
|
|
||||||
s = repr(n) + "\n -> " + str(adapt(n)) + \
|
|
||||||
"\n -> " + repr(x) + "\n -> " + x.isoformat()
|
|
||||||
except:
|
|
||||||
s = repr(n) + "\n -> " + str(adapt(n)) + \
|
|
||||||
"\n -> " + repr(x) + "\n -> " + str(x)
|
|
||||||
print s
|
|
||||||
print
|
|
||||||
|
|
||||||
print "Extracting values inserted with Python datetime wrappers:"
|
|
||||||
curs.execute("SELECT d, t, dt, z FROM test_dt WHERE k = 2")
|
|
||||||
for n, x in zip(dt1[1:], curs.fetchone()):
|
|
||||||
try:
|
|
||||||
# this will work only if psycopg has been compiled with datetime
|
|
||||||
# as the default typecaster for date/time values
|
|
||||||
s = repr(n) + "\n -> " + repr(x) + "\n -> " + x.isoformat()
|
|
||||||
except:
|
|
||||||
s = repr(n) + "\n -> " + repr(x) + "\n -> " + str(x)
|
|
||||||
print s
|
|
||||||
print
|
|
||||||
|
|
||||||
curs.execute("DROP TABLE test_dt")
|
|
||||||
conn.commit()
|
|
|
@ -1,105 +0,0 @@
|
||||||
# encoding.py - show to change client enkoding (and test it works)
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import psycopg2
|
|
||||||
import psycopg2.extensions
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
print "Initial encoding for this connection is", conn.encoding
|
|
||||||
|
|
||||||
print "\n** This example is supposed to be run in a UNICODE terminal! **\n"
|
|
||||||
|
|
||||||
print "Available encodings:"
|
|
||||||
encs = psycopg2.extensions.encodings.items()
|
|
||||||
encs.sort()
|
|
||||||
for a, b in encs:
|
|
||||||
print " ", a, "<->", b
|
|
||||||
|
|
||||||
print "Using STRING typecaster"
|
|
||||||
print "Setting backend encoding to LATIN1 and executing queries:"
|
|
||||||
conn.set_client_encoding('LATIN1')
|
|
||||||
curs = conn.cursor()
|
|
||||||
curs.execute("SELECT %s::TEXT AS foo", ('àèìòù',))
|
|
||||||
x = curs.fetchone()[0]
|
|
||||||
print " ->", unicode(x, 'latin-1').encode('utf-8'), type(x)
|
|
||||||
curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù',))
|
|
||||||
x = curs.fetchone()[0]
|
|
||||||
print " ->", unicode(x, 'latin-1').encode('utf-8'), type(x)
|
|
||||||
|
|
||||||
print "Setting backend encoding to UTF8 and executing queries:"
|
|
||||||
conn.set_client_encoding('UNICODE')
|
|
||||||
curs = conn.cursor()
|
|
||||||
curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù'.encode('utf-8'),))
|
|
||||||
x = curs.fetchone()[0]
|
|
||||||
print " ->", x, type(x)
|
|
||||||
curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù',))
|
|
||||||
x = curs.fetchone()[0]
|
|
||||||
print " ->", x, type(x)
|
|
||||||
|
|
||||||
print "Using UNICODE typecaster"
|
|
||||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
|
||||||
|
|
||||||
print "Setting backend encoding to LATIN1 and executing queries:"
|
|
||||||
conn.set_client_encoding('LATIN1')
|
|
||||||
curs = conn.cursor()
|
|
||||||
curs.execute("SELECT %s::TEXT AS foo", ('àèìòù',))
|
|
||||||
x = curs.fetchone()[0]
|
|
||||||
print " ->", x.encode('utf-8'), ":", type(x)
|
|
||||||
curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù',))
|
|
||||||
x = curs.fetchone()[0]
|
|
||||||
print " ->", x.encode('utf-8'), ":", type(x)
|
|
||||||
|
|
||||||
print "Setting backend encoding to UTF8 and executing queries:"
|
|
||||||
conn.set_client_encoding('UNICODE')
|
|
||||||
curs = conn.cursor()
|
|
||||||
curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù'.encode('utf-8'),))
|
|
||||||
x = curs.fetchone()[0]
|
|
||||||
print " ->", x.encode('utf-8'), ":", type(x)
|
|
||||||
curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù',))
|
|
||||||
x = curs.fetchone()[0]
|
|
||||||
print " ->", x.encode('utf-8'), ":", type(x)
|
|
||||||
|
|
||||||
print "Executing full UNICODE queries"
|
|
||||||
|
|
||||||
print "Setting backend encoding to LATIN1 and executing queries:"
|
|
||||||
conn.set_client_encoding('LATIN1')
|
|
||||||
curs = conn.cursor()
|
|
||||||
curs.execute(u"SELECT %s::TEXT AS foo", ('àèìòù',))
|
|
||||||
x = curs.fetchone()[0]
|
|
||||||
print " ->", x.encode('utf-8'), ":", type(x)
|
|
||||||
curs.execute(u"SELECT %s::TEXT AS foo", (u'àèìòù',))
|
|
||||||
x = curs.fetchone()[0]
|
|
||||||
print " ->", x.encode('utf-8'), ":", type(x)
|
|
||||||
|
|
||||||
print "Setting backend encoding to UTF8 and executing queries:"
|
|
||||||
conn.set_client_encoding('UNICODE')
|
|
||||||
curs = conn.cursor()
|
|
||||||
curs.execute(u"SELECT %s::TEXT AS foo", (u'àèìòù'.encode('utf-8'),))
|
|
||||||
x = curs.fetchone()[0]
|
|
||||||
print " ->", x.encode('utf-8'), ":", type(x)
|
|
||||||
curs.execute(u"SELECT %s::TEXT AS foo", (u'àèìòù',))
|
|
||||||
x = curs.fetchone()[0]
|
|
||||||
print " ->", x.encode('utf-8'), ":", type(x)
|
|
|
@ -1,80 +0,0 @@
|
||||||
# fetch.py -- example about declaring cursors
|
|
||||||
#
|
|
||||||
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import psycopg2
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
print "Encoding for this connection is", conn.encoding
|
|
||||||
|
|
||||||
curs = conn.cursor()
|
|
||||||
try:
|
|
||||||
curs.execute("CREATE TABLE test_fetch (val int4)")
|
|
||||||
except:
|
|
||||||
conn.rollback()
|
|
||||||
curs.execute("DROP TABLE test_fetch")
|
|
||||||
curs.execute("CREATE TABLE test_fetch (val int4)")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# we use this function to format the output
|
|
||||||
|
|
||||||
def flatten(l):
|
|
||||||
"""Flattens list of tuples l."""
|
|
||||||
return map(lambda x: x[0], l)
|
|
||||||
|
|
||||||
# insert 20 rows in the table
|
|
||||||
|
|
||||||
for i in range(20):
|
|
||||||
curs.execute("INSERT INTO test_fetch VALUES(%s)", (i,))
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# does some nice tricks with the transaction and postgres cursors
|
|
||||||
# (remember to always commit or rollback before a DECLARE)
|
|
||||||
#
|
|
||||||
# we don't need to DECLARE ourselves, psycopg now supports named
|
|
||||||
# cursors (but we leave the code here, comments, as an example of
|
|
||||||
# what psycopg is doing under the hood)
|
|
||||||
#
|
|
||||||
#curs.execute("DECLARE crs CURSOR FOR SELECT * FROM test_fetch")
|
|
||||||
#curs.execute("FETCH 10 FROM crs")
|
|
||||||
#print "First 10 rows:", flatten(curs.fetchall())
|
|
||||||
#curs.execute("MOVE -5 FROM crs")
|
|
||||||
#print "Moved back cursor by 5 rows (to row 5.)"
|
|
||||||
#curs.execute("FETCH 10 FROM crs")
|
|
||||||
#print "Another 10 rows:", flatten(curs.fetchall())
|
|
||||||
#curs.execute("FETCH 10 FROM crs")
|
|
||||||
#print "The remaining rows:", flatten(curs.fetchall())
|
|
||||||
|
|
||||||
ncurs = conn.cursor("crs")
|
|
||||||
ncurs.execute("SELECT * FROM test_fetch")
|
|
||||||
print "First 10 rows:", flatten(ncurs.fetchmany(10))
|
|
||||||
ncurs.scroll(-5)
|
|
||||||
print "Moved back cursor by 5 rows (to row 5.)"
|
|
||||||
print "Another 10 rows:", flatten(ncurs.fetchmany(10))
|
|
||||||
print "Another one:", list(ncurs.fetchone())
|
|
||||||
print "The remaining rows:", flatten(ncurs.fetchall())
|
|
||||||
conn.rollback()
|
|
||||||
|
|
||||||
curs.execute("DROP TABLE test_fetch")
|
|
||||||
conn.commit()
|
|
|
@ -1,59 +0,0 @@
|
||||||
# lastrowid.py - example of using .lastrowid attribute
|
|
||||||
#
|
|
||||||
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys, psycopg2
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
curs = conn.cursor()
|
|
||||||
|
|
||||||
try:
|
|
||||||
curs.execute("CREATE TABLE test_oid (name text, surname text)")
|
|
||||||
except:
|
|
||||||
conn.rollback()
|
|
||||||
curs.execute("DROP TABLE test_oid")
|
|
||||||
curs.execute("CREATE TABLE test_oid (name text, surname text)")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
data = ({'name':'Federico', 'surname':'Di Gregorio'},
|
|
||||||
{'name':'Pierluigi', 'surname':'Di Nunzio'})
|
|
||||||
|
|
||||||
curs.execute("""INSERT INTO test_oid
|
|
||||||
VALUES (%(name)s, %(surname)s)""", data[0])
|
|
||||||
|
|
||||||
foid = curs.lastrowid
|
|
||||||
print "Oid for %(name)s %(surname)s" % data[0], "is", foid
|
|
||||||
|
|
||||||
curs.execute("""INSERT INTO test_oid
|
|
||||||
VALUES (%(name)s, %(surname)s)""", data[1])
|
|
||||||
moid = curs.lastrowid
|
|
||||||
print "Oid for %(name)s %(surname)s" % data[1], "is", moid
|
|
||||||
|
|
||||||
curs.execute("SELECT * FROM test_oid WHERE oid = %s", (foid,))
|
|
||||||
print "Oid", foid, "selected %s %s" % curs.fetchone()
|
|
||||||
|
|
||||||
curs.execute("SELECT * FROM test_oid WHERE oid = %s", (moid,))
|
|
||||||
print "Oid", moid, "selected %s %s" % curs.fetchone()
|
|
||||||
|
|
||||||
curs.execute("DROP TABLE test_oid")
|
|
||||||
conn.commit()
|
|
|
@ -1,91 +0,0 @@
|
||||||
# lobject.py - lobject example
|
|
||||||
#
|
|
||||||
# Copyright (C) 2001-2006 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by the
|
|
||||||
# Free Software Foundation; either version 2, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful, but
|
|
||||||
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
|
|
||||||
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
||||||
# for more details.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import psycopg2
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
print "Encoding for this connection is", conn.encoding
|
|
||||||
|
|
||||||
# this will create a large object with a new random oid, we'll
|
|
||||||
# use it to make some basic tests about read/write and seek.
|
|
||||||
lobj = conn.lobject()
|
|
||||||
loid = lobj.oid
|
|
||||||
print "Created a new large object with oid", loid
|
|
||||||
|
|
||||||
print "Manually importing some binary data into the object:"
|
|
||||||
data = open("somehackers.jpg").read()
|
|
||||||
len = lobj.write(data)
|
|
||||||
print " imported", len, "bytes of data"
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
print "Trying to (re)open large object with oid", loid
|
|
||||||
lobj = conn.lobject(loid)
|
|
||||||
print "Manually exporting the data from the lobject:"
|
|
||||||
data1 = lobj.read()
|
|
||||||
len = lobj.tell()
|
|
||||||
lobj.seek(0, 0)
|
|
||||||
data2 = lobj.read()
|
|
||||||
if data1 != data2:
|
|
||||||
print "ERROR: read after seek returned different data"
|
|
||||||
open("somehackers_lobject1.jpg", 'wb').write(data1)
|
|
||||||
print " written", len, "bytes of data to somehackers_lobject1.jpg"
|
|
||||||
|
|
||||||
lobj.unlink()
|
|
||||||
print "Large object with oid", loid, "removed"
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# now we try to use the import and export functions to do the same
|
|
||||||
lobj = conn.lobject(0, 'n', 0, "somehackers.jpg")
|
|
||||||
loid = lobj.oid
|
|
||||||
print "Imported a new large object with oid", loid
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
print "Trying to (re)open large object with oid", loid
|
|
||||||
lobj = conn.lobject(loid, 'n')
|
|
||||||
print "Using export() to export the data from the large object:"
|
|
||||||
lobj.export("somehackers_lobject2.jpg")
|
|
||||||
print " exported large object to somehackers_lobject2.jpg"
|
|
||||||
|
|
||||||
lobj.unlink()
|
|
||||||
print "Large object with oid", loid, "removed"
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# this will create a very large object with a new random oid.
|
|
||||||
lobj = conn.lobject()
|
|
||||||
loid = lobj.oid
|
|
||||||
print "Created a new large object with oid", loid
|
|
||||||
|
|
||||||
print "Manually importing a lot of data into the object:"
|
|
||||||
data = "data" * 1000000
|
|
||||||
len = lobj.write(data)
|
|
||||||
print " imported", len, "bytes of data"
|
|
||||||
|
|
||||||
conn.rollback()
|
|
||||||
|
|
||||||
print "\nNow try to load the new images, to check it worked!"
|
|
|
@ -1,47 +0,0 @@
|
||||||
# mogrify.py - test all possible simple type mogrifications
|
|
||||||
# -*- encoding: latin1 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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..
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys, psycopg2
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
print "Encoding for this connection is", conn.encoding
|
|
||||||
|
|
||||||
curs = conn.cursor()
|
|
||||||
curs.execute("SELECT %(foo)s AS foo", {'foo':'bar'})
|
|
||||||
curs.execute("SELECT %(foo)s AS foo", {'foo':None})
|
|
||||||
curs.execute("SELECT %(foo)s AS foo", {'foo':True})
|
|
||||||
curs.execute("SELECT %(foo)s AS foo", {'foo':42})
|
|
||||||
curs.execute("SELECT %(foo)s AS foo", {'foo':u'yatt<EFBFBD>!'})
|
|
||||||
curs.execute("SELECT %(foo)s AS foo", {'foo':u'bar'})
|
|
||||||
|
|
||||||
print curs.mogrify("SELECT %(foo)s AS foo", {'foo':'bar'})
|
|
||||||
print curs.mogrify("SELECT %(foo)s AS foo", {'foo':None})
|
|
||||||
print curs.mogrify("SELECT %(foo)s AS foo", {'foo':True})
|
|
||||||
print curs.mogrify("SELECT %(foo)s AS foo", {'foo':42})
|
|
||||||
print curs.mogrify("SELECT %(foo)s AS foo", {'foo':u'yatt<EFBFBD>!'})
|
|
||||||
print curs.mogrify("SELECT %(foo)s AS foo", {'foo':u'bar'})
|
|
||||||
|
|
||||||
conn.rollback()
|
|
|
@ -1,126 +0,0 @@
|
||||||
"""
|
|
||||||
Using a tuple as a bound variable in "SELECT ... IN (...)" clauses
|
|
||||||
in PostgreSQL using psycopg2
|
|
||||||
|
|
||||||
Some time ago someone asked on the psycopg mailing list how to have a
|
|
||||||
bound variable expand to the right SQL for an SELECT IN clause:
|
|
||||||
|
|
||||||
SELECT * FROM atable WHERE afield IN (value1, value2, value3)
|
|
||||||
|
|
||||||
with the values to be used in the IN clause to be passed to the cursor
|
|
||||||
.execute() method in a tuple as a bound variable, i.e.:
|
|
||||||
|
|
||||||
in_values = ("value1", "value2", "value3")
|
|
||||||
curs.execute("SELECT ... IN %s", (in_values,))
|
|
||||||
|
|
||||||
psycopg 1 does support typecasting from Python to PostgreSQL (and back)
|
|
||||||
only for simple types and this problem has no elegant solution (short or
|
|
||||||
writing a wrapper class returning the pre-quoted text in an __str__
|
|
||||||
method.
|
|
||||||
|
|
||||||
But psycopg2 offers a simple and elegant solution by partially
|
|
||||||
implementing the Object Adaptation from PEP 246. psycopg2 moves
|
|
||||||
the type-casting logic into external adapters and a somehow
|
|
||||||
broken adapt() function.
|
|
||||||
|
|
||||||
While the original adapt() takes 3 arguments, psycopg2's one only takes
|
|
||||||
1: the bound variable to be adapted. The result is an object supporting
|
|
||||||
a not-yet well defined protocol that we can call ISQLQuote:
|
|
||||||
|
|
||||||
class ISQLQuote:
|
|
||||||
|
|
||||||
def getquoted(self):
|
|
||||||
"Returns a quoted string representing the bound variable."
|
|
||||||
|
|
||||||
def getbinary(self):
|
|
||||||
"Returns a binary quoted string representing the bound variable."
|
|
||||||
|
|
||||||
def getbuffer(self):
|
|
||||||
"Returns the wrapped object itself."
|
|
||||||
|
|
||||||
__str__ = getquoted
|
|
||||||
|
|
||||||
Then one of the functions (usually .getquoted()) is called by psycopg2 at
|
|
||||||
the right time to obtain the right, sql-quoted representation for the
|
|
||||||
corresponding bound variable.
|
|
||||||
|
|
||||||
The nice part is that the default, built-in adapters, derived from
|
|
||||||
psycopg 1 tyecasting code can be overridden by the programmer, simply
|
|
||||||
replacing them in the psycopg.extensions.adapters dictionary.
|
|
||||||
|
|
||||||
Then the solution to the original problem is now obvious: write an
|
|
||||||
adapter that adapts tuple objects into the right SQL string, by calling
|
|
||||||
recursively adapt() on each element.
|
|
||||||
|
|
||||||
psycopg2 development can be tracked on the psycopg mailing list:
|
|
||||||
|
|
||||||
http://lists.initd.org/mailman/listinfo/psycopg
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
import psycopg2
|
|
||||||
import psycopg2.extensions
|
|
||||||
from psycopg2.extensions import adapt as psycoadapt
|
|
||||||
from psycopg2.extensions import register_adapter
|
|
||||||
|
|
||||||
class AsIs(object):
|
|
||||||
"""An adapter that just return the object 'as is'.
|
|
||||||
|
|
||||||
psycopg 1.99.9 has some optimizations that make impossible to call
|
|
||||||
adapt() without adding some basic adapters externally. This limitation
|
|
||||||
will be lifted in a future release.
|
|
||||||
"""
|
|
||||||
def __init__(self, obj):
|
|
||||||
self.__obj = obj
|
|
||||||
def getquoted(self):
|
|
||||||
return self.__obj
|
|
||||||
|
|
||||||
class SQL_IN(object):
|
|
||||||
"""Adapt a tuple to an SQL quotable object."""
|
|
||||||
|
|
||||||
def __init__(self, seq):
|
|
||||||
self._seq = seq
|
|
||||||
|
|
||||||
def prepare(self, conn):
|
|
||||||
pass
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
qobjs = [str(psycoadapt(o).getquoted()) for o in self._seq]
|
|
||||||
|
|
||||||
return '(' + ', '.join(qobjs) + ')'
|
|
||||||
|
|
||||||
__str__ = getquoted
|
|
||||||
|
|
||||||
|
|
||||||
# add our new adapter class to psycopg list of adapters
|
|
||||||
register_adapter(tuple, SQL_IN)
|
|
||||||
register_adapter(float, AsIs)
|
|
||||||
register_adapter(int, AsIs)
|
|
||||||
|
|
||||||
# usually we would call:
|
|
||||||
#
|
|
||||||
# conn = psycopg.connect("...")
|
|
||||||
# curs = conn.cursor()
|
|
||||||
# curs.execute("SELECT ...", (("this", "is", "the", "tuple"),))
|
|
||||||
#
|
|
||||||
# but we have no connection to a database right now, so we just check
|
|
||||||
# the SQL_IN class by calling psycopg's adapt() directly:
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print "Note how the string will be SQL-quoted, but the number will not:"
|
|
||||||
print psycoadapt(("this is an 'sql quoted' str\\ing", 1, 2.0))
|
|
|
@ -1,45 +0,0 @@
|
||||||
# notify.py - example of getting notifies
|
|
||||||
#
|
|
||||||
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import select
|
|
||||||
import psycopg2
|
|
||||||
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
print "Encoding for this connection is", conn.encoding
|
|
||||||
|
|
||||||
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
|
|
||||||
curs = conn.cursor()
|
|
||||||
|
|
||||||
curs.execute("listen test")
|
|
||||||
|
|
||||||
print "Waiting for 'NOTIFY test'"
|
|
||||||
while 1:
|
|
||||||
if select.select([conn],[],[],5)==([],[],[]):
|
|
||||||
print "Timeout"
|
|
||||||
else:
|
|
||||||
conn.poll()
|
|
||||||
while conn.notifies:
|
|
||||||
print "Got NOTIFY:", conn.notifies.pop()
|
|
|
@ -1,54 +0,0 @@
|
||||||
# simple.py - very simple example of plain DBAPI-2.0 usage
|
|
||||||
#
|
|
||||||
# currently used as test-me-stress-me script for psycopg 2.0
|
|
||||||
#
|
|
||||||
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
class SimpleQuoter(object):
|
|
||||||
def sqlquote(x=None):
|
|
||||||
return "'bar'"
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import psycopg2
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
print "Encoding for this connection is", conn.encoding
|
|
||||||
|
|
||||||
curs = conn.cursor()
|
|
||||||
curs.execute("SELECT 1 AS foo")
|
|
||||||
print curs.fetchone()
|
|
||||||
curs.execute("SELECT 1 AS foo")
|
|
||||||
print curs.fetchmany()
|
|
||||||
curs.execute("SELECT 1 AS foo")
|
|
||||||
print curs.fetchall()
|
|
||||||
|
|
||||||
conn.rollback()
|
|
||||||
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
curs.execute("SELECT 1 AS foo", async=1)
|
|
||||||
|
|
||||||
curs.execute("SELECT %(foo)s AS foo", {'foo':'bar'})
|
|
||||||
curs.execute("SELECT %(foo)s AS foo", {'foo':None})
|
|
||||||
curs.execute("SELECT %(foo)f AS foo", {'foo':42})
|
|
||||||
curs.execute("SELECT %(foo)s AS foo", {'foo':SimpleQuoter()})
|
|
Binary file not shown.
Before Width: | Height: | Size: 22 KiB |
|
@ -1,161 +0,0 @@
|
||||||
# threads.py -- example of multiple threads using psycopg
|
|
||||||
# -*- encoding: latin1 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## some others parameters
|
|
||||||
INSERT_THREADS = ('A', 'B', 'C')
|
|
||||||
SELECT_THREADS = ('1', '2')
|
|
||||||
|
|
||||||
ROWS = 1000
|
|
||||||
|
|
||||||
COMMIT_STEP = 20
|
|
||||||
SELECT_SIZE = 10000
|
|
||||||
SELECT_STEP = 500
|
|
||||||
SELECT_DIV = 250
|
|
||||||
|
|
||||||
# the available modes are:
|
|
||||||
# 0 - one connection for all inserts and one for all select threads
|
|
||||||
# 1 - connections generated using the connection pool
|
|
||||||
|
|
||||||
MODE = 1
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys, psycopg2, threading
|
|
||||||
from psycopg2.pool import ThreadedConnectionPool
|
|
||||||
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
if len(sys.argv) > 2:
|
|
||||||
MODE = int(sys.argv[2])
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
curs = conn.cursor()
|
|
||||||
|
|
||||||
try:
|
|
||||||
curs.execute("""CREATE TABLE test_threads (
|
|
||||||
name text, value1 int4, value2 float)""")
|
|
||||||
except:
|
|
||||||
conn.rollback()
|
|
||||||
curs.execute("DROP TABLE test_threads")
|
|
||||||
curs.execute("""CREATE TABLE test_threads (
|
|
||||||
name text, value1 int4, value2 float)""")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
|
|
||||||
## this function inserts a big number of rows and creates and destroys
|
|
||||||
## a large number of cursors
|
|
||||||
|
|
||||||
def insert_func(conn_or_pool, rows):
|
|
||||||
name = threading.currentThread().getName()
|
|
||||||
|
|
||||||
if MODE == 0:
|
|
||||||
conn = conn_or_pool
|
|
||||||
else:
|
|
||||||
conn = conn_or_pool.getconn()
|
|
||||||
|
|
||||||
for i in range(rows):
|
|
||||||
if divmod(i, COMMIT_STEP)[1] == 0:
|
|
||||||
conn.commit()
|
|
||||||
if MODE == 1:
|
|
||||||
conn_or_pool.putconn(conn)
|
|
||||||
s = name + ": COMMIT STEP " + str(i)
|
|
||||||
print s
|
|
||||||
if MODE == 1:
|
|
||||||
conn = conn_or_pool.getconn()
|
|
||||||
c = conn.cursor()
|
|
||||||
try:
|
|
||||||
c.execute("INSERT INTO test_threads VALUES (%s, %s, %s)",
|
|
||||||
(str(i), i, float(i)))
|
|
||||||
except psycopg2.ProgrammingError, err:
|
|
||||||
print name, ": an error occurred; skipping this insert"
|
|
||||||
print err
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
## a nice select function that prints the current number of rows in the
|
|
||||||
## database (and transfer them, putting some pressure on the network)
|
|
||||||
|
|
||||||
def select_func(conn_or_pool, z):
|
|
||||||
name = threading.currentThread().getName()
|
|
||||||
|
|
||||||
if MODE == 0:
|
|
||||||
conn = conn_or_pool
|
|
||||||
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
|
|
||||||
|
|
||||||
for i in range(SELECT_SIZE):
|
|
||||||
if divmod(i, SELECT_STEP)[1] == 0:
|
|
||||||
try:
|
|
||||||
if MODE == 1:
|
|
||||||
conn = conn_or_pool.getconn()
|
|
||||||
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute("SELECT * FROM test_threads WHERE value2 < %s",
|
|
||||||
(int(i/z),))
|
|
||||||
l = c.fetchall()
|
|
||||||
if MODE == 1:
|
|
||||||
conn_or_pool.putconn(conn)
|
|
||||||
s = name + ": number of rows fetched: " + str(len(l))
|
|
||||||
print s
|
|
||||||
except psycopg2.ProgrammingError, err:
|
|
||||||
print name, ": an error occurred; skipping this select"
|
|
||||||
print err
|
|
||||||
|
|
||||||
## create the connection pool or the connections
|
|
||||||
if MODE == 0:
|
|
||||||
conn_insert = psycopg2.connect(DSN)
|
|
||||||
conn_select = psycopg2.connect(DSN)
|
|
||||||
else:
|
|
||||||
m = len(INSERT_THREADS) + len(SELECT_THREADS)
|
|
||||||
n = m/2
|
|
||||||
conn_insert = conn_select = ThreadedConnectionPool(n, m, DSN)
|
|
||||||
|
|
||||||
## create the threads
|
|
||||||
threads = []
|
|
||||||
|
|
||||||
print "Creating INSERT threads:"
|
|
||||||
for name in INSERT_THREADS:
|
|
||||||
t = threading.Thread(None, insert_func, 'Thread-'+name,
|
|
||||||
(conn_insert, ROWS))
|
|
||||||
t.setDaemon(0)
|
|
||||||
threads.append(t)
|
|
||||||
|
|
||||||
print "Creating SELECT threads:"
|
|
||||||
for name in SELECT_THREADS:
|
|
||||||
t = threading.Thread(None, select_func, 'Thread-'+name,
|
|
||||||
(conn_select, SELECT_DIV))
|
|
||||||
t.setDaemon(0)
|
|
||||||
threads.append(t)
|
|
||||||
|
|
||||||
## really start the threads now
|
|
||||||
for t in threads:
|
|
||||||
t.start()
|
|
||||||
|
|
||||||
# and wait for them to finish
|
|
||||||
for t in threads:
|
|
||||||
t.join()
|
|
||||||
print t.getName(), "exited OK"
|
|
||||||
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
curs.execute("SELECT count(name) FROM test_threads")
|
|
||||||
print "Inserted", curs.fetchone()[0], "rows."
|
|
||||||
|
|
||||||
curs.execute("DROP TABLE test_threads")
|
|
||||||
conn.commit()
|
|
|
@ -1,67 +0,0 @@
|
||||||
# typecast.py - example of per-cursor and per-connection typecasters.
|
|
||||||
#
|
|
||||||
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
class SimpleQuoter(object):
|
|
||||||
def sqlquote(x=None):
|
|
||||||
return "'bar'"
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import psycopg2
|
|
||||||
import psycopg2.extensions
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
print "Encoding for this connection is", conn.encoding
|
|
||||||
|
|
||||||
curs = conn.cursor()
|
|
||||||
curs.execute("SELECT 'text'::text AS foo")
|
|
||||||
textoid = curs.description[0][1]
|
|
||||||
print "Oid for the text datatype is", textoid
|
|
||||||
|
|
||||||
def castA(s, curs):
|
|
||||||
if s is not None: return "(A) " + s
|
|
||||||
TYPEA = psycopg2.extensions.new_type((textoid,), "TYPEA", castA)
|
|
||||||
|
|
||||||
def castB(s, curs):
|
|
||||||
if s is not None: return "(B) " + s
|
|
||||||
TYPEB = psycopg2.extensions.new_type((textoid,), "TYPEB", castB)
|
|
||||||
|
|
||||||
curs = conn.cursor()
|
|
||||||
curs.execute("SELECT 'some text.'::text AS foo")
|
|
||||||
print "Some text from plain connection:", curs.fetchone()[0]
|
|
||||||
|
|
||||||
psycopg2.extensions.register_type(TYPEA, conn)
|
|
||||||
curs = conn.cursor()
|
|
||||||
curs.execute("SELECT 'some text.'::text AS foo")
|
|
||||||
print "Some text from connection with typecaster:", curs.fetchone()[0]
|
|
||||||
|
|
||||||
curs = conn.cursor()
|
|
||||||
psycopg2.extensions.register_type(TYPEB, curs)
|
|
||||||
curs.execute("SELECT 'some text.'::text AS foo")
|
|
||||||
print "Some text from cursor with typecaster:", curs.fetchone()[0]
|
|
||||||
|
|
||||||
curs = conn.cursor()
|
|
||||||
curs.execute("SELECT 'some text.'::text AS foo")
|
|
||||||
print "Some text from connection with typecaster again:", curs.fetchone()[0]
|
|
||||||
|
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
# tz.py - example of datetime objects with time zones
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import psycopg2
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from psycopg2.tz import ZERO, LOCAL, FixedOffsetTimezone
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
curs = conn.cursor()
|
|
||||||
|
|
||||||
try:
|
|
||||||
curs.execute("CREATE TABLE test_tz (t timestamp with time zone)")
|
|
||||||
except:
|
|
||||||
conn.rollback()
|
|
||||||
curs.execute("DROP TABLE test_tz")
|
|
||||||
curs.execute("CREATE TABLE test_tz (t timestamp with time zone)")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
d = datetime.datetime(1971, 10, 19, 22, 30, 0, tzinfo=LOCAL)
|
|
||||||
curs.execute("INSERT INTO test_tz VALUES (%s)", (d,))
|
|
||||||
print "Inserted timestamp with timezone:", d
|
|
||||||
print "Time zone:", d.tzinfo.tzname(d), "offset:", d.tzinfo.utcoffset(d)
|
|
||||||
|
|
||||||
tz = FixedOffsetTimezone(-5*60, "EST")
|
|
||||||
d = datetime.datetime(1971, 10, 19, 22, 30, 0, tzinfo=tz)
|
|
||||||
curs.execute("INSERT INTO test_tz VALUES (%s)", (d,))
|
|
||||||
print "Inserted timestamp with timezone:", d
|
|
||||||
print "Time zone:", d.tzinfo.tzname(d), "offset:", d.tzinfo.utcoffset(d)
|
|
||||||
|
|
||||||
curs.execute("SELECT * FROM test_tz")
|
|
||||||
d = curs.fetchone()[0]
|
|
||||||
curs.execute("INSERT INTO test_tz VALUES (%s)", (d,))
|
|
||||||
print "Inserted SELECTed timestamp:", d
|
|
||||||
print "Time zone:", d.tzinfo.tzname(d), "offset:", d.tzinfo.utcoffset(d)
|
|
||||||
|
|
||||||
curs.execute("SELECT * FROM test_tz")
|
|
||||||
for d in curs:
|
|
||||||
u = d[0].utcoffset() or ZERO
|
|
||||||
print "UTC time: ", d[0] - u
|
|
||||||
print "Local time:", d[0]
|
|
||||||
print "Time zone:", d[0].tzinfo.tzname(d[0]), d[0].tzinfo.utcoffset(d[0])
|
|
||||||
|
|
||||||
|
|
||||||
curs.execute("DROP TABLE test_tz")
|
|
||||||
conn.commit()
|
|
|
@ -1,126 +0,0 @@
|
||||||
# usercast.py -- example of user defined typecasters
|
|
||||||
# -*- encoding: latin-1 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## put in DSN your DSN string
|
|
||||||
|
|
||||||
DSN = 'dbname=test'
|
|
||||||
|
|
||||||
## don't modify anything below this line (except for experimenting)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import psycopg2
|
|
||||||
import psycopg2.extensions
|
|
||||||
import whrandom
|
|
||||||
|
|
||||||
# importing psycopg.extras will give us a nice tuple adapter: this is wrong
|
|
||||||
# because the adapter is meant to be used in SQL IN clauses while we use
|
|
||||||
# tuples to represent points but it works and the example is about Rect, not
|
|
||||||
# "Point"
|
|
||||||
import psycopg2.extras
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
DSN = sys.argv[1]
|
|
||||||
|
|
||||||
print "Opening connection using dns:", DSN
|
|
||||||
conn = psycopg2.connect(DSN)
|
|
||||||
print "Initial encoding for this connection is", conn.encoding
|
|
||||||
|
|
||||||
curs = conn.cursor()
|
|
||||||
try:
|
|
||||||
curs.execute("CREATE TABLE test_cast (p1 point, p2 point, b box)")
|
|
||||||
except:
|
|
||||||
conn.rollback()
|
|
||||||
curs.execute("DROP TABLE test_cast")
|
|
||||||
curs.execute("CREATE TABLE test_cast (p1 point, p2 point, b box)")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# this is the callable object we use as a typecast (the typecast is
|
|
||||||
# usually a function, but we use a class, just to demonstrate the
|
|
||||||
# flexibility of the psycopg casting system
|
|
||||||
|
|
||||||
class Rect(object):
|
|
||||||
"""Very simple rectangle.
|
|
||||||
|
|
||||||
Note that we use this type as a data holder, as an adapter of itself for
|
|
||||||
the ISQLQuote protocol used by psycopg's adapt() (see __confrom__ below)
|
|
||||||
and eventually as a type-caster for the data extracted from the database
|
|
||||||
(that's why __init__ takes the curs argument.)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, s=None, curs=None):
|
|
||||||
"""Init the rectangle from the optional string s."""
|
|
||||||
self.x = self.y = self.width = self.height = 0.0
|
|
||||||
if s: self.from_string(s)
|
|
||||||
|
|
||||||
def __conform__(self, proto):
|
|
||||||
"""This is a terrible hack, just ignore proto and return self."""
|
|
||||||
if proto == psycopg2.extensions.ISQLQuote:
|
|
||||||
return self
|
|
||||||
|
|
||||||
def from_points(self, x0, y0, x1, y1):
|
|
||||||
"""Init the rectangle from points."""
|
|
||||||
if x0 > x1: (x0, x1) = (x1, x0)
|
|
||||||
if y0 > y1: (y0, y1) = (y1, y0)
|
|
||||||
self.x = x0
|
|
||||||
self.y = y0
|
|
||||||
self.width = x1 - x0
|
|
||||||
self.height = y1 - y0
|
|
||||||
|
|
||||||
def from_string(self, s):
|
|
||||||
"""Init the rectangle from a string."""
|
|
||||||
seq = eval(s)
|
|
||||||
self.from_points(seq[0][0], seq[0][1], seq[1][0], seq[1][1])
|
|
||||||
|
|
||||||
def getquoted(self):
|
|
||||||
"""Format self as a string usable by the db to represent a box."""
|
|
||||||
s = "'((%d,%d),(%d,%d))'" % (
|
|
||||||
self.x, self.y, self.x + self.width, self.y + self.height)
|
|
||||||
return s
|
|
||||||
|
|
||||||
def show(self):
|
|
||||||
"""Format a description of the box."""
|
|
||||||
s = "X: %d\tY: %d\tWidth: %d\tHeight: %d" % (
|
|
||||||
self.x, self.y, self.width, self.height)
|
|
||||||
return s
|
|
||||||
|
|
||||||
# here we select from the empty table, just to grab the description
|
|
||||||
curs.execute("SELECT b FROM test_cast WHERE 0=1")
|
|
||||||
boxoid = curs.description[0][1]
|
|
||||||
print "Oid for the box datatype is", boxoid
|
|
||||||
|
|
||||||
# and build the user cast object
|
|
||||||
BOX = psycopg2.extensions.new_type((boxoid,), "BOX", Rect)
|
|
||||||
psycopg2.extensions.register_type(BOX)
|
|
||||||
|
|
||||||
# now insert 100 random data (2 points and a box in each row)
|
|
||||||
for i in range(100):
|
|
||||||
p1 = (whrandom.randint(0,100), whrandom.randint(0,100))
|
|
||||||
p2 = (whrandom.randint(0,100), whrandom.randint(0,100))
|
|
||||||
b = Rect()
|
|
||||||
b.from_points(whrandom.randint(0,100), whrandom.randint(0,100),
|
|
||||||
whrandom.randint(0,100), whrandom.randint(0,100))
|
|
||||||
curs.execute("INSERT INTO test_cast VALUES ('%(p1)s', '%(p2)s', %(box)s)",
|
|
||||||
{'box':b, 'p1':p1, 'p2':p2})
|
|
||||||
print "Added 100 boxed to the database"
|
|
||||||
|
|
||||||
# select and print all boxes with at least one point inside
|
|
||||||
curs.execute("SELECT b FROM test_cast WHERE p1 @ b OR p2 @ b")
|
|
||||||
boxes = curs.fetchall()
|
|
||||||
print "Found %d boxes with at least a point inside:" % len(boxes)
|
|
||||||
for box in boxes:
|
|
||||||
print " ", box[0].show()
|
|
||||||
|
|
||||||
curs.execute("DROP TABLE test_cast")
|
|
||||||
conn.commit()
|
|
Binary file not shown.
Before Width: | Height: | Size: 34 KiB |
104
lib/__init__.py
104
lib/__init__.py
|
@ -6,10 +6,10 @@ 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
|
candies. Like the original, psycopg 2 was written with the aim of being very
|
||||||
small and fast, and stable as a rock.
|
small and fast, and stable as a rock.
|
||||||
|
|
||||||
Homepage: http://initd.org/projects/psycopg2
|
Homepage: https://psycopg.org/
|
||||||
|
|
||||||
.. _PostgreSQL: http://www.postgresql.org/
|
.. _PostgreSQL: https://www.postgresql.org/
|
||||||
.. _Python: http://www.python.org/
|
.. _Python: https://www.python.org/
|
||||||
|
|
||||||
:Groups:
|
:Groups:
|
||||||
* `Connections creation`: connect
|
* `Connections creation`: connect
|
||||||
|
@ -18,7 +18,8 @@ Homepage: http://initd.org/projects/psycopg2
|
||||||
"""
|
"""
|
||||||
# psycopg/__init__.py - initialization of the psycopg module
|
# psycopg/__init__.py - initialization of the psycopg module
|
||||||
#
|
#
|
||||||
# Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
# Copyright (C) 2020-2021 The Psycopg Team
|
||||||
#
|
#
|
||||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||||
# under the terms of the GNU Lesser General Public License as published
|
# under the terms of the GNU Lesser General Public License as published
|
||||||
|
@ -43,69 +44,44 @@ Homepage: http://initd.org/projects/psycopg2
|
||||||
|
|
||||||
# Note: the first internal import should be _psycopg, otherwise the real cause
|
# Note: the first internal import should be _psycopg, otherwise the real cause
|
||||||
# of a failed loading of the C module may get hidden, see
|
# of a failed loading of the C module may get hidden, see
|
||||||
# http://archives.postgresql.org/psycopg/2011-02/msg00044.php
|
# https://archives.postgresql.org/psycopg/2011-02/msg00044.php
|
||||||
|
|
||||||
# Import the DBAPI-2.0 stuff into top-level module.
|
# Import the DBAPI-2.0 stuff into top-level module.
|
||||||
|
|
||||||
from psycopg2._psycopg import BINARY, NUMBER, STRING, DATETIME, ROWID
|
from psycopg2._psycopg import ( # noqa
|
||||||
|
BINARY, NUMBER, STRING, DATETIME, ROWID,
|
||||||
|
|
||||||
from psycopg2._psycopg import Binary, Date, Time, Timestamp
|
Binary, Date, Time, Timestamp,
|
||||||
from psycopg2._psycopg import DateFromTicks, TimeFromTicks, TimestampFromTicks
|
DateFromTicks, TimeFromTicks, TimestampFromTicks,
|
||||||
|
|
||||||
from psycopg2._psycopg import Error, Warning, DataError, DatabaseError, ProgrammingError
|
Error, Warning, DataError, DatabaseError, ProgrammingError, IntegrityError,
|
||||||
from psycopg2._psycopg import IntegrityError, InterfaceError, InternalError
|
InterfaceError, InternalError, NotSupportedError, OperationalError,
|
||||||
from psycopg2._psycopg import NotSupportedError, OperationalError
|
|
||||||
|
|
||||||
from psycopg2._psycopg import _connect, apilevel, threadsafety, paramstyle
|
_connect, apilevel, threadsafety, paramstyle,
|
||||||
from psycopg2._psycopg import __version__
|
__version__, __libpq_version__,
|
||||||
|
)
|
||||||
from psycopg2 import tz
|
|
||||||
|
|
||||||
|
|
||||||
# Register default adapters.
|
# Register default adapters.
|
||||||
|
|
||||||
import psycopg2.extensions as _ext
|
from psycopg2 import extensions as _ext
|
||||||
_ext.register_adapter(tuple, _ext.SQL_IN)
|
_ext.register_adapter(tuple, _ext.SQL_IN)
|
||||||
_ext.register_adapter(type(None), _ext.NoneAdapter)
|
_ext.register_adapter(type(None), _ext.NoneAdapter)
|
||||||
|
|
||||||
# Register the Decimal adapter here instead of in the C layer.
|
# Register the Decimal adapter here instead of in the C layer.
|
||||||
# This way a new class is registered for each sub-interpreter.
|
# This way a new class is registered for each sub-interpreter.
|
||||||
# See ticket #52
|
# See ticket #52
|
||||||
try:
|
from decimal import Decimal # noqa
|
||||||
from decimal import Decimal
|
from psycopg2._psycopg import Decimal as Adapter # noqa
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
from psycopg2._psycopg import Decimal as Adapter
|
|
||||||
_ext.register_adapter(Decimal, Adapter)
|
_ext.register_adapter(Decimal, Adapter)
|
||||||
del Decimal, Adapter
|
del Decimal, Adapter
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
def _param_escape(s,
|
def connect(dsn=None, connection_factory=None, cursor_factory=None, **kwargs):
|
||||||
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
|
|
||||||
|
|
||||||
del re
|
|
||||||
|
|
||||||
|
|
||||||
def connect(dsn=None,
|
|
||||||
database=None, user=None, password=None, host=None, port=None,
|
|
||||||
connection_factory=None, cursor_factory=None, async=False, **kwargs):
|
|
||||||
"""
|
"""
|
||||||
Create a new database connection.
|
Create a new database connection.
|
||||||
|
|
||||||
The connection parameters can be specified either as a string:
|
The connection parameters can be specified as a string:
|
||||||
|
|
||||||
conn = psycopg2.connect("dbname=test user=postgres password=secret")
|
conn = psycopg2.connect("dbname=test user=postgres password=secret")
|
||||||
|
|
||||||
|
@ -113,9 +89,9 @@ def connect(dsn=None,
|
||||||
|
|
||||||
conn = psycopg2.connect(database="test", user="postgres", password="secret")
|
conn = psycopg2.connect(database="test", user="postgres", password="secret")
|
||||||
|
|
||||||
The basic connection parameters are:
|
Or as a mix of both. The basic connection parameters are:
|
||||||
|
|
||||||
- *dbname*: the database name (only in dsn string)
|
- *dbname*: the database name
|
||||||
- *database*: the database name (only as keyword argument)
|
- *database*: the database name (only as keyword argument)
|
||||||
- *user*: user name used to authenticate
|
- *user*: user name used to authenticate
|
||||||
- *password*: password used to authenticate
|
- *password*: password used to authenticate
|
||||||
|
@ -129,39 +105,21 @@ def connect(dsn=None,
|
||||||
Using the *cursor_factory* parameter, a new default cursor factory will be
|
Using the *cursor_factory* parameter, a new default cursor factory will be
|
||||||
used by cursor().
|
used by cursor().
|
||||||
|
|
||||||
Using *async*=True an asynchronous connection will be created.
|
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
|
Any other keyword parameter will be passed to the underlying client
|
||||||
library: the list of supported parameters depends on the library version.
|
library: the list of supported parameters depends on the library version.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
items = []
|
kwasync = {}
|
||||||
if database is not None:
|
if 'async' in kwargs:
|
||||||
items.append(('dbname', database))
|
kwasync['async'] = kwargs.pop('async')
|
||||||
if user is not None:
|
if 'async_' in kwargs:
|
||||||
items.append(('user', user))
|
kwasync['async_'] = kwargs.pop('async_')
|
||||||
if password is not None:
|
|
||||||
items.append(('password', password))
|
|
||||||
if host is not None:
|
|
||||||
items.append(('host', host))
|
|
||||||
if port is not None:
|
|
||||||
items.append(('port', port))
|
|
||||||
|
|
||||||
items.extend([(k, v) for (k, v) in kwargs.iteritems() if v is not None])
|
dsn = _ext.make_dsn(dsn, **kwargs)
|
||||||
|
conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
|
||||||
if dsn is not None and items:
|
|
||||||
raise TypeError(
|
|
||||||
"'%s' is an invalid keyword argument when the dsn is specified"
|
|
||||||
% items[0][0])
|
|
||||||
|
|
||||||
if dsn is None:
|
|
||||||
if not items:
|
|
||||||
raise TypeError('missing dsn and no parameters')
|
|
||||||
else:
|
|
||||||
dsn = " ".join(["%s=%s" % (k, _param_escape(str(v)))
|
|
||||||
for (k, v) in items])
|
|
||||||
|
|
||||||
conn = _connect(dsn, connection_factory=connection_factory, async=async)
|
|
||||||
if cursor_factory is not None:
|
if cursor_factory is not None:
|
||||||
conn.cursor_factory = cursor_factory
|
conn.cursor_factory = cursor_factory
|
||||||
|
|
||||||
|
|
90
lib/_ipaddress.py
Normal file
90
lib/_ipaddress.py
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
"""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-2021 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)
|
||||||
|
|
||||||
|
# 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(str(s))
|
||||||
|
|
||||||
|
|
||||||
|
def cast_network(s, cur=None):
|
||||||
|
if s is None:
|
||||||
|
return None
|
||||||
|
return ipaddress.ip_network(str(s))
|
||||||
|
|
||||||
|
|
||||||
|
def adapt_ipaddress(obj):
|
||||||
|
return QuotedString(str(obj))
|
109
lib/_json.py
109
lib/_json.py
|
@ -7,7 +7,8 @@ extensions importing register_json from extras.
|
||||||
|
|
||||||
# psycopg/_json.py - Implementation of the JSON adaptation objects
|
# psycopg/_json.py - Implementation of the JSON adaptation objects
|
||||||
#
|
#
|
||||||
# Copyright (C) 2012 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
# Copyright (C) 2012-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||||
|
# Copyright (C) 2020-2021 The Psycopg Team
|
||||||
#
|
#
|
||||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||||
# under the terms of the GNU Lesser General Public License as published
|
# under the terms of the GNU Lesser General Public License as published
|
||||||
|
@ -27,47 +28,35 @@ extensions importing register_json from extras.
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||||
# License for more details.
|
# License for more details.
|
||||||
|
|
||||||
import sys
|
import json
|
||||||
|
|
||||||
from psycopg2._psycopg import ISQLQuote, QuotedString
|
from psycopg2._psycopg import ISQLQuote, QuotedString
|
||||||
from psycopg2._psycopg import new_type, new_array_type, register_type
|
from psycopg2._psycopg import new_type, new_array_type, register_type
|
||||||
|
|
||||||
|
|
||||||
# import the best json implementation available
|
|
||||||
if sys.version_info[:2] >= (2,6):
|
|
||||||
import json
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
import simplejson as json
|
|
||||||
except ImportError:
|
|
||||||
json = None
|
|
||||||
|
|
||||||
|
|
||||||
# oids from PostgreSQL 9.2
|
# oids from PostgreSQL 9.2
|
||||||
JSON_OID = 114
|
JSON_OID = 114
|
||||||
JSONARRAY_OID = 199
|
JSONARRAY_OID = 199
|
||||||
|
|
||||||
class Json(object):
|
# oids from PostgreSQL 9.4
|
||||||
|
JSONB_OID = 3802
|
||||||
|
JSONBARRAY_OID = 3807
|
||||||
|
|
||||||
|
|
||||||
|
class Json:
|
||||||
"""
|
"""
|
||||||
An `~psycopg2.extensions.ISQLQuote` wrapper to adapt a Python object to
|
An `~psycopg2.extensions.ISQLQuote` wrapper to adapt a Python object to
|
||||||
:sql:`json` data type.
|
:sql:`json` data type.
|
||||||
|
|
||||||
`!Json` can be used to wrap any object supported by the provided *dumps*
|
`!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
|
function. If none is provided, the standard :py:func:`json.dumps()` is
|
||||||
used (`!simplejson` for Python < 2.6;
|
used.
|
||||||
`~psycopg2.extensions.ISQLQuote.getquoted()` will raise `!ImportError` if
|
|
||||||
the module is not available).
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, adapted, dumps=None):
|
def __init__(self, adapted, dumps=None):
|
||||||
self.adapted = adapted
|
self.adapted = adapted
|
||||||
|
self._conn = None
|
||||||
if dumps is not None:
|
self._dumps = dumps or json.dumps
|
||||||
self._dumps = dumps
|
|
||||||
elif json is not None:
|
|
||||||
self._dumps = json.dumps
|
|
||||||
else:
|
|
||||||
self._dumps = None
|
|
||||||
|
|
||||||
def __conform__(self, proto):
|
def __conform__(self, proto):
|
||||||
if proto is ISQLQuote:
|
if proto is ISQLQuote:
|
||||||
|
@ -80,21 +69,25 @@ class Json(object):
|
||||||
provided in the constructor. You can override this method to create a
|
provided in the constructor. You can override this method to create a
|
||||||
customized JSON wrapper.
|
customized JSON wrapper.
|
||||||
"""
|
"""
|
||||||
dumps = self._dumps
|
return self._dumps(obj)
|
||||||
if dumps is not None:
|
|
||||||
return dumps(obj)
|
def prepare(self, conn):
|
||||||
else:
|
self._conn = conn
|
||||||
raise ImportError(
|
|
||||||
"json module not available: "
|
|
||||||
"you should provide a dumps function")
|
|
||||||
|
|
||||||
def getquoted(self):
|
def getquoted(self):
|
||||||
s = self.dumps(self.adapted)
|
s = self.dumps(self.adapted)
|
||||||
return QuotedString(s).getquoted()
|
qs = QuotedString(s)
|
||||||
|
if self._conn is not None:
|
||||||
|
qs.prepare(self._conn)
|
||||||
|
return qs.getquoted()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
# getquoted is binary
|
||||||
|
return self.getquoted().decode('ascii', 'replace')
|
||||||
|
|
||||||
|
|
||||||
def register_json(conn_or_curs=None, globally=False, loads=None,
|
def register_json(conn_or_curs=None, globally=False, loads=None,
|
||||||
oid=None, array_oid=None):
|
oid=None, array_oid=None, name='json'):
|
||||||
"""Create and register typecasters converting :sql:`json` type to Python objects.
|
"""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`
|
:param conn_or_curs: a connection or cursor used to find the :sql:`json`
|
||||||
|
@ -110,17 +103,19 @@ def register_json(conn_or_curs=None, globally=False, loads=None,
|
||||||
queried on *conn_or_curs*
|
queried on *conn_or_curs*
|
||||||
:param array_oid: the OID of the :sql:`json[]` array type if known;
|
:param array_oid: the OID of the :sql:`json[]` array type if known;
|
||||||
if not, it will be queried on *conn_or_curs*
|
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
|
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. No query is
|
database and look for the OID of the :sql:`json` type (or an alternative
|
||||||
performed if *oid* and *array_oid* are provided. Raise
|
type if *name* if provided). No query is performed if *oid* and *array_oid*
|
||||||
`~psycopg2.ProgrammingError` if the type is not found.
|
are provided. Raise `~psycopg2.ProgrammingError` if the type is not found.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if oid is None:
|
if oid is None:
|
||||||
oid, array_oid = _get_json_oids(conn_or_curs)
|
oid, array_oid = _get_json_oids(conn_or_curs, name)
|
||||||
|
|
||||||
JSON, JSONARRAY = _create_json_typecasters(oid, array_oid, loads)
|
JSON, JSONARRAY = _create_json_typecasters(
|
||||||
|
oid, array_oid, loads=loads, name=name.upper())
|
||||||
|
|
||||||
register_type(JSON, not globally and conn_or_curs or None)
|
register_type(JSON, not globally and conn_or_curs or None)
|
||||||
|
|
||||||
|
@ -129,6 +124,7 @@ def register_json(conn_or_curs=None, globally=False, loads=None,
|
||||||
|
|
||||||
return JSON, JSONARRAY
|
return JSON, JSONARRAY
|
||||||
|
|
||||||
|
|
||||||
def register_default_json(conn_or_curs=None, globally=False, loads=None):
|
def register_default_json(conn_or_curs=None, globally=False, loads=None):
|
||||||
"""
|
"""
|
||||||
Create and register :sql:`json` typecasters for PostgreSQL 9.2 and following.
|
Create and register :sql:`json` typecasters for PostgreSQL 9.2 and following.
|
||||||
|
@ -141,12 +137,23 @@ def register_default_json(conn_or_curs=None, globally=False, loads=None):
|
||||||
return register_json(conn_or_curs=conn_or_curs, globally=globally,
|
return register_json(conn_or_curs=conn_or_curs, globally=globally,
|
||||||
loads=loads, oid=JSON_OID, array_oid=JSONARRAY_OID)
|
loads=loads, oid=JSON_OID, array_oid=JSONARRAY_OID)
|
||||||
|
|
||||||
def _create_json_typecasters(oid, array_oid, loads=None):
|
|
||||||
|
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."""
|
"""Create typecasters for json data type."""
|
||||||
if loads is None:
|
if loads is None:
|
||||||
if json is None:
|
|
||||||
raise ImportError("no json module available")
|
|
||||||
else:
|
|
||||||
loads = json.loads
|
loads = json.loads
|
||||||
|
|
||||||
def typecast_json(s, cur):
|
def typecast_json(s, cur):
|
||||||
|
@ -154,15 +161,16 @@ def _create_json_typecasters(oid, array_oid, loads=None):
|
||||||
return None
|
return None
|
||||||
return loads(s)
|
return loads(s)
|
||||||
|
|
||||||
JSON = new_type((oid, ), 'JSON', typecast_json)
|
JSON = new_type((oid, ), name, typecast_json)
|
||||||
if array_oid is not None:
|
if array_oid is not None:
|
||||||
JSONARRAY = new_array_type((array_oid, ), "JSONARRAY", JSON)
|
JSONARRAY = new_array_type((array_oid, ), f"{name}ARRAY", JSON)
|
||||||
else:
|
else:
|
||||||
JSONARRAY = None
|
JSONARRAY = None
|
||||||
|
|
||||||
return JSON, JSONARRAY
|
return JSON, JSONARRAY
|
||||||
|
|
||||||
def _get_json_oids(conn_or_curs):
|
|
||||||
|
def _get_json_oids(conn_or_curs, name='json'):
|
||||||
# lazy imports
|
# lazy imports
|
||||||
from psycopg2.extensions import STATUS_IN_TRANSACTION
|
from psycopg2.extensions import STATUS_IN_TRANSACTION
|
||||||
from psycopg2.extras import _solve_conn_curs
|
from psycopg2.extras import _solve_conn_curs
|
||||||
|
@ -173,22 +181,19 @@ def _get_json_oids(conn_or_curs):
|
||||||
conn_status = conn.status
|
conn_status = conn.status
|
||||||
|
|
||||||
# column typarray not available before PG 8.3
|
# column typarray not available before PG 8.3
|
||||||
typarray = conn.server_version >= 80300 and "typarray" or "NULL"
|
typarray = conn.info.server_version >= 80300 and "typarray" or "NULL"
|
||||||
|
|
||||||
# get the oid for the hstore
|
# get the oid for the hstore
|
||||||
curs.execute(
|
curs.execute(
|
||||||
"SELECT t.oid, %s FROM pg_type t WHERE t.typname = 'json';"
|
"SELECT t.oid, %s FROM pg_type t WHERE t.typname = %%s;"
|
||||||
% typarray)
|
% typarray, (name,))
|
||||||
r = curs.fetchone()
|
r = curs.fetchone()
|
||||||
|
|
||||||
# revert the status of the connection as before the command
|
# revert the status of the connection as before the command
|
||||||
if (conn_status != STATUS_IN_TRANSACTION and not conn.autocommit):
|
if conn_status != STATUS_IN_TRANSACTION and not conn.autocommit:
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
|
|
||||||
if not r:
|
if not r:
|
||||||
raise conn.ProgrammingError("json data type not found")
|
raise conn.ProgrammingError(f"{name} data type not found")
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
196
lib/_range.py
196
lib/_range.py
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
# psycopg/_range.py - Implementation of the Range type and adaptation
|
# psycopg/_range.py - Implementation of the Range type and adaptation
|
||||||
#
|
#
|
||||||
# Copyright (C) 2012 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
# Copyright (C) 2012-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||||
|
# Copyright (C) 2020-2021 The Psycopg Team
|
||||||
#
|
#
|
||||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||||
# under the terms of the GNU Lesser General Public License as published
|
# under the terms of the GNU Lesser General Public License as published
|
||||||
|
@ -27,10 +28,11 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from psycopg2._psycopg import ProgrammingError, InterfaceError
|
from psycopg2._psycopg import ProgrammingError, InterfaceError
|
||||||
from psycopg2.extensions import ISQLQuote, adapt, register_adapter, b
|
from psycopg2.extensions import ISQLQuote, adapt, register_adapter
|
||||||
from psycopg2.extensions import new_type, new_array_type, register_type
|
from psycopg2.extensions import new_type, new_array_type, register_type
|
||||||
|
|
||||||
class Range(object):
|
|
||||||
|
class Range:
|
||||||
"""Python representation for a PostgreSQL |range|_ type.
|
"""Python representation for a PostgreSQL |range|_ type.
|
||||||
|
|
||||||
:param lower: lower bound for the range. `!None` means unbound
|
:param lower: lower bound for the range. `!None` means unbound
|
||||||
|
@ -45,7 +47,7 @@ class Range(object):
|
||||||
def __init__(self, lower=None, upper=None, bounds='[)', empty=False):
|
def __init__(self, lower=None, upper=None, bounds='[)', empty=False):
|
||||||
if not empty:
|
if not empty:
|
||||||
if bounds not in ('[)', '(]', '()', '[]'):
|
if bounds not in ('[)', '(]', '()', '[]'):
|
||||||
raise ValueError("bound flags not valid: %r" % bounds)
|
raise ValueError(f"bound flags not valid: {bounds!r}")
|
||||||
|
|
||||||
self._lower = lower
|
self._lower = lower
|
||||||
self._upper = upper
|
self._upper = upper
|
||||||
|
@ -55,11 +57,24 @@ class Range(object):
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self._bounds is None:
|
if self._bounds is None:
|
||||||
return "%s(empty=True)" % self.__class__.__name__
|
return f"{self.__class__.__name__}(empty=True)"
|
||||||
else:
|
else:
|
||||||
return "%s(%r, %r, %r)" % (self.__class__.__name__,
|
return "{}({!r}, {!r}, {!r})".format(self.__class__.__name__,
|
||||||
self._lower, self._upper, self._bounds)
|
self._lower, self._upper, self._bounds)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self._bounds is None:
|
||||||
|
return 'empty'
|
||||||
|
|
||||||
|
items = [
|
||||||
|
self._bounds[0],
|
||||||
|
str(self._lower),
|
||||||
|
', ',
|
||||||
|
str(self._upper),
|
||||||
|
self._bounds[1]
|
||||||
|
]
|
||||||
|
return ''.join(items)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lower(self):
|
def lower(self):
|
||||||
"""The lower bound of the range. `!None` if empty or unbound."""
|
"""The lower bound of the range. `!None` if empty or unbound."""
|
||||||
|
@ -78,49 +93,59 @@ class Range(object):
|
||||||
@property
|
@property
|
||||||
def lower_inf(self):
|
def lower_inf(self):
|
||||||
"""`!True` if the range doesn't have a lower bound."""
|
"""`!True` if the range doesn't have a lower bound."""
|
||||||
if self._bounds is None: return False
|
if self._bounds is None:
|
||||||
|
return False
|
||||||
return self._lower is None
|
return self._lower is None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def upper_inf(self):
|
def upper_inf(self):
|
||||||
"""`!True` if the range doesn't have an upper bound."""
|
"""`!True` if the range doesn't have an upper bound."""
|
||||||
if self._bounds is None: return False
|
if self._bounds is None:
|
||||||
|
return False
|
||||||
return self._upper is None
|
return self._upper is None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lower_inc(self):
|
def lower_inc(self):
|
||||||
"""`!True` if the lower bound is included in the range."""
|
"""`!True` if the lower bound is included in the range."""
|
||||||
if self._bounds is None: return False
|
if self._bounds is None or self._lower is None:
|
||||||
if self._lower is None: return False
|
return False
|
||||||
return self._bounds[0] == '['
|
return self._bounds[0] == '['
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def upper_inc(self):
|
def upper_inc(self):
|
||||||
"""`!True` if the upper bound is included in the range."""
|
"""`!True` if the upper bound is included in the range."""
|
||||||
if self._bounds is None: return False
|
if self._bounds is None or self._upper is None:
|
||||||
if self._upper is None: return False
|
return False
|
||||||
return self._bounds[1] == ']'
|
return self._bounds[1] == ']'
|
||||||
|
|
||||||
def __contains__(self, x):
|
def __contains__(self, x):
|
||||||
if self._bounds is None: return False
|
if self._bounds is None:
|
||||||
|
return False
|
||||||
|
|
||||||
if self._lower is not None:
|
if self._lower is not None:
|
||||||
if self._bounds[0] == '[':
|
if self._bounds[0] == '[':
|
||||||
if x < self._lower: return False
|
if x < self._lower:
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
if x <= self._lower: return False
|
if x <= self._lower:
|
||||||
|
return False
|
||||||
|
|
||||||
if self._upper is not None:
|
if self._upper is not None:
|
||||||
if self._bounds[1] == ']':
|
if self._bounds[1] == ']':
|
||||||
if x > self._upper: return False
|
if x > self._upper:
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
if x >= self._upper: return False
|
if x >= self._upper:
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __nonzero__(self):
|
def __bool__(self):
|
||||||
return self._bounds is not None
|
return self._bounds is not None
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, Range):
|
||||||
|
return False
|
||||||
return (self._lower == other._lower
|
return (self._lower == other._lower
|
||||||
and self._upper == other._upper
|
and self._upper == other._upper
|
||||||
and self._bounds == other._bounds)
|
and self._bounds == other._bounds)
|
||||||
|
@ -131,12 +156,51 @@ class Range(object):
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash((self._lower, self._upper, self._bounds))
|
return hash((self._lower, self._upper, self._bounds))
|
||||||
|
|
||||||
def __lt__(self, other):
|
# as the postgres docs describe for the server-side stuff,
|
||||||
raise TypeError(
|
# ordering is rather arbitrary, but will remain stable
|
||||||
'Range objects cannot be ordered; please refer to the PostgreSQL'
|
# and consistent.
|
||||||
' documentation to perform this operation in the database')
|
|
||||||
|
|
||||||
__le__ = __gt__ = __ge__ = __lt__
|
def __lt__(self, other):
|
||||||
|
if not isinstance(other, Range):
|
||||||
|
return NotImplemented
|
||||||
|
for attr in ('_lower', '_upper', '_bounds'):
|
||||||
|
self_value = getattr(self, attr)
|
||||||
|
other_value = getattr(other, attr)
|
||||||
|
if self_value == other_value:
|
||||||
|
pass
|
||||||
|
elif self_value is None:
|
||||||
|
return True
|
||||||
|
elif other_value is None:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return self_value < other_value
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
if self == other:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return self.__lt__(other)
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
if isinstance(other, Range):
|
||||||
|
return other.__lt__(self)
|
||||||
|
else:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
if self == other:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return self.__gt__(other)
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return {slot: getattr(self, slot)
|
||||||
|
for slot in self.__slots__ if hasattr(self, slot)}
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
for slot, value in state.items():
|
||||||
|
setattr(self, slot, value)
|
||||||
|
|
||||||
|
|
||||||
def register_range(pgrange, pyrange, conn_or_curs, globally=False):
|
def register_range(pgrange, pyrange, conn_or_curs, globally=False):
|
||||||
|
@ -170,7 +234,7 @@ def register_range(pgrange, pyrange, conn_or_curs, globally=False):
|
||||||
return caster
|
return caster
|
||||||
|
|
||||||
|
|
||||||
class RangeAdapter(object):
|
class RangeAdapter:
|
||||||
"""`ISQLQuote` adapter for `Range` subclasses.
|
"""`ISQLQuote` adapter for `Range` subclasses.
|
||||||
|
|
||||||
This is an abstract class: concrete classes must set a `name` class
|
This is an abstract class: concrete classes must set a `name` class
|
||||||
|
@ -196,7 +260,7 @@ class RangeAdapter(object):
|
||||||
|
|
||||||
r = self.adapted
|
r = self.adapted
|
||||||
if r.isempty:
|
if r.isempty:
|
||||||
return b("'empty'::" + self.name)
|
return b"'empty'::" + self.name.encode('utf8')
|
||||||
|
|
||||||
if r.lower is not None:
|
if r.lower is not None:
|
||||||
a = adapt(r.lower)
|
a = adapt(r.lower)
|
||||||
|
@ -204,7 +268,7 @@ class RangeAdapter(object):
|
||||||
a.prepare(self._conn)
|
a.prepare(self._conn)
|
||||||
lower = a.getquoted()
|
lower = a.getquoted()
|
||||||
else:
|
else:
|
||||||
lower = b('NULL')
|
lower = b'NULL'
|
||||||
|
|
||||||
if r.upper is not None:
|
if r.upper is not None:
|
||||||
a = adapt(r.upper)
|
a = adapt(r.upper)
|
||||||
|
@ -212,13 +276,13 @@ class RangeAdapter(object):
|
||||||
a.prepare(self._conn)
|
a.prepare(self._conn)
|
||||||
upper = a.getquoted()
|
upper = a.getquoted()
|
||||||
else:
|
else:
|
||||||
upper = b('NULL')
|
upper = b'NULL'
|
||||||
|
|
||||||
return b(self.name + '(') + lower + b(', ') + upper \
|
return self.name.encode('utf8') + b'(' + lower + b', ' + upper \
|
||||||
+ b(", '%s')" % r._bounds)
|
+ b", '" + r._bounds.encode('utf8') + b"')"
|
||||||
|
|
||||||
|
|
||||||
class RangeCaster(object):
|
class RangeCaster:
|
||||||
"""Helper class to convert between `Range` and PostgreSQL range types.
|
"""Helper class to convert between `Range` and PostgreSQL range types.
|
||||||
|
|
||||||
Objects of this class are usually created by `register_range()`. Manual
|
Objects of this class are usually created by `register_range()`. Manual
|
||||||
|
@ -246,12 +310,13 @@ class RangeCaster(object):
|
||||||
# an implementation detail and is not documented. It is currently used
|
# an implementation detail and is not documented. It is currently used
|
||||||
# for the numeric ranges.
|
# for the numeric ranges.
|
||||||
self.adapter = None
|
self.adapter = None
|
||||||
if isinstance(pgrange, basestring):
|
if isinstance(pgrange, str):
|
||||||
self.adapter = type(pgrange, (RangeAdapter,), {})
|
self.adapter = type(pgrange, (RangeAdapter,), {})
|
||||||
self.adapter.name = pgrange
|
self.adapter.name = pgrange
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if issubclass(pgrange, RangeAdapter) and pgrange is not RangeAdapter:
|
if issubclass(pgrange, RangeAdapter) \
|
||||||
|
and pgrange is not RangeAdapter:
|
||||||
self.adapter = pgrange
|
self.adapter = pgrange
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
@ -262,7 +327,7 @@ class RangeCaster(object):
|
||||||
|
|
||||||
self.range = None
|
self.range = None
|
||||||
try:
|
try:
|
||||||
if isinstance(pyrange, basestring):
|
if isinstance(pyrange, str):
|
||||||
self.range = type(pyrange, (Range,), {})
|
self.range = type(pyrange, (Range,), {})
|
||||||
if issubclass(pyrange, Range) and pyrange is not Range:
|
if issubclass(pyrange, Range) and pyrange is not Range:
|
||||||
self.range = pyrange
|
self.range = pyrange
|
||||||
|
@ -283,9 +348,9 @@ class RangeCaster(object):
|
||||||
from psycopg2.extras import _solve_conn_curs
|
from psycopg2.extras import _solve_conn_curs
|
||||||
conn, curs = _solve_conn_curs(conn_or_curs)
|
conn, curs = _solve_conn_curs(conn_or_curs)
|
||||||
|
|
||||||
if conn.server_version < 90200:
|
if conn.info.server_version < 90200:
|
||||||
raise ProgrammingError("range types not available in version %s"
|
raise ProgrammingError("range types not available in version %s"
|
||||||
% conn.server_version)
|
% conn.info.server_version)
|
||||||
|
|
||||||
# Store the transaction status of the connection to revert it after use
|
# Store the transaction status of the connection to revert it after use
|
||||||
conn_status = conn.status
|
conn_status = conn.status
|
||||||
|
@ -298,33 +363,54 @@ class RangeCaster(object):
|
||||||
schema = 'public'
|
schema = 'public'
|
||||||
|
|
||||||
# get the type oid and attributes
|
# get the type oid and attributes
|
||||||
try:
|
|
||||||
curs.execute("""\
|
curs.execute("""\
|
||||||
select rngtypid, rngsubtype,
|
select rngtypid, rngsubtype, typarray
|
||||||
(select typarray from pg_type where oid = rngtypid)
|
|
||||||
from pg_range r
|
from pg_range r
|
||||||
join pg_type t on t.oid = rngtypid
|
join pg_type t on t.oid = rngtypid
|
||||||
join pg_namespace ns on ns.oid = typnamespace
|
join pg_namespace ns on ns.oid = typnamespace
|
||||||
where typname = %s and ns.nspname = %s;
|
where typname = %s and ns.nspname = %s;
|
||||||
""", (tname, schema))
|
""", (tname, schema))
|
||||||
|
|
||||||
except ProgrammingError:
|
|
||||||
if not conn.autocommit:
|
|
||||||
conn.rollback()
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
rec = curs.fetchone()
|
rec = curs.fetchone()
|
||||||
|
|
||||||
|
if not rec:
|
||||||
|
# The above algorithm doesn't work for customized seach_path
|
||||||
|
# (#1487) The implementation below works better, but, to guarantee
|
||||||
|
# backwards compatibility, use it only if the original one failed.
|
||||||
|
try:
|
||||||
|
savepoint = False
|
||||||
|
# Because we executed statements earlier, we are either INTRANS
|
||||||
|
# or we are IDLE only if the transaction is autocommit, in
|
||||||
|
# which case we don't need the savepoint anyway.
|
||||||
|
if conn.status == STATUS_IN_TRANSACTION:
|
||||||
|
curs.execute("SAVEPOINT register_type")
|
||||||
|
savepoint = True
|
||||||
|
|
||||||
|
curs.execute("""\
|
||||||
|
SELECT rngtypid, rngsubtype, typarray, typname, nspname
|
||||||
|
from pg_range r
|
||||||
|
join pg_type t on t.oid = rngtypid
|
||||||
|
join pg_namespace ns on ns.oid = typnamespace
|
||||||
|
WHERE t.oid = %s::regtype
|
||||||
|
""", (name, ))
|
||||||
|
except ProgrammingError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
rec = curs.fetchone()
|
||||||
|
if rec:
|
||||||
|
tname, schema = rec[3:]
|
||||||
|
finally:
|
||||||
|
if savepoint:
|
||||||
|
curs.execute("ROLLBACK TO SAVEPOINT register_type")
|
||||||
|
|
||||||
# revert the status of the connection as before the command
|
# revert the status of the connection as before the command
|
||||||
if (conn_status != STATUS_IN_TRANSACTION
|
if conn_status != STATUS_IN_TRANSACTION and not conn.autocommit:
|
||||||
and not conn.autocommit):
|
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
|
|
||||||
if not rec:
|
if not rec:
|
||||||
raise ProgrammingError(
|
raise ProgrammingError(
|
||||||
"PostgreSQL type '%s' not found" % name)
|
f"PostgreSQL range '{name}' not found")
|
||||||
|
|
||||||
type, subtype, array = rec
|
type, subtype, array = rec[:3]
|
||||||
|
|
||||||
return RangeCaster(name, pyrange,
|
return RangeCaster(name, pyrange,
|
||||||
oid=type, subtype_oid=subtype, array_oid=array)
|
oid=type, subtype_oid=subtype, array_oid=array)
|
||||||
|
@ -354,7 +440,7 @@ where typname = %s and ns.nspname = %s;
|
||||||
|
|
||||||
m = self._re_range.match(s)
|
m = self._re_range.match(s)
|
||||||
if m is None:
|
if m is None:
|
||||||
raise InterfaceError("failed to parse range: %s")
|
raise InterfaceError(f"failed to parse range: '{s}'")
|
||||||
|
|
||||||
lower = m.group(3)
|
lower = m.group(3)
|
||||||
if lower is None:
|
if lower is None:
|
||||||
|
@ -392,14 +478,17 @@ class NumericRange(Range):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DateRange(Range):
|
class DateRange(Range):
|
||||||
"""Represents :sql:`daterange` values."""
|
"""Represents :sql:`daterange` values."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DateTimeRange(Range):
|
class DateTimeRange(Range):
|
||||||
"""Represents :sql:`tsrange` values."""
|
"""Represents :sql:`tsrange` values."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DateTimeTZRange(Range):
|
class DateTimeTZRange(Range):
|
||||||
"""Represents :sql:`tstzrange` values."""
|
"""Represents :sql:`tstzrange` values."""
|
||||||
pass
|
pass
|
||||||
|
@ -415,7 +504,7 @@ class NumberRangeAdapter(RangeAdapter):
|
||||||
def getquoted(self):
|
def getquoted(self):
|
||||||
r = self.adapted
|
r = self.adapted
|
||||||
if r.isempty:
|
if r.isempty:
|
||||||
return "'empty'"
|
return b"'empty'"
|
||||||
|
|
||||||
if not r.lower_inf:
|
if not r.lower_inf:
|
||||||
# not exactly: we are relying that none of these object is really
|
# not exactly: we are relying that none of these object is really
|
||||||
|
@ -431,13 +520,12 @@ class NumberRangeAdapter(RangeAdapter):
|
||||||
else:
|
else:
|
||||||
upper = ''
|
upper = ''
|
||||||
|
|
||||||
return b("'%s%s,%s%s'" % (
|
return (f"'{r._bounds[0]}{lower},{upper}{r._bounds[1]}'").encode('ascii')
|
||||||
r._bounds[0], lower, upper, r._bounds[1]))
|
|
||||||
|
|
||||||
# TODO: probably won't work with infs, nans and other tricky cases.
|
# TODO: probably won't work with infs, nans and other tricky cases.
|
||||||
register_adapter(NumericRange, NumberRangeAdapter)
|
register_adapter(NumericRange, NumberRangeAdapter)
|
||||||
|
|
||||||
|
|
||||||
# Register globally typecasters and adapters for builtin range types.
|
# Register globally typecasters and adapters for builtin range types.
|
||||||
|
|
||||||
# note: the adapter is registered more than once, but this is harmless.
|
# note: the adapter is registered more than once, but this is harmless.
|
||||||
|
@ -464,5 +552,3 @@ tsrange_caster._register()
|
||||||
tstzrange_caster = RangeCaster('tstzrange', DateTimeTZRange,
|
tstzrange_caster = RangeCaster('tstzrange', DateTimeTZRange,
|
||||||
oid=3910, subtype_oid=1184, array_oid=3911)
|
oid=3910, subtype_oid=1184, array_oid=3911)
|
||||||
tstzrange_caster._register()
|
tstzrange_caster._register()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
"""Error codes for PostgresSQL
|
"""Error codes for PostgreSQL
|
||||||
|
|
||||||
This module contains symbolic names for all PostgreSQL error codes.
|
This module contains symbolic names for all PostgreSQL error codes.
|
||||||
"""
|
"""
|
||||||
# psycopg2/errorcodes.py - PostgreSQL error codes
|
# psycopg2/errorcodes.py - PostgreSQL error codes
|
||||||
#
|
#
|
||||||
# Copyright (C) 2006-2010 Johan Dahlin <jdahlin@async.com.br>
|
# Copyright (C) 2006-2019 Johan Dahlin <jdahlin@async.com.br>
|
||||||
|
# Copyright (C) 2020-2021 The Psycopg Team
|
||||||
#
|
#
|
||||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||||
# under the terms of the GNU Lesser General Public License as published
|
# under the terms of the GNU Lesser General Public License as published
|
||||||
|
@ -26,9 +27,10 @@ This module contains symbolic names for all PostgreSQL error codes.
|
||||||
#
|
#
|
||||||
# Based on:
|
# Based on:
|
||||||
#
|
#
|
||||||
# http://www.postgresql.org/docs/current/static/errcodes-appendix.html
|
# https://www.postgresql.org/docs/current/static/errcodes-appendix.html
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
def lookup(code, _cache={}):
|
def lookup(code, _cache={}):
|
||||||
"""Lookup an error code or class code and return its symbolic name.
|
"""Lookup an error code or class code and return its symbolic name.
|
||||||
|
|
||||||
|
@ -38,11 +40,18 @@ def lookup(code, _cache={}):
|
||||||
return _cache[code]
|
return _cache[code]
|
||||||
|
|
||||||
# Generate the lookup map at first usage.
|
# Generate the lookup map at first usage.
|
||||||
for k, v in globals().iteritems():
|
tmp = {}
|
||||||
|
for k, v in globals().items():
|
||||||
if isinstance(v, str) and len(v) in (2, 5):
|
if isinstance(v, str) and len(v) in (2, 5):
|
||||||
_cache[v] = k
|
# Strip trailing underscore used to disambiguate duplicate values
|
||||||
|
tmp[v] = k.rstrip("_")
|
||||||
|
|
||||||
return lookup(code)
|
assert tmp
|
||||||
|
|
||||||
|
# Atomic update, to avoid race condition on import (bug #382)
|
||||||
|
_cache.update(tmp)
|
||||||
|
|
||||||
|
return _cache[code]
|
||||||
|
|
||||||
|
|
||||||
# autogenerated data: do not edit below this point.
|
# autogenerated data: do not edit below this point.
|
||||||
|
@ -86,6 +95,7 @@ CLASS_PROGRAM_LIMIT_EXCEEDED = '54'
|
||||||
CLASS_OBJECT_NOT_IN_PREREQUISITE_STATE = '55'
|
CLASS_OBJECT_NOT_IN_PREREQUISITE_STATE = '55'
|
||||||
CLASS_OPERATOR_INTERVENTION = '57'
|
CLASS_OPERATOR_INTERVENTION = '57'
|
||||||
CLASS_SYSTEM_ERROR = '58'
|
CLASS_SYSTEM_ERROR = '58'
|
||||||
|
CLASS_SNAPSHOT_FAILURE = '72'
|
||||||
CLASS_CONFIGURATION_FILE_ERROR = 'F0'
|
CLASS_CONFIGURATION_FILE_ERROR = 'F0'
|
||||||
CLASS_FOREIGN_DATA_WRAPPER_ERROR = 'HV'
|
CLASS_FOREIGN_DATA_WRAPPER_ERROR = 'HV'
|
||||||
CLASS_PL_PGSQL_ERROR = 'P0'
|
CLASS_PL_PGSQL_ERROR = 'P0'
|
||||||
|
@ -97,7 +107,7 @@ SUCCESSFUL_COMPLETION = '00000'
|
||||||
# Class 01 - Warning
|
# Class 01 - Warning
|
||||||
WARNING = '01000'
|
WARNING = '01000'
|
||||||
NULL_VALUE_ELIMINATED_IN_SET_FUNCTION = '01003'
|
NULL_VALUE_ELIMINATED_IN_SET_FUNCTION = '01003'
|
||||||
STRING_DATA_RIGHT_TRUNCATION = '01004'
|
STRING_DATA_RIGHT_TRUNCATION_ = '01004'
|
||||||
PRIVILEGE_NOT_REVOKED = '01006'
|
PRIVILEGE_NOT_REVOKED = '01006'
|
||||||
PRIVILEGE_NOT_GRANTED = '01007'
|
PRIVILEGE_NOT_GRANTED = '01007'
|
||||||
IMPLICIT_ZERO_BIT_PADDING = '01008'
|
IMPLICIT_ZERO_BIT_PADDING = '01008'
|
||||||
|
@ -155,7 +165,7 @@ DATA_EXCEPTION = '22000'
|
||||||
STRING_DATA_RIGHT_TRUNCATION = '22001'
|
STRING_DATA_RIGHT_TRUNCATION = '22001'
|
||||||
NULL_VALUE_NO_INDICATOR_PARAMETER = '22002'
|
NULL_VALUE_NO_INDICATOR_PARAMETER = '22002'
|
||||||
NUMERIC_VALUE_OUT_OF_RANGE = '22003'
|
NUMERIC_VALUE_OUT_OF_RANGE = '22003'
|
||||||
NULL_VALUE_NOT_ALLOWED = '22004'
|
NULL_VALUE_NOT_ALLOWED_ = '22004'
|
||||||
ERROR_IN_ASSIGNMENT = '22005'
|
ERROR_IN_ASSIGNMENT = '22005'
|
||||||
INVALID_DATETIME_FORMAT = '22007'
|
INVALID_DATETIME_FORMAT = '22007'
|
||||||
DATETIME_FIELD_OVERFLOW = '22008'
|
DATETIME_FIELD_OVERFLOW = '22008'
|
||||||
|
@ -165,6 +175,7 @@ INVALID_USE_OF_ESCAPE_CHARACTER = '2200C'
|
||||||
INVALID_ESCAPE_OCTET = '2200D'
|
INVALID_ESCAPE_OCTET = '2200D'
|
||||||
ZERO_LENGTH_CHARACTER_STRING = '2200F'
|
ZERO_LENGTH_CHARACTER_STRING = '2200F'
|
||||||
MOST_SPECIFIC_TYPE_MISMATCH = '2200G'
|
MOST_SPECIFIC_TYPE_MISMATCH = '2200G'
|
||||||
|
SEQUENCE_GENERATOR_LIMIT_EXCEEDED = '2200H'
|
||||||
NOT_AN_XML_DOCUMENT = '2200L'
|
NOT_AN_XML_DOCUMENT = '2200L'
|
||||||
INVALID_XML_DOCUMENT = '2200M'
|
INVALID_XML_DOCUMENT = '2200M'
|
||||||
INVALID_XML_CONTENT = '2200N'
|
INVALID_XML_CONTENT = '2200N'
|
||||||
|
@ -173,6 +184,7 @@ INVALID_XML_PROCESSING_INSTRUCTION = '2200T'
|
||||||
INVALID_INDICATOR_PARAMETER_VALUE = '22010'
|
INVALID_INDICATOR_PARAMETER_VALUE = '22010'
|
||||||
SUBSTRING_ERROR = '22011'
|
SUBSTRING_ERROR = '22011'
|
||||||
DIVISION_BY_ZERO = '22012'
|
DIVISION_BY_ZERO = '22012'
|
||||||
|
INVALID_PRECEDING_OR_FOLLOWING_SIZE = '22013'
|
||||||
INVALID_ARGUMENT_FOR_NTILE_FUNCTION = '22014'
|
INVALID_ARGUMENT_FOR_NTILE_FUNCTION = '22014'
|
||||||
INTERVAL_FIELD_OVERFLOW = '22015'
|
INTERVAL_FIELD_OVERFLOW = '22015'
|
||||||
INVALID_ARGUMENT_FOR_NTH_VALUE_FUNCTION = '22016'
|
INVALID_ARGUMENT_FOR_NTH_VALUE_FUNCTION = '22016'
|
||||||
|
@ -193,6 +205,25 @@ INVALID_ESCAPE_SEQUENCE = '22025'
|
||||||
STRING_DATA_LENGTH_MISMATCH = '22026'
|
STRING_DATA_LENGTH_MISMATCH = '22026'
|
||||||
TRIM_ERROR = '22027'
|
TRIM_ERROR = '22027'
|
||||||
ARRAY_SUBSCRIPT_ERROR = '2202E'
|
ARRAY_SUBSCRIPT_ERROR = '2202E'
|
||||||
|
INVALID_TABLESAMPLE_REPEAT = '2202G'
|
||||||
|
INVALID_TABLESAMPLE_ARGUMENT = '2202H'
|
||||||
|
DUPLICATE_JSON_OBJECT_KEY_VALUE = '22030'
|
||||||
|
INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION = '22031'
|
||||||
|
INVALID_JSON_TEXT = '22032'
|
||||||
|
INVALID_SQL_JSON_SUBSCRIPT = '22033'
|
||||||
|
MORE_THAN_ONE_SQL_JSON_ITEM = '22034'
|
||||||
|
NO_SQL_JSON_ITEM = '22035'
|
||||||
|
NON_NUMERIC_SQL_JSON_ITEM = '22036'
|
||||||
|
NON_UNIQUE_KEYS_IN_A_JSON_OBJECT = '22037'
|
||||||
|
SINGLETON_SQL_JSON_ITEM_REQUIRED = '22038'
|
||||||
|
SQL_JSON_ARRAY_NOT_FOUND = '22039'
|
||||||
|
SQL_JSON_MEMBER_NOT_FOUND = '2203A'
|
||||||
|
SQL_JSON_NUMBER_NOT_FOUND = '2203B'
|
||||||
|
SQL_JSON_OBJECT_NOT_FOUND = '2203C'
|
||||||
|
TOO_MANY_JSON_ARRAY_ELEMENTS = '2203D'
|
||||||
|
TOO_MANY_JSON_OBJECT_MEMBERS = '2203E'
|
||||||
|
SQL_JSON_SCALAR_REQUIRED = '2203F'
|
||||||
|
SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE = '2203G'
|
||||||
FLOATING_POINT_EXCEPTION = '22P01'
|
FLOATING_POINT_EXCEPTION = '22P01'
|
||||||
INVALID_TEXT_REPRESENTATION = '22P02'
|
INVALID_TEXT_REPRESENTATION = '22P02'
|
||||||
INVALID_BINARY_REPRESENTATION = '22P03'
|
INVALID_BINARY_REPRESENTATION = '22P03'
|
||||||
|
@ -224,6 +255,8 @@ SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED = '25007'
|
||||||
HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL = '25008'
|
HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL = '25008'
|
||||||
NO_ACTIVE_SQL_TRANSACTION = '25P01'
|
NO_ACTIVE_SQL_TRANSACTION = '25P01'
|
||||||
IN_FAILED_SQL_TRANSACTION = '25P02'
|
IN_FAILED_SQL_TRANSACTION = '25P02'
|
||||||
|
IDLE_IN_TRANSACTION_SESSION_TIMEOUT = '25P03'
|
||||||
|
TRANSACTION_TIMEOUT = '25P04'
|
||||||
|
|
||||||
# Class 26 - Invalid SQL Statement Name
|
# Class 26 - Invalid SQL Statement Name
|
||||||
INVALID_SQL_STATEMENT_NAME = '26000'
|
INVALID_SQL_STATEMENT_NAME = '26000'
|
||||||
|
@ -244,9 +277,9 @@ INVALID_TRANSACTION_TERMINATION = '2D000'
|
||||||
|
|
||||||
# Class 2F - SQL Routine Exception
|
# Class 2F - SQL Routine Exception
|
||||||
SQL_ROUTINE_EXCEPTION = '2F000'
|
SQL_ROUTINE_EXCEPTION = '2F000'
|
||||||
MODIFYING_SQL_DATA_NOT_PERMITTED = '2F002'
|
MODIFYING_SQL_DATA_NOT_PERMITTED_ = '2F002'
|
||||||
PROHIBITED_SQL_STATEMENT_ATTEMPTED = '2F003'
|
PROHIBITED_SQL_STATEMENT_ATTEMPTED_ = '2F003'
|
||||||
READING_SQL_DATA_NOT_PERMITTED = '2F004'
|
READING_SQL_DATA_NOT_PERMITTED_ = '2F004'
|
||||||
FUNCTION_EXECUTED_NO_RETURN_STATEMENT = '2F005'
|
FUNCTION_EXECUTED_NO_RETURN_STATEMENT = '2F005'
|
||||||
|
|
||||||
# Class 34 - Invalid Cursor Name
|
# Class 34 - Invalid Cursor Name
|
||||||
|
@ -265,6 +298,7 @@ INVALID_SQLSTATE_RETURNED = '39001'
|
||||||
NULL_VALUE_NOT_ALLOWED = '39004'
|
NULL_VALUE_NOT_ALLOWED = '39004'
|
||||||
TRIGGER_PROTOCOL_VIOLATED = '39P01'
|
TRIGGER_PROTOCOL_VIOLATED = '39P01'
|
||||||
SRF_PROTOCOL_VIOLATED = '39P02'
|
SRF_PROTOCOL_VIOLATED = '39P02'
|
||||||
|
EVENT_TRIGGER_PROTOCOL_VIOLATED = '39P03'
|
||||||
|
|
||||||
# Class 3B - Savepoint Exception
|
# Class 3B - Savepoint Exception
|
||||||
SAVEPOINT_EXCEPTION = '3B000'
|
SAVEPOINT_EXCEPTION = '3B000'
|
||||||
|
@ -304,6 +338,7 @@ WRONG_OBJECT_TYPE = '42809'
|
||||||
INVALID_FOREIGN_KEY = '42830'
|
INVALID_FOREIGN_KEY = '42830'
|
||||||
CANNOT_COERCE = '42846'
|
CANNOT_COERCE = '42846'
|
||||||
UNDEFINED_FUNCTION = '42883'
|
UNDEFINED_FUNCTION = '42883'
|
||||||
|
GENERATED_ALWAYS = '428C9'
|
||||||
RESERVED_NAME = '42939'
|
RESERVED_NAME = '42939'
|
||||||
UNDEFINED_TABLE = '42P01'
|
UNDEFINED_TABLE = '42P01'
|
||||||
UNDEFINED_PARAMETER = '42P02'
|
UNDEFINED_PARAMETER = '42P02'
|
||||||
|
@ -349,6 +384,7 @@ OBJECT_NOT_IN_PREREQUISITE_STATE = '55000'
|
||||||
OBJECT_IN_USE = '55006'
|
OBJECT_IN_USE = '55006'
|
||||||
CANT_CHANGE_RUNTIME_PARAM = '55P02'
|
CANT_CHANGE_RUNTIME_PARAM = '55P02'
|
||||||
LOCK_NOT_AVAILABLE = '55P03'
|
LOCK_NOT_AVAILABLE = '55P03'
|
||||||
|
UNSAFE_NEW_ENUM_VALUE_USAGE = '55P04'
|
||||||
|
|
||||||
# Class 57 - Operator Intervention
|
# Class 57 - Operator Intervention
|
||||||
OPERATOR_INTERVENTION = '57000'
|
OPERATOR_INTERVENTION = '57000'
|
||||||
|
@ -357,6 +393,7 @@ ADMIN_SHUTDOWN = '57P01'
|
||||||
CRASH_SHUTDOWN = '57P02'
|
CRASH_SHUTDOWN = '57P02'
|
||||||
CANNOT_CONNECT_NOW = '57P03'
|
CANNOT_CONNECT_NOW = '57P03'
|
||||||
DATABASE_DROPPED = '57P04'
|
DATABASE_DROPPED = '57P04'
|
||||||
|
IDLE_SESSION_TIMEOUT = '57P05'
|
||||||
|
|
||||||
# Class 58 - System Error (errors external to PostgreSQL itself)
|
# Class 58 - System Error (errors external to PostgreSQL itself)
|
||||||
SYSTEM_ERROR = '58000'
|
SYSTEM_ERROR = '58000'
|
||||||
|
@ -364,6 +401,9 @@ IO_ERROR = '58030'
|
||||||
UNDEFINED_FILE = '58P01'
|
UNDEFINED_FILE = '58P01'
|
||||||
DUPLICATE_FILE = '58P02'
|
DUPLICATE_FILE = '58P02'
|
||||||
|
|
||||||
|
# Class 72 - Snapshot Failure
|
||||||
|
SNAPSHOT_TOO_OLD = '72000'
|
||||||
|
|
||||||
# Class F0 - Configuration File Error
|
# Class F0 - Configuration File Error
|
||||||
CONFIG_FILE_ERROR = 'F0000'
|
CONFIG_FILE_ERROR = 'F0000'
|
||||||
LOCK_FILE_EXISTS = 'F0001'
|
LOCK_FILE_EXISTS = 'F0001'
|
||||||
|
@ -402,6 +442,7 @@ PLPGSQL_ERROR = 'P0000'
|
||||||
RAISE_EXCEPTION = 'P0001'
|
RAISE_EXCEPTION = 'P0001'
|
||||||
NO_DATA_FOUND = 'P0002'
|
NO_DATA_FOUND = 'P0002'
|
||||||
TOO_MANY_ROWS = 'P0003'
|
TOO_MANY_ROWS = 'P0003'
|
||||||
|
ASSERT_FAILURE = 'P0004'
|
||||||
|
|
||||||
# Class XX - Internal Error
|
# Class XX - Internal Error
|
||||||
INTERNAL_ERROR = 'XX000'
|
INTERNAL_ERROR = 'XX000'
|
||||||
|
|
38
lib/errors.py
Normal file
38
lib/errors.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
"""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-2021 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]
|
|
@ -8,11 +8,12 @@ This module holds all the extensions to the DBAPI-2.0 provided by psycopg.
|
||||||
- `adapt()` -- exposes the PEP-246_ compatible adapting mechanism used
|
- `adapt()` -- exposes the PEP-246_ compatible adapting mechanism used
|
||||||
by psycopg to adapt Python types to PostgreSQL ones
|
by psycopg to adapt Python types to PostgreSQL ones
|
||||||
|
|
||||||
.. _PEP-246: http://www.python.org/peps/pep-0246.html
|
.. _PEP-246: https://www.python.org/dev/peps/pep-0246/
|
||||||
"""
|
"""
|
||||||
# psycopg/extensions.py - DBAPI-2.0 extensions specific to psycopg
|
# psycopg/extensions.py - DBAPI-2.0 extensions specific to psycopg
|
||||||
#
|
#
|
||||||
# Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
# Copyright (C) 2020-2021 The Psycopg Team
|
||||||
#
|
#
|
||||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||||
# under the terms of the GNU Lesser General Public License as published
|
# under the terms of the GNU Lesser General Public License as published
|
||||||
|
@ -32,40 +33,28 @@ This module holds all the extensions to the DBAPI-2.0 provided by psycopg.
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||||
# License for more details.
|
# License for more details.
|
||||||
|
|
||||||
from psycopg2._psycopg import UNICODE, INTEGER, LONGINTEGER, BOOLEAN, FLOAT
|
import re as _re
|
||||||
from psycopg2._psycopg import TIME, DATE, INTERVAL, DECIMAL
|
|
||||||
from psycopg2._psycopg import BINARYARRAY, BOOLEANARRAY, DATEARRAY, DATETIMEARRAY
|
|
||||||
from psycopg2._psycopg import DECIMALARRAY, FLOATARRAY, INTEGERARRAY, INTERVALARRAY
|
|
||||||
from psycopg2._psycopg import LONGINTEGERARRAY, ROWIDARRAY, STRINGARRAY, TIMEARRAY
|
|
||||||
from psycopg2._psycopg import UNICODEARRAY
|
|
||||||
|
|
||||||
from psycopg2._psycopg import Binary, Boolean, Int, Float, QuotedString, AsIs
|
from psycopg2._psycopg import ( # noqa
|
||||||
try:
|
BINARYARRAY, BOOLEAN, BOOLEANARRAY, BYTES, BYTESARRAY, DATE, DATEARRAY,
|
||||||
from psycopg2._psycopg import MXDATE, MXDATETIME, MXINTERVAL, MXTIME
|
DATETIMEARRAY, DECIMAL, DECIMALARRAY, FLOAT, FLOATARRAY, INTEGER,
|
||||||
from psycopg2._psycopg import MXDATEARRAY, MXDATETIMEARRAY, MXINTERVALARRAY, MXTIMEARRAY
|
INTEGERARRAY, INTERVAL, INTERVALARRAY, LONGINTEGER, LONGINTEGERARRAY,
|
||||||
from psycopg2._psycopg import DateFromMx, TimeFromMx, TimestampFromMx
|
ROWIDARRAY, STRINGARRAY, TIME, TIMEARRAY, UNICODE, UNICODEARRAY,
|
||||||
from psycopg2._psycopg import IntervalFromMx
|
AsIs, Binary, Boolean, Float, Int, QuotedString, )
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
from psycopg2._psycopg import ( # noqa
|
||||||
from psycopg2._psycopg import PYDATE, PYDATETIME, PYINTERVAL, PYTIME
|
PYDATE, PYDATETIME, PYDATETIMETZ, PYINTERVAL, PYTIME, PYDATEARRAY,
|
||||||
from psycopg2._psycopg import PYDATEARRAY, PYDATETIMEARRAY, PYINTERVALARRAY, PYTIMEARRAY
|
PYDATETIMEARRAY, PYDATETIMETZARRAY, PYINTERVALARRAY, PYTIMEARRAY,
|
||||||
from psycopg2._psycopg import DateFromPy, TimeFromPy, TimestampFromPy
|
DateFromPy, TimeFromPy, TimestampFromPy, IntervalFromPy, )
|
||||||
from psycopg2._psycopg import IntervalFromPy
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from psycopg2._psycopg import adapt, adapters, encodings, connection, cursor, lobject, Xid
|
from psycopg2._psycopg import ( # noqa
|
||||||
from psycopg2._psycopg import string_types, binary_types, new_type, new_array_type, register_type
|
adapt, adapters, encodings, connection, cursor,
|
||||||
from psycopg2._psycopg import ISQLQuote, Notify, Diagnostics
|
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, )
|
||||||
|
|
||||||
from psycopg2._psycopg import QueryCanceledError, TransactionRollbackError
|
|
||||||
|
|
||||||
try:
|
|
||||||
from psycopg2._psycopg import set_wait_callback, get_wait_callback
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
"""Isolation level values."""
|
"""Isolation level values."""
|
||||||
ISOLATION_LEVEL_AUTOCOMMIT = 0
|
ISOLATION_LEVEL_AUTOCOMMIT = 0
|
||||||
|
@ -73,6 +62,8 @@ ISOLATION_LEVEL_READ_UNCOMMITTED = 4
|
||||||
ISOLATION_LEVEL_READ_COMMITTED = 1
|
ISOLATION_LEVEL_READ_COMMITTED = 1
|
||||||
ISOLATION_LEVEL_REPEATABLE_READ = 2
|
ISOLATION_LEVEL_REPEATABLE_READ = 2
|
||||||
ISOLATION_LEVEL_SERIALIZABLE = 3
|
ISOLATION_LEVEL_SERIALIZABLE = 3
|
||||||
|
ISOLATION_LEVEL_DEFAULT = None
|
||||||
|
|
||||||
|
|
||||||
"""psycopg connection status values."""
|
"""psycopg connection status values."""
|
||||||
STATUS_SETUP = 0
|
STATUS_SETUP = 0
|
||||||
|
@ -82,15 +73,17 @@ STATUS_SYNC = 3 # currently unused
|
||||||
STATUS_ASYNC = 4 # currently unused
|
STATUS_ASYNC = 4 # currently unused
|
||||||
STATUS_PREPARED = 5
|
STATUS_PREPARED = 5
|
||||||
|
|
||||||
# This is a usefull mnemonic to check if the connection is in a transaction
|
# This is a useful mnemonic to check if the connection is in a transaction
|
||||||
STATUS_IN_TRANSACTION = STATUS_BEGIN
|
STATUS_IN_TRANSACTION = STATUS_BEGIN
|
||||||
|
|
||||||
|
|
||||||
"""psycopg asynchronous connection polling values"""
|
"""psycopg asynchronous connection polling values"""
|
||||||
POLL_OK = 0
|
POLL_OK = 0
|
||||||
POLL_READ = 1
|
POLL_READ = 1
|
||||||
POLL_WRITE = 2
|
POLL_WRITE = 2
|
||||||
POLL_ERROR = 3
|
POLL_ERROR = 3
|
||||||
|
|
||||||
|
|
||||||
"""Backend transaction status values."""
|
"""Backend transaction status values."""
|
||||||
TRANSACTION_STATUS_IDLE = 0
|
TRANSACTION_STATUS_IDLE = 0
|
||||||
TRANSACTION_STATUS_ACTIVE = 1
|
TRANSACTION_STATUS_ACTIVE = 1
|
||||||
|
@ -98,15 +91,6 @@ TRANSACTION_STATUS_INTRANS = 2
|
||||||
TRANSACTION_STATUS_INERROR = 3
|
TRANSACTION_STATUS_INERROR = 3
|
||||||
TRANSACTION_STATUS_UNKNOWN = 4
|
TRANSACTION_STATUS_UNKNOWN = 4
|
||||||
|
|
||||||
import sys as _sys
|
|
||||||
|
|
||||||
# Return bytes from a string
|
|
||||||
if _sys.version_info[0] < 3:
|
|
||||||
def b(s):
|
|
||||||
return s
|
|
||||||
else:
|
|
||||||
def b(s):
|
|
||||||
return s.encode('utf8')
|
|
||||||
|
|
||||||
def register_adapter(typ, callable):
|
def register_adapter(typ, callable):
|
||||||
"""Register 'callable' as an ISQLQuote adapter for type 'typ'."""
|
"""Register 'callable' as an ISQLQuote adapter for type 'typ'."""
|
||||||
|
@ -114,7 +98,7 @@ def register_adapter(typ, callable):
|
||||||
|
|
||||||
|
|
||||||
# The SQL_IN class is the official adapter for tuples starting from 2.0.6.
|
# The SQL_IN class is the official adapter for tuples starting from 2.0.6.
|
||||||
class SQL_IN(object):
|
class SQL_IN:
|
||||||
"""Adapt any iterable to an SQL quotable object."""
|
"""Adapt any iterable to an SQL quotable object."""
|
||||||
def __init__(self, seq):
|
def __init__(self, seq):
|
||||||
self._seq = seq
|
self._seq = seq
|
||||||
|
@ -132,13 +116,13 @@ class SQL_IN(object):
|
||||||
if hasattr(obj, 'prepare'):
|
if hasattr(obj, 'prepare'):
|
||||||
obj.prepare(self._conn)
|
obj.prepare(self._conn)
|
||||||
qobjs = [o.getquoted() for o in pobjs]
|
qobjs = [o.getquoted() for o in pobjs]
|
||||||
return b('(') + b(', ').join(qobjs) + b(')')
|
return b'(' + b', '.join(qobjs) + b')'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.getquoted())
|
return str(self.getquoted())
|
||||||
|
|
||||||
|
|
||||||
class NoneAdapter(object):
|
class NoneAdapter:
|
||||||
"""Adapt None to NULL.
|
"""Adapt None to NULL.
|
||||||
|
|
||||||
This adapter is not used normally as a fast path in mogrify uses NULL,
|
This adapter is not used normally as a fast path in mogrify uses NULL,
|
||||||
|
@ -147,30 +131,82 @@ class NoneAdapter(object):
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def getquoted(self, _null=b("NULL")):
|
def getquoted(self, _null=b"NULL"):
|
||||||
return _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(["{}={}".format(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
|
# Create default json typecasters for PostgreSQL 9.2 oids
|
||||||
from psycopg2._json import register_default_json
|
from psycopg2._json import register_default_json, register_default_jsonb # noqa
|
||||||
|
|
||||||
try:
|
try:
|
||||||
JSON, JSONARRAY = register_default_json()
|
JSON, JSONARRAY = register_default_json()
|
||||||
|
JSONB, JSONBARRAY = register_default_jsonb()
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
del register_default_json
|
del register_default_json, register_default_jsonb
|
||||||
|
|
||||||
|
|
||||||
# Create default Range typecasters
|
# Create default Range typecasters
|
||||||
from psycopg2. _range import Range
|
from psycopg2. _range import Range # noqa
|
||||||
del Range
|
del Range
|
||||||
|
|
||||||
|
|
||||||
# Add the "cleaned" version of the encodings to the key.
|
# Add the "cleaned" version of the encodings to the key.
|
||||||
# When the encoding is set its name is cleaned up from - and _ and turned
|
# 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
|
# 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
|
# encodings keys and would raise an exception with the unicode typecaster
|
||||||
for k, v in encodings.items():
|
for k, v in list(encodings.items()):
|
||||||
k = k.replace('_', '').replace('-', '').upper()
|
k = k.replace('_', '').replace('-', '').upper()
|
||||||
encodings[k] = v
|
encodings[k] = v
|
||||||
|
|
||||||
|
|
766
lib/extras.py
766
lib/extras.py
File diff suppressed because it is too large
Load Diff
88
lib/pool.py
88
lib/pool.py
|
@ -4,7 +4,8 @@ This module implements thread-safe (and not) connection pools.
|
||||||
"""
|
"""
|
||||||
# psycopg/pool.py - pooling code for psycopg
|
# psycopg/pool.py - pooling code for psycopg
|
||||||
#
|
#
|
||||||
# Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
# Copyright (C) 2020-2021 The Psycopg Team
|
||||||
#
|
#
|
||||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||||
# under the terms of the GNU Lesser General Public License as published
|
# under the terms of the GNU Lesser General Public License as published
|
||||||
|
@ -25,14 +26,14 @@ This module implements thread-safe (and not) connection pools.
|
||||||
# License for more details.
|
# License for more details.
|
||||||
|
|
||||||
import psycopg2
|
import psycopg2
|
||||||
import psycopg2.extensions as _ext
|
from psycopg2 import extensions as _ext
|
||||||
|
|
||||||
|
|
||||||
class PoolError(psycopg2.Error):
|
class PoolError(psycopg2.Error):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AbstractConnectionPool(object):
|
class AbstractConnectionPool:
|
||||||
"""Generic key-based pooling code."""
|
"""Generic key-based pooling code."""
|
||||||
|
|
||||||
def __init__(self, minconn, maxconn, *args, **kwargs):
|
def __init__(self, minconn, maxconn, *args, **kwargs):
|
||||||
|
@ -42,8 +43,8 @@ class AbstractConnectionPool(object):
|
||||||
with given parameters. The connection pool will support a maximum of
|
with given parameters. The connection pool will support a maximum of
|
||||||
about 'maxconn' connections.
|
about 'maxconn' connections.
|
||||||
"""
|
"""
|
||||||
self.minconn = minconn
|
self.minconn = int(minconn)
|
||||||
self.maxconn = maxconn
|
self.maxconn = int(maxconn)
|
||||||
self.closed = False
|
self.closed = False
|
||||||
|
|
||||||
self._args = args
|
self._args = args
|
||||||
|
@ -74,8 +75,10 @@ class AbstractConnectionPool(object):
|
||||||
|
|
||||||
def _getconn(self, key=None):
|
def _getconn(self, key=None):
|
||||||
"""Get a free connection and assign it to 'key' if not None."""
|
"""Get a free connection and assign it to 'key' if not None."""
|
||||||
if self.closed: raise PoolError("connection pool is closed")
|
if self.closed:
|
||||||
if key is None: key = self._getkey()
|
raise PoolError("connection pool is closed")
|
||||||
|
if key is None:
|
||||||
|
key = self._getkey()
|
||||||
|
|
||||||
if key in self._used:
|
if key in self._used:
|
||||||
return self._used[key]
|
return self._used[key]
|
||||||
|
@ -86,22 +89,24 @@ class AbstractConnectionPool(object):
|
||||||
return conn
|
return conn
|
||||||
else:
|
else:
|
||||||
if len(self._used) == self.maxconn:
|
if len(self._used) == self.maxconn:
|
||||||
raise PoolError("connection pool exausted")
|
raise PoolError("connection pool exhausted")
|
||||||
return self._connect(key)
|
return self._connect(key)
|
||||||
|
|
||||||
def _putconn(self, conn, key=None, close=False):
|
def _putconn(self, conn, key=None, close=False):
|
||||||
"""Put away a connection."""
|
"""Put away a connection."""
|
||||||
if self.closed: raise PoolError("connection pool is closed")
|
if self.closed:
|
||||||
if key is None: key = self._rused.get(id(conn))
|
raise PoolError("connection pool is closed")
|
||||||
|
|
||||||
if not key:
|
if key is None:
|
||||||
|
key = self._rused.get(id(conn))
|
||||||
|
if key is None:
|
||||||
raise PoolError("trying to put unkeyed connection")
|
raise PoolError("trying to put unkeyed connection")
|
||||||
|
|
||||||
if len(self._pool) < self.minconn and not close:
|
if len(self._pool) < self.minconn and not close:
|
||||||
# Return the connection into a consistent state before putting
|
# Return the connection into a consistent state before putting
|
||||||
# it back into the pool
|
# it back into the pool
|
||||||
if not conn.closed:
|
if not conn.closed:
|
||||||
status = conn.get_transaction_status()
|
status = conn.info.transaction_status
|
||||||
if status == _ext.TRANSACTION_STATUS_UNKNOWN:
|
if status == _ext.TRANSACTION_STATUS_UNKNOWN:
|
||||||
# server connection lost
|
# server connection lost
|
||||||
conn.close()
|
conn.close()
|
||||||
|
@ -129,11 +134,12 @@ class AbstractConnectionPool(object):
|
||||||
an already closed connection. If you call .closeall() make sure
|
an already closed connection. If you call .closeall() make sure
|
||||||
your code can deal with it.
|
your code can deal with it.
|
||||||
"""
|
"""
|
||||||
if self.closed: raise PoolError("connection pool is closed")
|
if self.closed:
|
||||||
|
raise PoolError("connection pool is closed")
|
||||||
for conn in self._pool + list(self._used.values()):
|
for conn in self._pool + list(self._used.values()):
|
||||||
try:
|
try:
|
||||||
conn.close()
|
conn.close()
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
self.closed = True
|
self.closed = True
|
||||||
|
|
||||||
|
@ -179,57 +185,3 @@ class ThreadedConnectionPool(AbstractConnectionPool):
|
||||||
self._closeall()
|
self._closeall()
|
||||||
finally:
|
finally:
|
||||||
self._lock.release()
|
self._lock.release()
|
||||||
|
|
||||||
|
|
||||||
class PersistentConnectionPool(AbstractConnectionPool):
|
|
||||||
"""A pool that assigns persistent connections to different threads.
|
|
||||||
|
|
||||||
Note that this connection pool generates by itself the required keys
|
|
||||||
using the current thread id. This means that until a thread puts away
|
|
||||||
a connection it will always get the same connection object by successive
|
|
||||||
`!getconn()` calls. This also means that a thread can't use more than one
|
|
||||||
single connection from the pool.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, minconn, maxconn, *args, **kwargs):
|
|
||||||
"""Initialize the threading lock."""
|
|
||||||
import warnings
|
|
||||||
warnings.warn("deprecated: use ZPsycopgDA.pool implementation",
|
|
||||||
DeprecationWarning)
|
|
||||||
|
|
||||||
import threading
|
|
||||||
AbstractConnectionPool.__init__(
|
|
||||||
self, minconn, maxconn, *args, **kwargs)
|
|
||||||
self._lock = threading.Lock()
|
|
||||||
|
|
||||||
# we we'll need the thread module, to determine thread ids, so we
|
|
||||||
# import it here and copy it in an instance variable
|
|
||||||
import thread
|
|
||||||
self.__thread = thread
|
|
||||||
|
|
||||||
def getconn(self):
|
|
||||||
"""Generate thread id and return a connection."""
|
|
||||||
key = self.__thread.get_ident()
|
|
||||||
self._lock.acquire()
|
|
||||||
try:
|
|
||||||
return self._getconn(key)
|
|
||||||
finally:
|
|
||||||
self._lock.release()
|
|
||||||
|
|
||||||
def putconn(self, conn=None, close=False):
|
|
||||||
"""Put away an unused connection."""
|
|
||||||
key = self.__thread.get_ident()
|
|
||||||
self._lock.acquire()
|
|
||||||
try:
|
|
||||||
if not conn: conn = self._used[key]
|
|
||||||
self._putconn(conn, key, close)
|
|
||||||
finally:
|
|
||||||
self._lock.release()
|
|
||||||
|
|
||||||
def closeall(self):
|
|
||||||
"""Close all connections (even the one currently in use.)"""
|
|
||||||
self._lock.acquire()
|
|
||||||
try:
|
|
||||||
self._closeall()
|
|
||||||
finally:
|
|
||||||
self._lock.release()
|
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
"""psycopg 1.1.x compatibility module
|
|
||||||
|
|
||||||
This module uses the new style connection and cursor types to build a psycopg
|
|
||||||
1.1.1.x compatibility layer. It should be considered a temporary hack to run
|
|
||||||
old code while porting to psycopg 2. Import it as follows::
|
|
||||||
|
|
||||||
from psycopg2 import psycopg1 as psycopg
|
|
||||||
"""
|
|
||||||
# psycopg/psycopg1.py - psycopg 1.1.x compatibility module
|
|
||||||
#
|
|
||||||
# Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
#
|
|
||||||
# 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 _psycopg as _2psycopg
|
|
||||||
from psycopg2.extensions import cursor as _2cursor
|
|
||||||
from psycopg2.extensions import connection as _2connection
|
|
||||||
|
|
||||||
from psycopg2 import *
|
|
||||||
import psycopg2.extensions as _ext
|
|
||||||
_2connect = connect
|
|
||||||
|
|
||||||
def connect(*args, **kwargs):
|
|
||||||
"""connect(dsn, ...) -> new psycopg 1.1.x compatible connection object"""
|
|
||||||
kwargs['connection_factory'] = connection
|
|
||||||
conn = _2connect(*args, **kwargs)
|
|
||||||
conn.set_isolation_level(_ext.ISOLATION_LEVEL_READ_COMMITTED)
|
|
||||||
return conn
|
|
||||||
|
|
||||||
class connection(_2connection):
|
|
||||||
"""psycopg 1.1.x connection."""
|
|
||||||
|
|
||||||
def cursor(self):
|
|
||||||
"""cursor() -> new psycopg 1.1.x compatible cursor object"""
|
|
||||||
return _2connection.cursor(self, cursor_factory=cursor)
|
|
||||||
|
|
||||||
def autocommit(self, on_off=1):
|
|
||||||
"""autocommit(on_off=1) -> switch autocommit on (1) or off (0)"""
|
|
||||||
if on_off > 0:
|
|
||||||
self.set_isolation_level(_ext.ISOLATION_LEVEL_AUTOCOMMIT)
|
|
||||||
else:
|
|
||||||
self.set_isolation_level(_ext.ISOLATION_LEVEL_READ_COMMITTED)
|
|
||||||
|
|
||||||
|
|
||||||
class cursor(_2cursor):
|
|
||||||
"""psycopg 1.1.x cursor.
|
|
||||||
|
|
||||||
Note that this cursor implements the exact procedure used by psycopg 1 to
|
|
||||||
build dictionaries out of result rows. The DictCursor in the
|
|
||||||
psycopg.extras modules implements a much better and faster algorithm.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __build_dict(self, row):
|
|
||||||
res = {}
|
|
||||||
for i in range(len(self.description)):
|
|
||||||
res[self.description[i][0]] = row[i]
|
|
||||||
return res
|
|
||||||
|
|
||||||
def dictfetchone(self):
|
|
||||||
row = _2cursor.fetchone(self)
|
|
||||||
if row:
|
|
||||||
return self.__build_dict(row)
|
|
||||||
else:
|
|
||||||
return row
|
|
||||||
|
|
||||||
def dictfetchmany(self, size):
|
|
||||||
res = []
|
|
||||||
rows = _2cursor.fetchmany(self, size)
|
|
||||||
for row in rows:
|
|
||||||
res.append(self.__build_dict(row))
|
|
||||||
return res
|
|
||||||
|
|
||||||
def dictfetchall(self):
|
|
||||||
res = []
|
|
||||||
rows = _2cursor.fetchall(self)
|
|
||||||
for row in rows:
|
|
||||||
res.append(self.__build_dict(row))
|
|
||||||
return res
|
|
||||||
|
|
455
lib/sql.py
Normal file
455
lib/sql.py
Normal file
|
@ -0,0 +1,455 @@
|
||||||
|
"""SQL composition utility module
|
||||||
|
"""
|
||||||
|
|
||||||
|
# psycopg/sql.py - SQL composition utility module
|
||||||
|
#
|
||||||
|
# Copyright (C) 2016-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||||
|
# Copyright (C) 2020-2021 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 string
|
||||||
|
|
||||||
|
from psycopg2 import extensions as ext
|
||||||
|
|
||||||
|
|
||||||
|
_formatter = string.Formatter()
|
||||||
|
|
||||||
|
|
||||||
|
class Composable:
|
||||||
|
"""
|
||||||
|
Abstract base class for objects that can be used to compose an SQL string.
|
||||||
|
|
||||||
|
`!Composable` objects can be passed directly to `~cursor.execute()`,
|
||||||
|
`~cursor.executemany()`, `~cursor.copy_expert()` in place of the query
|
||||||
|
string.
|
||||||
|
|
||||||
|
`!Composable` objects can be joined using the ``+`` operator: the result
|
||||||
|
will be a `Composed` instance containing the objects joined. The operator
|
||||||
|
``*`` is also supported with an integer argument: the result is a
|
||||||
|
`!Composed` instance containing the left argument repeated as many times as
|
||||||
|
requested.
|
||||||
|
"""
|
||||||
|
def __init__(self, wrapped):
|
||||||
|
self._wrapped = wrapped
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self._wrapped!r})"
|
||||||
|
|
||||||
|
def as_string(self, context):
|
||||||
|
"""
|
||||||
|
Return the string value of the object.
|
||||||
|
|
||||||
|
:param context: the context to evaluate the string into.
|
||||||
|
:type context: `connection` or `cursor`
|
||||||
|
|
||||||
|
The method is automatically invoked by `~cursor.execute()`,
|
||||||
|
`~cursor.executemany()`, `~cursor.copy_expert()` if a `!Composable` is
|
||||||
|
passed instead of the query string.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
if isinstance(other, Composed):
|
||||||
|
return Composed([self]) + other
|
||||||
|
if isinstance(other, Composable):
|
||||||
|
return Composed([self]) + Composed([other])
|
||||||
|
else:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __mul__(self, n):
|
||||||
|
return Composed([self] * n)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return type(self) is type(other) and self._wrapped == other._wrapped
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
|
||||||
|
class Composed(Composable):
|
||||||
|
"""
|
||||||
|
A `Composable` object made of a sequence of `!Composable`.
|
||||||
|
|
||||||
|
The object is usually created using `!Composable` operators and methods.
|
||||||
|
However it is possible to create a `!Composed` directly specifying a
|
||||||
|
sequence of `!Composable` as arguments.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> comp = sql.Composed(
|
||||||
|
... [sql.SQL("insert into "), sql.Identifier("table")])
|
||||||
|
>>> print(comp.as_string(conn))
|
||||||
|
insert into "table"
|
||||||
|
|
||||||
|
`!Composed` objects are iterable (so they can be used in `SQL.join` for
|
||||||
|
instance).
|
||||||
|
"""
|
||||||
|
def __init__(self, seq):
|
||||||
|
wrapped = []
|
||||||
|
for i in seq:
|
||||||
|
if not isinstance(i, Composable):
|
||||||
|
raise TypeError(
|
||||||
|
f"Composed elements must be Composable, got {i!r} instead")
|
||||||
|
wrapped.append(i)
|
||||||
|
|
||||||
|
super().__init__(wrapped)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def seq(self):
|
||||||
|
"""The list of the content of the `!Composed`."""
|
||||||
|
return list(self._wrapped)
|
||||||
|
|
||||||
|
def as_string(self, context):
|
||||||
|
rv = []
|
||||||
|
for i in self._wrapped:
|
||||||
|
rv.append(i.as_string(context))
|
||||||
|
return ''.join(rv)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._wrapped)
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
if isinstance(other, Composed):
|
||||||
|
return Composed(self._wrapped + other._wrapped)
|
||||||
|
if isinstance(other, Composable):
|
||||||
|
return Composed(self._wrapped + [other])
|
||||||
|
else:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def join(self, joiner):
|
||||||
|
"""
|
||||||
|
Return a new `!Composed` interposing the *joiner* with the `!Composed` items.
|
||||||
|
|
||||||
|
The *joiner* must be a `SQL` or a string which will be interpreted as
|
||||||
|
an `SQL`.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> fields = sql.Identifier('foo') + sql.Identifier('bar') # a Composed
|
||||||
|
>>> print(fields.join(', ').as_string(conn))
|
||||||
|
"foo", "bar"
|
||||||
|
|
||||||
|
"""
|
||||||
|
if isinstance(joiner, str):
|
||||||
|
joiner = SQL(joiner)
|
||||||
|
elif not isinstance(joiner, SQL):
|
||||||
|
raise TypeError(
|
||||||
|
"Composed.join() argument must be a string or an SQL")
|
||||||
|
|
||||||
|
return joiner.join(self)
|
||||||
|
|
||||||
|
|
||||||
|
class SQL(Composable):
|
||||||
|
"""
|
||||||
|
A `Composable` representing a snippet of SQL statement.
|
||||||
|
|
||||||
|
`!SQL` exposes `join()` and `format()` methods useful to create a template
|
||||||
|
where to merge variable parts of a query (for instance field or table
|
||||||
|
names).
|
||||||
|
|
||||||
|
The *string* doesn't undergo any form of escaping, so it is not suitable to
|
||||||
|
represent variable identifiers or values: you should only use it to pass
|
||||||
|
constant strings representing templates or snippets of SQL statements; use
|
||||||
|
other objects such as `Identifier` or `Literal` to represent variable
|
||||||
|
parts.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> query = sql.SQL("select {0} from {1}").format(
|
||||||
|
... sql.SQL(', ').join([sql.Identifier('foo'), sql.Identifier('bar')]),
|
||||||
|
... sql.Identifier('table'))
|
||||||
|
>>> print(query.as_string(conn))
|
||||||
|
select "foo", "bar" from "table"
|
||||||
|
"""
|
||||||
|
def __init__(self, string):
|
||||||
|
if not isinstance(string, str):
|
||||||
|
raise TypeError("SQL values must be strings")
|
||||||
|
super().__init__(string)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def string(self):
|
||||||
|
"""The string wrapped by the `!SQL` object."""
|
||||||
|
return self._wrapped
|
||||||
|
|
||||||
|
def as_string(self, context):
|
||||||
|
return self._wrapped
|
||||||
|
|
||||||
|
def format(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Merge `Composable` objects into a template.
|
||||||
|
|
||||||
|
:param `Composable` args: parameters to replace to numbered
|
||||||
|
(``{0}``, ``{1}``) or auto-numbered (``{}``) placeholders
|
||||||
|
:param `Composable` kwargs: parameters to replace to named (``{name}``)
|
||||||
|
placeholders
|
||||||
|
:return: the union of the `!SQL` string with placeholders replaced
|
||||||
|
:rtype: `Composed`
|
||||||
|
|
||||||
|
The method is similar to the Python `str.format()` method: the string
|
||||||
|
template supports auto-numbered (``{}``), numbered (``{0}``,
|
||||||
|
``{1}``...), and named placeholders (``{name}``), with positional
|
||||||
|
arguments replacing the numbered placeholders and keywords replacing
|
||||||
|
the named ones. However placeholder modifiers (``{0!r}``, ``{0:<10}``)
|
||||||
|
are not supported. Only `!Composable` objects can be passed to the
|
||||||
|
template.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> print(sql.SQL("select * from {} where {} = %s")
|
||||||
|
... .format(sql.Identifier('people'), sql.Identifier('id'))
|
||||||
|
... .as_string(conn))
|
||||||
|
select * from "people" where "id" = %s
|
||||||
|
|
||||||
|
>>> print(sql.SQL("select * from {tbl} where {pkey} = %s")
|
||||||
|
... .format(tbl=sql.Identifier('people'), pkey=sql.Identifier('id'))
|
||||||
|
... .as_string(conn))
|
||||||
|
select * from "people" where "id" = %s
|
||||||
|
|
||||||
|
"""
|
||||||
|
rv = []
|
||||||
|
autonum = 0
|
||||||
|
for pre, name, spec, conv in _formatter.parse(self._wrapped):
|
||||||
|
if spec:
|
||||||
|
raise ValueError("no format specification supported by SQL")
|
||||||
|
if conv:
|
||||||
|
raise ValueError("no format conversion supported by SQL")
|
||||||
|
if pre:
|
||||||
|
rv.append(SQL(pre))
|
||||||
|
|
||||||
|
if name is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if name.isdigit():
|
||||||
|
if autonum:
|
||||||
|
raise ValueError(
|
||||||
|
"cannot switch from automatic field numbering to manual")
|
||||||
|
rv.append(args[int(name)])
|
||||||
|
autonum = None
|
||||||
|
|
||||||
|
elif not name:
|
||||||
|
if autonum is None:
|
||||||
|
raise ValueError(
|
||||||
|
"cannot switch from manual field numbering to automatic")
|
||||||
|
rv.append(args[autonum])
|
||||||
|
autonum += 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
rv.append(kwargs[name])
|
||||||
|
|
||||||
|
return Composed(rv)
|
||||||
|
|
||||||
|
def join(self, seq):
|
||||||
|
"""
|
||||||
|
Join a sequence of `Composable`.
|
||||||
|
|
||||||
|
:param seq: the elements to join.
|
||||||
|
:type seq: iterable of `!Composable`
|
||||||
|
|
||||||
|
Use the `!SQL` object's *string* to separate the elements in *seq*.
|
||||||
|
Note that `Composed` objects are iterable too, so they can be used as
|
||||||
|
argument for this method.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> snip = sql.SQL(', ').join(
|
||||||
|
... sql.Identifier(n) for n in ['foo', 'bar', 'baz'])
|
||||||
|
>>> print(snip.as_string(conn))
|
||||||
|
"foo", "bar", "baz"
|
||||||
|
"""
|
||||||
|
rv = []
|
||||||
|
it = iter(seq)
|
||||||
|
try:
|
||||||
|
rv.append(next(it))
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for i in it:
|
||||||
|
rv.append(self)
|
||||||
|
rv.append(i)
|
||||||
|
|
||||||
|
return Composed(rv)
|
||||||
|
|
||||||
|
|
||||||
|
class Identifier(Composable):
|
||||||
|
"""
|
||||||
|
A `Composable` representing an SQL identifier or a dot-separated sequence.
|
||||||
|
|
||||||
|
Identifiers usually represent names of database objects, such as tables or
|
||||||
|
fields. PostgreSQL identifiers follow `different rules`__ than SQL string
|
||||||
|
literals for escaping (e.g. they use double quotes instead of single).
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html# \
|
||||||
|
SQL-SYNTAX-IDENTIFIERS
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> t1 = sql.Identifier("foo")
|
||||||
|
>>> t2 = sql.Identifier("ba'r")
|
||||||
|
>>> t3 = sql.Identifier('ba"z')
|
||||||
|
>>> print(sql.SQL(', ').join([t1, t2, t3]).as_string(conn))
|
||||||
|
"foo", "ba'r", "ba""z"
|
||||||
|
|
||||||
|
Multiple strings can be passed to the object to represent a qualified name,
|
||||||
|
i.e. a dot-separated sequence of identifiers.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> query = sql.SQL("select {} from {}").format(
|
||||||
|
... sql.Identifier("table", "field"),
|
||||||
|
... sql.Identifier("schema", "table"))
|
||||||
|
>>> print(query.as_string(conn))
|
||||||
|
select "table"."field" from "schema"."table"
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, *strings):
|
||||||
|
if not strings:
|
||||||
|
raise TypeError("Identifier cannot be empty")
|
||||||
|
|
||||||
|
for s in strings:
|
||||||
|
if not isinstance(s, str):
|
||||||
|
raise TypeError("SQL identifier parts must be strings")
|
||||||
|
|
||||||
|
super().__init__(strings)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def strings(self):
|
||||||
|
"""A tuple with the strings wrapped by the `Identifier`."""
|
||||||
|
return self._wrapped
|
||||||
|
|
||||||
|
@property
|
||||||
|
def string(self):
|
||||||
|
"""The string wrapped by the `Identifier`.
|
||||||
|
"""
|
||||||
|
if len(self._wrapped) == 1:
|
||||||
|
return self._wrapped[0]
|
||||||
|
else:
|
||||||
|
raise AttributeError(
|
||||||
|
"the Identifier wraps more than one than one string")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({', '.join(map(repr, self._wrapped))})"
|
||||||
|
|
||||||
|
def as_string(self, context):
|
||||||
|
return '.'.join(ext.quote_ident(s, context) for s in self._wrapped)
|
||||||
|
|
||||||
|
|
||||||
|
class Literal(Composable):
|
||||||
|
"""
|
||||||
|
A `Composable` representing an SQL value to include in a query.
|
||||||
|
|
||||||
|
Usually you will want to include placeholders in the query and pass values
|
||||||
|
as `~cursor.execute()` arguments. If however you really really need to
|
||||||
|
include a literal value in the query you can use this object.
|
||||||
|
|
||||||
|
The string returned by `!as_string()` follows the normal :ref:`adaptation
|
||||||
|
rules <python-types-adaptation>` for Python objects.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> s1 = sql.Literal("foo")
|
||||||
|
>>> s2 = sql.Literal("ba'r")
|
||||||
|
>>> s3 = sql.Literal(42)
|
||||||
|
>>> print(sql.SQL(', ').join([s1, s2, s3]).as_string(conn))
|
||||||
|
'foo', 'ba''r', 42
|
||||||
|
|
||||||
|
"""
|
||||||
|
@property
|
||||||
|
def wrapped(self):
|
||||||
|
"""The object wrapped by the `!Literal`."""
|
||||||
|
return self._wrapped
|
||||||
|
|
||||||
|
def as_string(self, context):
|
||||||
|
# is it a connection or cursor?
|
||||||
|
if isinstance(context, ext.connection):
|
||||||
|
conn = context
|
||||||
|
elif isinstance(context, ext.cursor):
|
||||||
|
conn = context.connection
|
||||||
|
else:
|
||||||
|
raise TypeError("context must be a connection or a cursor")
|
||||||
|
|
||||||
|
a = ext.adapt(self._wrapped)
|
||||||
|
if hasattr(a, 'prepare'):
|
||||||
|
a.prepare(conn)
|
||||||
|
|
||||||
|
rv = a.getquoted()
|
||||||
|
if isinstance(rv, bytes):
|
||||||
|
rv = rv.decode(ext.encodings[conn.encoding])
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class Placeholder(Composable):
|
||||||
|
"""A `Composable` representing a placeholder for query parameters.
|
||||||
|
|
||||||
|
If the name is specified, generate a named placeholder (e.g. ``%(name)s``),
|
||||||
|
otherwise generate a positional placeholder (e.g. ``%s``).
|
||||||
|
|
||||||
|
The object is useful to generate SQL queries with a variable number of
|
||||||
|
arguments.
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
|
||||||
|
>>> names = ['foo', 'bar', 'baz']
|
||||||
|
|
||||||
|
>>> q1 = sql.SQL("insert into table ({}) values ({})").format(
|
||||||
|
... sql.SQL(', ').join(map(sql.Identifier, names)),
|
||||||
|
... sql.SQL(', ').join(sql.Placeholder() * len(names)))
|
||||||
|
>>> print(q1.as_string(conn))
|
||||||
|
insert into table ("foo", "bar", "baz") values (%s, %s, %s)
|
||||||
|
|
||||||
|
>>> q2 = sql.SQL("insert into table ({}) values ({})").format(
|
||||||
|
... sql.SQL(', ').join(map(sql.Identifier, names)),
|
||||||
|
... sql.SQL(', ').join(map(sql.Placeholder, names)))
|
||||||
|
>>> print(q2.as_string(conn))
|
||||||
|
insert into table ("foo", "bar", "baz") values (%(foo)s, %(bar)s, %(baz)s)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name=None):
|
||||||
|
if isinstance(name, str):
|
||||||
|
if ')' in name:
|
||||||
|
raise ValueError(f"invalid name: {name!r}")
|
||||||
|
|
||||||
|
elif name is not None:
|
||||||
|
raise TypeError(f"expected string or None as name, got {name!r}")
|
||||||
|
|
||||||
|
super().__init__(name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""The name of the `!Placeholder`."""
|
||||||
|
return self._wrapped
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self._wrapped is None:
|
||||||
|
return f"{self.__class__.__name__}()"
|
||||||
|
else:
|
||||||
|
return f"{self.__class__.__name__}({self._wrapped!r})"
|
||||||
|
|
||||||
|
def as_string(self, context):
|
||||||
|
if self._wrapped is not None:
|
||||||
|
return f"%({self._wrapped})s"
|
||||||
|
else:
|
||||||
|
return "%s"
|
||||||
|
|
||||||
|
|
||||||
|
# Literals
|
||||||
|
NULL = SQL("NULL")
|
||||||
|
DEFAULT = SQL("DEFAULT")
|
55
lib/tz.py
55
lib/tz.py
|
@ -6,7 +6,8 @@ functions or used to set the .tzinfo_factory attribute in cursors.
|
||||||
"""
|
"""
|
||||||
# psycopg/tz.py - tzinfo implementation
|
# psycopg/tz.py - tzinfo implementation
|
||||||
#
|
#
|
||||||
# Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
# Copyright (C) 2020-2021 The Psycopg Team
|
||||||
#
|
#
|
||||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||||
# under the terms of the GNU Lesser General Public License as published
|
# under the terms of the GNU Lesser General Public License as published
|
||||||
|
@ -31,6 +32,7 @@ import time
|
||||||
|
|
||||||
ZERO = datetime.timedelta(0)
|
ZERO = datetime.timedelta(0)
|
||||||
|
|
||||||
|
|
||||||
class FixedOffsetTimezone(datetime.tzinfo):
|
class FixedOffsetTimezone(datetime.tzinfo):
|
||||||
"""Fixed offset in minutes east from UTC.
|
"""Fixed offset in minutes east from UTC.
|
||||||
|
|
||||||
|
@ -43,7 +45,12 @@ class FixedOffsetTimezone(datetime.tzinfo):
|
||||||
offset and name that instance will be returned. This saves memory and
|
offset and name that instance will be returned. This saves memory and
|
||||||
improves comparability.
|
improves comparability.
|
||||||
|
|
||||||
.. __: http://docs.python.org/library/datetime.html#datetime-tzinfo
|
.. versionchanged:: 2.9
|
||||||
|
|
||||||
|
The constructor can take either a timedelta or a number of minutes of
|
||||||
|
offset. Previously only minutes were supported.
|
||||||
|
|
||||||
|
.. __: https://docs.python.org/library/datetime.html
|
||||||
"""
|
"""
|
||||||
_name = None
|
_name = None
|
||||||
_offset = ZERO
|
_offset = ZERO
|
||||||
|
@ -52,7 +59,9 @@ class FixedOffsetTimezone(datetime.tzinfo):
|
||||||
|
|
||||||
def __init__(self, offset=None, name=None):
|
def __init__(self, offset=None, name=None):
|
||||||
if offset is not None:
|
if offset is not None:
|
||||||
self._offset = datetime.timedelta(minutes = offset)
|
if not isinstance(offset, datetime.timedelta):
|
||||||
|
offset = datetime.timedelta(minutes=offset)
|
||||||
|
self._offset = offset
|
||||||
if name is not None:
|
if name is not None:
|
||||||
self._name = name
|
self._name = name
|
||||||
|
|
||||||
|
@ -63,18 +72,28 @@ class FixedOffsetTimezone(datetime.tzinfo):
|
||||||
try:
|
try:
|
||||||
return cls._cache[key]
|
return cls._cache[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
tz = super(FixedOffsetTimezone, cls).__new__(cls, offset, name)
|
tz = super().__new__(cls, offset, name)
|
||||||
cls._cache[key] = tz
|
cls._cache[key] = tz
|
||||||
return tz
|
return tz
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
offset_mins = self._offset.seconds // 60 + self._offset.days * 24 * 60
|
|
||||||
return "psycopg2.tz.FixedOffsetTimezone(offset=%r, name=%r)" \
|
return "psycopg2.tz.FixedOffsetTimezone(offset=%r, name=%r)" \
|
||||||
% (offset_mins, self._name)
|
% (self._offset, self._name)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, FixedOffsetTimezone):
|
||||||
|
return self._offset == other._offset
|
||||||
|
else:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
if isinstance(other, FixedOffsetTimezone):
|
||||||
|
return self._offset != other._offset
|
||||||
|
else:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
def __getinitargs__(self):
|
def __getinitargs__(self):
|
||||||
offset_mins = self._offset.seconds // 60 + self._offset.days * 24 * 60
|
return self._offset, self._name
|
||||||
return (offset_mins, self._name)
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
def utcoffset(self, dt):
|
||||||
return self._offset
|
return self._offset
|
||||||
|
@ -82,14 +101,16 @@ class FixedOffsetTimezone(datetime.tzinfo):
|
||||||
def tzname(self, dt):
|
def tzname(self, dt):
|
||||||
if self._name is not None:
|
if self._name is not None:
|
||||||
return self._name
|
return self._name
|
||||||
else:
|
|
||||||
seconds = self._offset.seconds + self._offset.days * 86400
|
minutes, seconds = divmod(self._offset.total_seconds(), 60)
|
||||||
hours, seconds = divmod(seconds, 3600)
|
hours, minutes = divmod(minutes, 60)
|
||||||
minutes = seconds/60
|
rv = "%+03d" % hours
|
||||||
if minutes:
|
if minutes or seconds:
|
||||||
return "%+03d:%d" % (hours, minutes)
|
rv += ":%02d" % minutes
|
||||||
else:
|
if seconds:
|
||||||
return "%+03d" % hours
|
rv += ":%02d" % seconds
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
def dst(self, dt):
|
def dst(self, dt):
|
||||||
return ZERO
|
return ZERO
|
||||||
|
@ -102,6 +123,7 @@ else:
|
||||||
DSTOFFSET = STDOFFSET
|
DSTOFFSET = STDOFFSET
|
||||||
DSTDIFF = DSTOFFSET - STDOFFSET
|
DSTDIFF = DSTOFFSET - STDOFFSET
|
||||||
|
|
||||||
|
|
||||||
class LocalTimezone(datetime.tzinfo):
|
class LocalTimezone(datetime.tzinfo):
|
||||||
"""Platform idea of local timezone.
|
"""Platform idea of local timezone.
|
||||||
|
|
||||||
|
@ -130,6 +152,7 @@ class LocalTimezone(datetime.tzinfo):
|
||||||
tt = time.localtime(stamp)
|
tt = time.localtime(stamp)
|
||||||
return tt.tm_isdst > 0
|
return tt.tm_isdst > 0
|
||||||
|
|
||||||
|
|
||||||
LOCAL = LocalTimezone()
|
LOCAL = LocalTimezone()
|
||||||
|
|
||||||
# TODO: pre-generate some interesting time zones?
|
# TODO: pre-generate some interesting time zones?
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* adapter_asis.c - adapt types as they are
|
/* adapter_asis.c - adapt types as they are
|
||||||
*
|
*
|
||||||
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
* Copyright (C) 2020-2021 The Psycopg Team
|
||||||
*
|
*
|
||||||
* This file is part of psycopg.
|
* This file is part of psycopg.
|
||||||
*
|
*
|
||||||
|
@ -44,14 +45,12 @@ asis_getquoted(asisObject *self, PyObject *args)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
rv = PyObject_Str(self->wrapped);
|
rv = PyObject_Str(self->wrapped);
|
||||||
#if PY_MAJOR_VERSION > 2
|
/* unicode to bytes */
|
||||||
/* unicode to bytes in Py3 */
|
|
||||||
if (rv) {
|
if (rv) {
|
||||||
PyObject *tmp = PyUnicode_AsUTF8String(rv);
|
PyObject *tmp = PyUnicode_AsUTF8String(rv);
|
||||||
Py_DECREF(rv);
|
Py_DECREF(rv);
|
||||||
rv = tmp;
|
rv = tmp;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
|
@ -60,7 +59,7 @@ asis_getquoted(asisObject *self, PyObject *args)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
asis_str(asisObject *self)
|
asis_str(asisObject *self)
|
||||||
{
|
{
|
||||||
return psycopg_ensure_text(asis_getquoted(self, NULL));
|
return psyco_ensure_text(asis_getquoted(self, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -149,12 +148,6 @@ asis_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
return type->tp_alloc(type, 0);
|
return type->tp_alloc(type, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
asis_repr(asisObject *self)
|
|
||||||
{
|
|
||||||
return PyString_FromFormat("<psycopg2._psycopg.AsIs object at %p>", self);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* object type */
|
/* object type */
|
||||||
|
|
||||||
|
@ -163,14 +156,14 @@ asis_repr(asisObject *self)
|
||||||
|
|
||||||
PyTypeObject asisType = {
|
PyTypeObject asisType = {
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
"psycopg2._psycopg.AsIs",
|
"psycopg2.extensions.AsIs",
|
||||||
sizeof(asisObject), 0,
|
sizeof(asisObject), 0,
|
||||||
asis_dealloc, /*tp_dealloc*/
|
asis_dealloc, /*tp_dealloc*/
|
||||||
0, /*tp_print*/
|
0, /*tp_print*/
|
||||||
0, /*tp_getattr*/
|
0, /*tp_getattr*/
|
||||||
0, /*tp_setattr*/
|
0, /*tp_setattr*/
|
||||||
0, /*tp_compare*/
|
0, /*tp_compare*/
|
||||||
(reprfunc)asis_repr, /*tp_repr*/
|
0, /*tp_repr*/
|
||||||
0, /*tp_as_number*/
|
0, /*tp_as_number*/
|
||||||
0, /*tp_as_sequence*/
|
0, /*tp_as_sequence*/
|
||||||
0, /*tp_as_mapping*/
|
0, /*tp_as_mapping*/
|
||||||
|
@ -200,17 +193,3 @@ PyTypeObject asisType = {
|
||||||
0, /*tp_alloc*/
|
0, /*tp_alloc*/
|
||||||
asis_new, /*tp_new*/
|
asis_new, /*tp_new*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/** module-level functions **/
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_AsIs(PyObject *module, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *obj;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O", &obj))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return PyObject_CallFunctionObjArgs((PyObject *)&asisType, obj, NULL);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* adapter_asis.h - definition for the psycopg AsIs type wrapper
|
/* adapter_asis.h - definition for the psycopg AsIs type wrapper
|
||||||
*
|
*
|
||||||
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
* Copyright (C) 2020-2021 The Psycopg Team
|
||||||
*
|
*
|
||||||
* This file is part of psycopg.
|
* This file is part of psycopg.
|
||||||
*
|
*
|
||||||
|
@ -40,12 +41,6 @@ typedef struct {
|
||||||
|
|
||||||
} asisObject;
|
} asisObject;
|
||||||
|
|
||||||
/* functions exported to psycopgmodule.c */
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_AsIs(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_AsIs_doc \
|
|
||||||
"AsIs(obj) -> new AsIs wrapper object"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* adapter_binary.c - Binary objects
|
/* adapter_binary.c - Binary objects
|
||||||
*
|
*
|
||||||
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
* Copyright (C) 2020-2021 The Psycopg Team
|
||||||
*
|
*
|
||||||
* This file is part of psycopg.
|
* This file is part of psycopg.
|
||||||
*
|
*
|
||||||
|
@ -39,17 +40,12 @@ static unsigned char *
|
||||||
binary_escape(unsigned char *from, size_t from_length,
|
binary_escape(unsigned char *from, size_t from_length,
|
||||||
size_t *to_length, PGconn *conn)
|
size_t *to_length, PGconn *conn)
|
||||||
{
|
{
|
||||||
#if PG_VERSION_HEX >= 0x080104
|
|
||||||
if (conn)
|
if (conn)
|
||||||
return PQescapeByteaConn(conn, from, from_length, to_length);
|
return PQescapeByteaConn(conn, from, from_length, to_length);
|
||||||
else
|
else
|
||||||
#endif
|
|
||||||
return PQescapeBytea(from, from_length, to_length);
|
return PQescapeBytea(from, from_length, to_length);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HAS_BUFFER (PY_MAJOR_VERSION < 3)
|
|
||||||
#define HAS_MEMORYVIEW (PY_MAJOR_VERSION > 2 || PY_MINOR_VERSION >= 6)
|
|
||||||
|
|
||||||
/* binary_quote - do the quote process on plain and unicode strings */
|
/* binary_quote - do the quote process on plain and unicode strings */
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -60,10 +56,8 @@ binary_quote(binaryObject *self)
|
||||||
Py_ssize_t buffer_len;
|
Py_ssize_t buffer_len;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
PyObject *rv = NULL;
|
PyObject *rv = NULL;
|
||||||
#if HAS_MEMORYVIEW
|
|
||||||
Py_buffer view;
|
Py_buffer view;
|
||||||
int got_view = 0;
|
int got_view = 0;
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Allow Binary(None) to work */
|
/* Allow Binary(None) to work */
|
||||||
if (self->wrapped == Py_None) {
|
if (self->wrapped == Py_None) {
|
||||||
|
@ -73,8 +67,6 @@ binary_quote(binaryObject *self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if we got a plain string or a buffer we escape it and save the buffer */
|
/* if we got a plain string or a buffer we escape it and save the buffer */
|
||||||
|
|
||||||
#if HAS_MEMORYVIEW
|
|
||||||
if (PyObject_CheckBuffer(self->wrapped)) {
|
if (PyObject_CheckBuffer(self->wrapped)) {
|
||||||
if (0 > PyObject_GetBuffer(self->wrapped, &view, PyBUF_CONTIG_RO)) {
|
if (0 > PyObject_GetBuffer(self->wrapped, &view, PyBUF_CONTIG_RO)) {
|
||||||
goto exit;
|
goto exit;
|
||||||
|
@ -83,16 +75,6 @@ binary_quote(binaryObject *self)
|
||||||
buffer = (const char *)(view.buf);
|
buffer = (const char *)(view.buf);
|
||||||
buffer_len = view.len;
|
buffer_len = view.len;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
#if HAS_BUFFER
|
|
||||||
if (!buffer && (Bytes_Check(self->wrapped) || PyBuffer_Check(self->wrapped))) {
|
|
||||||
if (PyObject_AsReadBuffer(self->wrapped, (const void **)&buffer,
|
|
||||||
&buffer_len) < 0) {
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
goto exit;
|
goto exit;
|
||||||
|
@ -116,9 +98,7 @@ binary_quote(binaryObject *self)
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
if (to) { PQfreemem(to); }
|
if (to) { PQfreemem(to); }
|
||||||
#if HAS_MEMORYVIEW
|
|
||||||
if (got_view) { PyBuffer_Release(&view); }
|
if (got_view) { PyBuffer_Release(&view); }
|
||||||
#endif
|
|
||||||
|
|
||||||
/* if the wrapped object is not bytes or a buffer, this is an error */
|
/* if the wrapped object is not bytes or a buffer, this is an error */
|
||||||
if (!rv && !PyErr_Occurred()) {
|
if (!rv && !PyErr_Occurred()) {
|
||||||
|
@ -144,7 +124,7 @@ binary_getquoted(binaryObject *self, PyObject *args)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
binary_str(binaryObject *self)
|
binary_str(binaryObject *self)
|
||||||
{
|
{
|
||||||
return psycopg_ensure_text(binary_getquoted(self, NULL));
|
return psyco_ensure_text(binary_getquoted(self, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -254,11 +234,6 @@ binary_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
return type->tp_alloc(type, 0);
|
return type->tp_alloc(type, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
binary_repr(binaryObject *self)
|
|
||||||
{
|
|
||||||
return PyString_FromFormat("<psycopg2._psycopg.Binary object at %p>", self);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* object type */
|
/* object type */
|
||||||
|
|
||||||
|
@ -267,14 +242,14 @@ binary_repr(binaryObject *self)
|
||||||
|
|
||||||
PyTypeObject binaryType = {
|
PyTypeObject binaryType = {
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
"psycopg2._psycopg.Binary",
|
"psycopg2.extensions.Binary",
|
||||||
sizeof(binaryObject), 0,
|
sizeof(binaryObject), 0,
|
||||||
binary_dealloc, /*tp_dealloc*/
|
binary_dealloc, /*tp_dealloc*/
|
||||||
0, /*tp_print*/
|
0, /*tp_print*/
|
||||||
0, /*tp_getattr*/
|
0, /*tp_getattr*/
|
||||||
0, /*tp_setattr*/
|
0, /*tp_setattr*/
|
||||||
0, /*tp_compare*/
|
0, /*tp_compare*/
|
||||||
(reprfunc)binary_repr, /*tp_repr*/
|
0, /*tp_repr*/
|
||||||
0, /*tp_as_number*/
|
0, /*tp_as_number*/
|
||||||
0, /*tp_as_sequence*/
|
0, /*tp_as_sequence*/
|
||||||
0, /*tp_as_mapping*/
|
0, /*tp_as_mapping*/
|
||||||
|
@ -304,17 +279,3 @@ PyTypeObject binaryType = {
|
||||||
0, /*tp_alloc*/
|
0, /*tp_alloc*/
|
||||||
binary_new, /*tp_new*/
|
binary_new, /*tp_new*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/** module-level functions **/
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_Binary(PyObject *module, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *str;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O", &str))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return PyObject_CallFunctionObjArgs((PyObject *)&binaryType, str, NULL);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* adapter_binary.h - definition for the Binary type
|
/* adapter_binary.h - definition for the Binary type
|
||||||
*
|
*
|
||||||
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
* Copyright (C) 2020-2021 The Psycopg Team
|
||||||
*
|
*
|
||||||
* This file is part of psycopg.
|
* This file is part of psycopg.
|
||||||
*
|
*
|
||||||
|
@ -40,13 +41,6 @@ typedef struct {
|
||||||
PyObject *conn;
|
PyObject *conn;
|
||||||
} binaryObject;
|
} binaryObject;
|
||||||
|
|
||||||
/* functions exported to psycopgmodule.c */
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_Binary(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_Binary_doc \
|
|
||||||
"Binary(buffer) -> new binary object\n\n" \
|
|
||||||
"Build an object capable to hold a bynary string value."
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* adapter_datetime.c - python date/time objects
|
/* adapter_datetime.c - python date/time objects
|
||||||
*
|
*
|
||||||
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
* Copyright (C) 2020-2021 The Psycopg Team
|
||||||
*
|
*
|
||||||
* This file is part of psycopg.
|
* This file is part of psycopg.
|
||||||
*
|
*
|
||||||
|
@ -35,14 +36,9 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
extern HIDDEN PyObject *pyPsycopgTzModule;
|
RAISES_NEG int
|
||||||
extern HIDDEN PyObject *pyPsycopgTzLOCAL;
|
adapter_datetime_init(void)
|
||||||
|
|
||||||
int
|
|
||||||
psyco_adapter_datetime_init(void)
|
|
||||||
{
|
{
|
||||||
Dprintf("psyco_adapter_datetime_init: datetime init");
|
|
||||||
|
|
||||||
PyDateTime_IMPORT;
|
PyDateTime_IMPORT;
|
||||||
|
|
||||||
if (!PyDateTimeAPI) {
|
if (!PyDateTimeAPI) {
|
||||||
|
@ -65,7 +61,10 @@ _pydatetime_string_date_time(pydatetimeObject *self)
|
||||||
char *fmt = NULL;
|
char *fmt = NULL;
|
||||||
switch (self->type) {
|
switch (self->type) {
|
||||||
case PSYCO_DATETIME_TIME:
|
case PSYCO_DATETIME_TIME:
|
||||||
fmt = "'%s'::time";
|
tz = PyObject_GetAttrString(self->wrapped, "tzinfo");
|
||||||
|
if (!tz) { goto error; }
|
||||||
|
fmt = (tz == Py_None) ? "'%s'::time" : "'%s'::timetz";
|
||||||
|
Py_DECREF(tz);
|
||||||
break;
|
break;
|
||||||
case PSYCO_DATETIME_DATE:
|
case PSYCO_DATETIME_DATE:
|
||||||
fmt = "'%s'::date";
|
fmt = "'%s'::date";
|
||||||
|
@ -78,7 +77,7 @@ _pydatetime_string_date_time(pydatetimeObject *self)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(iso = psycopg_ensure_bytes(
|
if (!(iso = psyco_ensure_bytes(
|
||||||
PyObject_CallMethod(self->wrapped, "isoformat", NULL)))) {
|
PyObject_CallMethod(self->wrapped, "isoformat", NULL)))) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
@ -100,7 +99,7 @@ _pydatetime_string_delta(pydatetimeObject *self)
|
||||||
|
|
||||||
char buffer[8];
|
char buffer[8];
|
||||||
int i;
|
int i;
|
||||||
int a = obj->microseconds;
|
int a = PyDateTime_DELTA_GET_MICROSECONDS(obj);
|
||||||
|
|
||||||
for (i=0; i < 6 ; i++) {
|
for (i=0; i < 6 ; i++) {
|
||||||
buffer[5-i] = '0' + (a % 10);
|
buffer[5-i] = '0' + (a % 10);
|
||||||
|
@ -109,7 +108,9 @@ _pydatetime_string_delta(pydatetimeObject *self)
|
||||||
buffer[6] = '\0';
|
buffer[6] = '\0';
|
||||||
|
|
||||||
return Bytes_FromFormat("'%d days %d.%s seconds'::interval",
|
return Bytes_FromFormat("'%d days %d.%s seconds'::interval",
|
||||||
obj->days, obj->seconds, buffer);
|
PyDateTime_DELTA_GET_DAYS(obj),
|
||||||
|
PyDateTime_DELTA_GET_SECONDS(obj),
|
||||||
|
buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -126,7 +127,7 @@ pydatetime_getquoted(pydatetimeObject *self, PyObject *args)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
pydatetime_str(pydatetimeObject *self)
|
pydatetime_str(pydatetimeObject *self)
|
||||||
{
|
{
|
||||||
return psycopg_ensure_text(pydatetime_getquoted(self, NULL));
|
return psyco_ensure_text(pydatetime_getquoted(self, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -214,12 +215,6 @@ pydatetime_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
return type->tp_alloc(type, 0);
|
return type->tp_alloc(type, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
pydatetime_repr(pydatetimeObject *self)
|
|
||||||
{
|
|
||||||
return PyString_FromFormat("<psycopg2._psycopg.datetime object at %p>",
|
|
||||||
self);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* object type */
|
/* object type */
|
||||||
|
|
||||||
|
@ -235,7 +230,7 @@ PyTypeObject pydatetimeType = {
|
||||||
0, /*tp_getattr*/
|
0, /*tp_getattr*/
|
||||||
0, /*tp_setattr*/
|
0, /*tp_setattr*/
|
||||||
0, /*tp_compare*/
|
0, /*tp_compare*/
|
||||||
(reprfunc)pydatetime_repr, /*tp_repr*/
|
0, /*tp_repr*/
|
||||||
0, /*tp_as_number*/
|
0, /*tp_as_number*/
|
||||||
0, /*tp_as_sequence*/
|
0, /*tp_as_sequence*/
|
||||||
0, /*tp_as_mapping*/
|
0, /*tp_as_mapping*/
|
||||||
|
@ -269,8 +264,6 @@ PyTypeObject pydatetimeType = {
|
||||||
|
|
||||||
/** module-level functions **/
|
/** module-level functions **/
|
||||||
|
|
||||||
#ifdef PSYCOPG_DEFAULT_PYDATETIME
|
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
psyco_Date(PyObject *self, PyObject *args)
|
psyco_Date(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
|
@ -430,6 +423,8 @@ psyco_TimeFromTicks(PyObject *self, PyObject *args)
|
||||||
PyObject *
|
PyObject *
|
||||||
psyco_TimestampFromTicks(PyObject *self, PyObject *args)
|
psyco_TimestampFromTicks(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
|
pydatetimeObject *wrapper = NULL;
|
||||||
|
PyObject *dt_aware = NULL;
|
||||||
PyObject *res = NULL;
|
PyObject *res = NULL;
|
||||||
struct tm tm;
|
struct tm tm;
|
||||||
time_t t;
|
time_t t;
|
||||||
|
@ -440,21 +435,37 @@ psyco_TimestampFromTicks(PyObject *self, PyObject *args)
|
||||||
|
|
||||||
t = (time_t)floor(ticks);
|
t = (time_t)floor(ticks);
|
||||||
ticks -= (double)t;
|
ticks -= (double)t;
|
||||||
if (localtime_r(&t, &tm)) {
|
if (!localtime_r(&t, &tm)) {
|
||||||
res = _psyco_Timestamp(
|
|
||||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
|
||||||
tm.tm_hour, tm.tm_min, (double)tm.tm_sec + ticks,
|
|
||||||
pyPsycopgTzLOCAL);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
PyErr_SetString(InterfaceError, "failed localtime call");
|
PyErr_SetString(InterfaceError, "failed localtime call");
|
||||||
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Convert the tm to a wrapper containing a naive datetime.datetime */
|
||||||
|
if (!(wrapper = (pydatetimeObject *)_psyco_Timestamp(
|
||||||
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||||
|
tm.tm_hour, tm.tm_min, (double)tm.tm_sec + ticks, NULL))) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Localize the datetime and assign it back to the wrapper */
|
||||||
|
if (!(dt_aware = PyObject_CallMethod(
|
||||||
|
wrapper->wrapped, "astimezone", NULL))) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
Py_CLEAR(wrapper->wrapped);
|
||||||
|
wrapper->wrapped = dt_aware;
|
||||||
|
dt_aware = NULL;
|
||||||
|
|
||||||
|
/* the wrapper is ready to be returned */
|
||||||
|
res = (PyObject *)wrapper;
|
||||||
|
wrapper = NULL;
|
||||||
|
|
||||||
|
exit:
|
||||||
|
Py_XDECREF(dt_aware);
|
||||||
|
Py_XDECREF(wrapper);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
psyco_DateFromPy(PyObject *self, PyObject *args)
|
psyco_DateFromPy(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* adapter_datetime.h - definition for the python date/time types
|
/* adapter_datetime.h - definition for the python date/time types
|
||||||
*
|
*
|
||||||
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
* Copyright (C) 2020-2021 The Psycopg Team
|
||||||
*
|
*
|
||||||
* This file is part of psycopg.
|
* This file is part of psycopg.
|
||||||
*
|
*
|
||||||
|
@ -45,10 +46,7 @@ typedef struct {
|
||||||
} pydatetimeObject;
|
} pydatetimeObject;
|
||||||
|
|
||||||
|
|
||||||
HIDDEN int psyco_adapter_datetime_init(void);
|
RAISES_NEG HIDDEN int adapter_datetime_init(void);
|
||||||
|
|
||||||
/* functions exported to psycopgmodule.c */
|
|
||||||
#ifdef PSYCOPG_DEFAULT_PYDATETIME
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_Date(PyObject *module, PyObject *args);
|
HIDDEN PyObject *psyco_Date(PyObject *module, PyObject *args);
|
||||||
#define psyco_Date_doc \
|
#define psyco_Date_doc \
|
||||||
|
@ -86,8 +84,6 @@ HIDDEN PyObject *psyco_TimestampFromTicks(PyObject *module, PyObject *args);
|
||||||
"Ticks are the number of seconds since the epoch; see the documentation " \
|
"Ticks are the number of seconds since the epoch; see the documentation " \
|
||||||
"of the standard Python time module for details)."
|
"of the standard Python time module for details)."
|
||||||
|
|
||||||
#endif /* PSYCOPG_DEFAULT_PYDATETIME */
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_DateFromPy(PyObject *module, PyObject *args);
|
HIDDEN PyObject *psyco_DateFromPy(PyObject *module, PyObject *args);
|
||||||
#define psyco_DateFromPy_doc \
|
#define psyco_DateFromPy_doc \
|
||||||
"DateFromPy(datetime.date) -> new wrapper"
|
"DateFromPy(datetime.date) -> new wrapper"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* adapter_list.c - python list objects
|
/* adapter_list.c - python list objects
|
||||||
*
|
*
|
||||||
* Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
|
* Copyright (C) 2004-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
* Copyright (C) 2020-2021 The Psycopg Team
|
||||||
*
|
*
|
||||||
* This file is part of psycopg.
|
* This file is part of psycopg.
|
||||||
*
|
*
|
||||||
|
@ -38,55 +39,134 @@ list_quote(listObject *self)
|
||||||
{
|
{
|
||||||
/* adapt the list by calling adapt() recursively and then wrapping
|
/* adapt the list by calling adapt() recursively and then wrapping
|
||||||
everything into "ARRAY[]" */
|
everything into "ARRAY[]" */
|
||||||
PyObject *tmp = NULL, *str = NULL, *joined = NULL, *res = NULL;
|
PyObject *res = NULL;
|
||||||
|
PyObject **qs = NULL;
|
||||||
|
Py_ssize_t bufsize = 0;
|
||||||
|
char *buf = NULL, *ptr;
|
||||||
|
|
||||||
|
/* list consisting of only NULL don't work with the ARRAY[] construct
|
||||||
|
* so we use the {NULL,...} syntax. The same syntax is also necessary
|
||||||
|
* to convert array of arrays containing only nulls. */
|
||||||
|
int all_nulls = 1;
|
||||||
|
|
||||||
Py_ssize_t i, len;
|
Py_ssize_t i, len;
|
||||||
|
|
||||||
len = PyList_GET_SIZE(self->wrapped);
|
len = PyList_GET_SIZE(self->wrapped);
|
||||||
|
|
||||||
/* empty arrays are converted to NULLs (still searching for a way to
|
/* empty arrays are converted to NULLs (still searching for a way to
|
||||||
insert an empty array in postgresql */
|
insert an empty array in postgresql */
|
||||||
if (len == 0) return Bytes_FromString("'{}'");
|
if (len == 0) {
|
||||||
|
/* it cannot be ARRAY[] because it would make empty lists unusable
|
||||||
|
* in any() without a cast. But we may convert it into ARRAY[] below */
|
||||||
|
res = Bytes_FromString("'{}'");
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
tmp = PyTuple_New(len);
|
if (!(qs = PyMem_New(PyObject *, len))) {
|
||||||
|
PyErr_NoMemory();
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
memset(qs, 0, len * sizeof(PyObject *));
|
||||||
|
|
||||||
for (i = 0; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
PyObject *quoted;
|
|
||||||
PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i);
|
PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i);
|
||||||
if (wrapped == Py_None) {
|
if (wrapped == Py_None) {
|
||||||
Py_INCREF(psyco_null);
|
Py_INCREF(psyco_null);
|
||||||
quoted = psyco_null;
|
qs[i] = psyco_null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
quoted = microprotocol_getquoted(wrapped,
|
if (!(qs[i] = microprotocol_getquoted(
|
||||||
(connectionObject*)self->connection);
|
wrapped, (connectionObject*)self->connection))) {
|
||||||
if (quoted == NULL) goto error;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* here we don't loose a refcnt: SET_ITEM does not change the
|
/* Lists of arrays containing only nulls are also not supported
|
||||||
reference count and we are just transferring ownership of the tmp
|
* by the ARRAY construct so we should do some special casing */
|
||||||
object to the tuple */
|
if (PyList_Check(wrapped)) {
|
||||||
PyTuple_SET_ITEM(tmp, i, quoted);
|
if (Bytes_AS_STRING(qs[i])[0] == 'A') {
|
||||||
|
all_nulls = 0;
|
||||||
|
}
|
||||||
|
else if (0 == strcmp(Bytes_AS_STRING(qs[i]), "'{}'")) {
|
||||||
|
/* case of issue #788: '{{}}' is not supported but
|
||||||
|
* array[array[]] is */
|
||||||
|
all_nulls = 0;
|
||||||
|
Py_CLEAR(qs[i]);
|
||||||
|
if (!(qs[i] = Bytes_FromString("ARRAY[]"))) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
all_nulls = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bufsize += Bytes_GET_SIZE(qs[i]) + 1; /* this, and a comma */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* now that we have a tuple of adapted objects we just need to join them
|
/* Create an array literal, usually ARRAY[...] but if the contents are
|
||||||
and put "ARRAY[] around the result */
|
* all NULL or array of NULL we must use the '{...}' syntax
|
||||||
str = Bytes_FromString(", ");
|
*/
|
||||||
joined = PyObject_CallMethod(str, "join", "(O)", tmp);
|
if (!(ptr = buf = PyMem_Malloc(bufsize + 8))) {
|
||||||
if (joined == NULL) goto error;
|
PyErr_NoMemory();
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
res = Bytes_FromFormat("ARRAY[%s]", Bytes_AsString(joined));
|
if (!all_nulls) {
|
||||||
|
strcpy(ptr, "ARRAY[");
|
||||||
|
ptr += 6;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
Py_ssize_t sl;
|
||||||
|
sl = Bytes_GET_SIZE(qs[i]);
|
||||||
|
memcpy(ptr, Bytes_AS_STRING(qs[i]), sl);
|
||||||
|
ptr += sl;
|
||||||
|
*ptr++ = ',';
|
||||||
|
}
|
||||||
|
*(ptr - 1) = ']';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*ptr++ = '\'';
|
||||||
|
*ptr++ = '{';
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
/* in case all the adapted things are nulls (or array of nulls),
|
||||||
|
* the quoted string is either NULL or an array of the form
|
||||||
|
* '{NULL,...}', in which case we have to strip the extra quotes */
|
||||||
|
char *s;
|
||||||
|
Py_ssize_t sl;
|
||||||
|
s = Bytes_AS_STRING(qs[i]);
|
||||||
|
sl = Bytes_GET_SIZE(qs[i]);
|
||||||
|
if (s[0] != '\'') {
|
||||||
|
memcpy(ptr, s, sl);
|
||||||
|
ptr += sl;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
memcpy(ptr, s + 1, sl - 2);
|
||||||
|
ptr += sl - 2;
|
||||||
|
}
|
||||||
|
*ptr++ = ',';
|
||||||
|
}
|
||||||
|
*(ptr - 1) = '}';
|
||||||
|
*ptr++ = '\'';
|
||||||
|
}
|
||||||
|
|
||||||
|
res = Bytes_FromStringAndSize(buf, ptr - buf);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
if (qs) {
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
PyObject *q = qs[i];
|
||||||
|
Py_XDECREF(q);
|
||||||
|
}
|
||||||
|
PyMem_Free(qs);
|
||||||
|
}
|
||||||
|
PyMem_Free(buf);
|
||||||
|
|
||||||
error:
|
|
||||||
Py_XDECREF(tmp);
|
|
||||||
Py_XDECREF(str);
|
|
||||||
Py_XDECREF(joined);
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
list_str(listObject *self)
|
list_str(listObject *self)
|
||||||
{
|
{
|
||||||
return psycopg_ensure_text(list_quote(self));
|
return psyco_ensure_text(list_quote(self));
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -215,11 +295,6 @@ list_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
return type->tp_alloc(type, 0);
|
return type->tp_alloc(type, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
list_repr(listObject *self)
|
|
||||||
{
|
|
||||||
return PyString_FromFormat("<psycopg2._psycopg.List object at %p>", self);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* object type */
|
/* object type */
|
||||||
|
|
||||||
|
@ -235,7 +310,7 @@ PyTypeObject listType = {
|
||||||
0, /*tp_getattr*/
|
0, /*tp_getattr*/
|
||||||
0, /*tp_setattr*/
|
0, /*tp_setattr*/
|
||||||
0, /*tp_compare*/
|
0, /*tp_compare*/
|
||||||
(reprfunc)list_repr, /*tp_repr*/
|
0, /*tp_repr*/
|
||||||
0, /*tp_as_number*/
|
0, /*tp_as_number*/
|
||||||
0, /*tp_as_sequence*/
|
0, /*tp_as_sequence*/
|
||||||
0, /*tp_as_mapping*/
|
0, /*tp_as_mapping*/
|
||||||
|
@ -265,17 +340,3 @@ PyTypeObject listType = {
|
||||||
0, /*tp_alloc*/
|
0, /*tp_alloc*/
|
||||||
list_new, /*tp_new*/
|
list_new, /*tp_new*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/** module-level functions **/
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_List(PyObject *module, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *str;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O", &str))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return PyObject_CallFunctionObjArgs((PyObject *)&listType, "O", str, NULL);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* adapter_list.h - definition for the python list types
|
/* adapter_list.h - definition for the python list types
|
||||||
*
|
*
|
||||||
* Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
|
* Copyright (C) 2004-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
* Copyright (C) 2020-2021 The Psycopg Team
|
||||||
*
|
*
|
||||||
* This file is part of psycopg.
|
* This file is part of psycopg.
|
||||||
*
|
*
|
||||||
|
@ -39,10 +40,6 @@ typedef struct {
|
||||||
PyObject *connection;
|
PyObject *connection;
|
||||||
} listObject;
|
} listObject;
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_List(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_List_doc \
|
|
||||||
"List(list, enc) -> new quoted list"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,434 +0,0 @@
|
||||||
/* adapter_mxdatetime.c - mx date/time objects
|
|
||||||
*
|
|
||||||
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
*
|
|
||||||
* This file is part of psycopg.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define PSYCOPG_MODULE
|
|
||||||
#include "psycopg/psycopg.h"
|
|
||||||
|
|
||||||
#include "psycopg/adapter_mxdatetime.h"
|
|
||||||
#include "psycopg/microprotocols_proto.h"
|
|
||||||
|
|
||||||
#include <mxDateTime.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
|
|
||||||
/* Return 0 on success, -1 on failure, but don't set an exception */
|
|
||||||
|
|
||||||
int
|
|
||||||
psyco_adapter_mxdatetime_init(void)
|
|
||||||
{
|
|
||||||
Dprintf("psyco_adapter_mxdatetime_init: mx.DateTime init");
|
|
||||||
|
|
||||||
if (mxDateTime_ImportModuleAndAPI()) {
|
|
||||||
Dprintf("psyco_adapter_mxdatetime_init: mx.DateTime initialization failed");
|
|
||||||
PyErr_Clear();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* mxdatetime_str, mxdatetime_getquoted - return result of quoting */
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
mxdatetime_str(mxdatetimeObject *self)
|
|
||||||
{
|
|
||||||
mxDateTimeObject *dt;
|
|
||||||
mxDateTimeDeltaObject *dtd;
|
|
||||||
char buf[128] = { 0, };
|
|
||||||
|
|
||||||
switch (self->type) {
|
|
||||||
|
|
||||||
case PSYCO_MXDATETIME_DATE:
|
|
||||||
dt = (mxDateTimeObject *)self->wrapped;
|
|
||||||
if (dt->year >= 1)
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1, "'%04ld-%02d-%02d'::date",
|
|
||||||
dt->year, (int)dt->month, (int)dt->day);
|
|
||||||
else
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1, "'%04ld-%02d-%02d BC'::date",
|
|
||||||
1 - dt->year, (int)dt->month, (int)dt->day);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PSYCO_MXDATETIME_TIMESTAMP:
|
|
||||||
dt = (mxDateTimeObject *)self->wrapped;
|
|
||||||
if (dt->year >= 1)
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1,
|
|
||||||
"'%04ld-%02d-%02dT%02d:%02d:%09.6f'::timestamp",
|
|
||||||
dt->year, (int)dt->month, (int)dt->day,
|
|
||||||
(int)dt->hour, (int)dt->minute, dt->second);
|
|
||||||
else
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1,
|
|
||||||
"'%04ld-%02d-%02dT%02d:%02d:%09.6f BC'::timestamp",
|
|
||||||
1 - dt->year, (int)dt->month, (int)dt->day,
|
|
||||||
(int)dt->hour, (int)dt->minute, dt->second);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PSYCO_MXDATETIME_TIME:
|
|
||||||
case PSYCO_MXDATETIME_INTERVAL:
|
|
||||||
/* given the limitation of the mx.DateTime module that uses the same
|
|
||||||
type for both time and delta values we need to do some black magic
|
|
||||||
and make sure we're not using an adapt()ed interval as a simple
|
|
||||||
time */
|
|
||||||
dtd = (mxDateTimeDeltaObject *)self->wrapped;
|
|
||||||
if (0 <= dtd->seconds && dtd->seconds < 24*3600) {
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1, "'%02d:%02d:%09.6f'::time",
|
|
||||||
(int)dtd->hour, (int)dtd->minute, dtd->second);
|
|
||||||
} else {
|
|
||||||
double ss = dtd->hour*3600.0 + dtd->minute*60.0 + dtd->second;
|
|
||||||
|
|
||||||
if (dtd->seconds >= 0)
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1, "'%ld days %.6f seconds'::interval",
|
|
||||||
dtd->day, ss);
|
|
||||||
else
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1, "'-%ld days -%.6f seconds'::interval",
|
|
||||||
dtd->day, ss);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PyString_FromString(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
mxdatetime_getquoted(mxdatetimeObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
return mxdatetime_str(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
mxdatetime_conform(mxdatetimeObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *res, *proto;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
|
|
||||||
|
|
||||||
if (proto == (PyObject*)&isqlquoteType)
|
|
||||||
res = (PyObject*)self;
|
|
||||||
else
|
|
||||||
res = Py_None;
|
|
||||||
|
|
||||||
Py_INCREF(res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** the MxDateTime object **/
|
|
||||||
|
|
||||||
/* object member list */
|
|
||||||
|
|
||||||
static struct PyMemberDef mxdatetimeObject_members[] = {
|
|
||||||
{"adapted", T_OBJECT, offsetof(mxdatetimeObject, wrapped), READONLY},
|
|
||||||
{"type", T_INT, offsetof(mxdatetimeObject, type), READONLY},
|
|
||||||
{NULL}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* object method table */
|
|
||||||
|
|
||||||
static PyMethodDef mxdatetimeObject_methods[] = {
|
|
||||||
{"getquoted", (PyCFunction)mxdatetime_getquoted, METH_NOARGS,
|
|
||||||
"getquoted() -> wrapped object value as SQL date/time"},
|
|
||||||
{"__conform__", (PyCFunction)mxdatetime_conform, METH_VARARGS, NULL},
|
|
||||||
{NULL} /* Sentinel */
|
|
||||||
};
|
|
||||||
|
|
||||||
/* initialization and finalization methods */
|
|
||||||
|
|
||||||
static int
|
|
||||||
mxdatetime_setup(mxdatetimeObject *self, PyObject *obj, int type)
|
|
||||||
{
|
|
||||||
Dprintf("mxdatetime_setup: init mxdatetime object at %p, refcnt = "
|
|
||||||
FORMAT_CODE_PY_SSIZE_T,
|
|
||||||
self, Py_REFCNT(self)
|
|
||||||
);
|
|
||||||
|
|
||||||
self->type = type;
|
|
||||||
Py_INCREF(obj);
|
|
||||||
self->wrapped = obj;
|
|
||||||
|
|
||||||
Dprintf("mxdatetime_setup: good mxdatetime object at %p, refcnt = "
|
|
||||||
FORMAT_CODE_PY_SSIZE_T,
|
|
||||||
self, Py_REFCNT(self)
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
mxdatetime_dealloc(PyObject* obj)
|
|
||||||
{
|
|
||||||
mxdatetimeObject *self = (mxdatetimeObject *)obj;
|
|
||||||
|
|
||||||
Py_CLEAR(self->wrapped);
|
|
||||||
|
|
||||||
Dprintf("mxdatetime_dealloc: deleted mxdatetime object at %p, refcnt = "
|
|
||||||
FORMAT_CODE_PY_SSIZE_T,
|
|
||||||
obj, Py_REFCNT(obj)
|
|
||||||
);
|
|
||||||
|
|
||||||
Py_TYPE(obj)->tp_free(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
mxdatetime_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
|
||||||
{
|
|
||||||
PyObject *mx;
|
|
||||||
int type = -1; /* raise an error if type was not passed! */
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O|i", &mx, &type))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return mxdatetime_setup((mxdatetimeObject *)obj, mx, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
mxdatetime_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|
||||||
{
|
|
||||||
return type->tp_alloc(type, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
mxdatetime_repr(mxdatetimeObject *self)
|
|
||||||
{
|
|
||||||
return PyString_FromFormat("<psycopg2._psycopg.MxDateTime object at %p>",
|
|
||||||
self);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* object type */
|
|
||||||
|
|
||||||
#define mxdatetimeType_doc \
|
|
||||||
"MxDateTime(mx, type) -> new mx.DateTime wrapper object"
|
|
||||||
|
|
||||||
PyTypeObject mxdatetimeType = {
|
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
|
||||||
"psycopg2._psycopg.MxDateTime",
|
|
||||||
sizeof(mxdatetimeObject), 0,
|
|
||||||
mxdatetime_dealloc, /*tp_dealloc*/
|
|
||||||
0, /*tp_print*/
|
|
||||||
0, /*tp_getattr*/
|
|
||||||
0, /*tp_setattr*/
|
|
||||||
0, /*tp_compare*/
|
|
||||||
(reprfunc)mxdatetime_repr, /*tp_repr*/
|
|
||||||
0, /*tp_as_number*/
|
|
||||||
0, /*tp_as_sequence*/
|
|
||||||
0, /*tp_as_mapping*/
|
|
||||||
0, /*tp_hash */
|
|
||||||
0, /*tp_call*/
|
|
||||||
(reprfunc)mxdatetime_str, /*tp_str*/
|
|
||||||
0, /*tp_getattro*/
|
|
||||||
0, /*tp_setattro*/
|
|
||||||
0, /*tp_as_buffer*/
|
|
||||||
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
|
||||||
mxdatetimeType_doc, /*tp_doc*/
|
|
||||||
0, /*tp_traverse*/
|
|
||||||
0, /*tp_clear*/
|
|
||||||
0, /*tp_richcompare*/
|
|
||||||
0, /*tp_weaklistoffset*/
|
|
||||||
0, /*tp_iter*/
|
|
||||||
0, /*tp_iternext*/
|
|
||||||
mxdatetimeObject_methods, /*tp_methods*/
|
|
||||||
mxdatetimeObject_members, /*tp_members*/
|
|
||||||
0, /*tp_getset*/
|
|
||||||
0, /*tp_base*/
|
|
||||||
0, /*tp_dict*/
|
|
||||||
0, /*tp_descr_get*/
|
|
||||||
0, /*tp_descr_set*/
|
|
||||||
0, /*tp_dictoffset*/
|
|
||||||
mxdatetime_init, /*tp_init*/
|
|
||||||
0, /*tp_alloc*/
|
|
||||||
mxdatetime_new, /*tp_new*/
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/** module-level functions **/
|
|
||||||
|
|
||||||
#ifdef PSYCOPG_DEFAULT_MXDATETIME
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_Date(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *res, *mx;
|
|
||||||
int year, month, day;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "iii", &year, &month, &day))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
mx = mxDateTime.DateTime_FromDateAndTime(year, month, day, 0, 0, 0.0);
|
|
||||||
if (mx == NULL) return NULL;
|
|
||||||
|
|
||||||
res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_DATE);
|
|
||||||
Py_DECREF(mx);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_Time(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *res, *mx;
|
|
||||||
int hours, minutes=0;
|
|
||||||
double seconds=0.0;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "iid", &hours, &minutes, &seconds))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
mx = mxDateTime.DateTimeDelta_FromTime(hours, minutes, seconds);
|
|
||||||
if (mx == NULL) return NULL;
|
|
||||||
|
|
||||||
res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_TIME);
|
|
||||||
Py_DECREF(mx);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_Timestamp(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *res, *mx;
|
|
||||||
int year, month, day;
|
|
||||||
int hour=0, minute=0; /* default to midnight */
|
|
||||||
double second=0.0;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "lii|iid", &year, &month, &day,
|
|
||||||
&hour, &minute, &second))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
mx = mxDateTime.DateTime_FromDateAndTime(year, month, day,
|
|
||||||
hour, minute, second);
|
|
||||||
if (mx == NULL) return NULL;
|
|
||||||
|
|
||||||
res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_TIMESTAMP);
|
|
||||||
Py_DECREF(mx);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_DateFromTicks(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *res, *mx;
|
|
||||||
double ticks;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args,"d", &ticks))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (!(mx = mxDateTime.DateTime_FromTicks(ticks)))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_DATE);
|
|
||||||
Py_DECREF(mx);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_TimeFromTicks(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *res, *mx, *dt;
|
|
||||||
double ticks;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args,"d", &ticks))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (!(dt = mxDateTime.DateTime_FromTicks(ticks)))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (!(mx = mxDateTime.DateTimeDelta_FromDaysAndSeconds(
|
|
||||||
0, ((mxDateTimeObject*)dt)->abstime)))
|
|
||||||
{
|
|
||||||
Py_DECREF(dt);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
Py_DECREF(dt);
|
|
||||||
res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_TIME);
|
|
||||||
Py_DECREF(mx);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_TimestampFromTicks(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *mx, *res;
|
|
||||||
double ticks;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "d", &ticks))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (!(mx = mxDateTime.DateTime_FromTicks(ticks)))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_TIMESTAMP);
|
|
||||||
Py_DECREF(mx);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_DateFromMx(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *mx;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", mxDateTime.DateTime_Type, &mx))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_DATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_TimeFromMx(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *mx;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", mxDateTime.DateTimeDelta_Type, &mx))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_TimestampFromMx(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *mx;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", mxDateTime.DateTime_Type, &mx))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_TIMESTAMP);
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_IntervalFromMx(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *mx;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", mxDateTime.DateTime_Type, &mx))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_INTERVAL);
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
/* adapter_mxdatetime.h - definition for the mx date/time types
|
|
||||||
*
|
|
||||||
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
|
||||||
*
|
|
||||||
* This file is part of psycopg.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef PSYCOPG_MXDATETIME_H
|
|
||||||
#define PSYCOPG_MXDATETIME_H 1
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern HIDDEN PyTypeObject mxdatetimeType;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PyObject_HEAD
|
|
||||||
|
|
||||||
PyObject *wrapped;
|
|
||||||
int type;
|
|
||||||
#define PSYCO_MXDATETIME_TIME 0
|
|
||||||
#define PSYCO_MXDATETIME_DATE 1
|
|
||||||
#define PSYCO_MXDATETIME_TIMESTAMP 2
|
|
||||||
#define PSYCO_MXDATETIME_INTERVAL 3
|
|
||||||
|
|
||||||
} mxdatetimeObject;
|
|
||||||
|
|
||||||
/* functions exported to psycopgmodule.c */
|
|
||||||
#ifdef PSYCOPG_DEFAULT_MXDATETIME
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_Date(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_Date_doc \
|
|
||||||
"Date(year, month, day) -> new date"
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_Time(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_Time_doc \
|
|
||||||
"Time(hour, minutes, seconds) -> new time"
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_Timestamp(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_Timestamp_doc \
|
|
||||||
"Time(year, month, day, hour, minutes, seconds) -> new timestamp"
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_DateFromTicks(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_DateFromTicks_doc \
|
|
||||||
"DateFromTicks(ticks) -> new date"
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_TimeFromTicks(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_TimeFromTicks_doc \
|
|
||||||
"TimeFromTicks(ticks) -> new time"
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_TimestampFromTicks(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_TimestampFromTicks_doc \
|
|
||||||
"TimestampFromTicks(ticks) -> new timestamp"
|
|
||||||
|
|
||||||
#endif /* PSYCOPG_DEFAULT_MXDATETIME */
|
|
||||||
|
|
||||||
HIDDEN int psyco_adapter_mxdatetime_init(void);
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_DateFromMx(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_DateFromMx_doc \
|
|
||||||
"DateFromMx(mx) -> new date"
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_TimeFromMx(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_TimeFromMx_doc \
|
|
||||||
"TimeFromMx(mx) -> new time"
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_TimestampFromMx(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_TimestampFromMx_doc \
|
|
||||||
"TimestampFromMx(mx) -> new timestamp"
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_IntervalFromMx(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_IntervalFromMx_doc \
|
|
||||||
"IntervalFromMx(mx) -> new interval"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* !defined(PSYCOPG_MXDATETIME_H) */
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* adapter_pboolean.c - psycopg boolean type wrapper implementation
|
/* adapter_pboolean.c - psycopg boolean type wrapper implementation
|
||||||
*
|
*
|
||||||
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
* Copyright (C) 2020-2021 The Psycopg Team
|
||||||
*
|
*
|
||||||
* This file is part of psycopg.
|
* This file is part of psycopg.
|
||||||
*
|
*
|
||||||
|
@ -37,27 +38,18 @@
|
||||||
static PyObject *
|
static PyObject *
|
||||||
pboolean_getquoted(pbooleanObject *self, PyObject *args)
|
pboolean_getquoted(pbooleanObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
#ifdef PSYCOPG_NEW_BOOLEAN
|
|
||||||
if (PyObject_IsTrue(self->wrapped)) {
|
if (PyObject_IsTrue(self->wrapped)) {
|
||||||
return Bytes_FromString("true");
|
return Bytes_FromString("true");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return Bytes_FromString("false");
|
return Bytes_FromString("false");
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
if (PyObject_IsTrue(self->wrapped)) {
|
|
||||||
return Bytes_FromString("'t'");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return Bytes_FromString("'f'");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
pboolean_str(pbooleanObject *self)
|
pboolean_str(pbooleanObject *self)
|
||||||
{
|
{
|
||||||
return psycopg_ensure_text(pboolean_getquoted(self, NULL));
|
return psyco_ensure_text(pboolean_getquoted(self, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -146,13 +138,6 @@ pboolean_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
return type->tp_alloc(type, 0);
|
return type->tp_alloc(type, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
pboolean_repr(pbooleanObject *self)
|
|
||||||
{
|
|
||||||
return PyString_FromFormat("<psycopg2._psycopg.Boolean object at %p>",
|
|
||||||
self);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* object type */
|
/* object type */
|
||||||
|
|
||||||
|
@ -161,14 +146,14 @@ pboolean_repr(pbooleanObject *self)
|
||||||
|
|
||||||
PyTypeObject pbooleanType = {
|
PyTypeObject pbooleanType = {
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
"psycopg2._psycopg.Boolean",
|
"psycopg2.extensions.Boolean",
|
||||||
sizeof(pbooleanObject), 0,
|
sizeof(pbooleanObject), 0,
|
||||||
pboolean_dealloc, /*tp_dealloc*/
|
pboolean_dealloc, /*tp_dealloc*/
|
||||||
0, /*tp_print*/
|
0, /*tp_print*/
|
||||||
0, /*tp_getattr*/
|
0, /*tp_getattr*/
|
||||||
0, /*tp_setattr*/
|
0, /*tp_setattr*/
|
||||||
0, /*tp_compare*/
|
0, /*tp_compare*/
|
||||||
(reprfunc)pboolean_repr, /*tp_repr*/
|
0, /*tp_repr*/
|
||||||
0, /*tp_as_number*/
|
0, /*tp_as_number*/
|
||||||
0, /*tp_as_sequence*/
|
0, /*tp_as_sequence*/
|
||||||
0, /*tp_as_mapping*/
|
0, /*tp_as_mapping*/
|
||||||
|
@ -198,17 +183,3 @@ PyTypeObject pbooleanType = {
|
||||||
0, /*tp_alloc*/
|
0, /*tp_alloc*/
|
||||||
pboolean_new, /*tp_new*/
|
pboolean_new, /*tp_new*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/** module-level functions **/
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_Boolean(PyObject *module, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *obj;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O", &obj))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return PyObject_CallFunctionObjArgs((PyObject *)&pbooleanType, obj, NULL);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* adapter_pboolean.h - definition for the psycopg boolean type wrapper
|
/* adapter_pboolean.h - definition for the psycopg boolean type wrapper
|
||||||
*
|
*
|
||||||
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
* Copyright (C) 2020-2021 The Psycopg Team
|
||||||
*
|
*
|
||||||
* This file is part of psycopg.
|
* This file is part of psycopg.
|
||||||
*
|
*
|
||||||
|
@ -40,12 +41,6 @@ typedef struct {
|
||||||
|
|
||||||
} pbooleanObject;
|
} pbooleanObject;
|
||||||
|
|
||||||
/* functions exported to psycopgmodule.c */
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_Boolean(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_Boolean_doc \
|
|
||||||
"Boolean(obj) -> new boolean value"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* adapter_pdecimal.c - psycopg Decimal type wrapper implementation
|
/* adapter_pdecimal.c - psycopg Decimal type wrapper implementation
|
||||||
*
|
*
|
||||||
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
|
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||||
|
* Copyright (C) 2020-2021 The Psycopg Team
|
||||||
*
|
*
|
||||||
* This file is part of psycopg.
|
* This file is part of psycopg.
|
||||||
*
|
*
|
||||||
|
@ -80,8 +81,7 @@ pdecimal_getquoted(pdecimalObject *self, PyObject *args)
|
||||||
/* res may be unicode and may suffer for issue #57 */
|
/* res may be unicode and may suffer for issue #57 */
|
||||||
output:
|
output:
|
||||||
|
|
||||||
#if PY_MAJOR_VERSION > 2
|
/* unicode to bytes */
|
||||||
/* unicode to bytes in Py3 */
|
|
||||||
{
|
{
|
||||||
PyObject *tmp = PyUnicode_AsUTF8String(res);
|
PyObject *tmp = PyUnicode_AsUTF8String(res);
|
||||||
Py_DECREF(res);
|
Py_DECREF(res);
|
||||||
|
@ -89,7 +89,6 @@ output:
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
if ('-' == Bytes_AS_STRING(res)[0]) {
|
if ('-' == Bytes_AS_STRING(res)[0]) {
|
||||||
/* Prepend a space in front of negative numbers (ticket #57) */
|
/* Prepend a space in front of negative numbers (ticket #57) */
|
||||||
|
@ -113,7 +112,7 @@ end:
|
||||||
static PyObject *
|
static PyObject *
|
||||||
pdecimal_str(pdecimalObject *self)
|
pdecimal_str(pdecimalObject *self)
|
||||||
{
|
{
|
||||||
return psycopg_ensure_text(pdecimal_getquoted(self, NULL));
|
return psyco_ensure_text(pdecimal_getquoted(self, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -202,13 +201,6 @@ pdecimal_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
return type->tp_alloc(type, 0);
|
return type->tp_alloc(type, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
pdecimal_repr(pdecimalObject *self)
|
|
||||||
{
|
|
||||||
return PyString_FromFormat("<psycopg2._psycopg.Decimal object at %p>",
|
|
||||||
self);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* object type */
|
/* object type */
|
||||||
|
|
||||||
|
@ -224,7 +216,7 @@ PyTypeObject pdecimalType = {
|
||||||
0, /*tp_getattr*/
|
0, /*tp_getattr*/
|
||||||
0, /*tp_setattr*/
|
0, /*tp_setattr*/
|
||||||
0, /*tp_compare*/
|
0, /*tp_compare*/
|
||||||
(reprfunc)pdecimal_repr, /*tp_repr*/
|
0, /*tp_repr*/
|
||||||
0, /*tp_as_number*/
|
0, /*tp_as_number*/
|
||||||
0, /*tp_as_sequence*/
|
0, /*tp_as_sequence*/
|
||||||
0, /*tp_as_mapping*/
|
0, /*tp_as_mapping*/
|
||||||
|
@ -254,17 +246,3 @@ PyTypeObject pdecimalType = {
|
||||||
0, /*tp_alloc*/
|
0, /*tp_alloc*/
|
||||||
pdecimal_new, /*tp_new*/
|
pdecimal_new, /*tp_new*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/** module-level functions **/
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_Decimal(PyObject *module, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *obj;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O", &obj))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return PyObject_CallFunctionObjArgs((PyObject *)&pdecimalType, obj, NULL);
|
|
||||||
}
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user