Compare commits
1659
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fba9709593 | ||
|
|
bde3ceb1d9 | ||
|
|
c1e0978556 | ||
|
|
9a84fa585b | ||
|
|
af94c93387 | ||
|
|
53d7530ee8 | ||
|
|
0421668763 | ||
|
|
6b82c6e89b | ||
|
|
489d5010fd | ||
|
|
5d265624e7 | ||
|
|
bb0e6d5edd | ||
|
|
5935b9e367 | ||
|
|
dfcc375e17 | ||
|
|
a04c4dc42c | ||
|
|
e78cee6618 | ||
|
|
0b185fc84f | ||
|
|
bf163f0237 | ||
|
|
d1916eb6c9 | ||
|
|
0ca4107c3e | ||
|
|
a8220e2a8a | ||
|
|
ff09d5709d | ||
|
|
8d22a984be | ||
|
|
cdfe09794a | ||
|
|
73bfc87669 | ||
|
|
ba53c6e844 | ||
|
|
649b34155e | ||
|
|
934d3bc164 | ||
|
|
6a0fa04c58 | ||
|
|
67e7f45c59 | ||
|
|
c0835ffdc2 | ||
|
|
770e1ddc13 | ||
|
|
d3e49722d1 | ||
|
|
8bdf8b3a94 | ||
|
|
20a9cd8013 | ||
|
|
f20102829e | ||
|
|
dd01f817b7 | ||
|
|
9806d81675 | ||
|
|
72d1cbadde | ||
|
|
77b6e981a4 | ||
|
|
fc92d14bbe | ||
|
|
2d15d9b0d0 | ||
|
|
4a18506d4d | ||
|
|
6a09ccd0d3 | ||
|
|
8301a266b5 | ||
|
|
196284c083 | ||
|
|
bf6c88fe23 | ||
|
|
d6aa10d7e9 | ||
|
|
c5e29f0eed | ||
|
|
cd70a42007 | ||
|
|
da21efabb4 | ||
|
|
463f73f80d | ||
|
|
6973635136 | ||
|
|
c3702fa516 | ||
|
|
81372486d9 | ||
|
|
40f8aadd58 | ||
|
|
56fe538cc7 | ||
|
|
f15eb50a75 | ||
|
|
7f91210e94 | ||
|
|
8a9e2a6c37 | ||
|
|
9680e6483f | ||
|
|
e9076d04b0 | ||
|
|
8fa55917ac | ||
|
|
3188ad6043 | ||
|
|
8f4871eaf2 | ||
|
|
151fa4e75c | ||
|
|
2b1937a31e | ||
|
|
15bc27bfb7 | ||
|
|
943259264f | ||
|
|
abccc06242 | ||
|
|
00df05061e | ||
|
|
16ca025657 | ||
|
|
8c660d4611 | ||
|
|
abdf3a22cc | ||
|
|
f72378137a | ||
|
|
6e6b13b945 | ||
|
|
2d65e9772d | ||
|
|
db4af57e20 | ||
|
|
3c1a8adc78 | ||
|
|
fccc377417 | ||
|
|
d309827c75 | ||
|
|
7fedc9298a | ||
|
|
513b350ccc | ||
|
|
7b1631fea1 | ||
|
|
79e5c5087a | ||
|
|
01e9a49613 | ||
|
|
f4b143685c | ||
|
|
b729503a3e | ||
|
|
517ed6b7ce | ||
|
|
ed6cbe607c | ||
|
|
9ee458a619 | ||
|
|
6ee80994a3 | ||
|
|
7ead46cbf2 | ||
|
|
f07297e82d | ||
|
|
e6ba4d9426 | ||
|
|
0156f1a9a7 | ||
|
|
d24133e5c5 | ||
|
|
a2c727788e | ||
|
|
d66f470326 | ||
|
|
cae83dd7ea | ||
|
|
5d711348da | ||
|
|
87ff3c679c | ||
|
|
52d99aaa97 | ||
|
|
d05a35daf8 | ||
|
|
9cdbf8e8fe | ||
|
|
9825126a61 | ||
|
|
700b3a5295 | ||
|
|
d024426049 | ||
|
|
2698c8facb | ||
|
|
d732e7bf26 | ||
|
|
8c3c2ea508 | ||
|
|
ebb54d1bc2 | ||
|
|
67481f534f | ||
|
|
cf7c80b3f4 | ||
|
|
a572e39871 | ||
|
|
25ea5bb32b | ||
|
|
535a4a77bf | ||
|
|
a829db8dde | ||
|
|
bc5ad4e8af | ||
|
|
aa6f068a92 | ||
|
|
0446d17a3e | ||
|
|
68da6c5e1d | ||
|
|
62b2fb8756 | ||
|
|
33f2e2bfbb | ||
|
|
404be6e761 | ||
|
|
bc9e742b64 | ||
|
|
fc175f478b | ||
|
|
7f7a908d20 | ||
|
|
b9657c763a | ||
|
|
0683cc486d | ||
|
|
e8e446e92a | ||
|
|
6bc016a762 | ||
|
|
1805699923 | ||
|
|
54e6dedc5f | ||
|
|
dc1fc3a897 | ||
|
|
d39a157489 | ||
|
|
358bc9db1c | ||
|
|
1b1acad676 | ||
|
|
71c0be0921 | ||
|
|
048f70ce85 | ||
|
|
90efddbb1d | ||
|
|
2fbdb67cbe | ||
|
|
3b0f5d0cfd | ||
|
|
f1464da1e5 | ||
|
|
089dd19ca8 | ||
|
|
7dc4df8a68 | ||
|
|
f9cb6f5834 | ||
|
|
4ca12d76bd | ||
|
|
7fe0916aec | ||
|
|
ffdbcbc89a | ||
|
|
a52ffc3647 | ||
|
|
648e0eb70c | ||
|
|
72859dd13b | ||
|
|
7ea4fca4ed | ||
|
|
7fedc5e070 | ||
|
|
a358868d64 | ||
|
|
976b9223ae | ||
|
|
95e349a5dc | ||
|
|
6249922a26 | ||
|
|
d9388a5194 | ||
|
|
66b7d008ee | ||
|
|
26edef4f24 | ||
|
|
1bca0c42a3 | ||
|
|
f96130acef | ||
|
|
6f96478e76 | ||
|
|
734dce8b60 | ||
|
|
943ce6bf16 | ||
|
|
92a00ee663 | ||
|
|
447dc44e1f | ||
|
|
5f6ed1a3f8 | ||
|
|
b14d53aba1 | ||
|
|
4f74e231ba | ||
|
|
b0833b66a0 | ||
|
|
169c4fb3d3 | ||
|
|
9911443b9d | ||
|
|
e972bb569a | ||
|
|
55a3e5e30d | ||
|
|
12a323eb0c | ||
|
|
f8e098544b | ||
|
|
c6724a3ff0 | ||
|
|
89fcf7c3c9 | ||
|
|
ba00071e74 | ||
|
|
8a282a9b60 | ||
|
|
516dd178bf | ||
|
|
8f4373104f | ||
|
|
e4571c59a6 | ||
|
|
d00ee6bf56 | ||
|
|
a69a094db5 | ||
|
|
43348d6163 | ||
|
|
d79d48ca55 | ||
|
|
6acbb4fb93 | ||
|
|
cac9f6b3bb | ||
|
|
e35dcee6ca | ||
|
|
56f64add81 | ||
|
|
4b532e2084 | ||
|
|
b5af1759df | ||
|
|
adaecccda1 | ||
|
|
db3521823d | ||
|
|
db2ff89644 | ||
|
|
cc9c615c5a | ||
|
|
659ca0c508 | ||
|
|
e2ca4ebe58 | ||
|
|
9b8292ec6a | ||
|
|
e8854fe945 | ||
|
|
fb6be4d359 | ||
|
|
7115454c70 | ||
|
|
2cefbf11c3 | ||
|
|
98f80d6ce9 | ||
|
|
0d2ae16aee | ||
|
|
68451b990a | ||
|
|
1adf8e7fcd | ||
|
|
876b3fdd3f | ||
|
|
b1a7258750 | ||
|
|
8195ec35ad | ||
|
|
7c6e5dfffd | ||
|
|
b01d634161 | ||
|
|
953a653179 | ||
|
|
5e0a1d1ea1 | ||
|
|
62d2069210 | ||
|
|
bc714d614d | ||
|
|
4a7f889d8a | ||
|
|
5119ce78b5 | ||
|
|
eb9763febe | ||
|
|
a5f827b12d | ||
|
|
85d49bde73 | ||
|
|
a77a0fd3eb | ||
|
|
ed83a844bb | ||
|
|
aa557982fa | ||
|
|
82ea898ab0 | ||
|
|
fafbe57b30 | ||
|
|
52b1ace676 | ||
|
|
2e8b1639f8 | ||
|
|
52fab3ed34 | ||
|
|
a43a0e8b68 | ||
|
|
9ae821ee66 | ||
|
|
e552ce339e | ||
|
|
5a140299b3 | ||
|
|
7dddcd1403 | ||
|
|
aceb90525a | ||
|
|
404f43894a | ||
|
|
7b457b9731 | ||
|
|
f44f59e05e | ||
|
|
75a3baf94d | ||
|
|
b252fc249d | ||
|
|
4bd8451046 | ||
|
|
cad7e3ceca | ||
|
|
facb6516e9 | ||
|
|
1f9ee311cd | ||
|
|
e1d304ce6f | ||
|
|
eb6f1f87f6 | ||
|
|
53f1c775db | ||
|
|
066cdf13c2 | ||
|
|
6535456ea6 | ||
|
|
3984cad722 | ||
|
|
cf6c66e70e | ||
|
|
c5571ab19a | ||
|
|
bc351a2dc4 | ||
|
|
ad89932715 | ||
|
|
87824331c6 | ||
|
|
b16f1ca427 | ||
|
|
d6239f2262 | ||
|
|
114ad36894 | ||
|
|
b47f8237d8 | ||
|
|
8495152b30 | ||
|
|
9bab004a9b | ||
|
|
bf207ae1b6 | ||
|
|
9f2e94fc2c | ||
|
|
14390939bd | ||
|
|
680f87c9ce | ||
|
|
3c1e054251 | ||
|
|
6c12838f9a | ||
|
|
8e8da289d5 | ||
|
|
ddbcf97f33 | ||
|
|
0d8ba83046 | ||
|
|
e9acc2418f | ||
|
|
5a9c1b8824 | ||
|
|
79ce8e89bd | ||
|
|
7760e70d8d | ||
|
|
2acd40f0dc | ||
|
|
6d4186999f | ||
|
|
7aa0b15353 | ||
|
|
2dfc500ac3 | ||
|
|
8b328f6942 | ||
|
|
998183be6b | ||
|
|
89a8e000ef | ||
|
|
0515dc2480 | ||
|
|
75848ce2c8 | ||
|
|
f83c9a3959 | ||
|
|
6a8b52c406 | ||
|
|
d82894bc40 | ||
|
|
a5217abf5f | ||
|
|
fd22efe297 | ||
|
|
ed486e9523 | ||
|
|
4f87ca83ec | ||
|
|
5ca7fa3abd | ||
|
|
f385260987 | ||
|
|
0c26713326 | ||
|
|
d987afc8d9 | ||
|
|
a3499358f9 | ||
|
|
590d5d99f0 | ||
|
|
2c4311c2a9 | ||
|
|
0b292e8842 | ||
|
|
ebcdb289d9 | ||
|
|
ef8c8d21cd | ||
|
|
601e60e250 | ||
|
|
0eab503a87 | ||
|
|
31f26e561c | ||
|
|
4888c89ce5 | ||
|
|
aa73243fde | ||
|
|
fed57bc9dd | ||
|
|
2cb92cbbd0 | ||
|
|
358ab55c31 | ||
|
|
c3b0df488f | ||
|
|
3d38969fd4 | ||
|
|
f3480a7ff4 | ||
|
|
db9921f14e | ||
|
|
799f6c37d3 | ||
|
|
ff911f6211 | ||
|
|
a6b56237fe | ||
|
|
72b9be719f | ||
|
|
f892d1a3fe | ||
|
|
7e0614fc7a | ||
|
|
8b74ddd791 | ||
|
|
c90d7ef74d | ||
|
|
030ca77f68 | ||
|
|
7d07ee0b39 | ||
|
|
c43073b659 | ||
|
|
8e551942c0 | ||
|
|
19b8802892 | ||
|
|
fc59a5e0c4 | ||
|
|
f465044d26 | ||
|
|
da9c1405d6 | ||
|
|
bc8217cd76 | ||
|
|
e94210cdf5 | ||
|
|
4ec9926e96 | ||
|
|
f50bac1b74 | ||
|
|
1b43a06aac | ||
|
|
cd33d09ce0 | ||
|
|
57fe160fe7 | ||
|
|
347150090e | ||
|
|
c3bdad10a3 | ||
|
|
d564baf593 | ||
|
|
f0d2e599b6 | ||
|
|
00d247edcc | ||
|
|
e7241263aa | ||
|
|
7974625e8b | ||
|
|
a53d4e2ab4 | ||
|
|
f0b848c86f | ||
|
|
a04da2a417 | ||
|
|
cbdccb7fd2 | ||
|
|
3dc8678529 | ||
|
|
a7499ddfc0 | ||
|
|
491f6796f8 | ||
|
|
b365cc8396 | ||
|
|
57b36ebcb8 | ||
|
|
7b019f3d49 | ||
|
|
54dd8b851b | ||
|
|
8aa2886c1a | ||
|
|
1a9ce60778 | ||
|
|
328e2647be | ||
|
|
cfa1a42328 | ||
|
|
2a23578b55 | ||
|
|
8fc5005dfe | ||
|
|
7638aa7f24 | ||
|
|
5cac19a9c0 | ||
|
|
68c8b13ed6 | ||
|
|
e58b66838d | ||
|
|
458c7afa79 | ||
|
|
b303eb9342 | ||
|
|
9ac6f28996 | ||
|
|
6a67cb17c3 | ||
|
|
34aaabf7ea | ||
|
|
73554aa4fa | ||
|
|
7f812abb73 | ||
|
|
306c901946 | ||
|
|
05a82671c3 | ||
|
|
c1a36dfe41 | ||
|
|
a510e9f23a | ||
|
|
c30cd4a6ab | ||
|
|
818c9a617e | ||
|
|
4d036d6dd4 | ||
|
|
1e6df0eeb7 | ||
|
|
066bdaec75 | ||
|
|
ed8742e685 | ||
|
|
87b50f78f0 | ||
|
|
0eb14e7ec5 | ||
|
|
b9b62b2369 | ||
|
|
f300bb43de | ||
|
|
8804e1963f | ||
|
|
f843442d97 | ||
|
|
f5b055fabb | ||
|
|
5503028d8c | ||
|
|
40772955a5 | ||
|
|
d99c0a9ecc | ||
|
|
eeecbb8fc6 | ||
|
|
c8fbedfcb3 | ||
|
|
ca4744c1cb | ||
|
|
910961fccd | ||
|
|
4e4102f64d | ||
|
|
5045bf4f4b | ||
|
|
c2108dafbd | ||
|
|
41bc4d877f | ||
|
|
d8aa899bfb | ||
|
|
cea9cc5e8e | ||
|
|
8a72a5b0bc | ||
|
|
adde312c2a | ||
|
|
a7d0ed81bd | ||
|
|
964fac813c | ||
|
|
44b6977cf4 | ||
|
|
99d3c5f335 | ||
|
|
45cec095b4 | ||
|
|
698a3c1ac6 | ||
|
|
31b95e6cdc | ||
|
|
e83a1a20f9 | ||
|
|
4c786944e8 | ||
|
|
c84dd331df | ||
|
|
13d4004774 | ||
|
|
5ea1d67315 | ||
|
|
fe3902771f | ||
|
|
853992649c | ||
|
|
d6b9757778 | ||
|
|
3837a29bfc | ||
|
|
7ff23a5218 | ||
|
|
d7e531ce9f | ||
|
|
d1d3c1ad93 | ||
|
|
eb30cb1feb | ||
|
|
ab0434d648 | ||
|
|
9cafc85479 | ||
|
|
155466aad1 | ||
|
|
2be867ffac | ||
|
|
6515e84d4c | ||
|
|
7af9d1c04b | ||
|
|
f19d4dc815 | ||
|
|
d8c9dcc0ad | ||
|
|
01cdbd0734 | ||
|
|
f8daff6da1 | ||
|
|
f554d0f02b | ||
|
|
78da9e361b | ||
|
|
541a0c8491 | ||
|
|
85f7547514 | ||
|
|
1ec5704d14 | ||
|
|
3eeb240fd1 | ||
|
|
7d0c5f72f9 | ||
|
|
7bb91d034f | ||
|
|
33b3360e68 | ||
|
|
a7e0fdd5fc | ||
|
|
b5ad47271e | ||
|
|
eba7328c46 | ||
|
|
e14734c8dd | ||
|
|
2a962802de | ||
|
|
99d3e251cf | ||
|
|
0143770384 | ||
|
|
0790f9c6ac | ||
|
|
3d9389fcaf | ||
|
|
9ffd3ddb00 | ||
|
|
f77c9f950f | ||
|
|
d8a04804f9 | ||
|
|
a7574d3a26 | ||
|
|
7f1e6621f8 | ||
|
|
1954b87039 | ||
|
|
6890495d23 | ||
|
|
4e2ac31712 | ||
|
|
13cc1279f8 | ||
|
|
6010afb28f | ||
|
|
832ea82fef | ||
|
|
aca84bebb0 | ||
|
|
1ad157a0a1 | ||
|
|
d0f67e2c4d | ||
|
|
290ff9930a | ||
|
|
12b5ab7e4f | ||
|
|
8e8546c919 | ||
|
|
af012dd588 | ||
|
|
ccdd7f603e | ||
|
|
d65c25bcc9 | ||
|
|
e26b9e5f98 | ||
|
|
18106b5e5b | ||
|
|
3223bafafe | ||
|
|
533a0ab955 | ||
|
|
0bb1e1b8e3 | ||
|
|
0983b610b4 | ||
|
|
3ecf84f5ad | ||
|
|
53deb98918 | ||
|
|
3f0252abc7 | ||
|
|
bef0bb95e7 | ||
|
|
a4ee98fe66 | ||
|
|
68ee348d36 | ||
|
|
1b5d224df5 | ||
|
|
dc7974deae | ||
|
|
de06f512db | ||
|
|
3953fe0ad4 | ||
|
|
4928ea0e46 | ||
|
|
b84c3b3469 | ||
|
|
77d6d31542 | ||
|
|
8d295ec118 | ||
|
|
b2204616c7 | ||
|
|
a8021432b8 | ||
|
|
b60185a078 | ||
|
|
e8b57a47cd | ||
|
|
734e02c4f1 | ||
|
|
40b20c680a | ||
|
|
07e28e74ee | ||
|
|
911c7b9b36 | ||
|
|
38c9a5be50 | ||
|
|
689a144a71 | ||
|
|
e329fb3178 | ||
|
|
2cee9cb243 | ||
|
|
1de9012573 | ||
|
|
7954673ea7 | ||
|
|
f180cc0c9f | ||
|
|
2d6b3f09eb | ||
|
|
1d3185139a | ||
|
|
60dd7842f0 | ||
|
|
a9817ae244 | ||
|
|
03b5b34114 | ||
|
|
6336e55069 | ||
|
|
2dba55cb27 | ||
|
|
1c751e3ddb | ||
|
|
76e001d39e | ||
|
|
e39b98ca7b | ||
|
|
4b252f659e | ||
|
|
85e8509399 | ||
|
|
0749bc3850 | ||
|
|
f6718dc6d6 | ||
|
|
10dbde43db | ||
|
|
966119f4d3 | ||
|
|
b2ee806b6f | ||
|
|
22b32da23a | ||
|
|
397ba5d73d | ||
|
|
7e7aa8f7c2 | ||
|
|
332be8c396 | ||
|
|
cb69dd6aea | ||
|
|
20132a9caa | ||
|
|
c10104f0a8 | ||
|
|
febf00ea5d | ||
|
|
a03aed2dca | ||
|
|
84a38c81e4 | ||
|
|
0caf971263 | ||
|
|
fc7de2aafd | ||
|
|
3954df86cb | ||
|
|
a7459fe1ab | ||
|
|
52e2c2d916 | ||
|
|
7c56371f81 | ||
|
|
9fc05aae02 | ||
|
|
c1384f4c83 | ||
|
|
072039945b | ||
|
|
86fcffc854 | ||
|
|
27c4e7aade | ||
|
|
f5eca3933e | ||
|
|
2a3135744a | ||
|
|
2ab5b4f30c | ||
|
|
e1cdfc9400 | ||
|
|
b996bcff52 | ||
|
|
2ac2770155 | ||
|
|
345eaeed74 | ||
|
|
b58d6fe9d9 | ||
|
|
79a7f1723a | ||
|
|
454f1d7f58 | ||
|
|
37b7e458c2 | ||
|
|
76a7818013 | ||
|
|
4fad095858 | ||
|
|
49f6342e37 | ||
|
|
3e3f309cf9 | ||
|
|
b99bdc7cee | ||
|
|
b23f8c0f2e | ||
|
|
313762e86f | ||
|
|
4ae6690bb9 | ||
|
|
32add82d01 | ||
|
|
dc0bbdd92a | ||
|
|
a052a44f76 | ||
|
|
68c247358b | ||
|
|
fd3b037e52 | ||
|
|
ab9bf61445 | ||
|
|
91001b84c6 | ||
|
|
16c710a227 | ||
|
|
fc46b9eb5b | ||
|
|
f2c302d03a | ||
|
|
209e888e1b | ||
|
|
db33ad9dec | ||
|
|
9a3b662ec1 | ||
|
|
7b3b9f559a | ||
|
|
4316ecb358 | ||
|
|
e9f787f9fe | ||
|
|
c695c3e061 | ||
|
|
cacc64d809 | ||
|
|
4220fc0943 | ||
|
|
786a96fdc5 | ||
|
|
0e79d6e28e | ||
|
|
a3ab10c3f3 | ||
|
|
bd3d262640 | ||
|
|
d14317fd7a | ||
|
|
e1401bd28e | ||
|
|
bc2c1b2f94 | ||
|
|
c55d3f8a7c | ||
|
|
182badb266 | ||
|
|
b5e0d613fa | ||
|
|
29df16432d | ||
|
|
ec344ebf72 | ||
|
|
d90cf48c46 | ||
|
|
54c00bb127 | ||
|
|
653191d913 | ||
|
|
88a799379f | ||
|
|
db0d59191b | ||
|
|
770f603afb | ||
|
|
9b89103394 | ||
|
|
be0c5e4a0b | ||
|
|
7e57e282c5 | ||
|
|
67909d9645 | ||
|
|
4fcb1e97d9 | ||
|
|
de5a4c544d | ||
|
|
ba2bd53ec9 | ||
|
|
4dec075ee5 | ||
|
|
6b6848f124 | ||
|
|
c6558eecb5 | ||
|
|
3d487b64eb | ||
|
|
dd73bf16fd | ||
|
|
9fe69cb503 | ||
|
|
0bd31484ea | ||
|
|
6ea3521b45 | ||
|
|
5bc1f17cb5 | ||
|
|
2f2d84fbb1 | ||
|
|
54598789ee | ||
|
|
e878a96e6e | ||
|
|
023cc62bfc | ||
|
|
eb1a7c1bdf | ||
|
|
de97a18bb7 | ||
|
|
a69db2ae63 | ||
|
|
f793a88403 | ||
|
|
9c83fafc27 | ||
|
|
a7fe20bfda | ||
|
|
0f842d28a1 | ||
|
|
50dfbc7e79 | ||
|
|
a52a0c43c3 | ||
|
|
50e077ee52 | ||
|
|
3373994cfd | ||
|
|
7391c7bd4d | ||
|
|
96c76ec79e | ||
|
|
f62a78242d | ||
|
|
dfd80ba089 | ||
|
|
4e8a817ac2 | ||
|
|
df267fcc77 | ||
|
|
f9adad3acc | ||
|
|
8598780e8d | ||
|
|
bd23baf4ac | ||
|
|
8669eda82d | ||
|
|
7009653aa1 | ||
|
|
d245397bd2 | ||
|
|
a848e2ac47 | ||
|
|
cae83f6d4f | ||
|
|
defba2fc02 | ||
|
|
0465d0b880 | ||
|
|
8220d9cdbe | ||
|
|
f80471e6f3 | ||
|
|
fbcaa34b9e | ||
|
|
83a756c541 | ||
|
|
3ea2888b7f | ||
|
|
8a8a7ffc52 | ||
|
|
2876c7e46f | ||
|
|
a75af96bcc | ||
|
|
88aad2a374 | ||
|
|
10c62a94b0 | ||
|
|
864644bee4 | ||
|
|
5b09d8179e | ||
|
|
6cf4db7df3 | ||
|
|
886ddcd12d | ||
|
|
4bb8489073 | ||
|
|
b563084d0b | ||
|
|
8dc7bada91 | ||
|
|
f2774461d3 | ||
|
|
12307ecbcf | ||
|
|
0dc8bce4fe | ||
|
|
9cccdfb0fd | ||
|
|
16ef52392c | ||
|
|
1b950f13d6 | ||
|
|
146ec3469a | ||
|
|
7c803dfadb | ||
|
|
5ddce0ea00 | ||
|
|
d76dc73f33 | ||
|
|
be5d326343 | ||
|
|
98d7d70674 | ||
|
|
3c506294e3 | ||
|
|
b333789336 | ||
|
|
7933395055 | ||
|
|
6a99f29313 | ||
|
|
7b7530de19 | ||
|
|
6788024721 | ||
|
|
172781e678 | ||
|
|
163c4819e0 | ||
|
|
4a662d5966 | ||
|
|
d91219520f | ||
|
|
4e2d54fdd5 | ||
|
|
c712af4d4c | ||
|
|
f9b8641269 | ||
|
|
1d90146210 | ||
|
|
aaac4520d3 | ||
|
|
7247ca12cf | ||
|
|
b22edf1d8a | ||
|
|
8d3fab1250 | ||
|
|
f334da1f6b | ||
|
|
887c403f76 | ||
|
|
1e4eeb8098 | ||
|
|
8ffbc9a846 | ||
|
|
052d7f3643 | ||
|
|
5b0c456abd | ||
|
|
e98b936129 | ||
|
|
f527d3b9af | ||
|
|
a44f9d1bfd | ||
|
|
609d8e8bc3 | ||
|
|
4d698a28a2 | ||
|
|
a2f90fd003 | ||
|
|
41f5f3465a | ||
|
|
70f55bd2cd | ||
|
|
3e2d6a9a18 | ||
|
|
082d5db64f | ||
|
|
4c4568eeb8 | ||
|
|
6c7b506f0e | ||
|
|
dd91a165cc | ||
|
|
480436ff7c | ||
|
|
058c2895be | ||
|
|
9bab01d37b | ||
|
|
0dbbd6fe66 | ||
|
|
b11ae3a63a | ||
|
|
ee7fcf3110 | ||
|
|
bdf0117578 | ||
|
|
943abbce79 | ||
|
|
ea9db10daf | ||
|
|
994ab27aa3 | ||
|
|
de4ed96ec0 | ||
|
|
65390f035a | ||
|
|
1c329624a5 | ||
|
|
6eca02a31f | ||
|
|
ecc6c1da2a | ||
|
|
fff9409a33 | ||
|
|
0410eae36b | ||
|
|
851a14aa1a | ||
|
|
3c1aa033e6 | ||
|
|
55a6edef96 | ||
|
|
20b202e220 | ||
|
|
c136b7a6e9 | ||
|
|
8617b3fe34 | ||
|
|
181424b743 | ||
|
|
fc779a8355 | ||
|
|
7ea7e0422d | ||
|
|
332f5ee266 | ||
|
|
e3502f7f9d | ||
|
|
6c8af5108e | ||
|
|
75e8350e4d | ||
|
|
e7dd4efb3e | ||
|
|
2c69cdb350 | ||
|
|
f03e4ca595 | ||
|
|
31450d75e5 | ||
|
|
c08192ae5b | ||
|
|
7c0ba8028c | ||
|
|
1b2d7b1096 | ||
|
|
c2cbdb2ce0 | ||
|
|
e1268d4f57 | ||
|
|
1ff2b78268 | ||
|
|
1214e74a38 | ||
|
|
3e486caaab | ||
|
|
b2a7892b10 | ||
|
|
61178c5d6d | ||
|
|
1227a7674f | ||
|
|
43ecc7a581 | ||
|
|
f3de4d545f | ||
|
|
0148adec30 | ||
|
|
260c201f65 | ||
|
|
097f76b465 | ||
|
|
61db44d958 | ||
|
|
da0a8b913b | ||
|
|
d403d8b865 | ||
|
|
ea12a628e4 | ||
|
|
eaa359f177 | ||
|
|
2c5f3e8397 | ||
|
|
306bee4ecd | ||
|
|
6a9b2cb683 | ||
|
|
d360fd7fe3 | ||
|
|
a8cd5cfd39 | ||
|
|
69cc4e3232 | ||
|
|
e31c5326c4 | ||
|
|
0cff22720b | ||
|
|
043379efa5 | ||
|
|
3f8914b4b2 | ||
|
|
4cd99f5536 | ||
|
|
a8c308f349 | ||
|
|
d6806501f1 | ||
|
|
e91bc867f5 | ||
|
|
1901519fa7 | ||
|
|
17980ba83c | ||
|
|
8924a0e4fe | ||
|
|
cfca625e94 | ||
|
|
f4db072815 | ||
|
|
0a48075161 | ||
|
|
9b360dda29 | ||
|
|
e6141ef8ae | ||
|
|
da59591a9c | ||
|
|
17b758faed | ||
|
|
abf9bef1a9 | ||
|
|
8a04f99784 | ||
|
|
c3352e5542 | ||
|
|
84003a8d40 | ||
|
|
7576315169 | ||
|
|
9f894d2f26 | ||
|
|
89b86f41bb | ||
|
|
c38e5d043f | ||
|
|
3f1e5e213d | ||
|
|
89a28cffa9 | ||
|
|
47d2576365 | ||
|
|
a5dfbeedb9 | ||
|
|
334d5118bb | ||
|
|
27b48aa7e6 | ||
|
|
837f71eca5 | ||
|
|
50719c0bb0 | ||
|
|
ff2f799ba3 | ||
|
|
ea2e7fd365 | ||
|
|
be811d23fa | ||
|
|
9562ab8398 | ||
|
|
ed3e3f2571 | ||
|
|
bba54e320d | ||
|
|
c2a6ebb96e | ||
|
|
7a0a7af923 | ||
|
|
3882bb864b | ||
|
|
554f223f6b | ||
|
|
231839e037 | ||
|
|
199257d7fb | ||
|
|
0719e6f648 | ||
|
|
0fc32d7825 | ||
|
|
aa6c4df395 | ||
|
|
d4dcb2e217 | ||
|
|
c9591657dd | ||
|
|
d56c1f1663 | ||
|
|
c36271e23b | ||
|
|
76e8175971 | ||
|
|
25e5157785 | ||
|
|
9160ce0d45 | ||
|
|
e82eebb368 | ||
|
|
af1bb6b955 | ||
|
|
f7a00f30d5 | ||
|
|
baa9006c28 | ||
|
|
eba763b258 | ||
|
|
ecf22b390b | ||
|
|
3bbf8037f8 | ||
|
|
4ba4964425 | ||
|
|
99cd1346fb | ||
|
|
1ccdfb5172 | ||
|
|
3d0fe5bfe2 | ||
|
|
c4da034f7e | ||
|
|
f919df47c7 | ||
|
|
c356219690 | ||
|
|
ee7e964ad4 | ||
|
|
17adfc8adc | ||
|
|
e17b7f4bc9 | ||
|
|
e7b43dd33f | ||
|
|
00cec7c088 | ||
|
|
11619ad8ee | ||
|
|
9dd05715de | ||
|
|
c81c7ff3d5 | ||
|
|
abcb5605f9 | ||
|
|
291077f364 | ||
|
|
dfd71d6ac8 | ||
|
|
6ac91ccc8c | ||
|
|
edec6707ec | ||
|
|
a20222fc22 | ||
|
|
223bd3688d | ||
|
|
1127b10b27 | ||
|
|
654794cdcf | ||
|
|
6481d24642 | ||
|
|
fc35f5b6e0 | ||
|
|
4ca89fd3c4 | ||
|
|
40b1aa8f24 | ||
|
|
3bf1ddfb91 | ||
|
|
41e1f5526c | ||
|
|
b3c39decc1 | ||
|
|
188a990e22 | ||
|
|
e0742ada97 | ||
|
|
ff3df488f5 | ||
|
|
86c3992318 | ||
|
|
9e3d161d0d | ||
|
|
f0a40280fd | ||
|
|
4597c678ac | ||
|
|
8f4999a70b | ||
|
|
cba429c0bc | ||
|
|
c9afdb5072 | ||
|
|
a9a4da62cf | ||
|
|
f487169777 | ||
|
|
dc5f360cd3 | ||
|
|
56e88ed7c3 | ||
|
|
7005a9a42f | ||
|
|
abe17984fb | ||
|
|
95bedd0bad | ||
|
|
3eefe60bcf | ||
|
|
4c53406cab | ||
|
|
6122c3ad48 | ||
|
|
5f75197e86 | ||
|
|
15f1a5df20 | ||
|
|
17309da8e6 | ||
|
|
bcd7c81a46 | ||
|
|
8e5312975b | ||
|
|
0d9fc31fa1 | ||
|
|
29a6af6d46 | ||
|
|
a32c1a1e25 | ||
|
|
1cc094a004 | ||
|
|
a964a42000 | ||
|
|
8d2fd5f87a | ||
|
|
e395c05b9a | ||
|
|
d6f6afe1f4 | ||
|
|
0264fea050 | ||
|
|
bf493ac0b7 | ||
|
|
2c2ecbae86 | ||
|
|
73b591b8ff | ||
|
|
16dd8aab74 | ||
|
|
9632a752d2 | ||
|
|
d47a469732 | ||
|
|
e0cc32c937 | ||
|
|
5b99a7ccc8 | ||
|
|
01de711098 | ||
|
|
37d59c1b7f | ||
|
|
42d7298a50 | ||
|
|
53a3943b11 | ||
|
|
a21dc3d3e3 | ||
|
|
8010017e45 | ||
|
|
3b724ae1cc | ||
|
|
8204fa721d | ||
|
|
e158234478 | ||
|
|
bf71da5ee6 | ||
|
|
58c5bb7fc1 | ||
|
|
cc0dcca7b4 | ||
|
|
36570c6595 | ||
|
|
c336ac6063 | ||
|
|
f3cca5255b | ||
|
|
0fbb67b71a | ||
|
|
f432bd3550 | ||
|
|
c586c0fe89 | ||
|
|
94c32b19a0 | ||
|
|
cd8a3390e6 | ||
|
|
a229f0d9c7 | ||
|
|
57f79671bf | ||
|
|
9eafb43c0f | ||
|
|
920281ab55 | ||
|
|
c164c174a5 | ||
|
|
3829b89d69 | ||
|
|
3e25e6e6b0 | ||
|
|
d578d67035 | ||
|
|
714e629aeb | ||
|
|
91d8a87604 | ||
|
|
66dd5d79e8 | ||
|
|
4c6c996f9b | ||
|
|
ebbae4fa0a | ||
|
|
a18035cfb1 | ||
|
|
af342bba56 | ||
|
|
c99d54e762 | ||
|
|
0b4b2454af | ||
|
|
f39e9b8418 | ||
|
|
ad6f932538 | ||
|
|
dcad710de2 | ||
|
|
24ee97c610 | ||
|
|
34ecc55261 | ||
|
|
ae573e047a | ||
|
|
4c29ed71d0 | ||
|
|
6f60e76883 | ||
|
|
e01e972ac3 | ||
|
|
c03b6c2e41 | ||
|
|
3c80e59ebc | ||
|
|
d08821ee5e | ||
|
|
90ba350099 | ||
|
|
427ed1966f | ||
|
|
20c82967ca | ||
|
|
ee7366611b | ||
|
|
5ba782841e | ||
|
|
f893bb0b51 | ||
|
|
1b65c7eed5 | ||
|
|
9b6a9b7aea | ||
|
|
dd9d9bd9be | ||
|
|
8e08949eb5 | ||
|
|
05ac1efe0d | ||
|
|
10848388a3 | ||
|
|
4556f4b3df | ||
|
|
cc87685c93 | ||
|
|
aaf72e05f1 | ||
|
|
d70ed586c7 | ||
|
|
2d4908e88c | ||
|
|
47f1d41473 | ||
|
|
7acaa6c083 | ||
|
|
282313ced6 | ||
|
|
10aeb7346d | ||
|
|
cd7678a965 | ||
|
|
c374f7b8b5 | ||
|
|
d98caf9da8 | ||
|
|
6d01d96229 | ||
|
|
7897dd9827 | ||
|
|
aba1c2b118 | ||
|
|
bf43d45cea | ||
|
|
d55d29329e | ||
|
|
50c4825b54 | ||
|
|
c20f8b1cfe | ||
|
|
14c0dc7b4a | ||
|
|
4ab87682e1 | ||
|
|
cdd673f2e4 | ||
|
|
c73391c34c | ||
|
|
29d4335b73 | ||
|
|
2829092cb8 | ||
|
|
665cd4d36a | ||
|
|
4cd1902796 | ||
|
|
3c26d57003 | ||
|
|
045d0d63ea | ||
|
|
d371637af0 | ||
|
|
28ff190475 | ||
|
|
e53339cf74 | ||
|
|
18a4912f77 | ||
|
|
edf6b16fae | ||
|
|
8df49e7194 | ||
|
|
4769ea895b | ||
|
|
312647647d | ||
|
|
d28ba32271 | ||
|
|
19bc91c757 | ||
|
|
8c261ab7b7 | ||
|
|
72a4e6b265 | ||
|
|
c7eeea8a95 | ||
|
|
5d55eac6b6 | ||
|
|
2cb79b9808 | ||
|
|
86126c98a2 | ||
|
|
449b33e059 | ||
|
|
fd51706903 | ||
|
|
bdbf3e302a | ||
|
|
c622a86286 | ||
|
|
e411a83783 | ||
|
|
b403f156fe | ||
|
|
951fe224fa | ||
|
|
cfb9bbde7d | ||
|
|
59b25a513a | ||
|
|
419753f59b | ||
|
|
a4781e4d76 | ||
|
|
d56862cbca | ||
|
|
c5141f2981 | ||
|
|
e78d06a4db | ||
|
|
5c75aed9be | ||
|
|
0cb0dc0694 | ||
|
|
100c17229f | ||
|
|
5e7f90a1d6 | ||
|
|
5af88c5df1 | ||
|
|
0d9985588b | ||
|
|
a00b42b289 | ||
|
|
b155b60280 | ||
|
|
437dd22d49 | ||
|
|
bb784c8244 | ||
|
|
8b169bdc1a | ||
|
|
5f8554f0de | ||
|
|
38f982c022 | ||
|
|
3da6297d78 | ||
|
|
901d76beba | ||
|
|
acbfb9ea2b | ||
|
|
4f56fd23bc | ||
|
|
4eb747b61f | ||
|
|
8b12c8f1c2 | ||
|
|
1d37472fdd | ||
|
|
8fa48edbf9 | ||
|
|
6917ffb9bd | ||
|
|
4fe4127958 | ||
|
|
deaff3e97f | ||
|
|
af38982273 | ||
|
|
16e8d44686 | ||
|
|
44aa875212 | ||
|
|
01bce5c129 | ||
|
|
05b44779a7 | ||
|
|
45a2b0fedf | ||
|
|
e91072a95c | ||
|
|
29247e6b01 | ||
|
|
861ac64607 | ||
|
|
bdc0400ac0 | ||
|
|
472215eef2 | ||
|
|
23db10d940 | ||
|
|
faf347b5b9 | ||
|
|
151cac8bd2 | ||
|
|
f899157ca9 | ||
|
|
bad78d9767 | ||
|
|
b2edf4a0fd | ||
|
|
4611ad6889 | ||
|
|
8b6855fc2c | ||
|
|
f299d9ea04 | ||
|
|
5d375cd730 | ||
|
|
ff9c5007a8 | ||
|
|
e7e60c05c0 | ||
|
|
36e7efa4eb | ||
|
|
cec5947b5b | ||
|
|
93c706a03b | ||
|
|
0f42004dee | ||
|
|
cf9edea203 | ||
|
|
9d7c6901cb | ||
|
|
b11a772d1e | ||
|
|
7ade777df2 | ||
|
|
5af78e2946 | ||
|
|
d3581d1c09 | ||
|
|
4af4968926 | ||
|
|
f3a9acc7e2 | ||
|
|
644d7ea600 | ||
|
|
38180f855a | ||
|
|
c571fd6f8a | ||
|
|
64a23962ee | ||
|
|
c33e7c7bfa | ||
|
|
5873525e48 | ||
|
|
4095056a01 | ||
|
|
eaa4328aac | ||
|
|
8755dc3d66 | ||
|
|
4a66683c9f | ||
|
|
a02b12da7e | ||
|
|
d0c243711b | ||
|
|
22fcee0e07 | ||
|
|
3e8b095178 | ||
|
|
f2af07217f | ||
|
|
8c1f08c8aa | ||
|
|
b2b754c2ce | ||
|
|
be2d349ade | ||
|
|
c5e2d673a9 | ||
|
|
060c3ce33c | ||
|
|
8af372585a | ||
|
|
c49a7dd910 | ||
|
|
c1363d8aca | ||
|
|
3bff98d530 | ||
|
|
be818baf28 | ||
|
|
fd107f2329 | ||
|
|
d3fb8ff904 | ||
|
|
c8de80f4ea | ||
|
|
c48ea0ae43 | ||
|
|
887c8efc5b | ||
|
|
d733c2f829 | ||
|
|
e83459d37d | ||
|
|
3cd10102e4 | ||
|
|
cde133c45d | ||
|
|
86ded90c6e | ||
|
|
c998539b40 | ||
|
|
e525aee015 | ||
|
|
e3e1535720 | ||
|
|
29e34c2a4b | ||
|
|
59c1887945 | ||
|
|
ff7574ffa3 | ||
|
|
87718fa82b | ||
|
|
c194844692 | ||
|
|
4cccb50228 | ||
|
|
77c308367f | ||
|
|
5ccfa64294 | ||
|
|
946e3b7114 | ||
|
|
cd95c479e9 | ||
|
|
ae9297bd18 | ||
|
|
fa42abd213 | ||
|
|
6510bfbcf7 | ||
|
|
4a742751d5 | ||
|
|
1b1b68a337 | ||
|
|
b985f34f7a | ||
|
|
eb8a6ed51a | ||
|
|
cff1686cb9 | ||
|
|
07a3b8520b | ||
|
|
afb2ca13a3 | ||
|
|
38681c6b39 | ||
|
|
790f3d44d9 | ||
|
|
a66c441043 | ||
|
|
3d17498c1d | ||
|
|
d1c80500c5 | ||
|
|
b1911f0efa | ||
|
|
cfc59b1713 | ||
|
|
8adc6f2008 | ||
|
|
92c7a834d7 | ||
|
|
6536af53fc | ||
|
|
af11465840 | ||
|
|
3b90b8d772 | ||
|
|
731115ffbd | ||
|
|
30ad04ba6d | ||
|
|
938badb2bb | ||
|
|
9be3b882b2 | ||
|
|
18a31d0316 | ||
|
|
8e9fce417a | ||
|
|
856e8b1cae | ||
|
|
b3102a097a | ||
|
|
ad23f3b068 | ||
|
|
ced326dc1f | ||
|
|
f071ba1d3b | ||
|
|
dbda75ec5f | ||
|
|
89a8546d00 | ||
|
|
82f80559b9 | ||
|
|
01b952c336 | ||
|
|
d4f2a5d6c5 | ||
|
|
fa56f0acb4 | ||
|
|
664687adb9 | ||
|
|
fcc4f9193a | ||
|
|
7d3ba0dec7 | ||
|
|
07496da9b5 | ||
|
|
2beb99a60e | ||
|
|
e314a548bd | ||
|
|
806c7024b3 | ||
|
|
f7cc199d37 | ||
|
|
590e74182f | ||
|
|
2f2bce5651 | ||
|
|
d044346a3c | ||
|
|
00475df2ef | ||
|
|
d415a8edd4 | ||
|
|
f56065c098 | ||
|
|
0905bb4ba6 | ||
|
|
c6e654db79 | ||
|
|
746bd51c48 | ||
|
|
dd20ca5cb9 | ||
|
|
b2504db4f7 | ||
|
|
81b1df8fe1 | ||
|
|
78dc35822e | ||
|
|
65f4f02ec8 | ||
|
|
a52a8dd876 | ||
|
|
918853a27d | ||
|
|
623adee1c4 | ||
|
|
1524330c10 | ||
|
|
ac934fc533 | ||
|
|
17684a1b81 | ||
|
|
1b6306c69e | ||
|
|
72dc624bb6 | ||
|
|
6524df9214 | ||
|
|
b97c8f2d60 | ||
|
|
559af99865 | ||
|
|
a11b5fc176 | ||
|
|
78c9603af4 | ||
|
|
b0fd5ac899 | ||
|
|
e02114ce49 | ||
|
|
4b6e1a40e5 | ||
|
|
b7d0832968 | ||
|
|
6d27d85297 | ||
|
|
52c965a188 | ||
|
|
5200cd16f5 | ||
|
|
973bc8831c | ||
|
|
426bf407e0 | ||
|
|
81860dc0a4 | ||
|
|
526db1d944 | ||
|
|
0490fbc666 | ||
|
|
92fbce57a3 | ||
|
|
8413454b50 | ||
|
|
9d700bc60c | ||
|
|
2e2eb9bdb5 | ||
|
|
0ca037c65e | ||
|
|
5a77974f7d | ||
|
|
a27deecfef | ||
|
|
77a707d629 | ||
|
|
333a163ac2 | ||
|
|
9fcc995b9d | ||
|
|
bd8514e80b | ||
|
|
2de2900e59 | ||
|
|
d3621ae961 | ||
|
|
6935add67e | ||
|
|
e41c0f4107 | ||
|
|
a2122a89f6 | ||
|
|
d3b4e624a1 | ||
|
|
6840fc6bb6 | ||
|
|
84ec085d47 | ||
|
|
cbabd91c21 | ||
|
|
0bbb0e9b57 | ||
|
|
54296c9628 | ||
|
|
42bfd060a0 | ||
|
|
da44a7ac56 | ||
|
|
497b962f73 | ||
|
|
6dffbb2571 | ||
|
|
4a6afd469f | ||
|
|
46b7c9dc57 | ||
|
|
db4449455f | ||
|
|
a5290b0d0f | ||
|
|
30f55a5016 | ||
|
|
e435591259 | ||
|
|
f0be8f1264 | ||
|
|
b9e1a24898 | ||
|
|
fc2c8d68b7 | ||
|
|
538861143f | ||
|
|
a9ff52f18b | ||
|
|
6cfea9df08 | ||
|
|
ba06967cce | ||
|
|
586e5be1bb | ||
|
|
a566964ac8 | ||
|
|
71e266f77e | ||
|
|
18d0066f8f | ||
|
|
1589bc5f25 | ||
|
|
fd5543b78e | ||
|
|
635e61bdeb | ||
|
|
dc5ab50a43 | ||
|
|
b69bb282a5 | ||
|
|
a61dfeeff3 | ||
|
|
99f02b59af | ||
|
|
0e3021baf4 | ||
|
|
d89b840be9 | ||
|
|
5932e8649d | ||
|
|
e3460573d0 | ||
|
|
c407a608c3 | ||
|
|
e7f3f997fe | ||
|
|
009cfe67c4 | ||
|
|
f035981a66 | ||
|
|
629d6dc568 | ||
|
|
ee27bfdbb7 | ||
|
|
d95b063618 | ||
|
|
032e358970 | ||
|
|
2ffe47e32b | ||
|
|
3560379ff7 | ||
|
|
f3bcc15c5c | ||
|
|
b089e8615b | ||
|
|
49895a84fd | ||
|
|
b24f982a90 | ||
|
|
c2fd90a4d4 | ||
|
|
9feff14932 | ||
|
|
f472959846 | ||
|
|
ab5adea360 | ||
|
|
4cf26a9e1b | ||
|
|
c94ccaf932 | ||
|
|
2479711370 | ||
|
|
f5126ab3a1 | ||
|
|
6a98ffb059 | ||
|
|
1d1484b210 | ||
|
|
2a3f58b5b8 | ||
|
|
c79342ef95 | ||
|
|
ce109f7adf | ||
|
|
a27cc907a5 | ||
|
|
86d088835a | ||
|
|
fdb9440d2e | ||
|
|
28ecb49eac | ||
|
|
5e2170f56a | ||
|
|
9130f64522 | ||
|
|
5080a17409 | ||
|
|
c7587e4d6c | ||
|
|
e3b2305d67 | ||
|
|
5b3cddc48e | ||
|
|
afd8431d4d | ||
|
|
de209ded31 | ||
|
|
1a68456795 | ||
|
|
c4eeabc60b | ||
|
|
a83d216ab6 | ||
|
|
9d3ab4aaee | ||
|
|
ea4900dcd6 | ||
|
|
1dbed0b2b4 | ||
|
|
9dd01e52e2 | ||
|
|
d78f39d005 | ||
|
|
ca1ad4cbb9 | ||
|
|
abb10856dc | ||
|
|
a27d6be28a | ||
|
|
921efb250c | ||
|
|
f899d79005 | ||
|
|
6b5051845b | ||
|
|
d17cb855bf | ||
|
|
bf77ddaabb | ||
|
|
0359a6a13d | ||
|
|
d9dce78a3b | ||
|
|
f16c41b00b | ||
|
|
38b2869bb8 | ||
|
|
fef66e5c5b | ||
|
|
cfd7807838 | ||
|
|
52b69d0e25 | ||
|
|
ac7d092ef0 | ||
|
|
d8d3637389 | ||
|
|
f12969a4d8 | ||
|
|
a000007543 | ||
|
|
c08c6c2185 | ||
|
|
61d8644320 | ||
|
|
f01e13ca72 | ||
|
|
b93eb67f4b | ||
|
|
8f2ff2a648 | ||
|
|
b041ff1e62 | ||
|
|
7ae89c28f0 | ||
|
|
ad231da3b8 | ||
|
|
1e0a91fe81 | ||
|
|
73a4e9481d | ||
|
|
6f65b002c8 | ||
|
|
b371c0c9dd | ||
|
|
85497ae58d | ||
|
|
58b3f3aa9a | ||
|
|
128ee627e1 | ||
|
|
0f8896e6fa | ||
|
|
5291acd597 | ||
|
|
833696aa0a | ||
|
|
f11e9585b8 | ||
|
|
bd0bcc9f57 | ||
|
|
7512b5e548 | ||
|
|
c096aeefe0 | ||
|
|
6aaa74e283 | ||
|
|
0f0b9552fb | ||
|
|
6a1970e978 | ||
|
|
4fef0a23a1 | ||
|
|
99ed392267 | ||
|
|
c4955c05a3 | ||
|
|
30020880d9 | ||
|
|
784ff76cef | ||
|
|
fc2cf22038 | ||
|
|
92a5df7753 | ||
|
|
df1000839b | ||
|
|
bade0092d1 | ||
|
|
dde6466660 | ||
|
|
8ef1bded27 | ||
|
|
82198afee9 | ||
|
|
2cff3ad9f8 | ||
|
|
98f212667e | ||
|
|
6bc2784a4d | ||
|
|
55192da899 | ||
|
|
6d5cb2522b | ||
|
|
50206ec2ad | ||
|
|
869f9e0a2a | ||
|
|
04b81eecba | ||
|
|
8d0c5672f0 | ||
|
|
0cc04e6e1b | ||
|
|
a86dc8cbac | ||
|
|
fb9f459d71 | ||
|
|
6b387602d6 | ||
|
|
7d33391e29 | ||
|
|
13475937e4 | ||
|
|
b9a67d1458 | ||
|
|
031c500ff4 | ||
|
|
67fc7abe6d | ||
|
|
3d5a852b1d | ||
|
|
c35a717140 | ||
|
|
c462e42dfd | ||
|
|
ad4e425676 | ||
|
|
f979c19841 | ||
|
|
22197ca9c5 | ||
|
|
c37ed5cbb3 | ||
|
|
e17edb3240 | ||
|
|
dbf45bd4a6 | ||
|
|
190436e58d | ||
|
|
ef07d002d7 | ||
|
|
763575246d | ||
|
|
0bb1406bfb | ||
|
|
9e15993083 | ||
|
|
288f9d53e3 | ||
|
|
e15813837f | ||
|
|
a4003c0883 | ||
|
|
ec89c5ae51 | ||
|
|
dff5e27c0d | ||
|
|
2396541930 | ||
|
|
100338f436 | ||
|
|
d1326cf549 | ||
|
|
713279c53d | ||
|
|
f58037b0a5 | ||
|
|
07d89b09bb | ||
|
|
3bcf4e69b9 | ||
|
|
35902d0c2a | ||
|
|
073880269e | ||
|
|
2d1acf1abc | ||
|
|
9f3f2accc8 | ||
|
|
0cb4ceeb82 | ||
|
|
39355cfef7 | ||
|
|
d910cce949 | ||
|
|
50d476ebca | ||
|
|
80d52d7a24 | ||
|
|
0ad1aaa388 | ||
|
|
6eeb43e5c2 | ||
|
|
f405880ca3 | ||
|
|
ac13a8445b | ||
|
|
79004f1ede | ||
|
|
88a8cc0c9e | ||
|
|
9d3216d3f3 | ||
|
|
4bd956b50f | ||
|
|
9ea6574841 | ||
|
|
aa033afeee | ||
|
|
90a7553b5b | ||
|
|
f621df6ce6 | ||
|
|
afc0cdfe74 | ||
|
|
4f35e8120f | ||
|
|
63b904881b | ||
|
|
d8e258410e | ||
|
|
50155f8c9f | ||
|
|
4f6d0ff71e | ||
|
|
06d55b8e1d | ||
|
|
b536650170 | ||
|
|
e193854f95 | ||
|
|
38606681e7 | ||
|
|
5413a207f0 | ||
|
|
70d8a9c7a6 | ||
|
|
793b7c2e6b | ||
|
|
41e7542220 | ||
|
|
97e32c995c | ||
|
|
390c3e4d5c | ||
|
|
cef292c042 | ||
|
|
8e0ea84c33 | ||
|
|
2f0a163656 | ||
|
|
3175686514 | ||
|
|
56d18ef597 | ||
|
|
83459e47e7 | ||
|
|
8bd3707031 | ||
|
|
eb51dfc4d5 | ||
|
|
afa08cbd06 | ||
|
|
a8c2322588 | ||
|
|
8fee8e963d | ||
|
|
075eb9076b | ||
|
|
bda6f1e06f | ||
|
|
76e9f646b1 | ||
|
|
9302f2b2fb | ||
|
|
82edfd0e91 | ||
|
|
47418e0f87 | ||
|
|
98d54ac067 | ||
|
|
b80245d746 | ||
|
|
5ec76a5bc3 | ||
|
|
bb6ec17708 | ||
|
|
e203b4dd4e | ||
|
|
b1c9082c94 | ||
|
|
6abe14f852 | ||
|
|
65da266daa | ||
|
|
334668d904 | ||
|
|
f827e3c0b7 | ||
|
|
7f43bc55e0 | ||
|
|
3e6e61dbe7 | ||
|
|
1aebdb231f | ||
|
|
a3f67fecb2 | ||
|
|
191dbee5c8 | ||
|
|
29e456d51c | ||
|
|
14b3a913ac | ||
|
|
911c1ec513 | ||
|
|
90e3532d0c | ||
|
|
bd27fb07d4 | ||
|
|
c055143ba9 | ||
|
|
14542f4c07 | ||
|
|
71e745e96b | ||
|
|
8dd5eb402e | ||
|
|
84485fb7bb | ||
|
|
eddae08fdf | ||
|
|
a612c8a5e2 | ||
|
|
3b23d1d6d6 | ||
|
|
9fffa2c7e1 | ||
|
|
85e8cb7ffb | ||
|
|
adc929c0f1 | ||
|
|
6f9aa3a900 | ||
|
|
645fa5255d | ||
|
|
c0b5a0446b | ||
|
|
90c572b513 | ||
|
|
1ddf4af355 | ||
|
|
7313f5c714 | ||
|
|
2269bee87f | ||
|
|
3ab7149e25 | ||
|
|
1a3dc51993 | ||
|
|
0b890e1ccd | ||
|
|
842934f40c | ||
|
|
1b228e8481 | ||
|
|
426b6d9baf | ||
|
|
21c2976870 | ||
|
|
2999ea9554 | ||
|
|
5049242f3b | ||
|
|
7e6cd582ca | ||
|
|
668a8ae21b | ||
|
|
6e498f1a21 | ||
|
|
4506425966 | ||
|
|
56410e3158 | ||
|
|
9bc19046d1 | ||
|
|
b0991bf661 | ||
|
|
e0f41c6475 | ||
|
|
23836ee146 | ||
|
|
5320a47a14 | ||
|
|
28af2439d9 | ||
|
|
b06491586c | ||
|
|
2b6e2defe5 | ||
|
|
96549e6b8f | ||
|
|
dc98505f2f | ||
|
|
6d843aeeb2 | ||
|
|
6c73fbb422 | ||
|
|
d2f9015c12 | ||
|
|
bb1dd85dcc | ||
|
|
0de289921c | ||
|
|
66df4b4958 | ||
|
|
72184bc814 | ||
|
|
b79f23d3d0 | ||
|
|
a0ffeb5464 | ||
|
|
7bf0fca858 | ||
|
|
3aed5fa544 | ||
|
|
e1aa7573f2 | ||
|
|
e13fdb965f | ||
|
|
a5b23bda66 | ||
|
|
19c3c4c2e0 | ||
|
|
2d2042cc2b | ||
|
|
d3e6ccc625 | ||
|
|
204e7201d2 | ||
|
|
d2e4c52b9f | ||
|
|
da7fef941c | ||
|
|
b5dd96590a | ||
|
|
7ed5faff26 | ||
|
|
88f42cf1f4 | ||
|
|
63d2ce5191 | ||
|
|
33a6724e33 | ||
|
|
fadc61e8ca | ||
|
|
ed74083956 | ||
|
|
e94c3ba27a | ||
|
|
42a3344eba | ||
|
|
f6439ffa2c | ||
|
|
29f7a38ee0 | ||
|
|
c8b50c1ffb | ||
|
|
f980c5c88f | ||
|
|
bc998a14a5 | ||
|
|
2dbf2a3ef8 | ||
|
|
a829b5c51b | ||
|
|
74a128c686 | ||
|
|
47a8b6d10d | ||
|
|
7e0f72cde4 | ||
|
|
b3cc2f7e0c | ||
|
|
5bc0fe9e16 | ||
|
|
342adac637 | ||
|
|
412c80dd6c | ||
|
|
08bbc3dfd8 | ||
|
|
8c3ede4b6f | ||
|
|
ef63a84a49 | ||
|
|
e15f6c7327 | ||
|
|
f645c0a420 | ||
|
|
9f366afdda | ||
|
|
4be99db15b | ||
|
|
21193cebe2 | ||
|
|
bd3a65252d | ||
|
|
840a2fabb8 | ||
|
|
6d486fb2e7 | ||
|
|
b640456af1 | ||
|
|
55c0ab5b2b | ||
|
|
626e83dc28 | ||
|
|
924b639215 | ||
|
|
2c06e82557 | ||
|
|
9eced72c03 | ||
|
|
4870a41d27 | ||
|
|
868a0584ba | ||
|
|
b2d1c5aa87 | ||
|
|
9e1a35ef3d | ||
|
|
188c2ac8e5 | ||
|
|
17d3c8764e | ||
|
|
c194962019 | ||
|
|
05a693fcb7 | ||
|
|
6d2d5e923e | ||
|
|
664ba44679 | ||
|
|
a19dc80cfb | ||
|
|
8dd825b413 | ||
|
|
3e9df22546 | ||
|
|
04ad3303dc | ||
|
|
f1cb136a62 | ||
|
|
062b8c0eb1 | ||
|
|
37570dc25e | ||
|
|
63662e37ca | ||
|
|
fc537e41cb | ||
|
|
bf36c648f2 | ||
|
|
84576e3258 | ||
|
|
046ec98a0b | ||
|
|
0a860c6fb0 | ||
|
|
912432179a | ||
|
|
4c705b6089 | ||
|
|
54f7111662 | ||
|
|
c83bb94e0d | ||
|
|
979c9323dc | ||
|
|
eefd1f78a2 | ||
|
|
6eb9c11e7b | ||
|
|
93d6f1d58a | ||
|
|
c111f6f93d | ||
|
|
2fc1bf2615 | ||
|
|
500982b7b3 | ||
|
|
46720f0bbc | ||
|
|
e8feacf1db | ||
|
|
acd13f99f1 | ||
|
|
d879135085 | ||
|
|
a78914942c | ||
|
|
8fe38c7e95 | ||
|
|
68a9e6cb1f | ||
|
|
40efd3a9c1 | ||
|
|
35f7a0b594 | ||
|
|
2bc1c28c44 | ||
|
|
f9fd5bfb86 | ||
|
|
829e9f8a91 | ||
|
|
e5b6c7bc33 | ||
|
|
9123a1d5ce | ||
|
|
5e12394aac | ||
|
|
38bcc2b2f8 | ||
|
|
a230391a37 | ||
|
|
0c515c5117 | ||
|
|
57a5b5f58e | ||
|
|
64de56e55e | ||
|
|
1e69e26924 | ||
|
|
efb89f2113 | ||
|
|
ebe83b95ad |
+4107
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
This is the MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
Copyright (c) 2005, 2006, 2007, 2008 Michael Bayer and contributors. SQLAlchemy is a trademark of Michael
|
||||
Bayer.
|
||||
Copyright (c) 2005, 2006, 2007, 2008, 2009, 2010 Michael Bayer and contributors.
|
||||
SQLAlchemy is a trademark of Michael Bayer.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
software and associated documentation files (the "Software"), to deal in the Software
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
include doc/*.html
|
||||
include doc/*.css
|
||||
recursive-include doc *.html *.css *.txt *.js *.jpg
|
||||
prune doc/build/output
|
||||
|
||||
@@ -1,17 +1,49 @@
|
||||
SQLAlchemy is licensed under an MIT-style license (see LICENSE).
|
||||
Other incorporated projects may be licensed under different licenses.
|
||||
All licenses allow for non-commercial and commercial use.
|
||||
SQLAlchemy
|
||||
++++++++++
|
||||
|
||||
To install:
|
||||
The Python SQL Toolkit and Object Relational Mapper
|
||||
|
||||
python setup.py install
|
||||
Requirements
|
||||
------------
|
||||
|
||||
SVN checkouts also include setup.cfg file allowing setuptools to create
|
||||
an svn-tagged build.
|
||||
SQLAlchemy requires Python 2.4 or higher. One or more DB-API implementations
|
||||
are also required for database access. See docs/intro.html for more
|
||||
information on supported DB-API implementations.
|
||||
|
||||
Documentation is available in HTML format in the ./doc/ directory.
|
||||
Installing
|
||||
----------
|
||||
|
||||
Information running unit tests is in README.unittests.
|
||||
To install::
|
||||
|
||||
good luck !
|
||||
|
||||
python setup.py install
|
||||
|
||||
To use without installation, include the ``lib`` directory in your Python
|
||||
path.
|
||||
|
||||
Package Contents
|
||||
----------------
|
||||
|
||||
doc/
|
||||
HTML documentation, including tutorials and API reference.
|
||||
|
||||
examples/
|
||||
Fully commented and executable implementations for a variety of tasks.
|
||||
|
||||
lib/
|
||||
SQLAlchemy.
|
||||
|
||||
test/
|
||||
Unit tests for SQLAlchemy. See ``README.unittests`` for more
|
||||
information.
|
||||
|
||||
Help
|
||||
----
|
||||
|
||||
Mailing lists, wiki, and more are available on-line at
|
||||
http://www.sqlalchemy.org.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
SQLAlchemy is distributed under the `MIT license
|
||||
<http://www.opensource.org/licenses/mit-license.php>`_.
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
=================
|
||||
PYTHON 3 SUPPORT
|
||||
=================
|
||||
|
||||
Current Python 3k support in SQLAlchemy is provided by a customized
|
||||
2to3 script which wraps Python's 2to3 tool.
|
||||
|
||||
This document will refer to the Python 2.6 interpreter binary as
|
||||
"python26" and the Python 3.xx interpreter binary as "python3".
|
||||
|
||||
To build the Python 3K version, use the Python 2.6 interpreter to
|
||||
run the 2to3 script on the lib/ directory, and optionally the test/
|
||||
directory. The -w flag indicates that the new files should be
|
||||
written.
|
||||
|
||||
python26 sa2to3.py ./lib/ ./test/ -w
|
||||
|
||||
You now have a Python 3 version of SQLAlchemy in lib/.
|
||||
|
||||
Current 3k Issues
|
||||
-----------------
|
||||
|
||||
Current bugs and tickets related to Py3k are on the Py3k milestone in trac:
|
||||
|
||||
http://www.sqlalchemy.org/trac/query?status=new&status=assigned&status=reopened&milestone=py3k
|
||||
|
||||
Running Tests
|
||||
-------------
|
||||
|
||||
The unit test runner, described in README.unittests, is built on
|
||||
Nose, and uses a plugin that is ordinarily installed using setuptools
|
||||
entry points. At the time of this writing setuptools isn't available
|
||||
for Python 3 although the "Distribute" project does seem to provide support.
|
||||
Additionally, Nose itself is only available in an old version for Python 3,
|
||||
which is available at http://bitbucket.org/jpellerin/nose3/ .
|
||||
|
||||
To run the unit tests using the old version of nose and without the usage of
|
||||
setuptools, use the "sqla_nose.py" script:
|
||||
|
||||
python3 sqla_nose.py
|
||||
|
||||
When running with Python 3, lots of debug output is dumped to the console.
|
||||
This is due to hacking around the old version of Nose to support the
|
||||
SQLAlchemy test plugin without setuptools (details at
|
||||
http://groups.google.com/group/nose-dev/browse_thread/thread/c6a25531baaa2531).
|
||||
+191
-70
@@ -1,101 +1,222 @@
|
||||
=====================
|
||||
SQLALCHEMY UNIT TESTS
|
||||
----------------------
|
||||
=====================
|
||||
|
||||
SQLAlchemy unit tests by default run using Python's built-in sqlite3
|
||||
module. If running on Python 2.4, pysqlite must be installed.
|
||||
|
||||
As of 0.5.5, unit tests are run using nose. Documentation and
|
||||
downloads for nose are available at:
|
||||
|
||||
http://somethingaboutorange.com/mrl/projects/nose/0.11.1/index.html
|
||||
|
||||
SQLAlchemy implements a nose plugin that must be present when tests are run.
|
||||
This plugin is available when SQLAlchemy is installed via setuptools.
|
||||
|
||||
INSTANT TEST RUNNER
|
||||
-------------------
|
||||
|
||||
A plain vanilla run of all tests using sqlite can be run via setup.py:
|
||||
|
||||
$ python setup.py test
|
||||
|
||||
Setuptools will take care of the rest ! To run nose directly and have
|
||||
its full set of options available, read on...
|
||||
|
||||
SETUP
|
||||
-----
|
||||
To run unit tests (assuming unix-style commandline, adjust as needed for windows):
|
||||
|
||||
Python 2.4 or greater is required since the unit tests use decorators.
|
||||
All that's required is for SQLAlchemy to be installed via setuptools.
|
||||
For example, to create a local install in a source distribution directory:
|
||||
|
||||
cd into the SQLAlchemy distribution directory.
|
||||
$ export PYTHONPATH=.
|
||||
$ python setup.py develop -d .
|
||||
|
||||
Set up the PYTHONPATH. In bash:
|
||||
|
||||
export PYTHONPATH=./test/
|
||||
|
||||
On windows:
|
||||
|
||||
set PYTHONPATH=test\
|
||||
|
||||
The unittest framework will automatically prepend the lib directory to
|
||||
sys.path. This forces the local version of SQLAlchemy to be used,
|
||||
bypassing any setuptools-installed installations (setuptools places
|
||||
.egg files ahead of plain directories, even if on PYTHONPATH,
|
||||
unfortunately).
|
||||
The above will create a setuptools "development" distribution in the local
|
||||
path, which allows the Nose plugin to be available when nosetests is run.
|
||||
The plugin is enabled using the "with-sqlalchemy=True" configuration
|
||||
in setup.cfg.
|
||||
|
||||
RUNNING ALL TESTS
|
||||
-----------------
|
||||
To run all tests:
|
||||
|
||||
python test/alltests.py
|
||||
$ nosetests
|
||||
|
||||
COMMAND LINE OPTIONS
|
||||
--------------------
|
||||
Help is available via:
|
||||
Assuming all tests pass, this is a very unexciting output. To make it more
|
||||
intersesting:
|
||||
|
||||
python test/alltests.py --help
|
||||
|
||||
usage: alltests.py [options] [tests...]
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--dburi=DBURI database uri (overrides --db)
|
||||
--db=DB prefab database uri (sqlite, sqlite_file, postgres,
|
||||
mysql, oracle, oracle8, mssql)
|
||||
--mockpool use mock pool
|
||||
--verbose enable stdout echoing/printing
|
||||
--log-info=LOG_INFO turn on info logging for <LOG> (multiple OK)
|
||||
--log-debug=LOG_DEBUG
|
||||
turn on debug logging for <LOG> (multiple OK)
|
||||
--quiet suppress unittest output
|
||||
--nothreadlocal dont use thread-local mod
|
||||
--enginestrategy=ENGINESTRATEGY
|
||||
engine strategy (plain or threadlocal, defaults to SA
|
||||
default)
|
||||
--coverage Dump a full coverage report after running
|
||||
|
||||
NON-SQLITE DATABASES
|
||||
--------------------
|
||||
The prefab database connections expect to log in to localhost on the
|
||||
default port as user "scott", password "tiger", database "test" (where
|
||||
applicable). E.g. for postgresql the this translates to
|
||||
"postgres://scott:tiger@127.0.0.1:5432/test".
|
||||
$ nosetests -v
|
||||
|
||||
RUNNING INDIVIDUAL TESTS
|
||||
-------------------------
|
||||
Any unittest module can be run directly from the module file (same commandline options):
|
||||
Any directory of test modules can be run at once by specifying the directory
|
||||
path:
|
||||
|
||||
python test/orm/mapper.py
|
||||
$ nosetest test/dialect
|
||||
|
||||
Additionally, to run a speciic test within the module, specify it as ClassName.methodname:
|
||||
Any test module can be run directly by specifying its module name:
|
||||
|
||||
python test/orm/mapper.py MapperTest.testget
|
||||
$ nosetests test.orm.test_mapper
|
||||
|
||||
To run a specific test within the module, specify it as module:ClassName.methodname:
|
||||
|
||||
$ nosetests test.orm.test_mapper:MapperTest.test_utils
|
||||
|
||||
|
||||
COMMAND LINE OPTIONS
|
||||
--------------------
|
||||
Help is available via --help:
|
||||
|
||||
$ nosetests --help
|
||||
|
||||
The --help screen is a combination of common nose options and options which
|
||||
the SQLAlchemy nose plugin adds. The most commonly SQLAlchemy-specific
|
||||
options used are '--db' and '--dburi'.
|
||||
|
||||
|
||||
DATABASE TARGETS
|
||||
----------------
|
||||
|
||||
Tests will target an in-memory SQLite database by default. To test against
|
||||
another database, use the --dburi option with any standard SQLAlchemy URL:
|
||||
|
||||
--dburi=postgresql://user:password@localhost/test
|
||||
|
||||
Use an empty database and a database user with general DBA privileges.
|
||||
The test suite will be creating and dropping many tables and other DDL, and
|
||||
preexisting tables will interfere with the tests.
|
||||
|
||||
Several tests require alternate schemas to be present. This requirement
|
||||
applies to all backends except SQLite and Firebird. These schemas are:
|
||||
|
||||
test_schema
|
||||
test_schema_2
|
||||
|
||||
Please refer to your vendor documentation for the proper syntax to create
|
||||
these schemas - the database user must have permission to create and drop
|
||||
tables within these schemas. Its perfectly fine to run the test suite
|
||||
without these schemas present, it only means that a handful of tests which
|
||||
expect them to be present will fail.
|
||||
|
||||
Additional steps specific to individual databases are as follows:
|
||||
|
||||
ORACLE: the test_schema and test_schema_2 schemas are created as
|
||||
users, as the "owner" in Oracle is considered like a "schema" in
|
||||
SQLAlchemy.
|
||||
|
||||
The primary database user needs to be able to create and drop tables,
|
||||
synonyms, and constraints in these schemas. Unfortunately, many hours of
|
||||
googling and experimentation cannot find a GRANT option that allows the
|
||||
primary user the "REFERENCES" role in a remote schema for tables not yet
|
||||
defined (REFERENCES is per-table) - the only thing that works is to put
|
||||
the user in the "DBA" role:
|
||||
|
||||
grant dba to scott;
|
||||
|
||||
Any ideas on what specific privileges within "DBA" allow an open-ended
|
||||
REFERENCES grant would be appreciated, or if in fact "DBA" has some kind
|
||||
of "magic" flag not accessible otherwise. So, running SQLA tests on oracle
|
||||
requires access to a completely open Oracle database - Oracle XE is
|
||||
obviously a terrific choice since its just a local engine. As always,
|
||||
leaving the schemas out means those few dozen tests will fail and is
|
||||
otherwise harmless.
|
||||
|
||||
|
||||
MSSQL: Tests that involve multiple connections require Snapshot Isolation
|
||||
ability implented on the test database in order to prevent deadlocks that
|
||||
will occur with record locking isolation. This feature is only available
|
||||
with MSSQL 2005 and greater. You must enable snapshot isolation at the
|
||||
database level and set the default cursor isolation with two SQL commands:
|
||||
|
||||
ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON
|
||||
|
||||
ALTER DATABASE MyDatabase SET READ_COMMITTED_SNAPSHOT ON
|
||||
|
||||
MSSQL+zxJDBC: Trying to run the unit tests on Windows against SQL Server
|
||||
requires using a test.cfg configuration file as the cmd.exe shell won't
|
||||
properly pass the URL arguments into the nose test runner.
|
||||
|
||||
If you'll be running the tests frequently, database aliases can save a lot of
|
||||
typing. The --dbs option lists the built-in aliases and their matching URLs:
|
||||
|
||||
$ nosetests --dbs
|
||||
Available --db options (use --dburi to override)
|
||||
mysql mysql://scott:tiger@127.0.0.1:3306/test
|
||||
oracle oracle://scott:tiger@127.0.0.1:1521
|
||||
postgresql postgresql://scott:tiger@127.0.0.1:5432/test
|
||||
[...]
|
||||
|
||||
To run tests against an aliased database:
|
||||
|
||||
$ nosetests --db=postgresql
|
||||
|
||||
To customize the URLs with your own users or hostnames, make a simple .ini
|
||||
file called `test.cfg` at the top level of the SQLAlchemy source distribution
|
||||
or a `.satest.cfg` in your home directory:
|
||||
|
||||
[db]
|
||||
postgresql=postgresql://myuser:mypass@localhost/mydb
|
||||
|
||||
Your custom entries will override the defaults and you'll see them reflected
|
||||
in the output of --dbs.
|
||||
|
||||
CONFIGURING LOGGING
|
||||
---------------------
|
||||
Logging is now available via Python's logging package. Any area of SQLAlchemy can be logged
|
||||
through the unittest interface, such as:
|
||||
-------------------
|
||||
SQLAlchemy logs its activity and debugging through Python's logging package.
|
||||
Any log target can be directed to the console with command line options, such
|
||||
as:
|
||||
|
||||
Log mapper configuration, connection pool checkouts, and SQL statement execution:
|
||||
$ nosetests test.orm.unitofwork --log-info=sqlalchemy.orm.mapper \
|
||||
--log-debug=sqlalchemy.pool --log-info=sqlalchemy.engine
|
||||
|
||||
This would log mapper configuration, connection pool checkouts, and SQL
|
||||
statement execution.
|
||||
|
||||
python test/orm/unitofwork.py --log-info=sqlalchemy.orm.mapper --log-debug=sqlalchemy.pool --log-info=sqlalchemy.engine
|
||||
|
||||
BUILT-IN COVERAGE REPORTING
|
||||
------------------------------
|
||||
Coverage is now integrated through the coverage.py module, included in the './test/' directory. Running the test suite with
|
||||
the --coverage switch will generate a local file ".coverage" containing coverage details, and a report will be printed
|
||||
to standard output with an overview of the coverage gathered from the last unittest run (the file is deleted between runs).
|
||||
Coverage is tracked using Nose's coverage plugin. See the nose
|
||||
documentation for details. Basic usage is:
|
||||
|
||||
After the suite has been run with --coverage, an annotated version of any source file can be generated
|
||||
marking statements that are executed with > and statements that are missed with !, by running the coverage.py
|
||||
utility with the "-a" (annotate) option, such as:
|
||||
$ nosetests test.sql.test_query --with-coverage
|
||||
|
||||
python ./test/coverage.py -a ./lib/sqlalchemy/sql.py
|
||||
BIG COVERAGE TIP !!! There is an issue where existing .pyc files may
|
||||
store the incorrect filepaths, which will break the coverage system. If
|
||||
coverage numbers are coming out as low/zero, try deleting all .pyc files.
|
||||
|
||||
TESTING NEW DIALECTS
|
||||
--------------------
|
||||
You can use the SQLAlchemy test suite to test any new database dialect in
|
||||
development. All possible database features will be exercised by default.
|
||||
Test decorators are provided that can exclude unsupported tests for a
|
||||
particular dialect. You'll see them all over the source, feel free to add
|
||||
your dialect to them or apply new decorations to existing tests as required.
|
||||
|
||||
It's fine to start out with very broad exclusions, e.g. "2-phase commit is not
|
||||
supported on this database" and later refine that as needed "2-phase commit is
|
||||
not available until server version 8".
|
||||
|
||||
To be considered for inclusion in the SQLAlchemy distribution, a dialect must
|
||||
be integrated with the standard test suite. Dialect-specific tests can be
|
||||
placed in the 'dialects/' directory. Comprehensive testing of
|
||||
database-specific column types and their proper reflection are a very good
|
||||
place to start.
|
||||
|
||||
When working through the tests, start with 'engine' and 'sql' tests. 'engine'
|
||||
performs a wide range of transaction tests that might deadlock on a brand-new
|
||||
dialect- try disabling those if you're having problems and revisit them later.
|
||||
|
||||
Once the 'sql' tests are passing, the 'orm' tests should pass as well, modulo
|
||||
any adjustments needed for SQL features the ORM uses that might not be
|
||||
available in your database. But if an 'orm' test requires changes to your
|
||||
dialect or the SQLAlchemy core to pass, there's a test missing in 'sql'! Any
|
||||
time you can spend boiling down the problem to it's essential sql roots and
|
||||
adding a 'sql' test will be much appreciated.
|
||||
|
||||
The test suite is very effective at illuminating bugs and inconsistencies in
|
||||
an underlying DB-API (or database!) implementation. Workarounds are almost
|
||||
always possible. If you hit a wall, join us on the mailing list or, better,
|
||||
IRC!
|
||||
|
||||
which will create a new annotated file ./lib/sqlalchemy/sql.py,cover . Pretty cool !
|
||||
|
||||
TIPS
|
||||
----
|
||||
When running the tests on postgres, postgres gets slower and slower each time you run the tests.
|
||||
This seems to be related to the constant creation/dropping of tables. Running a "VACUUM FULL"
|
||||
on the database will speed it up again.
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<link href="style.css" rel="stylesheet" type="text/css"></link>
|
||||
<link href="docs.css" rel="stylesheet" type="text/css"></link>
|
||||
<script src="scripts.js"></script>
|
||||
<title>SQLAlchemy Documentation</title>
|
||||
</head>
|
||||
<body>
|
||||
<h3>What is an Alpha API Feature?</h3>
|
||||
<p><b>Alpha API</b> indicates that the best way for a particular feature to be presented hasn't been firmly settled on as of yet, and the current way is being introduced on a trial basis. Its spirit is not as much a warning that "this API might change", its more an invitation to the users saying, "heres a new idea I had. I'm not sure if this is the best way to do it. Do you like it ? Should we do this differently? Or is it good the way it is ?". Alpha API features are always small in scope and are presented in releases so that the greatest number of users get some hands-on experience with it; large-scoped API or architectural changes will always be discussed on the mailing list/Wiki first.</p>
|
||||
|
||||
<p>Reasons why a feature might want to change include:
|
||||
<ul>
|
||||
<li>The API for the feature is too difficult to use for the typical task, and needs to be more "convenient"</li>
|
||||
<li>The feature only implements a subsection of what it really should be doing</li>
|
||||
<li>The feature's interface is inconsistent with that of other features which operate at a similar level</li>
|
||||
<li>The feature is confusing and is often misunderstood, and would be better replaced by a more manual feature that makes the task clearer</li>
|
||||
<li>The feature overlaps with another feature and effectively provides too many ways to do the same thing</li>
|
||||
<li>The feature made some assumptions about the total field of use cases which is not really true, and it breaks in other scenarios</li>
|
||||
</ul>
|
||||
|
||||
</p>
|
||||
<p>A good example of what was essentially an "alpha feature" is the <code>private=True</code> flag. This flag on a <code>relation()</code> indicates that child objects should be deleted along with the parent. After this flag experienced some usage by the SA userbase, some users remarked that a more generic and configurable way was Hibernates <code>cascade="all, delete-orphan"</code>, and also that the term <code>cascade</code> was clearer in purpose than the more ambiguous <code>private</code> keyword, which could be construed as a "private variable".</p>
|
||||
|
||||
<center><input type="button" value="close window" onclick="window.close()"></center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,16 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<link href="style.css" rel="stylesheet" type="text/css"></link>
|
||||
<link href="docs.css" rel="stylesheet" type="text/css"></link>
|
||||
<script src="scripts.js"></script>
|
||||
<title>SQLAlchemy Documentation</title>
|
||||
</head>
|
||||
<body>
|
||||
<h3>What is an Alpha Implementation Feature?</h3>
|
||||
<p><b>Alpha Implementation</b> indicates a feature where developer confidence in its functionality has not yet been firmly established. This typically includes brand new features for which adequate unit tests have not been completed, and/or features whose scope is broad enough that its not clear what additional unit tests might be needed.</p>
|
||||
|
||||
<p>Alpha implementation is not meant to discourage the usage of a feature, it is only meant to indicate that some difficulties in getting full functionality from the feature may occur, and to encourage the reporting of these difficulties either via the mailing list or through <a href="http://www.sqlalchemy.org/trac/newticket" target="_blank">submitting a ticket</a>.</p>
|
||||
|
||||
<center><input type="button" value="close window" onclick="window.close()"></center>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d output/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html latex site-mako
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dist-html same as html, but places files in /doc"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
|
||||
clean:
|
||||
-rm -rf output/*
|
||||
|
||||
html:
|
||||
mkdir -p output/html output/doctrees
|
||||
$(SPHINXBUILD) -b html -A mako_layout=html $(ALLSPHINXOPTS) output/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in output/html."
|
||||
|
||||
dist-html:
|
||||
$(SPHINXBUILD) -b html -A mako_layout=html $(ALLSPHINXOPTS) ..
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in ../."
|
||||
|
||||
site-mako:
|
||||
mkdir -p output/site output/doctrees
|
||||
$(SPHINXBUILD) -b html -A mako_layout=site $(ALLSPHINXOPTS) output/site
|
||||
@echo
|
||||
@echo "Build finished. The Mako pages are in output/site."
|
||||
|
||||
latex:
|
||||
mkdir -p output/latex output/doctrees
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) output/latex
|
||||
cp texinputs/* output/latex/
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in output/latex."
|
||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
"run these through (pdf)latex."
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest -d output/doctrees . .
|
||||
Vendored
-10
@@ -1,10 +0,0 @@
|
||||
Documentation exists in its original format as Markdown files in the ./content directory.
|
||||
|
||||
To generate documentation:
|
||||
|
||||
python genhtml.py
|
||||
|
||||
This generates the Markdown files into Myghty templates as an interim step and then into HTML. It also
|
||||
creates two pickled datafiles corresponding to the table of contents and all the generated docstrings
|
||||
for the SQLAlchemy sourcecode.
|
||||
|
||||
Vendored
+164
@@ -0,0 +1,164 @@
|
||||
from sphinx.application import TemplateBridge
|
||||
from sphinx.builders.html import StandaloneHTMLBuilder
|
||||
from sphinx.highlighting import PygmentsBridge
|
||||
from pygments import highlight
|
||||
from pygments.lexer import RegexLexer, bygroups, using
|
||||
from pygments.token import *
|
||||
from pygments.filter import Filter, apply_filters
|
||||
from pygments.lexers import PythonLexer, PythonConsoleLexer
|
||||
from pygments.formatters import HtmlFormatter, LatexFormatter
|
||||
import re
|
||||
from mako.lookup import TemplateLookup
|
||||
from mako.template import Template
|
||||
|
||||
class MakoBridge(TemplateBridge):
|
||||
def init(self, builder, *args, **kw):
|
||||
self.layout = builder.config.html_context.get('mako_layout', 'html')
|
||||
|
||||
self.lookup = TemplateLookup(directories=builder.config.templates_path,
|
||||
format_exceptions=True,
|
||||
imports=[
|
||||
"from builder import util"
|
||||
]
|
||||
)
|
||||
|
||||
def render(self, template, context):
|
||||
template = template.replace(".html", ".mako")
|
||||
context['prevtopic'] = context.pop('prev', None)
|
||||
context['nexttopic'] = context.pop('next', None)
|
||||
context['mako_layout'] = self.layout == 'html' and 'static_base.mako' or 'site_base.mako'
|
||||
return self.lookup.get_template(template).render_unicode(**context)
|
||||
|
||||
|
||||
def render_string(self, template, context):
|
||||
context['prevtopic'] = context.pop('prev', None)
|
||||
context['nexttopic'] = context.pop('next', None)
|
||||
context['mako_layout'] = self.layout == 'html' and 'static_base.mako' or 'site_base.mako'
|
||||
return Template(template, lookup=self.lookup,
|
||||
format_exceptions=True,
|
||||
imports=[
|
||||
"from builder import util"
|
||||
]
|
||||
).render_unicode(**context)
|
||||
|
||||
class StripDocTestFilter(Filter):
|
||||
def filter(self, lexer, stream):
|
||||
for ttype, value in stream:
|
||||
if ttype is Token.Comment and re.match(r'#\s*doctest:', value):
|
||||
continue
|
||||
yield ttype, value
|
||||
|
||||
class PyConWithSQLLexer(RegexLexer):
|
||||
name = 'PyCon+SQL'
|
||||
aliases = ['pycon+sql']
|
||||
|
||||
flags = re.IGNORECASE | re.DOTALL
|
||||
|
||||
tokens = {
|
||||
'root': [
|
||||
(r'{sql}', Token.Sql.Link, 'sqlpopup'),
|
||||
(r'{opensql}', Token.Sql.Open, 'opensqlpopup'),
|
||||
(r'.*?\n', using(PythonConsoleLexer))
|
||||
],
|
||||
'sqlpopup':[
|
||||
(
|
||||
r'(.*?\n)((?:PRAGMA|BEGIN|SELECT|INSERT|DELETE|ROLLBACK|COMMIT|ALTER|UPDATE|CREATE|DROP|PRAGMA|DESCRIBE).*?(?:{stop}\n?|$))',
|
||||
bygroups(using(PythonConsoleLexer), Token.Sql.Popup),
|
||||
"#pop"
|
||||
)
|
||||
],
|
||||
'opensqlpopup':[
|
||||
(
|
||||
r'.*?(?:{stop}\n*|$)',
|
||||
Token.Sql,
|
||||
"#pop"
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class PythonWithSQLLexer(RegexLexer):
|
||||
name = 'Python+SQL'
|
||||
aliases = ['pycon+sql']
|
||||
|
||||
flags = re.IGNORECASE | re.DOTALL
|
||||
|
||||
tokens = {
|
||||
'root': [
|
||||
(r'{sql}', Token.Sql.Link, 'sqlpopup'),
|
||||
(r'{opensql}', Token.Sql.Open, 'opensqlpopup'),
|
||||
(r'.*?\n', using(PythonLexer))
|
||||
],
|
||||
'sqlpopup':[
|
||||
(
|
||||
r'(.*?\n)((?:PRAGMA|BEGIN|SELECT|INSERT|DELETE|ROLLBACK|COMMIT|ALTER|UPDATE|CREATE|DROP|PRAGMA|DESCRIBE).*?(?:{stop}\n?|$))',
|
||||
bygroups(using(PythonLexer), Token.Sql.Popup),
|
||||
"#pop"
|
||||
)
|
||||
],
|
||||
'opensqlpopup':[
|
||||
(
|
||||
r'.*?(?:{stop}\n*|$)',
|
||||
Token.Sql,
|
||||
"#pop"
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def _strip_trailing_whitespace(iter_):
|
||||
buf = list(iter_)
|
||||
if buf:
|
||||
buf[-1] = (buf[-1][0], buf[-1][1].rstrip())
|
||||
for t, v in buf:
|
||||
yield t, v
|
||||
|
||||
class PopupSQLFormatter(HtmlFormatter):
|
||||
def _format_lines(self, tokensource):
|
||||
buf = []
|
||||
for ttype, value in apply_filters(tokensource, [StripDocTestFilter()]):
|
||||
if ttype in Token.Sql:
|
||||
for t, v in HtmlFormatter._format_lines(self, iter(buf)):
|
||||
yield t, v
|
||||
buf = []
|
||||
|
||||
if ttype is Token.Sql:
|
||||
yield 1, "<div class='show_sql'>%s</div>" % re.sub(r'(?:[{stop}|\n]*)$', '', value)
|
||||
elif ttype is Token.Sql.Link:
|
||||
yield 1, "<a href='#' class='sql_link'>sql</a>"
|
||||
elif ttype is Token.Sql.Popup:
|
||||
yield 1, "<div class='popup_sql'>%s</div>" % re.sub(r'(?:[{stop}|\n]*)$', '', value)
|
||||
else:
|
||||
buf.append((ttype, value))
|
||||
|
||||
for t, v in _strip_trailing_whitespace(HtmlFormatter._format_lines(self, iter(buf))):
|
||||
yield t, v
|
||||
|
||||
class PopupLatexFormatter(LatexFormatter):
|
||||
def _filter_tokens(self, tokensource):
|
||||
for ttype, value in apply_filters(tokensource, [StripDocTestFilter()]):
|
||||
if ttype in Token.Sql:
|
||||
if ttype is not Token.Sql.Link and ttype is not Token.Sql.Open:
|
||||
yield Token.Literal, re.sub(r'(?:[{stop}|\n]*)$', '', value)
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
yield ttype, value
|
||||
|
||||
def format(self, tokensource, outfile):
|
||||
LatexFormatter.format(self, self._filter_tokens(tokensource), outfile)
|
||||
|
||||
def autodoc_skip_member(app, what, name, obj, skip, options):
|
||||
if what == 'class' and skip and name == '__init__':
|
||||
return False
|
||||
else:
|
||||
return skip
|
||||
|
||||
def setup(app):
|
||||
app.add_lexer('pycon+sql', PyConWithSQLLexer())
|
||||
app.add_lexer('python+sql', PythonWithSQLLexer())
|
||||
app.connect('autodoc-skip-member', autodoc_skip_member)
|
||||
PygmentsBridge.html_formatter = PopupSQLFormatter
|
||||
PygmentsBridge.latex_formatter = PopupLatexFormatter
|
||||
|
||||
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
import re
|
||||
|
||||
def striptags(text):
|
||||
return re.compile(r'<[^>]*>').sub('', text)
|
||||
|
||||
def strip_toplevel_anchors(text):
|
||||
return re.compile(r'\.html#.*-toplevel').sub('.html', text)
|
||||
|
||||
Vendored
+195
@@ -0,0 +1,195 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Foo Bar documentation build configuration file, created by
|
||||
# sphinx-quickstart on Wed Nov 26 19:50:10 2008.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# The contents of this file are pickled, so don't put values in the namespace
|
||||
# that aren't pickleable (module imports are okay, they're removed automatically).
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If your extensions are in another directory, add it here. If the directory
|
||||
# is relative to the documentation root, use os.path.abspath to make it
|
||||
# absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('../../lib'))
|
||||
sys.path.insert(0, os.path.abspath('../../examples'))
|
||||
sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
# General configuration
|
||||
# ---------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'builder.builders']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
template_bridge = "builder.builders.MakoBridge"
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'SQLAlchemy'
|
||||
copyright = u'2007, 2008, 2009, 2010, the SQLAlchemy authors and contributors'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = sqlalchemy.__version__
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = sqlalchemy.__version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of documents that shouldn't be included in the build.
|
||||
unused_docs = ['output.txt']
|
||||
|
||||
# List of directories, relative to source directory, that shouldn't be searched
|
||||
# for source files.
|
||||
exclude_trees = ['build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
|
||||
# Options for HTML output
|
||||
# -----------------------
|
||||
|
||||
# The style sheet to use for HTML and HTML Help pages. A file of that name
|
||||
# must exist either in Sphinx' static/ path, or in one of the custom paths
|
||||
# given in html_static_path.
|
||||
html_style = 'default.css'
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
html_title = "%s %s Documentation" % (project, release)
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
html_last_updated_fmt = '%m/%d/%Y %H:%M:%S'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
html_use_modindex = False
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, the reST sources are included in the HTML build as _sources/<name>.
|
||||
#html_copy_source = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'FooBardoc'
|
||||
|
||||
#autoclass_content = 'both'
|
||||
|
||||
# Options for LaTeX output
|
||||
# ------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, document class [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'sqlalchemy_%s.tex' % release.replace('.', '_'), ur'SQLAlchemy Documentation',
|
||||
ur'Mike Bayer', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# sets TOC depth to 2.
|
||||
latex_preamble = '\setcounter{tocdepth}{3}'
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_use_modindex = True
|
||||
Vendored
-438
@@ -1,438 +0,0 @@
|
||||
Database Engines {@name=dbengine}
|
||||
============================
|
||||
|
||||
The **Engine** is the starting point for any SQLAlchemy application. It's "home base" for the actual database and its DBAPI, delivered to the SQLAlchemy application through a connection pool and a **Dialect**, which describes how to talk to a specific kind of database and DBAPI combination.
|
||||
|
||||
The general structure is this:
|
||||
|
||||
{diagram}
|
||||
+-----------+ __________
|
||||
/---| Pool |---\ (__________)
|
||||
+-------------+ / +-----------+ \ +--------+ | |
|
||||
connect() <--| Engine |---x x----| DBAPI |---| database |
|
||||
+-------------+ \ +-----------+ / +--------+ | |
|
||||
\---| Dialect |---/ |__________|
|
||||
+-----------+ (__________)
|
||||
|
||||
Where above, a [sqlalchemy.engine.Engine](rel:docstrings_sqlalchemy.engine_Engine) references both a [sqlalchemy.engine.Dialect](rel:docstrings_sqlalchemy.engine_Dialect) and [sqlalchemy.pool.Pool](rel:docstrings_sqlalchemy.pool_Pool), which together interpret the DBAPI's module functions as well as the behavior of the database.
|
||||
|
||||
Creating an engine is just a matter of issuing a single call, `create_engine()`:
|
||||
|
||||
{python}
|
||||
engine = create_engine('postgres://scott:tiger@localhost:5432/mydatabase')
|
||||
|
||||
The above engine invokes the `postgres` dialect and a connection pool which references `localhost:5432`.
|
||||
|
||||
The engine can be used directly to issue SQL to the database. The most generic way is to use connections, which you get via the `connect()` method:
|
||||
|
||||
{python}
|
||||
connection = engine.connect()
|
||||
result = connection.execute("select username from users")
|
||||
for row in result:
|
||||
print "username:", row['username']
|
||||
connection.close()
|
||||
|
||||
The connection is an instance of [sqlalchemy.engine.Connection](rel:docstrings_sqlalchemy.engine_Connection), which is a **proxy** object for an actual DBAPI connection. The returned result is an instance of [sqlalchemy.engine.ResultProxy](rel:docstrings_sqlalchemy.engine_ResultProxy), which acts very much like a DBAPI cursor.
|
||||
|
||||
When you say `engine.connect()`, a new `Connection` object is created, and a DBAPI connection is retrieved from the connection pool. Later, when you call `connection.close()`, the DBAPI connection is returned to the pool; nothing is actually "closed" from the perspective of the database.
|
||||
|
||||
To execute some SQL more quickly, you can skip the `Connection` part and just say:
|
||||
|
||||
{python}
|
||||
result = engine.execute("select username from users")
|
||||
for row in result:
|
||||
print "username:", row['username']
|
||||
result.close()
|
||||
|
||||
Where above, the `execute()` method on the `Engine` does the `connect()` part for you, and returns the `ResultProxy` directly. The actual `Connection` is *inside* the `ResultProxy`, waiting for you to finish reading the result. In this case, when you `close()` the `ResultProxy`, the underlying `Connection` is closed, which returns the DBAPI connection to the pool.
|
||||
|
||||
To summarize the above two examples, when you use a `Connection` object, its known as **explicit execution**. When you don't see the `Connection` object, but you still use the `execute()` method on the `Engine`, its called **explicit, connectionless execution**. A third variant of execution also exists called **implicit execution**; this will be described later.
|
||||
|
||||
The `Engine` and `Connection` can do a lot more than what we illustrated above; SQL strings are only its most rudimental function. Later chapters will describe how "constructed SQL" expressions can be used with engines; in many cases, you don't have to deal with the `Engine` at all after it's created. The Object Relational Mapper (ORM), an optional feature of SQLAlchemy, also uses the `Engine` in order to get at connections; that's also a case where you can often create the engine once, and then forget about it.
|
||||
|
||||
### Supported Databases {@name=supported}
|
||||
|
||||
Recall that the `Dialect` is used to describe how to talk to a specific kind of database. Dialects are included with SQLAlchemy for SQLite, Postgres, MySQL, MS-SQL, Firebird, Informix, and Oracle; these can each be seen as a Python module present in the `sqlalchemy.databases` package. Each dialect requires the appropriate DBAPI drivers to be installed separately.
|
||||
|
||||
Downloads for each DBAPI at the time of this writing are as follows:
|
||||
|
||||
* Postgres: [psycopg2](http://www.initd.org/tracker/psycopg/wiki/PsycopgTwo)
|
||||
* SQLite: [pysqlite](http://initd.org/tracker/pysqlite)
|
||||
* MySQL: [MySQLDB](http://sourceforge.net/projects/mysql-python)
|
||||
* Oracle: [cx_Oracle](http://www.cxtools.net/default.aspx?nav=home)
|
||||
* MS-SQL: [pyodbc](http://pyodbc.sourceforge.net/) (recommended) [adodbapi](http://adodbapi.sourceforge.net/) [pymssql](http://pymssql.sourceforge.net/)
|
||||
* Firebird: [kinterbasdb](http://kinterbasdb.sourceforge.net/)
|
||||
* Informix: [informixdb](http://informixdb.sourceforge.net/)
|
||||
|
||||
The SQLAlchemy Wiki contains a page of database notes, describing whatever quirks and behaviors have been observed. Its a good place to check for issues with specific databases. [Database Notes](http://www.sqlalchemy.org/trac/wiki/DatabaseNotes)
|
||||
|
||||
### create_engine() URL Arguments {@name=establishing}
|
||||
|
||||
SQLAlchemy indicates the source of an Engine strictly via [RFC-1738](http://rfc.net/rfc1738.html) style URLs, combined with optional keyword arguments to specify options for the Engine. The form of the URL is:
|
||||
|
||||
driver://username:password@host:port/database
|
||||
|
||||
Available drivernames are `sqlite`, `mysql`, `postgres`, `oracle`, `mssql`, and `firebird`. For sqlite, the database name is the filename to connect to, or the special name ":memory:" which indicates an in-memory database. The URL is typically sent as a string to the `create_engine()` function:
|
||||
|
||||
{python}
|
||||
# postgres
|
||||
pg_db = create_engine('postgres://scott:tiger@localhost:5432/mydatabase')
|
||||
|
||||
# sqlite (note the four slashes for an absolute path)
|
||||
sqlite_db = create_engine('sqlite:////absolute/path/to/database.txt')
|
||||
sqlite_db = create_engine('sqlite:///relative/path/to/database.txt')
|
||||
sqlite_db = create_engine('sqlite://') # in-memory database
|
||||
sqlite_db = create_engine('sqlite://:memory:') # the same
|
||||
|
||||
# mysql
|
||||
mysql_db = create_engine('mysql://localhost/foo')
|
||||
|
||||
# oracle via TNS name
|
||||
oracle_db = create_engine('oracle://scott:tiger@dsn')
|
||||
|
||||
# oracle will feed host/port/SID into cx_oracle.makedsn
|
||||
oracle_db = create_engine('oracle://scott:tiger@127.0.0.1:1521/sidname')
|
||||
|
||||
The `Engine` will ask the connection pool for a connection when the `connect()` or `execute()` methods are called. The default connection pool, `QueuePool`, as well as the default connection pool used with SQLite, `SingletonThreadPool`, will open connections to the database on an as-needed basis. As concurrent statements are executed, `QueuePool` will grow its pool of connections to a default size of five, and will allow a default "overflow" of ten. Since the `Engine` is essentially "home base" for the connection pool, it follows that you should keep a single `Engine` per database established within an application, rather than creating a new one for each connection.
|
||||
|
||||
#### Custom DBAPI connect() arguments
|
||||
|
||||
Custom arguments used when issuing the `connect()` call to the underlying DBAPI may be issued in three distinct ways. String-based arguments can be passed directly from the URL string as query arguments:
|
||||
|
||||
{python}
|
||||
db = create_engine('postgres://scott:tiger@localhost/test?argument1=foo&argument2=bar')
|
||||
|
||||
If SQLAlchemy's database connector is aware of a particular query argument, it may convert its type from string to its proper type.
|
||||
|
||||
`create_engine` also takes an argument `connect_args` which is an additional dictionary that will be passed to `connect()`. This can be used when arguments of a type other than string are required, and SQLAlchemy's database connector has no type conversion logic present for that parameter:
|
||||
|
||||
{python}
|
||||
db = create_engine('postgres://scott:tiger@localhost/test', connect_args = {'argument1':17, 'argument2':'bar'})
|
||||
|
||||
The most customizable connection method of all is to pass a `creator` argument, which specifies a callable that returns a DBAPI connection:
|
||||
|
||||
{python}
|
||||
def connect():
|
||||
return psycopg.connect(user='scott', host='localhost')
|
||||
|
||||
db = create_engine('postgres://', creator=connect)
|
||||
|
||||
### Database Engine Options {@name=options}
|
||||
|
||||
Keyword options can also be specified to `create_engine()`, following the string URL as follows:
|
||||
|
||||
{python}
|
||||
db = create_engine('postgres://...', encoding='latin1', echo=True)
|
||||
|
||||
A list of all standard options, as well as several that are used by particular database dialects, is as follows:
|
||||
|
||||
* **assert_unicode=False** - When set to `True` alongside convert_unicode=`True`, asserts that incoming string bind parameters are instances of `unicode`, otherwise raises an error. Only takes effect when `convert_unicode==True`. This flag is also available on the `String` type and its decsendants. New in 0.4.2.
|
||||
* **connect_args** - a dictionary of options which will be passed directly to the DBAPI's `connect()` method as additional keyword arguments.
|
||||
* **convert_unicode=False** - if set to True, all String/character based types will convert Unicode values to raw byte values going into the database, and all raw byte values to Python Unicode coming out in result sets. This is an engine-wide method to provide unicode conversion across the board. For unicode conversion on a column-by-column level, use the `Unicode` column type instead, described in [types](rel:types).
|
||||
* **creator** - a callable which returns a DBAPI connection. This creation function will be passed to the underlying connection pool and will be used to create all new database connections. Usage of this function causes connection parameters specified in the URL argument to be bypassed.
|
||||
* **echo=False** - if True, the Engine will log all statements as well as a repr() of their parameter lists to the engines logger, which defaults to sys.stdout. The `echo` attribute of `Engine` can be modified at any time to turn logging on and off. If set to the string `"debug"`, result rows will be printed to the standard output as well. This flag ultimately controls a Python logger; see [dbengine_logging](rel:dbengine_logging) at the end of this chapter for information on how to configure logging directly.
|
||||
* **echo_pool=False** - if True, the connection pool will log all checkouts/checkins to the logging stream, which defaults to sys.stdout. This flag ultimately controls a Python logger; see [dbengine_logging](rel:dbengine_logging) for information on how to configure logging directly.
|
||||
* **encoding='utf-8'** - the encoding to use for all Unicode translations, both by engine-wide unicode conversion as well as the `Unicode` type object.
|
||||
* **module=None** - used by database implementations which support multiple DBAPI modules, this is a reference to a DBAPI2 module to be used instead of the engine's default module. For Postgres, the default is psycopg2. For Oracle, its cx_Oracle.
|
||||
* **pool=None** - an already-constructed instance of `sqlalchemy.pool.Pool`, such as a `QueuePool` instance. If non-None, this pool will be used directly as the underlying connection pool for the engine, bypassing whatever connection parameters are present in the URL argument. For information on constructing connection pools manually, see [pooling](rel:pooling).
|
||||
* **poolclass=None** - a `sqlalchemy.pool.Pool` subclass, which will be used to create a connection pool instance using the connection parameters given in the URL. Note this differs from `pool` in that you don't actually instantiate the pool in this case, you just indicate what type of pool to be used.
|
||||
* **max_overflow=10** - the number of connections to allow in connection pool "overflow", that is connections that can be opened above and beyond the pool_size setting, which defaults to five. this is only used with `QueuePool`.
|
||||
* **pool_size=5** - the number of connections to keep open inside the connection pool. This used with `QueuePool` as well as `SingletonThreadPool`.
|
||||
* **pool_recycle=-1** - this setting causes the pool to recycle connections after the given number of seconds has passed. It defaults to -1, or no timeout. For example, setting to 3600 means connections will be recycled after one hour. Note that MySQL in particular will **disconnect automatically** if no activity is detected on a connection for eight hours (although this is configurable with the MySQLDB connection itself and the server configuration as well).
|
||||
* **pool_timeout=30** - number of seconds to wait before giving up on getting a connection from the pool. This is only used with `QueuePool`.
|
||||
* **strategy='plain'** - the Strategy argument is used to select alternate implementations of the underlying Engine object, which coordinates operations between dialects, compilers, connections, and so on. Currently, the only alternate strategy besides the default value of "plain" is the "threadlocal" strategy, which selects the usage of the `TLEngine` class that provides a modified connection scope for connectionless executions. Connectionless execution as well as further detail on this setting are described in [dbengine_implicit](rel:dbengine_implicit).
|
||||
* **threaded=True** - used by cx_Oracle; sets the `threaded` parameter of the connection indicating thread-safe usage. cx_Oracle docs indicate setting this flag to `False` will speed performance by 10-15%. While this defaults to `False` in cx_Oracle, SQLAlchemy defaults it to `True`, preferring stability over early optimization.
|
||||
* **use_ansi=True** - used only by Oracle; when False, the Oracle driver attempts to support a particular "quirk" of Oracle versions 8 and previous, that the LEFT OUTER JOIN SQL syntax is not supported, and the "Oracle join" syntax of using `column1(+)=column2` must be used in order to achieve a LEFT OUTER JOIN.
|
||||
* **use_oids=False** - used only by Postgres, will enable the column name "oid" as the object ID column, which is also used for the default sort order of tables. Postgres as of 8.1 has object IDs disabled by default.
|
||||
|
||||
### More On Connections {@name=connections}
|
||||
|
||||
Recall from the beginning of this section that the Engine provides a `connect()` method which returns a `Connection` object. `Connection` is a *proxy* object which maintains a reference to a DBAPI connection instance. The `close()` method on `Connection` does not actually close the DBAPI connection, but instead returns it to the connection pool referenced by the `Engine`. `Connection` will also automatically return its resources to the connection pool when the object is garbage collected, i.e. its `__del__()` method is called. When using the standard C implementation of Python, this method is usually called immediately as soon as the object is dereferenced. With other Python implementations such as Jython, this is not so guaranteed.
|
||||
|
||||
The `execute()` methods on both `Engine` and `Connection` can also receive SQL clause constructs as well:
|
||||
|
||||
{python}
|
||||
connection = engine.connect()
|
||||
result = connection.execute(select([table1], table1.c.col1==5))
|
||||
for row in result:
|
||||
print row['col1'], row['col2']
|
||||
connection.close()
|
||||
|
||||
The above SQL construct is known as a `select()`. The full range of SQL constructs available are described in [sql](rel:sql).
|
||||
|
||||
Both `Connection` and `Engine` fulfill an interface known as `Connectable` which specifies common functionality between the two objects, namely being able to call `connect()` to return a `Connection` object (`Connection` just returns itself), and being able to call `execute()` to get a result set. Following this, most SQLAlchemy functions and objects which accept an `Engine` as a parameter or attribute with which to execute SQL will also accept a `Connection`. As of SQLAlchemy 0.3.9, this argument is named `bind`.
|
||||
|
||||
{python title="Specify Engine or Connection"}
|
||||
engine = create_engine('sqlite:///:memory:')
|
||||
|
||||
# specify some Table metadata
|
||||
metadata = MetaData()
|
||||
table = Table('sometable', metadata, Column('col1', Integer))
|
||||
|
||||
# create the table with the Engine
|
||||
table.create(bind=engine)
|
||||
|
||||
# drop the table with a Connection off the Engine
|
||||
connection = engine.connect()
|
||||
table.drop(bind=connection)
|
||||
|
||||
Connection facts:
|
||||
|
||||
* the Connection object is **not threadsafe**. While a Connection can be shared among threads using properly synchronized access, this is also not recommended as many DBAPIs have issues with, if not outright disallow, sharing of connection state between threads.
|
||||
* The Connection object represents a single dbapi connection checked out from the connection pool. In this state, the connection pool has no affect upon the connection, including its expiration or timeout state. For the connection pool to properly manage connections, **connections should be returned to the connection pool (i.e. `connection.close()`) whenever the connection is not in use**. If your application has a need for management of multiple connections or is otherwise long running (this includes all web applications, threaded or not), don't hold a single connection open at the module level.
|
||||
|
||||
### Using Transactions with Connection {@name=transactions}
|
||||
|
||||
The `Connection` object provides a `begin()` method which returns a `Transaction` object. This object is usually used within a try/except clause so that it is guaranteed to `rollback()` or `commit()`:
|
||||
|
||||
{python}
|
||||
trans = connection.begin()
|
||||
try:
|
||||
r1 = connection.execute(table1.select())
|
||||
connection.execute(table1.insert(), col1=7, col2='this is some data')
|
||||
trans.commit()
|
||||
except:
|
||||
trans.rollback()
|
||||
raise
|
||||
|
||||
The `Transaction` object also handles "nested" behavior by keeping track of the outermost begin/commit pair. In this example, two functions both issue a transaction on a Connection, but only the outermost Transaction object actually takes effect when it is committed.
|
||||
|
||||
{python}
|
||||
# method_a starts a transaction and calls method_b
|
||||
def method_a(connection):
|
||||
trans = connection.begin() # open a transaction
|
||||
try:
|
||||
method_b(connection)
|
||||
trans.commit() # transaction is committed here
|
||||
except:
|
||||
trans.rollback() # this rolls back the transaction unconditionally
|
||||
raise
|
||||
|
||||
# method_b also starts a transaction
|
||||
def method_b(connection):
|
||||
trans = connection.begin() # open a transaction - this runs in the context of method_a's transaction
|
||||
try:
|
||||
connection.execute("insert into mytable values ('bat', 'lala')")
|
||||
connection.execute(mytable.insert(), col1='bat', col2='lala')
|
||||
trans.commit() # transaction is not committed yet
|
||||
except:
|
||||
trans.rollback() # this rolls back the transaction unconditionally
|
||||
raise
|
||||
|
||||
# open a Connection and call method_a
|
||||
conn = engine.connect()
|
||||
method_a(conn)
|
||||
conn.close()
|
||||
|
||||
Above, `method_a` is called first, which calls `connection.begin()`. Then it calls `method_b`. When `method_b` calls `connection.begin()`, it just increments a counter that is decremented when it calls `commit()`. If either `method_a` or `method_b` calls `rollback()`, the whole transaction is rolled back. The transaction is not committed until `method_a` calls the `commit()` method. This "nesting" behavior allows the creation of functions which "guarantee" that a transaction will be used if one was not already available, but will automatically participate in an enclosing transaction if one exists.
|
||||
|
||||
Note that SQLAlchemy's Object Relational Mapper also provides a way to control transaction scope at a higher level; this is described in [unitofwork_transaction](rel:unitofwork_transaction).
|
||||
|
||||
Transaction Facts:
|
||||
|
||||
* the Transaction object, just like its parent Connection, is **not threadsafe**.
|
||||
* SQLAlchemy 0.4 will feature transactions with two-phase commit capability as well as SAVEPOINT capability.
|
||||
|
||||
#### Understanding Autocommit
|
||||
|
||||
The above transaction example illustrates how to use `Transaction` so that several executions can take part in the same transaction. What happens when we issue an INSERT, UPDATE or DELETE call without using `Transaction`? The answer is **autocommit**. While many DBAPIs implement a flag called `autocommit`, the current SQLAlchemy behavior is such that it implements its own autocommit. This is achieved by searching the statement for strings like INSERT, UPDATE, DELETE, etc. and then issuing a COMMIT automatically if no transaction is in progress.
|
||||
|
||||
{python}
|
||||
conn = engine.connect()
|
||||
conn.execute("INSERT INTO users VALUES (1, 'john')") # autocommits
|
||||
|
||||
### Connectionless Execution, Implicit Execution {@name=implicit}
|
||||
|
||||
Recall from the first section we mentioned executing with and without a `Connection`. `Connectionless` execution refers to calling the `execute()` method on an object which is not a `Connection`, which could be on the the `Engine` itself, or could be a constructed SQL object. When we say "implicit", we mean that we are calling the `execute()` method on an object which is neither a `Connection` nor an `Engine` object; this can only be used with constructed SQL objects which have their own `execute()` method, and can be "bound" to an `Engine`. A description of "constructed SQL objects" may be found in [sql](rel:sql).
|
||||
|
||||
A summary of all three methods follows below. First, assume the usage of the following `MetaData` and `Table` objects; while we haven't yet introduced these concepts, for now you only need to know that we are representing a database table, and are creating an "executeable" SQL construct which issues a statement to the database. These objects are described in [metadata](rel:metadata).
|
||||
|
||||
{python}
|
||||
meta = MetaData()
|
||||
users_table = Table('users', meta,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('name', String(50))
|
||||
)
|
||||
|
||||
Explicit execution delivers the SQL text or constructed SQL expression to the `execute()` method of `Connection`:
|
||||
|
||||
{python}
|
||||
engine = create_engine('sqlite:///file.db')
|
||||
connection = engine.connect()
|
||||
result = connection.execute(users_table.select())
|
||||
for row in result:
|
||||
# ....
|
||||
connection.close()
|
||||
|
||||
Explicit, connectionless execution delivers the expression to the `execute()` method of `Engine`:
|
||||
|
||||
{python}
|
||||
engine = create_engine('sqlite:///file.db')
|
||||
result = engine.execute(users_table.select())
|
||||
for row in result:
|
||||
# ....
|
||||
result.close()
|
||||
|
||||
Implicit execution is also connectionless, and calls the `execute()` method on the expression itself, utilizing the fact that either an `Engine` or `Connection` has been *bound* to the expression object (binding is discussed further in the next section, [metadata](rel:metadata)):
|
||||
|
||||
{python}
|
||||
engine = create_engine('sqlite:///file.db')
|
||||
meta.bind = engine
|
||||
result = users_table.select().execute()
|
||||
for row in result:
|
||||
# ....
|
||||
result.close()
|
||||
|
||||
In both "connectionless" examples, the `Connection` is created behind the scenes; the `ResultProxy` returned by the `execute()` call references the `Connection` used to issue the SQL statement. When we issue `close()` on the `ResultProxy`, or if the result set object falls out of scope and is garbage collected, the underlying `Connection` is closed for us, resulting in the DBAPI connection being returned to the pool.
|
||||
|
||||
#### Using the Threadlocal Execution Strategy {@name=strategies}
|
||||
|
||||
With connectionless execution, each returned `ResultProxy` object references its own distinct DBAPI connection object. This means that multiple executions will result in multiple DBAPI connections being used at the same time; the example below illustrates this:
|
||||
|
||||
{python}
|
||||
db = create_engine('mysql://localhost/test')
|
||||
|
||||
# execute one statement and receive results. r1 now references a DBAPI connection resource.
|
||||
r1 = db.execute("select * from table1")
|
||||
|
||||
# execute a second statement and receive results. r2 now references a *second* DBAPI connection resource.
|
||||
r2 = db.execute("select * from table2")
|
||||
for row in r1:
|
||||
...
|
||||
for row in r2:
|
||||
...
|
||||
# release connection 1
|
||||
r1.close()
|
||||
|
||||
# release connection 2
|
||||
r2.close()
|
||||
|
||||
Where above, we have two result sets in scope at the same time, therefore we have two distinct DBAPI connections, both separately checked out from the connection pool, in scope at the same time.
|
||||
|
||||
An option exists to `create_engine()` called `strategy="threadlocal"`, which changes this behavior. When this option is used, the `Engine` which is returned by `create_engine()` is a special subclass of engine called `TLEngine`. This engine, when it creates the `Connection` used by a connectionless execution, checks a **threadlocal variable** for an existing DBAPI connection that was already checked out from the pool, within the current thread. If one exists, it uses that one.
|
||||
|
||||
The usage of "threadlocal" modifies the underlying behavior of our example above, as follows:
|
||||
|
||||
{python title="Threadlocal Strategy"}
|
||||
db = create_engine('mysql://localhost/test', strategy='threadlocal')
|
||||
|
||||
# execute one statement and receive results. r1 now references a DBAPI connection resource.
|
||||
r1 = db.execute("select * from table1")
|
||||
|
||||
# execute a second statement and receive results. r2 now references the *same* resource as r1
|
||||
r2 = db.execute("select * from table2")
|
||||
|
||||
for row in r1:
|
||||
...
|
||||
for row in r2:
|
||||
...
|
||||
# close r1. the connection is still held by r2.
|
||||
r1.close()
|
||||
|
||||
# close r2. with no more references to the underlying connection resources, they
|
||||
# are returned to the pool.
|
||||
r2.close()
|
||||
|
||||
Where above, we again have two result sets in scope at the same time, but because they are present in the same thread, there is only **one DBAPI connection in use**.
|
||||
|
||||
While the above distinction may not seem like much, it has several potentially desireable effects. One is that you can in some cases reduce the number of concurrent connections checked out from the connection pool, in the case that a `ResultProxy` is still opened and a second statement is issued. A second advantage is that by limiting the number of checked out connections in a thread to just one, you eliminate the issue of deadlocks within a single thread, such as when connection A locks a table, and connection B attempts to read from the same table in the same thread, it will "deadlock" on waiting for connection A to release its lock; the `threadlocal` strategy eliminates this possibility.
|
||||
|
||||
A third advantage to the `threadlocal` strategy is that it allows the `Transaction` object to be used in combination with connectionless execution. Recall from the section on transactions, that the `Transaction` is returned by the `begin()` method on a `Connection`; all statements which wish to participate in this transaction must be executed by the same `Connection`, thereby forcing the usage of an explicit connection. However, the `TLEngine` provides a `Transaction` that is local to the current thread; using it, one can issue many "connectionless" statements within a thread and they will all automatically partake in the current transaction, as in the example below:
|
||||
|
||||
{python title="threadlocal connection sharing"}
|
||||
# get a TLEngine
|
||||
engine = create_engine('mysql://localhost/test', strategy='threadlocal')
|
||||
|
||||
engine.begin()
|
||||
try:
|
||||
engine.execute("insert into users values (?, ?)", 1, "john")
|
||||
users.update(users.c.user_id==5).execute(name='ed')
|
||||
engine.commit()
|
||||
except:
|
||||
engine.rollback()
|
||||
|
||||
Notice that no `Connection` needed to be used; the `begin()` method on `TLEngine` (which note is not available on the regular `Engine`) created a `Transaction` as well as a `Connection`, and held onto both in a context corresponding to the current thread. Each `execute()` call made use of the same connection, allowing them all to participate in the same transaction.
|
||||
|
||||
Complex application flows can take advantage of the "threadlocal" strategy in order to allow many disparate parts of an application to take place in the same transaction automatically. The example below demonstrates several forms of "connectionless execution" as well as some specialized explicit ones:
|
||||
|
||||
{python title="threadlocal connection sharing"}
|
||||
engine = create_engine('mysql://localhost/test', strategy='threadlocal')
|
||||
|
||||
def dosomethingimplicit():
|
||||
table1.execute("some sql")
|
||||
table1.execute("some other sql")
|
||||
|
||||
def dosomethingelse():
|
||||
table2.execute("some sql")
|
||||
conn = engine.contextual_connect()
|
||||
# do stuff with conn
|
||||
conn.execute("some other sql")
|
||||
conn.close()
|
||||
|
||||
def dosomethingtransactional():
|
||||
conn = engine.contextual_connect()
|
||||
trans = conn.begin()
|
||||
# do stuff
|
||||
trans.commit()
|
||||
|
||||
engine.begin()
|
||||
try:
|
||||
dosomethingimplicit()
|
||||
dosomethingelse()
|
||||
dosomethingtransactional()
|
||||
engine.commit()
|
||||
except:
|
||||
engine.rollback()
|
||||
|
||||
In the above example, the program calls three functions `dosomethingimplicit()`, `dosomethingelse()` and `dosomethingtransactional()`. All three functions use either connectionless execution, or a special function `contextual_connect()` which we will describe in a moment. These two styles of execution both indicate that all executions will use the same connection object. Additionally, the method `dosomethingtransactional()` begins and commits its own `Transaction`. But only one transaction is used, too; it's controlled completely by the `engine.begin()`/`engine.commit()` calls at the bottom. Recall that `Transaction` supports "nesting" behavior, whereby transactions begun on a `Connection` which already has a tranasaction open, will "nest" into the enclosing transaction. Since the transaction opened in `dosomethingtransactional()` occurs using the same connection which already has a transaction begun, it "nests" into that transaction and therefore has no effect on the actual transaction scope (unless it calls `rollback()`).
|
||||
|
||||
Some of the functions in the above example make use of a method called `engine.contextual_connect()`. This method is available on both `Engine` as well as `TLEngine`, and returns the `Connection` that applies to the current **connection context**. When using the `TLEngine`, this is just another term for the "thread local connection" that is being used for all connectionless executions. When using just the regular `Engine` (i.e. the "default" strategy), `contextual_connect()` is synonymous with `connect()`. Below we illustrate that two connections opened via `contextual_connect()` at the same time, both reference the same underlying DBAPI connection:
|
||||
|
||||
{python title="Contextual Connection"}
|
||||
# threadlocal strategy
|
||||
db = create_engine('mysql://localhost/test', strategy='threadlocal')
|
||||
|
||||
conn1 = db.contextual_connect()
|
||||
conn2 = db.contextual_connect()
|
||||
|
||||
>>> conn1.connection is conn2.connection
|
||||
True
|
||||
|
||||
The basic idea of `contextual_connect()` is that its the "connection used by connectionless execution". It's different from the `connect()` method in that `connect()` is always used when handling an explicit `Connection`, which will always reference distinct DBAPI connection. Using `connect()` in combination with `TLEngine` allows one to "circumvent" the current thread local context, as in this example where a single statement issues data to the database externally to the current transaction:
|
||||
|
||||
{python}
|
||||
engine.begin()
|
||||
engine.execute("insert into users values (?, ?)", 1, "john")
|
||||
connection = engine.connect()
|
||||
connection.execute(users.update(users.c.user_id==5).execute(name='ed'))
|
||||
engine.rollback()
|
||||
|
||||
In the above example, a thread-local transaction is begun, but is later rolled back. The statement `insert into users values (?, ?)` is executed without using a connection, therefore uses the thread-local transaction. So its data is rolled back when the transaction is rolled back. However, the `users.update()` statement is executed using a distinct `Connection` returned by the `engine.connect()` method, so it therefore is not part of the threadlocal transaction; it autocommits immediately.
|
||||
|
||||
### Configuring Logging {@name=logging}
|
||||
|
||||
As of the 0.3 series of SQLAlchemy, Python's standard [logging](http://www.python.org/doc/lib/module-logging.html) module is used to implement informational and debug log output. This allows SQLAlchemy's logging to integrate in a standard way with other applications and libraries. The `echo` and `echo_pool` flags that are present on `create_engine()`, as well as the `echo_uow` flag used on `Session`, all interact with regular loggers.
|
||||
|
||||
This section assumes familiarity with the above linked logging module. All logging performed by SQLAlchemy exists underneath the `sqlalchemy` namespace, as used by `logging.getLogger('sqlalchemy')`. When logging has been configured (i.e. such as via `logging.basicConfig()`), the general namespace of SA loggers that can be turned on is as follows:
|
||||
|
||||
* `sqlalchemy.engine` - controls SQL echoing. set to `logging.INFO` for SQL query output, `logging.DEBUG` for query + result set output.
|
||||
* `sqlalchemy.pool` - controls connection pool logging. set to `logging.INFO` or lower to log connection pool checkouts/checkins.
|
||||
* `sqlalchemy.orm` - controls logging of various ORM functions. set to `logging.INFO` for configurational logging as well as unit of work dumps, `logging.DEBUG` for extensive logging during query and flush() operations. Subcategories of `sqlalchemy.orm` include:
|
||||
* `sqlalchemy.orm.attributes` - logs certain instrumented attribute operations, such as triggered callables
|
||||
* `sqlalchemy.orm.mapper` - logs Mapper configuration and operations
|
||||
* `sqlalchemy.orm.unitofwork` - logs flush() operations, including dependency sort graphs and other operations
|
||||
* `sqlalchemy.orm.strategies` - logs relation loader operations (i.e. lazy and eager loads)
|
||||
* `sqlalchemy.orm.sync` - logs synchronization of attributes from parent to child instances during a flush()
|
||||
|
||||
For example, to log SQL queries as well as unit of work debugging:
|
||||
|
||||
{python}
|
||||
import logging
|
||||
|
||||
logging.basicConfig()
|
||||
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
|
||||
logging.getLogger('sqlalchemy.orm.unitofwork').setLevel(logging.DEBUG)
|
||||
|
||||
By default, the log level is set to `logging.ERROR` within the entire `sqlalchemy` namespace so that no log operations occur, even within an application that has logging enabled otherwise.
|
||||
|
||||
The `echo` flags present as keyword arguments to `create_engine()` and others as well as the `echo` property on `Engine`, when set to `True`, will first attempt to ensure that logging is enabled. Unfortunately, the `logging` module provides no way of determining if output has already been configured (note we are referring to if a logging configuration has been set up, not just that the logging level is set). For this reason, any `echo=True` flags will result in a call to `logging.basicConfig()` using sys.stdout as the destination. It also sets up a default format using the level name, timestamp, and logger name. Note that this configuration has the affect of being configured **in addition** to any existing logger configurations. Therefore, **when using Python logging, ensure all echo flags are set to False at all times**, to avoid getting duplicate log lines.
|
||||
Vendored
-12
@@ -1,12 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
<%inherit file="content_layout.html"/>
|
||||
<%page args="toc, extension, paged"/>
|
||||
<%namespace name="formatting" file="formatting.html"/>
|
||||
<%namespace name="nav" file="nav.html"/>
|
||||
<%namespace name="pydoc" file="pydoc.html"/>
|
||||
<%def name="title()">SQLAlchemy 0.4 Documentation - Modules and Classes</%def>
|
||||
|
||||
<%!
|
||||
filename = 'docstrings'
|
||||
%>
|
||||
|
||||
Vendored
-25
@@ -1,25 +0,0 @@
|
||||
<%inherit file="base.html"/>
|
||||
<%namespace name="tocns" file="toc.html"/>
|
||||
<%namespace name="nav" file="nav.html"/>
|
||||
<%page args="toc, extension"/>
|
||||
|
||||
${tocns.toc(toc, extension=extension, paged=False)}
|
||||
|
||||
<%def name="title()">
|
||||
SQLAlchemy Documentation
|
||||
</%def>
|
||||
|
||||
% for file in toc.filenames:
|
||||
<%
|
||||
item = toc.get_by_file(file)
|
||||
%>
|
||||
|
||||
<A name="${item.path}"></a>
|
||||
|
||||
% if not item.requires_paged:
|
||||
${nav.pagenav(item=item, paged=False, extension=extension)}
|
||||
|
||||
${self.get_namespace(file + '.html').body(toc=toc, extension=extension, paged=False)}
|
||||
% endif
|
||||
% endfor
|
||||
|
||||
Vendored
-6
@@ -1,6 +0,0 @@
|
||||
<%inherit file="base.html"/>
|
||||
<%page args="toc, extension"/>
|
||||
|
||||
<%namespace name="tocns" file="toc.html"/>
|
||||
|
||||
${tocns.toc(toc, paged=True, extension=extension)}
|
||||
Vendored
-162
@@ -1,162 +0,0 @@
|
||||
Overview / Installation
|
||||
============
|
||||
|
||||
## Overview
|
||||
|
||||
The SQLAlchemy SQL Toolkit and Object Relational Mapper is a comprehensive set of tools for working with databases and Python. It has several distinct areas of functionality which can be used individually or combined together. Its major API components, all public-facing, are illustrated below:
|
||||
|
||||
{diagram}
|
||||
+-----------------------------------------------------------+
|
||||
| Object Relational Mapper (ORM) |
|
||||
| [[tutorial]](rel:datamapping) [[docs]](rel:advdatamapping) |
|
||||
+-----------------------------------------------------------+
|
||||
+---------+ +------------------------------------+ +--------+
|
||||
| | | SQL Expression Language | | |
|
||||
| | | [[tutorial]](rel:sql) [[docs]](rel:docstrings_sqlalchemy.sql.expression) | | |
|
||||
| | +------------------------------------+ | |
|
||||
| +-----------------------+ +--------------+ |
|
||||
| Dialect/Execution | | Schema Management |
|
||||
| [[docs]](rel:dbengine) | | [[docs]](rel:metadata) |
|
||||
+---------------------------------+ +-----------------------+
|
||||
+----------------------+ +----------------------------------+
|
||||
| Connection Pooling | | Types |
|
||||
| [[docs]](rel:pooling) | | [[docs]](rel:types) |
|
||||
+----------------------+ +----------------------------------+
|
||||
|
||||
Above, the two most significant front-facing portions of SQLAlchemy are the **Object Relational Mapper** and the **SQL Expression Language**. These are two separate toolkits, one building off the other. SQL Expressions can be used independently of the ORM. When using the ORM, the SQL Expression language is used to establish object-relational configurations as well as in querying.
|
||||
|
||||
## Tutorials
|
||||
|
||||
* [Object Relational Tutorial](rel:datamapping) - This describes the richest feature of SQLAlchemy, its object relational mapper. If you want to work with higher-level SQL which is constructed automatically for you, as well as management of Python objects, proceed to this tutorial.
|
||||
* [SQL Expression Tutorial](rel:sql) - The core of SQLAlchemy is its SQL expression language. The SQL Expression Language is a toolkit all its own, independent of the ORM package, which can be used to construct manipulable SQL expressions which can be programmatically constructed, modified, and executed, returning cursor-like result sets. It's a lot more lightweight than the ORM and is appropriate for higher scaling SQL operations. It's also heavily present within the ORM's public facing API, so advanced ORM users will want to master this language as well.
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
* [Datamapping](rel:advdatamapping) - A comprehensive walkthrough of major ORM patterns and techniques.
|
||||
* [Session](rel:unitofwork) - A detailed description of SQLAlchemy's Session object
|
||||
* [Engines](rel:dbengine) - Describes SQLAlchemy's database-connection facilities, including connection documentation and working with connections and transactions.
|
||||
* [Connection Pools](rel:pooling) - Further detail about SQLAlchemy's connection pool library.
|
||||
* [Metadata](rel:metadata) - All about schema management using `MetaData` and `Table` objects; reading database schemas into your application, creating and dropping tables, constraints, defaults, sequences, indexes.
|
||||
* [Types](rel:types) - Datatypes included with SQLAlchemy, their functions, as well as how to create your own types.
|
||||
* [Plugins](rel:plugins) - Included addons for SQLAlchemy
|
||||
|
||||
## Installing SQLAlchemy {@name=sqlalchemy}
|
||||
|
||||
Installing SQLAlchemy from scratch is most easily achieved with [setuptools][]. ([setuptools installation][install setuptools]). Just run this from the command-line:
|
||||
|
||||
# easy_install SQLAlchemy
|
||||
|
||||
This command will download the latest version of SQLAlchemy from the [Python Cheese Shop][pypi] and install it to your system.
|
||||
|
||||
[setuptools]: http://peak.telecommunity.com/DevCenter/setuptools
|
||||
[install setuptools]: http://peak.telecommunity.com/DevCenter/EasyInstall#installation-instructions
|
||||
[pypi]: http://pypi.python.org/pypi/SQLAlchemy
|
||||
|
||||
Otherwise, you can install from the distribution using the `setup.py` script:
|
||||
|
||||
# python setup.py install
|
||||
|
||||
### Installing a Database API {@name=dbms}
|
||||
|
||||
SQLAlchemy is designed to operate with a [DB-API](http://www.python.org/doc/peps/pep-0249/) implementation built for a particular database, and includes support for the most popular databases:
|
||||
|
||||
* Postgres: [psycopg2](http://www.initd.org/tracker/psycopg/wiki/PsycopgTwo)
|
||||
* SQLite: [pysqlite](http://initd.org/tracker/pysqlite), [sqlite3](http://docs.python.org/lib/module-sqlite3.html) (included with Python 2.5 or greater)
|
||||
* MySQL: [MySQLdb](http://sourceforge.net/projects/mysql-python)
|
||||
* Oracle: [cx_Oracle](http://www.cxtools.net/default.aspx?nav=home)
|
||||
* MS-SQL: [pyodbc](http://pyodbc.sourceforge.net/) (recommended), [adodbapi](http://adodbapi.sourceforge.net/) or [pymssql](http://pymssql.sourceforge.net/)
|
||||
* Firebird: [kinterbasdb](http://kinterbasdb.sourceforge.net/)
|
||||
* Informix: [informixdb](http://informixdb.sourceforge.net/)
|
||||
|
||||
### Checking the Installed SQLAlchemy Version
|
||||
|
||||
This documentation covers SQLAlchemy version 0.4. If you're working on a system that already has SQLAlchemy installed, check the version from your Python prompt like this:
|
||||
|
||||
{python}
|
||||
>>> import sqlalchemy
|
||||
>>> sqlalchemy.__version__ # doctest: +SKIP
|
||||
0.4.0
|
||||
|
||||
## 0.3 to 0.4 Migration {@name=migration}
|
||||
|
||||
From version 0.3 to version 0.4 of SQLAlchemy, some conventions have changed. Most of these conventions are available in the most recent releases of the 0.3 series starting with version 0.3.9, so that you can make a 0.3 application compatible with 0.4 in most cases.
|
||||
|
||||
This section will detail only those things that have changed in a backwards-incompatible manner. For a full overview of everything that's new and changed, see [WhatsNewIn04](http://www.sqlalchemy.org/trac/wiki/WhatsNewIn04).
|
||||
|
||||
### ORM Package is now sqlalchemy.orm {@name=imports}
|
||||
|
||||
All symbols related to the SQLAlchemy Object Relational Mapper, i.e. names like `mapper()`, `relation()`, `backref()`, `create_session()` `synonym()`, `eagerload()`, etc. are now only in the `sqlalchemy.orm` package, and **not** in `sqlalchemy`. So if you were previously importing everything on an asterisk:
|
||||
|
||||
{python}
|
||||
from sqlalchemy import *
|
||||
|
||||
You should now import separately from orm:
|
||||
|
||||
{python}
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
|
||||
Or more commonly, just pull in the names you'll need:
|
||||
|
||||
{python}
|
||||
from sqlalchemy import create_engine, MetaData, Table, Column, types
|
||||
from sqlalchemy.orm import mapper, relation, backref, create_session
|
||||
|
||||
### BoundMetaData is now MetaData {@name=metadata}
|
||||
|
||||
The `BoundMetaData` name is removed. Now, you just use `MetaData`. Additionally, the `engine` parameter/attribute is now called `bind`, and `connect()` is deprecated:
|
||||
|
||||
{python}
|
||||
# plain metadata
|
||||
meta = MetaData()
|
||||
|
||||
# metadata bound to an engine
|
||||
meta = MetaData(engine)
|
||||
|
||||
# bind metadata to an engine later
|
||||
meta.bind = engine
|
||||
|
||||
Additionally, `DynamicMetaData` is now known as `ThreadLocalMetaData`.
|
||||
|
||||
### Some existing select() methods become generative {@name=generative}
|
||||
|
||||
The methods `correlate()`, `order_by()`, and `group_by()` on the `select()` construct now return a **new** select object, and do not change the original one. Additionally, the generative methods `where()`, `column()`, `distinct()`, and several others have been added:
|
||||
|
||||
{python}
|
||||
s = table.select().order_by(table.c.id).where(table.c.x==7)
|
||||
result = engine.execute(s)
|
||||
|
||||
### collection_class behavior is changed {@name=collection}
|
||||
|
||||
If you've been using the `collection_class` option on `mapper()`, the requirements for instrumented collections have changed. For an overview, see [advdatamapping_relation_collections](rel:advdatamapping_relation_collections).
|
||||
|
||||
### All "engine", "bind_to", "connectable" Keyword Arguments Changed to "bind" {@name=bind}
|
||||
|
||||
This is for create/drop statements, sessions, SQL constructs, metadatas:
|
||||
|
||||
{python}
|
||||
myengine = create_engine('sqlite://')
|
||||
|
||||
meta = MetaData(myengine)
|
||||
|
||||
meta2 = MetaData()
|
||||
meta2.bind = myengine
|
||||
|
||||
session = create_session(bind=myengine)
|
||||
|
||||
statement = select([table], bind=myengine)
|
||||
|
||||
meta.create_all(bind=myengine)
|
||||
|
||||
### All "type" Keyword Arguments Changed to "type_" {@name=type}
|
||||
|
||||
This mostly applies to SQL constructs where you pass a type in:
|
||||
|
||||
{python}
|
||||
s = select([mytable], mytable.c.x=bindparam(y, type_=DateTime))
|
||||
|
||||
func.now(type_=DateTime)
|
||||
|
||||
### Mapper Extensions must return EXT_CONTINUE to continue execution to the next mapper
|
||||
|
||||
If you extend the mapper, the methods in your mapper extension must return EXT_CONTINUE to continue executing additional mappers.
|
||||
Vendored
-1414
File diff suppressed because it is too large
Load Diff
Vendored
-500
@@ -1,500 +0,0 @@
|
||||
[alpha_api]: javascript:alphaApi()
|
||||
[alpha_implementation]: javascript:alphaImplementation()
|
||||
|
||||
Database Meta Data {@name=metadata}
|
||||
==================
|
||||
|
||||
### Describing Databases with MetaData {@name=tables}
|
||||
|
||||
The core of SQLAlchemy's query and object mapping operations are supported by **database metadata**, which is comprised of Python objects that describe tables and other schema-level objects. These objects can be created by explicitly naming the various components and their properties, using the Table, Column, ForeignKey, Index, and Sequence objects imported from `sqlalchemy.schema`. There is also support for **reflection** of some entities, which means you only specify the *name* of the entities and they are recreated from the database automatically.
|
||||
|
||||
A collection of metadata entities is stored in an object aptly named `MetaData`:
|
||||
|
||||
{python}
|
||||
from sqlalchemy import *
|
||||
|
||||
metadata = MetaData()
|
||||
|
||||
To represent a Table, use the `Table` class:
|
||||
|
||||
{python}
|
||||
users = Table('users', metadata,
|
||||
Column('user_id', Integer, primary_key = True),
|
||||
Column('user_name', String(16), nullable = False),
|
||||
Column('email_address', String(60), key='email'),
|
||||
Column('password', String(20), nullable = False)
|
||||
)
|
||||
|
||||
user_prefs = Table('user_prefs', metadata,
|
||||
Column('pref_id', Integer, primary_key=True),
|
||||
Column('user_id', Integer, ForeignKey("users.user_id"), nullable=False),
|
||||
Column('pref_name', String(40), nullable=False),
|
||||
Column('pref_value', String(100))
|
||||
)
|
||||
|
||||
The specific datatypes for each Column, such as Integer, String, etc. are described in [types](rel:types), and exist within the module `sqlalchemy.types` as well as the global `sqlalchemy` namespace.
|
||||
|
||||
Foreign keys are most easily specified by the `ForeignKey` object within a `Column` object. For a composite foreign key, i.e. a foreign key that contains multiple columns referencing multiple columns to a composite primary key, an explicit syntax is provided which allows the correct table CREATE statements to be generated:
|
||||
|
||||
{python}
|
||||
# a table with a composite primary key
|
||||
invoices = Table('invoices', metadata,
|
||||
Column('invoice_id', Integer, primary_key=True),
|
||||
Column('ref_num', Integer, primary_key=True),
|
||||
Column('description', String(60), nullable=False)
|
||||
)
|
||||
|
||||
# a table with a composite foreign key referencing the parent table
|
||||
invoice_items = Table('invoice_items', metadata,
|
||||
Column('item_id', Integer, primary_key=True),
|
||||
Column('item_name', String(60), nullable=False),
|
||||
Column('invoice_id', Integer, nullable=False),
|
||||
Column('ref_num', Integer, nullable=False),
|
||||
ForeignKeyConstraint(['invoice_id', 'ref_num'], ['invoices.invoice_id', 'invoices.ref_num'])
|
||||
)
|
||||
|
||||
Above, the `invoice_items` table will have `ForeignKey` objects automatically added to the `invoice_id` and `ref_num` `Column` objects as a result of the additional `ForeignKeyConstraint` object.
|
||||
|
||||
The `MetaData` object supports some handy methods, such as getting a list of Tables in the order (or reverse) of their dependency:
|
||||
|
||||
{python}
|
||||
>>> for t in metadata.table_iterator(reverse=False):
|
||||
... print t.name
|
||||
users
|
||||
user_prefs
|
||||
|
||||
And `Table` provides an interface to the table's properties as well as that of its columns:
|
||||
|
||||
{python}
|
||||
employees = Table('employees', metadata,
|
||||
Column('employee_id', Integer, primary_key=True),
|
||||
Column('employee_name', String(60), nullable=False, key='name'),
|
||||
Column('employee_dept', Integer, ForeignKey("departments.department_id"))
|
||||
)
|
||||
|
||||
# access the column "EMPLOYEE_ID":
|
||||
employees.columns.employee_id
|
||||
|
||||
# or just
|
||||
employees.c.employee_id
|
||||
|
||||
# via string
|
||||
employees.c['employee_id']
|
||||
|
||||
# iterate through all columns
|
||||
for c in employees.c:
|
||||
# ...
|
||||
|
||||
# get the table's primary key columns
|
||||
for primary_key in employees.primary_key:
|
||||
# ...
|
||||
|
||||
# get the table's foreign key objects:
|
||||
for fkey in employees.foreign_keys:
|
||||
# ...
|
||||
|
||||
# access the table's MetaData:
|
||||
employees.metadata
|
||||
|
||||
# access the table's bound Engine or Connection, if its MetaData is bound:
|
||||
employees.bind
|
||||
|
||||
# access a column's name, type, nullable, primary key, foreign key
|
||||
employees.c.employee_id.name
|
||||
employees.c.employee_id.type
|
||||
employees.c.employee_id.nullable
|
||||
employees.c.employee_id.primary_key
|
||||
employees.c.employee_dept.foreign_key
|
||||
|
||||
# get the "key" of a column, which defaults to its name, but can
|
||||
# be any user-defined string:
|
||||
employees.c.name.key
|
||||
|
||||
# access a column's table:
|
||||
employees.c.employee_id.table is employees
|
||||
>>> True
|
||||
|
||||
# get the table related by a foreign key
|
||||
fcolumn = employees.c.employee_dept.foreign_key.column.table
|
||||
|
||||
#### Binding MetaData to an Engine or Connection {@name=binding}
|
||||
|
||||
A `MetaData` object can be associated with an `Engine` or an individual `Connection`; this process is called **binding**. The term used to describe "an engine or a connection" is often referred to as a **connectable**. Binding allows the `MetaData` and the elements which it contains to perform operations against the database directly, using the connection resources to which it's bound. Common operations which are made more convenient through binding include being able to generate SQL constructs which know how to execute themselves, creating `Table` objects which query the database for their column and constraint information, and issuing CREATE or DROP statements.
|
||||
|
||||
To bind `MetaData` to an `Engine`, use the `bind` attribute:
|
||||
|
||||
{python}
|
||||
engine = create_engine('sqlite://', **kwargs)
|
||||
|
||||
# create MetaData
|
||||
meta = MetaData()
|
||||
|
||||
# bind to an engine
|
||||
meta.bind = engine
|
||||
|
||||
Once this is done, the `MetaData` and its contained `Table` objects can access the database directly:
|
||||
|
||||
{python}
|
||||
meta.create_all() # issue CREATE statements for all tables
|
||||
|
||||
# describe a table called 'users', query the database for its columns
|
||||
users_table = Table('users', meta, autoload=True)
|
||||
|
||||
# generate a SELECT statement and execute
|
||||
result = users_table.select().execute()
|
||||
|
||||
Note that the feature of binding engines is **completely optional**. All of the operations which take advantage of "bound" `MetaData` also can be given an `Engine` or `Connection` explicitly with which to perform the operation. The equivalent "non-bound" of the above would be:
|
||||
|
||||
{python}
|
||||
meta.create_all(engine) # issue CREATE statements for all tables
|
||||
|
||||
# describe a table called 'users', query the database for its columns
|
||||
users_table = Table('users', meta, autoload=True, autoload_with=engine)
|
||||
|
||||
# generate a SELECT statement and execute
|
||||
result = engine.execute(users_table.select())
|
||||
|
||||
#### Reflecting Tables
|
||||
|
||||
A `Table` object can be created without specifying any of its contained attributes, using the argument `autoload=True` in conjunction with the table's name and possibly its schema (if not the databases "default" schema). (You can also specify a list or set of column names to autoload as the kwarg include_columns, if you only want to load a subset of the columns in the actual database.) This will issue the appropriate queries to the database in order to locate all properties of the table required for SQLAlchemy to use it effectively, including its column names and datatypes, foreign and primary key constraints, and in some cases its default-value generating attributes. To use `autoload=True`, the table's `MetaData` object need be bound to an `Engine` or `Connection`, or alternatively the `autoload_with=<some connectable>` argument can be passed. Below we illustrate autoloading a table and then iterating through the names of its columns:
|
||||
|
||||
{python}
|
||||
>>> messages = Table('messages', meta, autoload=True)
|
||||
>>> [c.name for c in messages.columns]
|
||||
['message_id', 'message_name', 'date']
|
||||
|
||||
Note that if a reflected table has a foreign key referencing another table, the related `Table` object will be automatically created within the `MetaData` object if it does not exist already. Below, suppose table `shopping_cart_items` references a table `shopping_carts`. After reflecting, the `shopping carts` table is present:
|
||||
|
||||
{python}
|
||||
>>> shopping_cart_items = Table('shopping_cart_items', meta, autoload=True)
|
||||
>>> 'shopping_carts' in meta.tables:
|
||||
True
|
||||
|
||||
To get direct access to 'shopping_carts', simply instantiate it via the `Table` constructor. `Table` uses a special contructor that will return the already created `Table` instance if its already present:
|
||||
|
||||
{python}
|
||||
shopping_carts = Table('shopping_carts', meta)
|
||||
|
||||
Of course, its a good idea to use `autoload=True` with the above table regardless. This is so that if it hadn't been loaded already, the operation will load the table. The autoload operation only occurs for the table if it hasn't already been loaded; once loaded, new calls to `Table` will not re-issue any reflection queries.
|
||||
|
||||
##### Overriding Reflected Columns {@name=overriding}
|
||||
|
||||
Individual columns can be overridden with explicit values when reflecting tables; this is handy for specifying custom datatypes, constraints such as primary keys that may not be configured within the database, etc.
|
||||
|
||||
{python}
|
||||
>>> mytable = Table('mytable', meta,
|
||||
... Column('id', Integer, primary_key=True), # override reflected 'id' to have primary key
|
||||
... Column('mydata', Unicode(50)), # override reflected 'mydata' to be Unicode
|
||||
... autoload=True)
|
||||
|
||||
#### Specifying the Schema Name {@name=schema}
|
||||
|
||||
Some databases support the concept of multiple schemas. A `Table` can reference this by specifying the `schema` keyword argument:
|
||||
|
||||
{python}
|
||||
financial_info = Table('financial_info', meta,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('value', String(100), nullable=False),
|
||||
schema='remote_banks'
|
||||
)
|
||||
|
||||
Within the `MetaData` collection, this table will be identified by the combination of `financial_info` and `remote_banks`. If another table called `financial_info` is referenced without the `remote_banks` schema, it will refer to a different `Table`. `ForeignKey` objects can reference columns in this table using the form `remote_banks.financial_info.id`.
|
||||
|
||||
#### ON UPDATE and ON DELETE {@name=onupdate}
|
||||
|
||||
`ON UPDATE` and `ON DELETE` clauses to a table create are specified within the `ForeignKeyConstraint` object, using the `onupdate` and `ondelete` keyword arguments:
|
||||
|
||||
{python}
|
||||
foobar = Table('foobar', meta,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('lala', String(40)),
|
||||
ForeignKeyConstraint(['lala'],['hoho.lala'], onupdate="CASCADE", ondelete="CASCADE"))
|
||||
|
||||
Note that these clauses are not supported on SQLite, and require `InnoDB` tables when used with MySQL. They may also not be supported on other databases.
|
||||
|
||||
#### Other Options {@name=options}
|
||||
|
||||
`Tables` may support database-specific options, such as MySQL's `engine` option that can specify "MyISAM", "InnoDB", and other backends for the table:
|
||||
|
||||
{python}
|
||||
addresses = Table('engine_email_addresses', meta,
|
||||
Column('address_id', Integer, primary_key = True),
|
||||
Column('remote_user_id', Integer, ForeignKey(users.c.user_id)),
|
||||
Column('email_address', String(20)),
|
||||
mysql_engine='InnoDB'
|
||||
)
|
||||
|
||||
### Creating and Dropping Database Tables {@name=creating}
|
||||
|
||||
Creating and dropping individual tables can be done via the `create()` and `drop()` methods of `Table`; these methods take an optional `bind` parameter which references an `Engine` or a `Connection`. If not supplied, the `Engine` bound to the `MetaData` will be used, else an error is raised:
|
||||
|
||||
{python}
|
||||
meta = MetaData()
|
||||
meta.bind = 'sqlite:///:memory:'
|
||||
|
||||
employees = Table('employees', meta,
|
||||
Column('employee_id', Integer, primary_key=True),
|
||||
Column('employee_name', String(60), nullable=False, key='name'),
|
||||
Column('employee_dept', Integer, ForeignKey("departments.department_id"))
|
||||
)
|
||||
{sql}employees.create()
|
||||
CREATE TABLE employees(
|
||||
employee_id SERIAL NOT NULL PRIMARY KEY,
|
||||
employee_name VARCHAR(60) NOT NULL,
|
||||
employee_dept INTEGER REFERENCES departments(department_id)
|
||||
)
|
||||
{}
|
||||
|
||||
`drop()` method:
|
||||
|
||||
{python}
|
||||
{sql}employees.drop(bind=e)
|
||||
DROP TABLE employees
|
||||
{}
|
||||
|
||||
The `create()` and `drop()` methods also support an optional keyword argument `checkfirst` which will issue the database's appropriate pragma statements to check if the table exists before creating or dropping:
|
||||
|
||||
{python}
|
||||
employees.create(bind=e, checkfirst=True)
|
||||
employees.drop(checkfirst=False)
|
||||
|
||||
Entire groups of Tables can be created and dropped directly from the `MetaData` object with `create_all()` and `drop_all()`. These methods always check for the existence of each table before creating or dropping. Each method takes an optional `bind` keyword argument which can reference an `Engine` or a `Connection`. If no engine is specified, the underlying bound `Engine`, if any, is used:
|
||||
|
||||
{python}
|
||||
engine = create_engine('sqlite:///:memory:')
|
||||
|
||||
metadata = MetaData()
|
||||
|
||||
users = Table('users', metadata,
|
||||
Column('user_id', Integer, primary_key = True),
|
||||
Column('user_name', String(16), nullable = False),
|
||||
Column('email_address', String(60), key='email'),
|
||||
Column('password', String(20), nullable = False)
|
||||
)
|
||||
|
||||
user_prefs = Table('user_prefs', metadata,
|
||||
Column('pref_id', Integer, primary_key=True),
|
||||
Column('user_id', Integer, ForeignKey("users.user_id"), nullable=False),
|
||||
Column('pref_name', String(40), nullable=False),
|
||||
Column('pref_value', String(100))
|
||||
)
|
||||
|
||||
{sql}metadata.create_all(bind=engine)
|
||||
PRAGMA table_info(users){}
|
||||
CREATE TABLE users(
|
||||
user_id INTEGER NOT NULL PRIMARY KEY,
|
||||
user_name VARCHAR(16) NOT NULL,
|
||||
email_address VARCHAR(60),
|
||||
password VARCHAR(20) NOT NULL
|
||||
)
|
||||
PRAGMA table_info(user_prefs){}
|
||||
CREATE TABLE user_prefs(
|
||||
pref_id INTEGER NOT NULL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||
pref_name VARCHAR(40) NOT NULL,
|
||||
pref_value VARCHAR(100)
|
||||
)
|
||||
|
||||
### Column Insert/Update Defaults {@name=defaults}
|
||||
|
||||
SQLAlchemy includes several constructs which provide default values provided during INSERT and UPDATE statements. The defaults may be provided as Python constants, Python functions, or SQL expressions, and the SQL expressions themselves may be "pre-executed", executed inline within the insert/update statement itself, or can be created as a SQL level "default" placed on the table definition itself. A "default" value by definition is only invoked if no explicit value is passed into the INSERT or UPDATE statement.
|
||||
|
||||
#### Pre-Executed Python Functions {@name=preexecute_functions}
|
||||
|
||||
The "default" keyword argument on Column can reference a Python value or callable which is invoked at the time of an insert:
|
||||
|
||||
{python}
|
||||
# a function which counts upwards
|
||||
i = 0
|
||||
def mydefault():
|
||||
global i
|
||||
i += 1
|
||||
return i
|
||||
|
||||
t = Table("mytable", meta,
|
||||
# function-based default
|
||||
Column('id', Integer, primary_key=True, default=mydefault),
|
||||
|
||||
# a scalar default
|
||||
Column('key', String(10), default="default")
|
||||
)
|
||||
|
||||
Similarly, the "onupdate" keyword does the same thing for update statements:
|
||||
|
||||
{python}
|
||||
import datetime
|
||||
|
||||
t = Table("mytable", meta,
|
||||
Column('id', Integer, primary_key=True),
|
||||
|
||||
# define 'last_updated' to be populated with datetime.now()
|
||||
Column('last_updated', DateTime, onupdate=datetime.now),
|
||||
)
|
||||
|
||||
#### Pre-executed and Inline SQL Expressions {@name=sqlexpression}
|
||||
|
||||
The "default" and "onupdate" keywords may also be passed SQL expressions, including select statements or direct function calls:
|
||||
|
||||
{python}
|
||||
t = Table("mytable", meta,
|
||||
Column('id', Integer, primary_key=True),
|
||||
|
||||
# define 'create_date' to default to now()
|
||||
Column('create_date', DateTime, default=func.now()),
|
||||
|
||||
# define 'key' to pull its default from the 'keyvalues' table
|
||||
Column('key', String(20), default=keyvalues.select(keyvalues.c.type='type1', limit=1))
|
||||
|
||||
# define 'last_modified' to use the current_timestamp SQL function on update
|
||||
Column('last_modified', DateTime, onupdate=func.current_timestamp())
|
||||
)
|
||||
|
||||
The above SQL functions are usually executed "inline" with the INSERT or UPDATE statement being executed. In some cases, the function is "pre-executed" and its result pre-fetched explicitly. This happens under the following circumstances:
|
||||
|
||||
* the column is a primary key column
|
||||
|
||||
* the database dialect does not support a usable `cursor.lastrowid` accessor (or equivalent); this currently includes Postgres, Oracle, and Firebird.
|
||||
|
||||
* the statement is a single execution, i.e. only supplies one set of parameters and doesn't use "executemany" behavior
|
||||
|
||||
* the `inline=True` flag is not set on the `Insert()` or `Update()` construct.
|
||||
|
||||
For a statement execution which is not an executemany, the returned `ResultProxy` will contain a collection accessible via `result.postfetch_cols()` which contains a list of all `Column` objects which had an inline-executed default. Similarly, all parameters which were bound to the statement, including all Python and SQL expressions which were pre-executed, are present in the `last_inserted_params()` or `last_updated_params()` collections on `ResultProxy`. The `last_inserted_ids()` collection contains a list of primary key values for the row inserted.
|
||||
|
||||
#### DDL-Level Defaults {@name=passive}
|
||||
|
||||
A variant on a SQL expression default is the `PassiveDefault`, which gets placed in the CREATE TABLE statement during a `create()` operation:
|
||||
|
||||
{python}
|
||||
t = Table('test', meta,
|
||||
Column('mycolumn', DateTime, PassiveDefault(text("sysdate")))
|
||||
)
|
||||
|
||||
A create call for the above table will produce:
|
||||
|
||||
{code}
|
||||
CREATE TABLE test (
|
||||
mycolumn datetime default sysdate
|
||||
)
|
||||
|
||||
The behavior of `PassiveDefault` is similar to that of a regular SQL default; if it's placed on a primary key column for a database which doesn't have a way to "postfetch" the ID, and the statement is not "inlined", the SQL expression is pre-executed; otherwise, SQLAlchemy lets the default fire off on the database side normally.
|
||||
|
||||
#### Defining Sequences {@name=sequences}
|
||||
|
||||
A table with a sequence looks like:
|
||||
|
||||
{python}
|
||||
table = Table("cartitems", meta,
|
||||
Column("cart_id", Integer, Sequence('cart_id_seq'), primary_key=True),
|
||||
Column("description", String(40)),
|
||||
Column("createdate", DateTime())
|
||||
)
|
||||
|
||||
The `Sequence` object works a lot like the `default` keyword on `Column`, except that it only takes effect on a database which supports sequences. When used with a database that does not support sequences, the `Sequence` object has no effect; therefore it's safe to place on a table which is used against multiple database backends. The same rules for pre- and inline execution apply.
|
||||
|
||||
When the `Sequence` is associated with a table, CREATE and DROP statements issued for that table will also issue CREATE/DROP for the sequence object as well, thus "bundling" the sequence object with its parent table.
|
||||
|
||||
The flag `optional=True` on `Sequence` will produce a sequence that is only used on databases which have no "autoincrementing" capability. For example, Postgres supports primary key generation using the SERIAL keyword, whereas Oracle has no such capability. Therefore, a `Sequence` placed on a primary key column with `optional=True` will only be used with an Oracle backend but not Postgres.
|
||||
|
||||
A sequence can also be executed standalone, using an `Engine` or `Connection`, returning its next value in a database-independent fashion:
|
||||
|
||||
{python}
|
||||
seq = Sequence('some_sequence')
|
||||
nextid = connection.execute(seq)
|
||||
|
||||
### Defining Constraints and Indexes {@name=constraints}
|
||||
|
||||
#### UNIQUE Constraint
|
||||
|
||||
Unique constraints can be created anonymously on a single column using the `unique` keyword on `Column`. Explicitly named unique constraints and/or those with multiple columns are created via the `UniqueConstraint` table-level construct.
|
||||
|
||||
{python}
|
||||
meta = MetaData()
|
||||
mytable = Table('mytable', meta,
|
||||
|
||||
# per-column anonymous unique constraint
|
||||
Column('col1', Integer, unique=True),
|
||||
|
||||
Column('col2', Integer),
|
||||
Column('col3', Integer),
|
||||
|
||||
# explicit/composite unique constraint. 'name' is optional.
|
||||
UniqueConstraint('col2', 'col3', name='uix_1')
|
||||
)
|
||||
|
||||
#### CHECK Constraint
|
||||
|
||||
Check constraints can be named or unnamed and can be created at the Column or Table level, using the `CheckConstraint` construct. The text of the check constraint is passed directly through to the database, so there is limited "database independent" behavior. Column level check constraints generally should only refer to the column to which they are placed, while table level constraints can refer to any columns in the table.
|
||||
|
||||
Note that some databases do not actively support check constraints such as MySQL and sqlite.
|
||||
|
||||
{python}
|
||||
meta = MetaData()
|
||||
mytable = Table('mytable', meta,
|
||||
|
||||
# per-column CHECK constraint
|
||||
Column('col1', Integer, CheckConstraint('col1>5')),
|
||||
|
||||
Column('col2', Integer),
|
||||
Column('col3', Integer),
|
||||
|
||||
# table level CHECK constraint. 'name' is optional.
|
||||
CheckConstraint('col2 > col3 + 5', name='check1')
|
||||
)
|
||||
|
||||
#### Indexes
|
||||
|
||||
Indexes can be created anonymously (using an auto-generated name "ix_<column label>") for a single column using the inline `index` keyword on `Column`, which also modifies the usage of `unique` to apply the uniqueness to the index itself, instead of adding a separate UNIQUE constraint. For indexes with specific names or which encompass more than one column, use the `Index` construct, which requires a name.
|
||||
|
||||
Note that the `Index` construct is created **externally** to the table which it corresponds, using `Column` objects and not strings.
|
||||
|
||||
{python}
|
||||
meta = MetaData()
|
||||
mytable = Table('mytable', meta,
|
||||
# an indexed column, with index "ix_mytable_col1"
|
||||
Column('col1', Integer, index=True),
|
||||
|
||||
# a uniquely indexed column with index "ix_mytable_col2"
|
||||
Column('col2', Integer, index=True, unique=True),
|
||||
|
||||
Column('col3', Integer),
|
||||
Column('col4', Integer),
|
||||
|
||||
Column('col5', Integer),
|
||||
Column('col6', Integer),
|
||||
)
|
||||
|
||||
# place an index on col3, col4
|
||||
Index('idx_col34', mytable.c.col3, mytable.c.col4)
|
||||
|
||||
# place a unique index on col5, col6
|
||||
Index('myindex', mytable.c.col5, mytable.c.col6, unique=True)
|
||||
|
||||
The `Index` objects will be created along with the CREATE statements for the table itself. An index can also be created on its own independently of the table:
|
||||
|
||||
{python}
|
||||
# create a table
|
||||
sometable.create()
|
||||
|
||||
# define an index
|
||||
i = Index('someindex', sometable.c.col5)
|
||||
|
||||
# create the index, will use the table's bound connectable if the `bind` keyword argument not specified
|
||||
i.create()
|
||||
|
||||
### Adapting Tables to Alternate Metadata {@name=adapting}
|
||||
|
||||
A `Table` object created against a specific `MetaData` object can be re-created against a new MetaData using the `tometadata` method:
|
||||
|
||||
{python}
|
||||
# create two metadata
|
||||
meta1 = MetaData('sqlite:///querytest.db')
|
||||
meta2 = MetaData()
|
||||
|
||||
# load 'users' from the sqlite engine
|
||||
users_table = Table('users', meta1, autoload=True)
|
||||
|
||||
# create the same Table object for the plain metadata
|
||||
users_table_2 = users_table.tometadata(meta2)
|
||||
|
||||
|
||||
Vendored
-1133
File diff suppressed because it is too large
Load Diff
Vendored
-499
@@ -1,499 +0,0 @@
|
||||
Plugins {@name=plugins}
|
||||
======================
|
||||
|
||||
SQLAlchemy has a variety of extensions available which provide extra functionality to SA, either via explicit usage or by augmenting the core behavior. Several of these extensions are designed to work together.
|
||||
|
||||
|
||||
### associationproxy
|
||||
|
||||
**Author:** Mike Bayer and Jason Kirtland<br/>
|
||||
**Version:** 0.3.1 or greater
|
||||
|
||||
`associationproxy` is used to create a simplified, read/write view of a relationship. It can be used to cherry-pick fields from a collection of related objects or to greatly simplify access to associated objects in an association relationship.
|
||||
|
||||
#### Simplifying Relations
|
||||
|
||||
{python}
|
||||
users_table = Table('users', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('name', String(64)),
|
||||
)
|
||||
|
||||
keywords_table = Table('keywords', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('keyword', String(64))
|
||||
)
|
||||
|
||||
userkeywords_table = Table('userkeywords', metadata,
|
||||
Column('user_id', Integer, ForeignKey("users.id"),
|
||||
primary_key=True),
|
||||
Column('keyword_id', Integer, ForeignKey("keywords.id"),
|
||||
primary_key=True)
|
||||
)
|
||||
|
||||
class User(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
class Keyword(object):
|
||||
def __init__(self, keyword):
|
||||
self.keyword = keyword
|
||||
|
||||
mapper(User, users_table, properties={
|
||||
'kw': relation(Keyword, secondary=userkeywords_table)
|
||||
})
|
||||
mapper(Keyword, keywords_table)
|
||||
|
||||
Above are three simple tables, modeling users, keywords and a many-to-many relationship between the two. These ``Keyword`` objects are little more than a container for a name, and accessing them via the relation is awkward:
|
||||
|
||||
{python}
|
||||
user = User('jek')
|
||||
user.kw.append(Keyword('cheese inspector'))
|
||||
print user.kw
|
||||
# [<__main__.Keyword object at 0xb791ea0c>]
|
||||
print user.kw[0].keyword
|
||||
# 'cheese inspector'
|
||||
print [keyword.keyword for keyword in u._keywords]
|
||||
# ['cheese inspector']
|
||||
|
||||
With ``association_proxy`` you have a "view" of the relation that contains just the `.keyword` of the related objects. The proxy is a Python property, and unlike the mapper relation, is defined in your class:
|
||||
|
||||
{python}
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
|
||||
class User(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
# proxy the 'keyword' attribute from the 'kw' relation
|
||||
keywords = association_proxy('kw', 'keyword')
|
||||
|
||||
# ...
|
||||
>>> user.kw
|
||||
[<__main__.Keyword object at 0xb791ea0c>]
|
||||
>>> user.keywords
|
||||
['cheese inspector']
|
||||
>>> user.keywords.append('snack ninja')
|
||||
>>> user.keywords
|
||||
['cheese inspector', 'snack ninja']
|
||||
>>> user.kw
|
||||
[<__main__.Keyword object at 0x9272a4c>, <__main__.Keyword object at 0xb7b396ec>]
|
||||
|
||||
The proxy is read/write. New associated objects are created on demand when values are added to the proxy, and modifying or removing an entry through the proxy also affects the underlying collection.
|
||||
|
||||
- The association proxy property is backed by a mapper-defined relation, either a collection or scalar.
|
||||
- You can access and modify both the proxy and the backing relation. Changes in one are immediate in the other.
|
||||
- The proxy acts like the type of the underlying collection. A list gets a list-like proxy, a dict a dict-like proxy, and so on.
|
||||
- Multiple proxies for the same relation are fine.
|
||||
- Proxies are lazy, and won't triger a load of the backing relation until they are accessed.
|
||||
- The relation is inspected to determine the type of the related objects.
|
||||
- To construct new instances, the type is called with the value being assigned, or key and value for dicts.
|
||||
- A ``creator`` function can be used to create instances instead.
|
||||
|
||||
Above, the ``Keyword.__init__`` takes a single argument ``keyword``, which maps conveniently to the value being set through the proxy. A ``creator`` function could have been used instead if more flexiblity was required.
|
||||
|
||||
Because the proxies are backed a regular relation collection, all of the usual hooks and patterns for using collections are still in effect. The most convenient behavior is the automatic setting of "parent"-type relationships on assignment. In the example above, nothing special had to be done to associate the Keyword to the User. Simply adding it to the collection is sufficient.
|
||||
|
||||
#### Simplifying Association Object Relations
|
||||
|
||||
Association proxies are also useful for keeping [association objects](rel:datamapping_association) out the way during regular use. For example, the ``userkeywords`` table might have a bunch of auditing columns that need to get updated when changes are made- columns that are updated but seldom, if ever, accessed in your application. A proxy can provide a very natural access pattern for the relation.
|
||||
|
||||
{python}
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
|
||||
# users_table and keywords_table tables as above, then:
|
||||
|
||||
userkeywords_table = Table('userkeywords', metadata,
|
||||
Column('user_id', Integer, ForeignKey("users.id"), primary_key=True),
|
||||
Column('keyword_id', Integer, ForeignKey("keywords.id"), primary_key=True),
|
||||
# add some auditing columns
|
||||
Column('updated_at', DateTime, default=datetime.now),
|
||||
Column('updated_by', Integer, default=get_current_uid, onupdate=get_current_uid),
|
||||
)
|
||||
|
||||
def _create_uk_by_keyword(keyword):
|
||||
"""A creator function."""
|
||||
return UserKeyword(keyword=keyword)
|
||||
|
||||
class User(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
keywords = association_proxy('user_keywords', 'keyword', creator=_create_uk_by_keyword)
|
||||
|
||||
class Keyword(object):
|
||||
def __init__(self, keyword):
|
||||
self.keyword = keyword
|
||||
def __repr__(self):
|
||||
return 'Keyword(%s)' % repr(self.keyword)
|
||||
|
||||
class UserKeyword(object):
|
||||
def __init__(self, user=None, keyword=None):
|
||||
self.user = user
|
||||
self.keyword = keyword
|
||||
|
||||
mapper(User, users_table, properties={
|
||||
'user_keywords': relation(UserKeyword)
|
||||
})
|
||||
mapper(Keyword, keywords_table)
|
||||
mapper(UserKeyword, userkeywords_table, properties={
|
||||
'user': relation(User),
|
||||
'keyword': relation(Keyword),
|
||||
})
|
||||
|
||||
|
||||
user = User('log')
|
||||
kw1 = Keyword('new_from_blammo')
|
||||
|
||||
# Adding a Keyword requires creating a UserKeyword association object
|
||||
user.user_keywords.append(UserKeyword(user, kw1))
|
||||
|
||||
# And accessing Keywords requires traverrsing UserKeywords
|
||||
print user.user_keywords[0]
|
||||
# <__main__.UserKeyword object at 0xb79bbbec>
|
||||
|
||||
print user.user_keywords[0].keyword
|
||||
# Keyword('new_from_blammo')
|
||||
|
||||
# Lots of work.
|
||||
|
||||
# It's much easier to go through the association proxy!
|
||||
for kw in (Keyword('its_big'), Keyword('its_heavy'), Keyword('its_wood')):
|
||||
user.keywords.append(kw)
|
||||
|
||||
print user.keywords
|
||||
# [Keyword('new_from_blammo'), Keyword('its_big'), Keyword('its_heavy'), Keyword('its_wood')]
|
||||
|
||||
|
||||
#### Building Complex Views
|
||||
|
||||
{python}
|
||||
stocks = Table("stocks", meta,
|
||||
Column('symbol', String(10), primary_key=True),
|
||||
Column('description', String(100), nullable=False),
|
||||
Column('last_price', Numeric)
|
||||
)
|
||||
|
||||
brokers = Table("brokers", meta,
|
||||
Column('id', Integer,primary_key=True),
|
||||
Column('name', String(100), nullable=False)
|
||||
)
|
||||
|
||||
holdings = Table("holdings", meta,
|
||||
Column('broker_id', Integer, ForeignKey('brokers.id'), primary_key=True),
|
||||
Column('symbol', String(10), ForeignKey('stocks.symbol'), primary_key=True),
|
||||
Column('shares', Integer)
|
||||
)
|
||||
|
||||
Above are three tables, modeling stocks, their brokers and the number of shares of a stock held by each broker. This situation is quite different from the association example above. `shares` is a _property of the relation_, an important one that we need to use all the time.
|
||||
|
||||
For this example, it would be very convenient if `Broker` objects had a dictionary collection that mapped `Stock` instances to the shares held for each. That's easy.
|
||||
|
||||
{python}
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||
|
||||
def _create_holding(stock, shares):
|
||||
"""A creator function, constructs Holdings from Stock and share quantity."""
|
||||
return Holding(stock=stock, shares=shares)
|
||||
|
||||
class Broker(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
holdings = association_proxy('by_stock', 'shares', creator=_create_holding)
|
||||
|
||||
class Stock(object):
|
||||
def __init__(self, symbol, description=None):
|
||||
self.symbol = symbol
|
||||
self.description = description
|
||||
self.last_price = 0
|
||||
|
||||
class Holding(object):
|
||||
def __init__(self, broker=None, stock=None, shares=0):
|
||||
self.broker = broker
|
||||
self.stock = stock
|
||||
self.shares = shares
|
||||
|
||||
mapper(Stock, stocks_table)
|
||||
mapper(Broker, brokers_table, properties={
|
||||
'by_stock': relation(Holding,
|
||||
collection_class=attribute_mapped_collection('stock'))
|
||||
})
|
||||
mapper(Holding, holdings_table, properties={
|
||||
'stock': relation(Stock),
|
||||
'broker': relation(Broker)
|
||||
})
|
||||
|
||||
Above, we've set up the 'by_stock' relation collection to act as a dictionary, using the `.stock` property of each Holding as a key.
|
||||
|
||||
Populating and accessing that dictionary manually is slightly inconvenient because of the complexity of the Holdings association object:
|
||||
|
||||
{python}
|
||||
stock = Stock('ZZK')
|
||||
broker = Broker('paj')
|
||||
|
||||
broker.holdings[stock] = Holding(broker, stock, 10)
|
||||
print broker.holdings[stock].shares
|
||||
# 10
|
||||
|
||||
The `by_stock` proxy we've added to the `Broker` class hides the details of the `Holding` while also giving access to `.shares`:
|
||||
|
||||
{python}
|
||||
for stock in (Stock('JEK'), Stock('STPZ')):
|
||||
broker.holdings[stock] = 123
|
||||
|
||||
for stock, shares in broker.holdings.items():
|
||||
print stock, shares
|
||||
|
||||
# lets take a peek at that holdings_table after committing changes to the db
|
||||
print list(holdings_table.select().execute())
|
||||
# [(1, 'ZZK', 10), (1, 'JEK', 123), (1, 'STEPZ', 123)]
|
||||
|
||||
Further examples can be found in the `examples/` directory in the SQLAlchemy distribution.
|
||||
|
||||
The `association_proxy` convenience function is not present in SQLAlchemy versions 0.3.1 through 0.3.7, instead instantiate the class directly:
|
||||
|
||||
{python}
|
||||
from sqlalchemy.ext.associationproxy import AssociationProxy
|
||||
|
||||
class Article(object):
|
||||
keywords = AssociationProxy('keyword_associations', 'keyword')
|
||||
|
||||
|
||||
### orderinglist
|
||||
|
||||
**Author:** Jason Kirtland
|
||||
|
||||
`orderinglist` is a helper for mutable ordered relations. It will intercept
|
||||
list operations performed on a relation collection and automatically
|
||||
synchronize changes in list position with an attribute on the related objects.
|
||||
(See [advdatamapping_properties_entitycollections](rel:advdatamapping_properties_customcollections) for more information on the general pattern.)
|
||||
|
||||
Example: Two tables that store slides in a presentation. Each slide
|
||||
has a number of bullet points, displayed in order by the 'position'
|
||||
column on the bullets table. These bullets can be inserted and re-ordered
|
||||
by your end users, and you need to update the 'position' column of all
|
||||
affected rows when changes are made.
|
||||
|
||||
{python}
|
||||
slides_table = Table('Slides', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('name', String))
|
||||
|
||||
bullets_table = Table('Bullets', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('slide_id', Integer, ForeignKey('Slides.id')),
|
||||
Column('position', Integer),
|
||||
Column('text', String))
|
||||
|
||||
class Slide(object):
|
||||
pass
|
||||
class Bullet(object):
|
||||
pass
|
||||
|
||||
mapper(Slide, slides_table, properties={
|
||||
'bullets': relation(Bullet, order_by=[bullets_table.c.position])
|
||||
})
|
||||
mapper(Bullet, bullets_table)
|
||||
|
||||
The standard relation mapping will produce a list-like attribute on each Slide
|
||||
containing all related Bullets, but coping with changes in ordering is totally
|
||||
your responsibility. If you insert a Bullet into that list, there is no
|
||||
magic- it won't have a position attribute unless you assign it it one, and
|
||||
you'll need to manually renumber all the subsequent Bullets in the list to
|
||||
accommodate the insert.
|
||||
|
||||
An `orderinglist` can automate this and manage the 'position' attribute on all
|
||||
related bullets for you.
|
||||
|
||||
{python}
|
||||
mapper(Slide, slides_table, properties={
|
||||
'bullets': relation(Bullet,
|
||||
collection_class=ordering_list('position'),
|
||||
order_by=[bullets_table.c.position])
|
||||
})
|
||||
mapper(Bullet, bullets_table)
|
||||
|
||||
s = Slide()
|
||||
s.bullets.append(Bullet())
|
||||
s.bullets.append(Bullet())
|
||||
s.bullets[1].position
|
||||
>>> 1
|
||||
s.bullets.insert(1, Bullet())
|
||||
s.bullets[2].position
|
||||
>>> 2
|
||||
|
||||
Use the `ordering_list` function to set up the `collection_class` on relations
|
||||
(as in the mapper example above). This implementation depends on the list
|
||||
starting in the proper order, so be SURE to put an order_by on your relation.
|
||||
|
||||
`ordering_list` takes the name of the related object's ordering attribute as
|
||||
an argument. By default, the zero-based integer index of the object's
|
||||
position in the `ordering_list` is synchronized with the ordering attribute:
|
||||
index 0 will get position 0, index 1 position 1, etc. To start numbering at 1
|
||||
or some other integer, provide `count_from=1`.
|
||||
|
||||
Ordering values are not limited to incrementing integers. Almost any scheme
|
||||
can implemented by supplying a custom `ordering_func` that maps a Python list
|
||||
index to any value you require. See the [module
|
||||
documentation](rel:docstrings_sqlalchemy.ext.orderinglist) for more
|
||||
information, and also check out the unit tests for examples of stepped
|
||||
numbering, alphabetical and Fibonacci numbering.
|
||||
|
||||
### SqlSoup
|
||||
|
||||
**Author:** Jonathan Ellis
|
||||
|
||||
SqlSoup creates mapped classes on the fly from tables, which are automatically reflected from the database based on name. It is essentially a nicer version of the "row data gateway" pattern.
|
||||
|
||||
{python}
|
||||
>>> from sqlalchemy.ext.sqlsoup import SqlSoup
|
||||
>>> soup = SqlSoup('sqlite:///')
|
||||
|
||||
>>> db.users.select(order_by=[db.users.c.name])
|
||||
[MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1),
|
||||
MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0)]
|
||||
|
||||
Full SqlSoup documentation is on the [SQLAlchemy Wiki](http://www.sqlalchemy.org/trac/wiki/SqlSoup).
|
||||
|
||||
|
||||
### Deprecated Extensions
|
||||
|
||||
A lot of our extensions are deprecated. But this is a good thing. Why ? Because all of them have been refined and focused, and rolled into the core of SQLAlchemy (or in the case of `ActiveMapper`, it's become **Elixir**). So they aren't removed, they've just graduated into fully integrated features. Below we describe a set of extensions which are present in 0.4 but are deprecated.
|
||||
|
||||
#### SelectResults
|
||||
|
||||
**Author:** Jonas Borgström
|
||||
|
||||
*NOTE:* As of verison 0.3.6 of SQLAlchemy, most behavior of `SelectResults` has been rolled into the base `Query` object. Explicit usage of `SelectResults` is therefore no longer needed.
|
||||
|
||||
`SelectResults` gives transformative behavior to the results returned from the `select` and `select_by` methods of `Query`.
|
||||
|
||||
{python}
|
||||
from sqlalchemy.ext.selectresults import SelectResults
|
||||
|
||||
query = session.query(MyClass)
|
||||
res = SelectResults(query)
|
||||
|
||||
res = res.filter(table.c.column == "something") # adds a WHERE clause (or appends to the existing via "and")
|
||||
res = res.order_by([table.c.column]) # adds an ORDER BY clause
|
||||
|
||||
for x in res[:10]: # Fetch and print the top ten instances - adds OFFSET 0 LIMIT 10 or equivalent
|
||||
print x.column2
|
||||
|
||||
# evaluate as a list, which executes the query
|
||||
x = list(res)
|
||||
|
||||
# Count how many instances that have column2 > 42
|
||||
# and column == "something"
|
||||
print res.filter(table.c.column2 > 42).count()
|
||||
|
||||
# select() is a synonym for filter()
|
||||
session.query(MyClass).select(mytable.c.column=="something").order_by([mytable.c.column])[2:7]
|
||||
|
||||
An important facet of SelectResults is that the actual SQL execution does not occur until the object is used in a list or iterator context. This means you can call any number of transformative methods (including `filter`, `order_by`, list range expressions, etc) before any SQL is actually issued.
|
||||
|
||||
Configuration of SelectResults may be per-Query, per Mapper, or per application:
|
||||
|
||||
{python}
|
||||
from sqlalchemy.ext.selectresults import SelectResults, SelectResultsExt
|
||||
|
||||
# construct a SelectResults for an individual Query
|
||||
sel = SelectResults(session.query(MyClass))
|
||||
|
||||
# construct a Mapper where the Query.select()/select_by() methods will return a SelectResults:
|
||||
mapper(MyClass, mytable, extension=SelectResultsExt())
|
||||
|
||||
# globally configure all Mappers to return SelectResults, using the "selectresults" mod
|
||||
import sqlalchemy.mods.selectresults
|
||||
|
||||
SelectResults greatly enhances querying and is highly recommended. For example, heres an example of constructing a query using a combination of joins and outerjoins:
|
||||
|
||||
{python}
|
||||
mapper(User, users_table, properties={
|
||||
'orders':relation(mapper(Order, orders_table, properties={
|
||||
'items':relation(mapper(Item, items_table))
|
||||
}))
|
||||
})
|
||||
session = create_session()
|
||||
query = SelectResults(session.query(User))
|
||||
|
||||
result = query.outerjoin_to('orders').outerjoin_to('items').select(or_(Order.c.order_id==None,Item.c.item_id==2))
|
||||
|
||||
For a full listing of methods, see the [generated documentation](rel:docstrings_sqlalchemy.ext.selectresults).
|
||||
|
||||
#### SessionContext
|
||||
|
||||
**Author:** Daniel Miller
|
||||
|
||||
The `SessionContext` extension is still available in the 0.4 release of SQLAlchemy, but has been deprecated in favor of the [scoped_session()](rel:unitofwork_contextual) function, which provides a class-like object that constructs a `Session` on demand which references a thread-local scope.
|
||||
|
||||
For docs on `SessionContext`, see the SQLAlchemy 0.3 documentation.
|
||||
|
||||
#### assignmapper
|
||||
|
||||
**Author:** Mike Bayer
|
||||
|
||||
The `assignmapper` extension is still available in the 0.4 release of SQLAlchemy, but has been deprecated in favor of the [scoped_session()](rel:unitofwork_contextual) function, which provides a `mapper` callable that works similarly to `assignmapper`.
|
||||
|
||||
For docs on `assignmapper`, see the SQLAlchemy 0.3 documentation.
|
||||
|
||||
#### ActiveMapper
|
||||
|
||||
**Author:** Jonathan LaCour
|
||||
|
||||
Please note that ActiveMapper has been deprecated in favor of [Elixir](http://elixir.ematia.de/), a more comprehensive solution to declarative mapping, of which Jonathan is a co-author.
|
||||
|
||||
ActiveMapper is a so-called "declarative layer" which allows the construction of a class, a `Table`, and a `Mapper` all in one step:
|
||||
|
||||
{python}
|
||||
class Person(ActiveMapper):
|
||||
class mapping:
|
||||
id = column(Integer, primary_key=True)
|
||||
full_name = column(String)
|
||||
first_name = column(String)
|
||||
middle_name = column(String)
|
||||
last_name = column(String)
|
||||
birth_date = column(DateTime)
|
||||
ssn = column(String)
|
||||
gender = column(String)
|
||||
home_phone = column(String)
|
||||
cell_phone = column(String)
|
||||
work_phone = column(String)
|
||||
prefs_id = column(Integer, foreign_key=ForeignKey('preferences.id'))
|
||||
addresses = one_to_many('Address', colname='person_id', backref='person')
|
||||
preferences = one_to_one('Preferences', colname='pref_id', backref='person')
|
||||
|
||||
def __str__(self):
|
||||
s = '%s\n' % self.full_name
|
||||
s += ' * birthdate: %s\n' % (self.birth_date or 'not provided')
|
||||
s += ' * fave color: %s\n' % (self.preferences.favorite_color or 'Unknown')
|
||||
s += ' * personality: %s\n' % (self.preferences.personality_type or 'Unknown')
|
||||
|
||||
for address in self.addresses:
|
||||
s += ' * address: %s\n' % address.address_1
|
||||
s += ' %s, %s %s\n' % (address.city, address.state, address.postal_code)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
class Preferences(ActiveMapper):
|
||||
class mapping:
|
||||
__table__ = 'preferences'
|
||||
id = column(Integer, primary_key=True)
|
||||
favorite_color = column(String)
|
||||
personality_type = column(String)
|
||||
|
||||
|
||||
class Address(ActiveMapper):
|
||||
class mapping:
|
||||
id = column(Integer, primary_key=True)
|
||||
type = column(String)
|
||||
address_1 = column(String)
|
||||
city = column(String)
|
||||
state = column(String)
|
||||
postal_code = column(String)
|
||||
person_id = column(Integer, foreign_key=ForeignKey('person.id'))
|
||||
|
||||
More discussion on ActiveMapper can be found at [Jonathan LaCour's Blog](http://cleverdevil.org/computing/35/declarative-mapping-with-sqlalchemy) as well as the [SQLAlchemy Wiki](http://www.sqlalchemy.org/trac/wiki/ActiveMapper).
|
||||
|
||||
Vendored
-95
@@ -1,95 +0,0 @@
|
||||
Connection Pooling {@name=pooling}
|
||||
======================
|
||||
|
||||
This section describes the connection pool module of SQLAlchemy. The `Pool` object it provides is normally embedded within an `Engine` instance. For most cases, explicit access to the pool module is not required. However, the `Pool` object can be used on its own, without the rest of SA, to manage DBAPI connections; this section describes that usage. Also, this section will describe in more detail how to customize the pooling strategy used by an `Engine`.
|
||||
|
||||
At the base of any database helper library is a system of efficiently acquiring connections to the database. Since the establishment of a database connection is typically a somewhat expensive operation, an application needs a way to get at database connections repeatedly without incurring the full overhead each time. Particularly for server-side web applications, a connection pool is the standard way to maintain a "pool" of database connections which are used over and over again among many requests. Connection pools typically are configured to maintain a certain "size", which represents how many connections can be used simultaneously without resorting to creating more newly-established connections.
|
||||
|
||||
### Establishing a Transparent Connection Pool {@name=establishing}
|
||||
|
||||
Any DBAPI module can be "proxied" through the connection pool using the following technique (note that the usage of 'psycopg2' is **just an example**; substitute whatever DBAPI module you'd like):
|
||||
|
||||
{python}
|
||||
import sqlalchemy.pool as pool
|
||||
import psycopg2 as psycopg
|
||||
psycopg = pool.manage(psycopg)
|
||||
|
||||
# then connect normally
|
||||
connection = psycopg.connect(database='test', username='scott', password='tiger')
|
||||
|
||||
This produces a `sqlalchemy.pool.DBProxy` object which supports the same `connect()` function as the original DBAPI module. Upon connection, a connection proxy object is returned, which delegates its calls to a real DBAPI connection object. This connection object is stored persistently within a connection pool (an instance of `sqlalchemy.pool.Pool`) that corresponds to the exact connection arguments sent to the `connect()` function.
|
||||
|
||||
The connection proxy supports all of the methods on the original connection object, most of which are proxied via `__getattr__()`. The `close()` method will return the connection to the pool, and the `cursor()` method will return a proxied cursor object. Both the connection proxy and the cursor proxy will also return the underlying connection to the pool after they have both been garbage collected, which is detected via the `__del__()` method.
|
||||
|
||||
Additionally, when connections are returned to the pool, a `rollback()` is issued on the connection unconditionally. This is to release any locks still held by the connection that may have resulted from normal activity.
|
||||
|
||||
By default, the `connect()` method will return the same connection that is already checked out in the current thread. This allows a particular connection to be used in a given thread without needing to pass it around between functions. To disable this behavior, specify `use_threadlocal=False` to the `manage()` function.
|
||||
|
||||
### Connection Pool Configuration {@name=configuration}
|
||||
|
||||
For all types of Pool construction, which includes the "transparent proxy" described in the previous section, using an `Engine` via `create_engine()`, or constructing a pool through direct class instantiation, the options are generally the same. Additional options may be available based on the specific subclass of `Pool` being used.
|
||||
|
||||
For a description of all pool classes, see the [generated documentation](rel:docstrings_sqlalchemy.pool).
|
||||
|
||||
Common options include:
|
||||
|
||||
* echo=False : if set to True, connections being pulled and retrieved from/to the pool will
|
||||
be logged to the standard output, as well as pool sizing information. Echoing can also
|
||||
be achieved by enabling logging for the "sqlalchemy.pool" namespace. When using create_engine(),
|
||||
this option is specified as `echo_pool`.
|
||||
* use_threadlocal=False : if set to True, repeated calls to connect() within the same
|
||||
application thread will be guaranteed to return the same connection object, if one has
|
||||
already been retrieved from the pool and has not been returned yet. This allows code to
|
||||
retrieve a connection from the pool, and then while still holding on to that connection,
|
||||
to call other functions which also ask the pool for a connection of the same arguments;
|
||||
those functions will act upon the same connection that the calling method is using.
|
||||
This option is overridden during `create_engine()`, corresponding to the "plain" or
|
||||
"threadlocal" connection strategy.
|
||||
* recycle=-1 : if set to non -1, a number of seconds between connection recycling, which
|
||||
means upon checkout, if this timeout is surpassed the connection will be closed and replaced
|
||||
with a newly opened connection.
|
||||
|
||||
QueuePool options include:
|
||||
|
||||
* pool_size=5 : the size of the pool to be maintained. This is the
|
||||
largest number of connections that will be kept persistently in the pool. Note that the
|
||||
pool begins with no connections; once this number of connections is requested, that
|
||||
number of connections will remain.
|
||||
* max_overflow=10 : the maximum overflow size of the pool. When the number of checked-out
|
||||
connections reaches the size set in pool_size, additional connections will be returned up
|
||||
to this limit. When those additional connections are returned to the pool, they are
|
||||
disconnected and discarded. It follows then that the total number of simultaneous
|
||||
connections the pool will allow is pool_size + max_overflow, and the total number of
|
||||
"sleeping" connections the pool will allow is pool_size. max_overflow can be set to -1 to
|
||||
indicate no overflow limit; no limit will be placed on the total number of concurrent
|
||||
connections.
|
||||
* timeout=30 : the number of seconds to wait before giving up on returning a connection
|
||||
|
||||
### Custom Pool Construction {@name=custom}
|
||||
|
||||
Besides using the transparent proxy, instances of `sqlalchemy.pool.Pool` can be created directly. Constructing your own pool involves passing a callable used to create a connection. Through this method, custom connection schemes can be made, such as a connection that automatically executes some initialization commands to start.
|
||||
|
||||
{python title="Constructing a QueuePool"}
|
||||
import sqlalchemy.pool as pool
|
||||
import psycopg2
|
||||
|
||||
def getconn():
|
||||
c = psycopg2.connect(username='ed', host='127.0.0.1', dbname='test')
|
||||
# execute an initialization function on the connection before returning
|
||||
c.cursor.execute("setup_encodings()")
|
||||
return c
|
||||
|
||||
p = pool.QueuePool(getconn, max_overflow=10, pool_size=5, use_threadlocal=True)
|
||||
|
||||
Or with SingletonThreadPool:
|
||||
|
||||
{python title="Constructing a SingletonThreadPool"}
|
||||
import sqlalchemy.pool as pool
|
||||
import sqlite
|
||||
|
||||
def getconn():
|
||||
return sqlite.connect(filename='myfile.db')
|
||||
|
||||
# SQLite connections require the SingletonThreadPool
|
||||
p = pool.SingletonThreadPool(getconn)
|
||||
|
||||
Vendored
-793
@@ -1,793 +0,0 @@
|
||||
Using the Session {@name=unitofwork}
|
||||
============
|
||||
|
||||
The [Mapper](rel:advdatamapping) is the entrypoint to the configurational API of the SQLAlchemy object relational mapper. But the primary object one works with when using the ORM is the [Session](rel:docstrings_sqlalchemy.orm.session_Session).
|
||||
|
||||
## What does the Session do ?
|
||||
|
||||
In the most general sense, the `Session` establishes all conversations with the database and represents a "holding zone" for all the mapped instances which you've loaded or created during its lifespan. It implements the [Unit of Work](http://martinfowler.com/eaaCatalog/unitOfWork.html) pattern, which means it keeps track of all changes which occur, and is capable of **flushing** those changes to the database as appropriate. Another important facet of the `Session` is that it's also maintaining **unique** copies of each instance, where "unique" means "only one object with a particular primary key" - this pattern is called the [Identity Map](http://martinfowler.com/eaaCatalog/identityMap.html).
|
||||
|
||||
Beyond that, the `Session` implements an interface which let's you move objects in or out of the session in a variety of ways, it provides the entryway to a `Query` object which is used to query the database for data, it is commonly used to provide transactional boundaries (though this is optional), and it also can serve as a configurational "home base" for one or more `Engine` objects, which allows various vertical and horizontal partitioning strategies to be achieved.
|
||||
|
||||
## Getting a Session
|
||||
|
||||
The `Session` object exists just as a regular Python object, which can be directly instantiated. However, it takes a fair amount of keyword options, several of which you probably want to set explicitly. It's fairly inconvenient to deal with the "configuration" of a session every time you want to create one. Therefore, SQLAlchemy recommends the usage of a helper function called `sessionmaker()`, which typically you call only once for the lifespan of an application. This function creates a customized `Session` subclass for you, with your desired configurational arguments pre-loaded. Then, whenever you need a new `Session`, you use your custom `Session` class with no arguments to create the session.
|
||||
|
||||
### Using a sessionmaker() Configuration {@name=sessionmaker}
|
||||
|
||||
The usage of `sessionmaker()` is illustrated below:
|
||||
|
||||
{python}
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
# create a configured "Session" class
|
||||
Session = sessionmaker(autoflush=True, transactional=True)
|
||||
|
||||
# create a Session
|
||||
sess = Session()
|
||||
|
||||
# work with sess
|
||||
sess.save(x)
|
||||
sess.commit()
|
||||
|
||||
# close when finished
|
||||
sess.close()
|
||||
|
||||
Above, the `sessionmaker` call creates a class for us, which we assign to the name `Session`. This class is a subclass of the actual `sqlalchemy.orm.session.Session` class, which will instantiate with the arguments of `autoflush=True` and `transactional=True`.
|
||||
|
||||
When you write your application, place the call to `sessionmaker()` somewhere global, and then make your new `Session` class available to the rest of your application.
|
||||
|
||||
### Binding Session to an Engine or Connection {@name=binding}
|
||||
|
||||
In our previous example regarding `sessionmaker()`, nowhere did we specify how our session would connect to our database. When the session is configured in this manner, it will look for a database engine to connect with via the `Table` objects that it works with - the chapter called [metadata_tables_binding](rel:metadata_tables_binding) describes how to associate `Table` objects directly with a source of database connections.
|
||||
|
||||
However, it is often more straightforward to explicitly tell the session what database engine (or engines) you'd like it to communicate with. This is particularly handy with multiple-database scenarios where the session can be used as the central point of configuration. To acheive this, the constructor keyword `bind` is used for a basic single-database configuration:
|
||||
|
||||
{python}
|
||||
# create engine
|
||||
engine = create_engine('postgres://...')
|
||||
|
||||
# bind custom Session class to the engine
|
||||
Session = sessionmaker(bind=engine, autoflush=True, transactional=True)
|
||||
|
||||
# work with the session
|
||||
sess = Session()
|
||||
|
||||
One common issue with the above scenario is that an application will often organize its global imports before it ever connects to a database. Since the `Session` class created by `sessionmaker()` is meant to be a global application object (note we are saying the session *class*, not a session *instance*), we may not have a `bind` argument available. For this, the `Session` class returned by `sessionmaker()` supports post-configuration of all options, through its method `configure()`:
|
||||
|
||||
{python}
|
||||
# configure Session class with desired options
|
||||
Session = sessionmaker(autoflush=True, transactional=True)
|
||||
|
||||
# later, we create the engine
|
||||
engine = create_engine('postgres://...')
|
||||
|
||||
# associate it with our custom Session class
|
||||
Session.configure(bind=engine)
|
||||
|
||||
# work with the session
|
||||
sess = Session()
|
||||
|
||||
The `Session` also has the ability to be bound to multiple engines. Descriptions of these scenarios are described in [unitofwork_partitioning](rel:unitofwork_partitioning).
|
||||
|
||||
|
||||
#### Binding Session to a Connection {@name=connection}
|
||||
|
||||
The examples involving `bind` so far are dealing with the `Engine` object, which is, like the `Session` class itself, a global configurational object. The `Session` can also be bound to an individual database `Connection`. The reason you might want to do this is if your application controls the boundaries of transactions using distinct `Transaction` objects (these objects are described in [dbengine_transactions](rel:dbengine_transactions)). You'd have a transactional `Connection`, and then you'd want to work with an ORM-level `Session` which participates in that transaction. Since `Connection` is definitely not a globally-scoped object in all but the most rudimental commandline applications, you can bind an individual `Session()` instance to a particular `Connection` not at class configuration time, but at session instance construction time:
|
||||
|
||||
{python}
|
||||
# global application scope. create Session class, engine
|
||||
Session = sessionmaker(autoflush=True, transactional=True)
|
||||
|
||||
engine = create_engine('postgres://...')
|
||||
|
||||
...
|
||||
|
||||
# local scope, such as within a controller function
|
||||
|
||||
# connect to the database
|
||||
connection = engine.connect()
|
||||
|
||||
# bind an individual Session to the connection
|
||||
sess = Session(bind=connection)
|
||||
|
||||
### Using create_session() {@name=createsession}
|
||||
|
||||
As an alternative to `sessionmaker()`, `create_session()` exists literally as a function which calls the normal `Session` constructor directly. All arguments are passed through and the new `Session` object is returned:
|
||||
|
||||
{python}
|
||||
session = create_session(bind=myengine)
|
||||
|
||||
The `create_session()` function doesn't add any functionality to the regular `Session`, it just sets up a default argument set of `autoflush=False, transactional=False`. But also, by calling `create_session()` instead of instantiating `Session` directly, you leave room in your application to change the type of session which the function creates. For example, an application which is calling `create_session()` in many places, which is typical for a pre-0.4 application, can be changed to use a `sessionmaker()` by just assigning the return of `sessionmaker()` to the `create_session` name:
|
||||
|
||||
{python}
|
||||
# change from:
|
||||
from sqlalchemy.orm import create_session
|
||||
|
||||
# to:
|
||||
create_session = sessionmaker()
|
||||
|
||||
## Using the Session
|
||||
|
||||
A typical session conversation starts with creating a new session, or acquiring one from an ongoing context. You save new objects and load existing ones, make changes, mark some as deleted, and then persist your changes to the database. If your session is transactional, you use `commit()` to persist any remaining changes and to commit the transaction. If not, you call `flush()` which will flush any remaining data to the database.
|
||||
|
||||
Below, we open a new `Session` using a configured `sessionmaker()`, make some changes, and commit:
|
||||
|
||||
{python}
|
||||
# configured Session class
|
||||
Session = sessionmaker(autoflush=True, transactional=True)
|
||||
|
||||
sess = Session()
|
||||
d = Data(value=10)
|
||||
sess.save(d)
|
||||
d2 = sess.query(Data).filter(Data.value=15).one()
|
||||
d2.value = 19
|
||||
sess.commit()
|
||||
|
||||
### Quickie Intro to Object States {@name=states}
|
||||
|
||||
It's helpful to know the states which an instance can have within a session:
|
||||
|
||||
* *Transient* - an instance that's not in a session, and is not saved to the database; i.e. it has no database identity. The only relationship such an object has to the ORM is that its class has a `mapper()` associated with it.
|
||||
|
||||
* *Pending* - when you `save()` a transient instance, it becomes pending. It still wasn't actually flushed to the database yet, but it will be when the next flush occurs.
|
||||
|
||||
* *Persistent* - An instance which is present in the session and has a record in the database. You get persistent instances by either flushing so that the pending instances become persistent, or by querying the database for existing instances (or moving persistent instances from other sessions into your local session).
|
||||
|
||||
* *Detached* - an instance which has a record in the database, but is not in any session. Theres nothing wrong with this, and you can use objects normally when they're detached, **except** they will not be able to issue any SQL in order to load collections or attributes which are not yet loaded, or were marked as "expired".
|
||||
|
||||
Knowing these states is important, since the `Session` tries to be strict about ambiguous operations (such as trying to save the same object to two different sessions at the same time).
|
||||
|
||||
### Frequently Asked Questions {@name=faq}
|
||||
|
||||
* When do I make a `sessionmaker` ?
|
||||
|
||||
Just one time, somewhere in your application's global scope. It should be looked upon as part of your application's configuration. If your application has three .py files in a package, you could, for example, place the `sessionmaker` line in your `__init__.py` file; from that point on your other modules say "from mypackage import Session". That way, everyone else just uses `Session()`, and the configuration of that session is controlled by that central point.
|
||||
|
||||
If your application starts up, does imports, but does not know what database it's going to be connecting to, you can bind the `Session` at the "class" level to the engine later on, using `configure()`.
|
||||
|
||||
In the examples in this section, we will frequently show the `sessionmaker` being created right above the line where we actually invoke `Session()`. But that's just for example's sake ! In reality, the `sessionmaker` would be somewhere at the module level, and your individual `Session()` calls would be sprinkled all throughout your app, such as in a web application within each controller method.
|
||||
|
||||
* When do I make a `Session` ?
|
||||
|
||||
You typically invoke `Session()` when you first need to talk to your database, and want to save some objects or load some existing ones. Then, you work with it, save your changes, and then dispose of it....or at the very least `close()` it. It's not a "global" kind of object, and should be handled more like a "local variable", as it's generally **not** safe to use with concurrent threads. Sessions are very inexpensive to make, and don't use any resources whatsoever until they are first used...so create some !
|
||||
|
||||
There is also a pattern whereby you're using a **contextual session**, this is described later in [unitofwork_contextual](rel:unitofwork_contextual). In this pattern, a helper object is maintaining a `Session` for you, most commonly one that is local to the current thread (and sometimes also local to an application instance). SQLAlchemy 0.4 has worked this pattern out such that it still *looks* like you're creating a new session as you need one...so in that case, it's still a guaranteed win to just say `Session()` whenever you want a session.
|
||||
|
||||
* Is the Session a cache ?
|
||||
|
||||
Yeee...no. It's somewhat used as a cache, in that it implements the identity map pattern, and stores objects keyed to their primary key. However, it doesn't do any kind of query caching. This means, if you say `session.query(Foo).filter_by(name='bar')`, even if `Foo(name='bar')` is right there, in the identity map, the session has no idea about that. It has to issue SQL to the database, get the rows back, and then when it sees the primary key in the row, *then* it can look in the local identity map and see that the object is already there. It's only when you say `query.get({some primary key})` that the `Session` doesn't have to issue a query.
|
||||
|
||||
Additionally, the Session stores object instances using a weak reference by default. This also defeats the purpose of using the Session as a cache, unless the `weak_identity_map` flag is set to `False`.
|
||||
|
||||
The `Session` is not designed to be a global object from which everyone consults as a "registry" of objects. That is the job of a **second level cache**. A good library for implementing second level caching is [Memcached](http://www.danga.com/memcached/). It *is* possible to "sort of" use the `Session` in this manner, if you set it to be non-transactional and it never flushes any SQL, but it's not a terrific solution, since if concurrent threads load the same objects at the same time, you may have multiple copies of the same objects present in collections.
|
||||
|
||||
* How can I get the `Session` for a certain object ?
|
||||
|
||||
Use the `object_session()` classmethod available on `Session`:
|
||||
|
||||
{python}
|
||||
session = Session.object_session(someobject)
|
||||
|
||||
* Is the session threadsafe ?
|
||||
|
||||
Nope. It has no thread synchronization of any kind built in, and particularly when you do a flush operation, it definitely is not open to concurrent threads accessing it, because it holds onto a single database connection at that point. If you use a session which is non-transactional for read operations only, it's still not thread-"safe", but you also wont get any catastrophic failures either, since it opens and closes connections on an as-needed basis; its just that different threads might load the same objects independently of each other, but only one will wind up in the identity map (however, the other one might still live in a collection somewhere).
|
||||
|
||||
But the bigger point here is, you should not *want* to use the session with multiple concurrent threads. That would be like having everyone at a restaurant all eat from the same plate. The session is a local "workspace" that you use for a specific set of tasks; you don't want to, or need to, share that session with other threads who are doing some other task. If, on the other hand, there are other threads participating in the same task you are, such as in a desktop graphical application, then you would be sharing the session with those threads, but you also will have implemented a proper locking scheme (or your graphical framework does) so that those threads do not collide.
|
||||
|
||||
### Session Attributes {@name=attributes}
|
||||
|
||||
The session provides a set of attributes and collection-oriented methods which allow you to view the current state of the session.
|
||||
|
||||
The **identity map** is accessed by the `identity_map` attribute, which provides a dictionary interface. The keys are "identity keys", which are attached to all persistent objects by the attribute `_instance_key`:
|
||||
|
||||
{python}
|
||||
>>> myobject._instance_key
|
||||
(<class 'test.tables.User'>, (7,))
|
||||
|
||||
>>> myobject._instance_key in session.identity_map
|
||||
True
|
||||
|
||||
>>> session.identity_map.values()
|
||||
[<__main__.User object at 0x712630>, <__main__.Address object at 0x712a70>]
|
||||
|
||||
The identity map is a weak-referencing dictionary by default. This means that objects which are dereferenced on the outside will be removed from the session automatically. Note that objects which are marked as "dirty" will not fall out of scope until after changes on them have been flushed; special logic kicks in at the point of auto-removal which ensures that no pending changes remain on the object, else a temporary strong reference is created to the object.
|
||||
|
||||
Some people prefer objects to stay in the session until explicitly removed in all cases; for this, you can specify the flag `weak_identity_map=False` to the `create_session` or `sessionmaker` functions so that the `Session` will use a regular dictionary.
|
||||
|
||||
While the `identity_map` accessor is currently the actual dictionary used by the `Session` to store instances, you should not add or remove items from this dictionary. Use the session methods `save_or_update()` and `expunge()` to add or remove items.
|
||||
|
||||
The Session also supports an iterator interface in order to see all objects in the identity map:
|
||||
|
||||
{python}
|
||||
for obj in session:
|
||||
print obj
|
||||
|
||||
As well as `__contains__()`:
|
||||
|
||||
{python}
|
||||
if obj in session:
|
||||
print "Object is present"
|
||||
|
||||
The session is also keeping track of all newly created (i.e. pending) objects, all objects which have had changes since they were last loaded or saved (i.e. "dirty"), and everything that's been marked as deleted.
|
||||
|
||||
{python}
|
||||
# pending objects recently added to the Session
|
||||
session.new
|
||||
|
||||
# persistent objects which currently have changes detected
|
||||
# (this collection is now created on the fly each time the property is called)
|
||||
session.dirty
|
||||
|
||||
# persistent objects that have been marked as deleted via session.delete(obj)
|
||||
session.deleted
|
||||
|
||||
### Querying
|
||||
|
||||
The `query()` function takes one or more classes and/or mappers, along with an optional `entity_name` parameter, and returns a new `Query` object which will issue mapper queries within the context of this Session. For each mapper is passed, the Query uses that mapper. For each class, the Query will locate the primary mapper for the class using `class_mapper()`.
|
||||
|
||||
{python}
|
||||
# query from a class
|
||||
session.query(User).filter_by(name='ed').all()
|
||||
|
||||
# query with multiple classes, returns tuples
|
||||
session.query(User).add_entity(Address).join('addresses').filter_by(name='ed').all()
|
||||
|
||||
# query from a mapper
|
||||
query = session.query(usermapper)
|
||||
x = query.get(1)
|
||||
|
||||
# query from a class mapped with entity name 'alt_users'
|
||||
q = session.query(User, entity_name='alt_users')
|
||||
y = q.options(eagerload('orders')).all()
|
||||
|
||||
`entity_name` is an optional keyword argument sent with a class object, in order to further qualify which primary mapper to be used; this only applies if there was a `Mapper` created with that particular class/entity name combination, else an exception is raised. All of the methods on Session which take a class or mapper argument also take the `entity_name` argument, so that a given class can be properly matched to the desired primary mapper.
|
||||
|
||||
All instances retrieved by the returned `Query` object will be stored as persistent instances within the originating `Session`.
|
||||
|
||||
### Saving New Instances
|
||||
|
||||
`save()` is called with a single transient instance as an argument, which is then added to the Session and becomes pending. When the session is next flushed, the instance will be saved to the database. If the given instance is not transient, meaning it is either attached to an existing Session or it has a database identity, an exception is raised.
|
||||
|
||||
{python}
|
||||
user1 = User(name='user1')
|
||||
user2 = User(name='user2')
|
||||
session.save(user1)
|
||||
session.save(user2)
|
||||
|
||||
session.commit() # write changes to the database
|
||||
|
||||
There's also other ways to have objects saved to the session automatically; one is by using cascade rules, and the other is by using a contextual session. Both of these are described later.
|
||||
|
||||
### Updating/Merging Existing Instances
|
||||
|
||||
The `update()` method is used when you have a detached instance, and you want to put it back into a `Session`. Recall that "detached" means the object has a database identity.
|
||||
|
||||
Since `update()` is a little picky that way, most people use `save_or_update()`, which checks for an `_instance_key` attribute, and based on whether it's there or not, calls either `save()` or `update()`:
|
||||
|
||||
{python}
|
||||
# load user1 using session 1
|
||||
user1 = sess1.query(User).get(5)
|
||||
|
||||
# remove it from session 1
|
||||
sess1.expunge(user1)
|
||||
|
||||
# move it into session 2
|
||||
sess2.save_or_update(user1)
|
||||
|
||||
`update()` is also an operation that can happen automatically using cascade rules, just like `save()`.
|
||||
|
||||
`merge()` on the other hand is a little like `update()`, except it creates a **copy** of the given instance in the session, and returns to you that instance; the instance you send it never goes into the session. `merge()` is much fancier than `update()`; it will actually look to see if an object with the same primary key is already present in the session, and if not will load it by primary key. Then, it will merge the attributes of the given object into the one which it just located.
|
||||
|
||||
This method is useful for bringing in objects which may have been restored from a serialization, such as those stored in an HTTP session, where the object may be present in the session already:
|
||||
|
||||
{python}
|
||||
# deserialize an object
|
||||
myobj = pickle.loads(mystring)
|
||||
|
||||
# "merge" it. if the session already had this object in the
|
||||
# identity map, then you get back the one from the current session.
|
||||
myobj = session.merge(myobj)
|
||||
|
||||
`merge()` includes an important option called `dont_load`. When this boolean flag is set to `True`, the merge of a detached object will not force a `get()` of that object from the database. Normally, `merge()` issues a `get()` for every existing object so that it can load the most recent state of the object, which is then modified according to the state of the given object. With `dont_load=True`, the `get()` is skipped and `merge()` places an exact copy of the given object in the session. This allows objects which were retrieved from a caching system to be copied back into a session without any SQL overhead being added.
|
||||
|
||||
### Deleting
|
||||
|
||||
The `delete` method places an instance into the Session's list of objects to be marked as deleted:
|
||||
|
||||
{python}
|
||||
# mark two objects to be deleted
|
||||
session.delete(obj1)
|
||||
session.delete(obj2)
|
||||
|
||||
# commit (or flush)
|
||||
session.commit()
|
||||
|
||||
The big gotcha with `delete()` is that **nothing is removed from collections**. Such as, if a `User` has a collection of three `Addresses`, deleting an `Address` will not remove it from `user.addresses`:
|
||||
|
||||
{python}
|
||||
>>> address = user.addresses[1]
|
||||
>>> session.delete(address)
|
||||
>>> session.flush()
|
||||
>>> address in user.addresses
|
||||
True
|
||||
|
||||
The solution is to use proper cascading:
|
||||
|
||||
{python}
|
||||
mapper(User, users_table, properties={
|
||||
'addresses':relation(Address, cascade="all, delete")
|
||||
})
|
||||
del user.addresses[1]
|
||||
session.flush()
|
||||
|
||||
### Flushing
|
||||
|
||||
This is the main gateway to what the `Session` does best, which is save everything ! It should be clear by now what a flush looks like:
|
||||
|
||||
{python}
|
||||
session.flush()
|
||||
|
||||
It also can be called with a list of objects; in this form, the flush operation will be limited only to the objects specified in the list:
|
||||
|
||||
{python}
|
||||
# saves only user1 and address2. all other modified
|
||||
# objects remain present in the session.
|
||||
session.flush([user1, address2])
|
||||
|
||||
This second form of flush should be used carefully as it will not necessarily locate other dependent objects within the session, whose database representation may have foreign constraint relationships with the objects being operated upon.
|
||||
|
||||
Theres also a way to have `flush()` called automatically before each query; this is called "autoflush" and is described below.
|
||||
|
||||
Note that flush **does not change** the state of any collections or entity relationships in memory; for example, if you set a foreign key attribute `b_id` on object `A` with the the identifier `B.id`, the change will be flushed to the database, but `A` will not have `B` added to its collection. If you want to manipulate foreign key attributes directly, `refresh()` or `expire()` the objects whose state needs to be refreshed subsequent to flushing.
|
||||
|
||||
### Autoflush
|
||||
|
||||
A session can be configured to issue `flush()` calls before each query. This allows you to immediately have DB access to whatever has been saved to the session. It's recommended to use autoflush with `transactional=True`, that way an unexpected flush call won't permanently save to the database:
|
||||
|
||||
{python}
|
||||
Session = sessionmaker(autoflush=True, transactional=True)
|
||||
sess = Session()
|
||||
u1 = User(name='jack')
|
||||
sess.save(u1)
|
||||
|
||||
# reload user1
|
||||
u2 = sess.query(User).filter_by(name='jack').one()
|
||||
assert u2 is u1
|
||||
|
||||
# commit session, flushes whatever is remaining
|
||||
sess.commit()
|
||||
|
||||
Autoflush is particularly handy when using "dynamic" mapper relations, so that changes to the underlying collection are immediately available via its query interface.
|
||||
|
||||
### Expunge / Clear
|
||||
|
||||
Expunge removes an object from the Session, sending persistent instances to the detached state, and pending instances to the transient state:
|
||||
|
||||
{python}
|
||||
session.expunge(obj1)
|
||||
|
||||
Use `expunge` when youd like to remove an object altogether from memory, such as before calling `del` on it, which will prevent any "ghost" operations occuring when the session is flushed.
|
||||
|
||||
This `clear()` method is equivalent to `expunge()`-ing everything from the Session:
|
||||
|
||||
{python}
|
||||
session.clear()
|
||||
|
||||
However note that the `clear()` method does not reset any transactional state or connection resources; therefore what you usually want to call instead of `clear()` is `close()`.
|
||||
|
||||
### Closing
|
||||
|
||||
The `close()` method issues a `clear()`, and releases any transactional/connection resources. When connections are returned to the connection pool, whatever transactional state exists is rolled back.
|
||||
|
||||
When `close()` is called, the `Session` is in the same state as when it was first created, and is safe to be used again. `close()` is especially important when using a contextual session, which remains in memory after usage. By issuing `close()`, the session will be clean for the next request that makes use of it.
|
||||
|
||||
### Refreshing / Expiring
|
||||
|
||||
To assist with the Session's "sticky" behavior of instances which are present, individual objects can have all of their attributes immediately re-loaded from the database, or marked as "expired" which will cause a re-load to occur upon the next access of any of the object's mapped attributes. This includes all relationships, so lazy-loaders will be re-initialized, eager relationships will be repopulated. Any changes marked on the object are discarded:
|
||||
|
||||
{python}
|
||||
# immediately re-load attributes on obj1, obj2
|
||||
session.refresh(obj1)
|
||||
session.refresh(obj2)
|
||||
|
||||
# expire objects obj1, obj2, attributes will be reloaded
|
||||
# on the next access:
|
||||
session.expire(obj1)
|
||||
session.expire(obj2)
|
||||
|
||||
`refresh()` and `expire()` also support being passed a list of individual attribute names in which to be refreshed. These names can reference any attribute, column-based or relation based:
|
||||
|
||||
{python}
|
||||
# immediately re-load the attributes 'hello', 'world' on obj1, obj2
|
||||
session.refresh(obj1, ['hello', 'world'])
|
||||
session.refresh(obj2, ['hello', 'world'])
|
||||
|
||||
# expire the attriibutes 'hello', 'world' objects obj1, obj2, attributes will be reloaded
|
||||
# on the next access:
|
||||
session.expire(obj1, ['hello', 'world'])
|
||||
session.expire(obj2, ['hello', 'world'])
|
||||
|
||||
## Cascades
|
||||
|
||||
Mappers support the concept of configurable *cascade* behavior on `relation()`s. This behavior controls how the Session should treat the instances that have a parent-child relationship with another instance that is operated upon by the Session. Cascade is indicated as a comma-separated list of string keywords, with the possible values `all`, `delete`, `save-update`, `refresh-expire`, `merge`, `expunge`, and `delete-orphan`.
|
||||
|
||||
Cascading is configured by setting the `cascade` keyword argument on a `relation()`:
|
||||
|
||||
{python}
|
||||
mapper(Order, order_table, properties={
|
||||
'items' : relation(Item, items_table, cascade="all, delete-orphan"),
|
||||
'customer' : relation(User, users_table, user_orders_table, cascade="save-update"),
|
||||
})
|
||||
|
||||
The above mapper specifies two relations, `items` and `customer`. The `items` relationship specifies "all, delete-orphan" as its `cascade` value, indicating that all `save`, `update`, `merge`, `expunge`, `refresh` `delete` and `expire` operations performed on a parent `Order` instance should also be performed on the child `Item` instances attached to it (`save` and `update` are cascaded using the `save_or_update()` method, so that the database identity of the instance doesn't matter). The `delete-orphan` cascade value additionally indicates that if an `Item` instance is no longer associated with an `Order`, it should also be deleted. The "all, delete-orphan" cascade argument allows a so-called *lifecycle* relationship between an `Order` and an `Item` object.
|
||||
|
||||
The `customer` relationship specifies only the "save-update" cascade value, indicating most operations will not be cascaded from a parent `Order` instance to a child `User` instance, except for if the `Order` is attached with a particular session, either via the `save()`, `update()`, or `save-update()` method.
|
||||
|
||||
Additionally, when a child item is attached to a parent item that specifies the "save-update" cascade value on the relationship, the child is automatically passed to `save_or_update()` (and the operation is further cascaded to the child item).
|
||||
|
||||
Note that cascading doesn't do anything that isn't possible by manually calling Session methods on individual instances within a hierarchy, it merely automates common operations on a group of associated instances.
|
||||
|
||||
The default value for `cascade` on `relation()`s is `save-update, merge`.
|
||||
|
||||
## Managing Transactions
|
||||
|
||||
The Session can manage transactions automatically, including across multiple engines. When the Session is in a transaction, as it receives requests to execute SQL statements, it adds each indivdual Connection/Engine encountered to its transactional state. At commit time, all unflushed data is flushed, and each individual transaction is committed. If the underlying databases support two-phase semantics, this may be used by the Session as well if two-phase transactions are enabled.
|
||||
|
||||
The easiest way to use a Session with transactions is just to declare it as transactional. The session will remain in a transaction at all times:
|
||||
|
||||
{python}
|
||||
# transactional session
|
||||
Session = sessionmaker(transactional=True)
|
||||
sess = Session()
|
||||
item1 = sess.query(Item).get(1)
|
||||
item2 = sess.query(Item).get(2)
|
||||
item1.foo = 'bar'
|
||||
item2.bar = 'foo'
|
||||
|
||||
# commit- will immediately go into a new transaction afterwards
|
||||
sess.commit()
|
||||
|
||||
Alternatively, a transaction can be begun explicitly using `begin()`:
|
||||
|
||||
{python}
|
||||
# non transactional session
|
||||
Session = sessionmaker(transactional=False)
|
||||
sess = Session()
|
||||
sess.begin()
|
||||
try:
|
||||
item1 = sess.query(Item).get(1)
|
||||
item2 = sess.query(Item).get(2)
|
||||
item1.foo = 'bar'
|
||||
item2.bar = 'foo'
|
||||
except:
|
||||
sess.rollback()
|
||||
raise
|
||||
sess.commit()
|
||||
|
||||
Session also supports Python 2.5's with statement so that the example above can be written as:
|
||||
|
||||
{python}
|
||||
Session = sessionmaker(transactional=False)
|
||||
sess = Session()
|
||||
with sess.begin():
|
||||
item1 = sess.query(Item).get(1)
|
||||
item2 = sess.query(Item).get(2)
|
||||
item1.foo = 'bar'
|
||||
item2.bar = 'foo'
|
||||
|
||||
For MySQL and Postgres (and soon Oracle), "nested" transactions can be accomplished which use SAVEPOINT behavior, via the `begin_nested()` method:
|
||||
|
||||
{python}
|
||||
Session = sessionmaker(transactional=False)
|
||||
sess = Session()
|
||||
sess.begin()
|
||||
sess.save(u1)
|
||||
sess.save(u2)
|
||||
sess.flush()
|
||||
|
||||
sess.begin_nested() # establish a savepoint
|
||||
sess.save(u3)
|
||||
sess.rollback() # rolls back u3, keeps u1 and u2
|
||||
|
||||
sess.commit() # commits u1 and u2
|
||||
|
||||
Finally, for MySQL, Postgres, and soon Oracle as well, the session can be instructed to use two-phase commit semantics using the flag `twophase=True`, which coordinates transactions across multiple databases:
|
||||
|
||||
{python}
|
||||
engine1 = create_engine('postgres://db1')
|
||||
engine2 = create_engine('postgres://db2')
|
||||
|
||||
Session = sessionmaker(twophase=True, transactional=True)
|
||||
|
||||
# bind User operations to engine 1, Account operations to engine 2
|
||||
Session.configure(binds={User:engine1, Account:engine2})
|
||||
|
||||
sess = Session()
|
||||
|
||||
# .... work with accounts and users
|
||||
|
||||
# commit. session will issue a flush to all DBs, and a prepare step to all DBs,
|
||||
# before committing both transactions
|
||||
sess.commit()
|
||||
|
||||
## Embedding SQL Insert/Update Expressions into a Flush {@name=flushsql}
|
||||
|
||||
This feature allows the value of a database column to be set to a SQL expression instead of a literal value. It's especially useful for atomic updates, calling stored procedures, etc. All you do is assign an expression to an attribute:
|
||||
|
||||
{python}
|
||||
class SomeClass(object):
|
||||
pass
|
||||
mapper(SomeClass, some_table)
|
||||
|
||||
someobject = session.query(SomeClass).get(5)
|
||||
|
||||
# set 'value' attribute to a SQL expression adding one
|
||||
someobject.value = some_table.c.value + 1
|
||||
|
||||
# issues "UPDATE some_table SET value=value+1"
|
||||
session.commit()
|
||||
|
||||
This works both for INSERT and UPDATE statements. After the flush/commit operation, the `value` attribute on `someobject` gets "deferred", so that when you again access it the newly generated value will be loaded from the database. This is the same mechanism at work when database-side column defaults fire off.
|
||||
|
||||
## Using SQL Expressions with Sessions {@name=sql}
|
||||
|
||||
SQL constructs and string statements can be executed via the `Session`. You'd want to do this normally when your `Session` is transactional and youd like your free-standing SQL statements to participate in the same transaction.
|
||||
|
||||
The two ways to do this are to use the connection/execution services of the Session, or to have your Session participate in a regular SQL transaction.
|
||||
|
||||
First, a Session thats associated with an Engine or Connection can execute statements immediately (whether or not its transactional):
|
||||
|
||||
{python}
|
||||
Session = sessionmaker(bind=engine, transactional=True)
|
||||
sess = Session()
|
||||
result = sess.execute("select * from table where id=:id", {'id':7})
|
||||
result2 = sess.execute(select([mytable], mytable.c.id==7))
|
||||
|
||||
To get at the current connection used by the session, which will be part of the current transaction if one is in progress, use `connection()`:
|
||||
|
||||
{python}
|
||||
connection = sess.connection()
|
||||
|
||||
A second scenario is that of a Session which is not directly bound to a connectable. This session executes statements relative to a particular `Mapper`, since the mappers are bound to tables which are in turn bound to connectables via their `MetaData` (either the session or the mapped tables need to be bound). In this case, the Session can conceivably be associated with multiple databases through different mappers; so it wants you to send along a `mapper` argument, which can be any mapped class or mapper instance:
|
||||
|
||||
{python}
|
||||
# session is *not* bound to an engine or connection
|
||||
Session = sessionmaker(transactional=True)
|
||||
sess = Session()
|
||||
|
||||
# need to specify mapper or class when executing
|
||||
result = sess.execute("select * from table where id=:id", {'id':7}, mapper=MyMappedClass)
|
||||
result2 = sess.execute(select([mytable], mytable.c.id==7), mapper=MyMappedClass)
|
||||
|
||||
# need to specify mapper or class when you call connection()
|
||||
connection = sess.connection(MyMappedClass)
|
||||
|
||||
The third scenario is when you are using `Connection` and `Transaction` yourself, and want the `Session` to participate. This is easy, as you just bind the `Session` to the connection:
|
||||
|
||||
{python}
|
||||
# non-transactional session
|
||||
Session = sessionmaker(transactional=False)
|
||||
|
||||
# non-ORM connection + transaction
|
||||
conn = engine.connect()
|
||||
trans = conn.begin()
|
||||
|
||||
# bind the Session *instance* to the connection
|
||||
sess = Session(bind=conn)
|
||||
|
||||
# ... etc
|
||||
|
||||
trans.commit()
|
||||
|
||||
It's safe to use a `Session` which is transactional or autoflushing, as well as to call `begin()`/`commit()` on the session too; the outermost Transaction object, the one we declared explicitly, controls the scope of the transaction.
|
||||
|
||||
When using the `threadlocal` engine context, things are that much easier; the `Session` uses the same connection/transaction as everyone else in the current thread, whether or not you explicitly bind it:
|
||||
|
||||
{python}
|
||||
engine = create_engine('postgres://mydb', strategy="threadlocal")
|
||||
engine.begin()
|
||||
|
||||
sess = Session() # session takes place in the transaction like everyone else
|
||||
|
||||
# ... go nuts
|
||||
|
||||
engine.commit() # commit the transaction
|
||||
|
||||
## Contextual/Thread-local Sessions {@name=contextual}
|
||||
|
||||
A common need in applications, particularly those built around web frameworks, is the ability to "share" a `Session` object among disparate parts of an application, without needing to pass the object explicitly to all method and function calls. What you're really looking for is some kind of "global" session object, or at least "global" to all the parts of an application which are tasked with servicing the current request. For this pattern, SQLAlchemy provides the ability to enhance the `Session` class generated by `sessionmaker()` to provide auto-contextualizing support. This means that whenever you create a `Session` instance with its constructor, you get an *existing* `Session` object which is bound to some "context". By default, this context is the current thread. This feature is what previously was accomplished using the `sessioncontext` SQLAlchemy extension.
|
||||
|
||||
### Creating a Thread-local Context {@name=creating}
|
||||
|
||||
The `scoped_session()` function wraps around the `sessionmaker()` function, and produces an object which behaves the same as the `Session` subclass returned by `sessionmaker()`:
|
||||
|
||||
{python}
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
Session = scoped_session(sessionmaker(autoflush=True, transactional=True))
|
||||
|
||||
However, when you instantiate this `Session` "class", in reality the object is pulled from a threadlocal variable, or if it doesn't exist yet, it's created using the underlying class generated by `sessionmaker()`:
|
||||
|
||||
{python}
|
||||
>>> # call Session() the first time. the new Session instance is created.
|
||||
>>> sess = Session()
|
||||
|
||||
>>> # later, in the same application thread, someone else calls Session()
|
||||
>>> sess2 = Session()
|
||||
|
||||
>>> # the two Session objects are *the same* object
|
||||
>>> sess is sess2
|
||||
True
|
||||
|
||||
Since the `Session()` constructor now returns the same `Session` object every time within the current thread, the object returned by `scoped_session()` also implements most of the `Session` methods and properties at the "class" level, such that you don't even need to instantiate `Session()`:
|
||||
|
||||
{python}
|
||||
# create some objects
|
||||
u1 = User()
|
||||
u2 = User()
|
||||
|
||||
# save to the contextual session, without instantiating
|
||||
Session.save(u1)
|
||||
Session.save(u2)
|
||||
|
||||
# view the "new" attribute
|
||||
assert u1 in Session.new
|
||||
|
||||
# flush changes (if not using autoflush)
|
||||
Session.flush()
|
||||
|
||||
# commit transaction (if using a transactional session)
|
||||
Session.commit()
|
||||
|
||||
To "dispose" of the `Session`, theres two general approaches. One is to close out the current session, but to leave it assigned to the current context. This allows the same object to be re-used on another operation. This may be called from a current, instantiated `Session`:
|
||||
|
||||
{python}
|
||||
sess.close()
|
||||
|
||||
Or, when using `scoped_session()`, the `close()` method may also be called as a classmethod on the `Session` "class":
|
||||
|
||||
{python}
|
||||
Session.close()
|
||||
|
||||
When the `Session` is closed, it remains attached, but clears all of its contents and releases any ongoing transactional resources, including rolling back any remaining transactional state. The `Session` can then be used again.
|
||||
|
||||
The other method is to remove the current session from the current context altogether. This is accomplished using the classmethod `remove()`:
|
||||
|
||||
{python}
|
||||
Session.remove()
|
||||
|
||||
After `remove()` is called, the next call to `Session()` will create a *new* `Session` object which then becomes the contextual session.
|
||||
|
||||
That, in a nutshell, is all there really is to it. Now for all the extra things one should know.
|
||||
|
||||
### Lifespan of a Contextual Session {@name=lifespan}
|
||||
|
||||
A (really, really) common question is when does the contextual session get created, when does it get disposed ? We'll consider a typical lifespan as used in a web application:
|
||||
|
||||
{diagram}
|
||||
Web Server Web Framework User-defined Controller Call
|
||||
-------------- -------------- ------------------------------
|
||||
web request ->
|
||||
call controller -> # call Session(). this establishes a new,
|
||||
# contextual Session.
|
||||
sess = Session()
|
||||
|
||||
# load some objects, save some changes
|
||||
objects = sess.query(MyClass).all()
|
||||
|
||||
# some other code calls Session, its the
|
||||
# same contextual session as "sess"
|
||||
sess2 = Session()
|
||||
sess2.save(foo)
|
||||
sess2.commit()
|
||||
|
||||
# generate content to be returned
|
||||
return generate_content()
|
||||
Session.remove() <-
|
||||
web response <-
|
||||
|
||||
Above, we illustrate a *typical* organization of duties, where the "Web Framework" layer has some integration built-in to manage the span of ORM sessions. Upon the initial handling of an incoming web request, the framework passes control to a controller. The controller then calls `Session()` when it wishes to work with the ORM; this method establishes the contextual Session which will remain until it's removed. Disparate parts of the controller code may all call `Session()` and will get the same session object. Then, when the controller has completed and the reponse is to be sent to the web server, the framework **closes out** the current contextual session, above using the `remove()` method which removes the session from the context altogether.
|
||||
|
||||
As an alternative, the "finalization" step can also call `Session.close()`, which will leave the same session object in place. Which one is better ? For a web framework which runs from a fixed pool of threads, it doesn't matter much. For a framework which runs a **variable** number of threads, or which **creates and disposes** of a thread for each request, `remove()` is better, since it leaves no resources associated with the thread which might not exist.
|
||||
|
||||
* Why close out the session at all ? Why not just leave it going so the next request doesn't have to do as many queries ?
|
||||
|
||||
There are some cases where you may actually want to do this. However, this is a special case where you are dealing with data which **does not change** very often, or you don't care about the "freshness" of the data. In reality, a single thread of a web server may, on a slow day, sit around for many minutes or even hours without being accessed. When it's next accessed, if data from the previous request still exists in the session, that data may be very stale indeed. So its generally better to have an empty session at the start of a web request.
|
||||
|
||||
### Associating Classes and Mappers with a Contextual Session {@name=associating}
|
||||
|
||||
Another luxury we gain, when we've established a `Session()` that can be globally accessed, is the ability for mapped classes and objects to provide us with session-oriented functionality automatically. When using the `scoped_session()` function, we access this feature using the `mapper` attribute on the object in place of the normal `sqlalchemy.orm.mapper` function:
|
||||
|
||||
{python}
|
||||
# "contextual" mapper function
|
||||
mapper = Session.mapper
|
||||
|
||||
# use normally
|
||||
mapper(User, users_table, properties={
|
||||
relation(Address)
|
||||
})
|
||||
mapper(Address, addresses_table)
|
||||
|
||||
When we use the contextual `mapper()` function, our `User` and `Address` now gain a new attribute `query`, which will create a `Query` object for us against the contextual session:
|
||||
|
||||
{python}
|
||||
wendy = User.query.filter_by(name='wendy').one()
|
||||
|
||||
#### Auto-Save Behavior with Contextual Session's Mapper {@name=autosave}
|
||||
|
||||
By default, when using Session.mapper, **new instances are saved into the contextual session automatically upon construction;** there is no longer a need to call `save()`:
|
||||
|
||||
{python}
|
||||
>>> newuser = User(name='ed')
|
||||
>>> assert newuser in Session.new
|
||||
True
|
||||
|
||||
The auto-save functionality can cause problems, namely that any `flush()` which occurs before a newly constructed object is fully populated will result in that object being INSERTed without all of its attributes completed. As a `flush()` is more frequent when using sessions with `autoflush=True`, **the auto-save behavior can be disabled**, using the `save_on_init=False` flag:
|
||||
|
||||
{python}
|
||||
# "contextual" mapper function
|
||||
mapper = Session.mapper
|
||||
|
||||
# use normally, specify no save on init:
|
||||
mapper(User, users_table, properties={
|
||||
relation(Address)
|
||||
}, save_on_init=False)
|
||||
mapper(Address, addresses_table, save_on_init=False)
|
||||
|
||||
# objects now again require expicit "save"
|
||||
>>> newuser = User(name='ed')
|
||||
>>> assert newuser in Session.new
|
||||
False
|
||||
|
||||
>>> Session.save(newuser)
|
||||
>>> assert newuser in Session.new
|
||||
True
|
||||
|
||||
The functionality of `Session.mapper` is an updated version of what used to be accomplished by the `assignmapper()` SQLAlchemy extension.
|
||||
|
||||
[Generated docstrings for scoped_session()](rel:docstrings_sqlalchemy.orm_modfunc_scoped_session)
|
||||
|
||||
## Partitioning Strategies
|
||||
|
||||
this section is TODO
|
||||
|
||||
### Vertical Partitioning
|
||||
|
||||
Vertical partitioning places different kinds of objects, or different tables, across multiple databases.
|
||||
|
||||
{python}
|
||||
engine1 = create_engine('postgres://db1')
|
||||
engine2 = create_engine('postgres://db2')
|
||||
|
||||
Session = sessionmaker(twophase=True, transactional=True)
|
||||
|
||||
# bind User operations to engine 1, Account operations to engine 2
|
||||
Session.configure(binds={User:engine1, Account:engine2})
|
||||
|
||||
sess = Session()
|
||||
|
||||
### Horizontal Partitioning
|
||||
|
||||
Horizontal partitioning partitions the rows of a single table (or a set of tables) across multiple databases.
|
||||
|
||||
See the "sharding" example in [attribute_shard.py](http://www.sqlalchemy.org/trac/browser/sqlalchemy/trunk/examples/sharding/attribute_shard.py)
|
||||
|
||||
## Extending Session
|
||||
|
||||
Extending the session can be achieved through subclassing as well as through a simple extension class, which resembles the style of [advdatamapping_mapper_extending](rel:advdatamapping_mapper_extending) called [SessionExtension](rel:docstrings_sqlalchemy.orm.session_SessionExtension). See the docstrings for more information on this class' methods.
|
||||
|
||||
Basic usage is similar to `MapperExtension`:
|
||||
|
||||
{python}
|
||||
class MySessionExtension(SessionExtension):
|
||||
def before_commit(self, session):
|
||||
print "before commit!"
|
||||
|
||||
Session = sessionmaker(extension=MySessionExtension())
|
||||
|
||||
or with `create_session()`:
|
||||
|
||||
{python}
|
||||
sess = create_session(extension=MySessionExtension())
|
||||
|
||||
The same `SessionExtension` instance can be used with any number of sessions.
|
||||
Vendored
-523
@@ -1,523 +0,0 @@
|
||||
Tutorial
|
||||
========
|
||||
This tutorial provides a relatively simple walking tour through the basic concepts of SQLAlchemy. You may wish to skip it and dive into the [main manual][manual] which is more reference-oriented. The examples in this tutorial comprise a fully working interactive Python session, and are guaranteed to be functioning courtesy of [doctest][].
|
||||
|
||||
[doctest]: http://www.python.org/doc/lib/module-doctest.html
|
||||
[manual]: rel:metadata
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
### Installing SQLAlchemy {@name=sqlalchemy}
|
||||
|
||||
Installing SQLAlchemy from scratch is most easily achieved with [setuptools][]. ([setuptools installation][install setuptools]). Just run this from the command-line:
|
||||
|
||||
# easy_install SQLAlchemy
|
||||
|
||||
This command will download the latest version of SQLAlchemy from the [Python Cheese Shop][cheese] and install it to your system.
|
||||
|
||||
[setuptools]: http://peak.telecommunity.com/DevCenter/setuptools
|
||||
[install setuptools]: http://peak.telecommunity.com/DevCenter/EasyInstall#installation-instructions
|
||||
[cheese]: http://cheeseshop.python.org/pypi/SQLAlchemy
|
||||
|
||||
Otherwise, you can install from the distribution using the `setup.py` script:
|
||||
|
||||
# python setup.py install
|
||||
|
||||
### Installing a Database API {@name=dbms}
|
||||
|
||||
SQLAlchemy is designed to operate with a [DBAPI](http://www.python.org/doc/peps/pep-0249/) implementation built for a particular database, and includes support for the most popular databases. If you have one of the [supported DBAPI implementations](rel:dbengine_supported), you can proceed to the following section. Otherwise [SQLite][] is an easy-to-use database to get started with, which works with plain files or in-memory databases.
|
||||
|
||||
SQLite is included with Python 2.5 and greater.
|
||||
|
||||
If you are working with Python 2.3 or 2.4, SQLite and the Python API for SQLite can be installed from the following packages:
|
||||
|
||||
* [pysqlite][] - Python interface for SQLite
|
||||
* [SQLite library](http://sqlite.org)
|
||||
|
||||
Note that the SQLite library download is not required with Windows, as the Windows Pysqlite library already includes it linked in. Pysqlite and SQLite can also be installed on Linux or FreeBSD via pre-made [packages][pysqlite packages] or [from sources][pysqlite].
|
||||
|
||||
[sqlite]: http://sqlite.org/
|
||||
[pysqlite]: http://pysqlite.org/
|
||||
[pysqlite packages]: http://initd.org/tracker/pysqlite/wiki/PysqlitePackages
|
||||
|
||||
Getting Started {@name=gettingstarted}
|
||||
--------------------------
|
||||
### Checking the Version
|
||||
|
||||
**Note: This tutorial is oriented towards version 0.4 of SQLAlchemy. ** Check the version of SQLAlchemy you have installed via:
|
||||
|
||||
{python}
|
||||
>>> import sqlalchemy
|
||||
>>> sqlalchemy.__version__ # doctest: +SKIP
|
||||
0.4.0
|
||||
|
||||
### Imports
|
||||
|
||||
To start connecting to databases and begin issuing queries, we want to import the base of SQLAlchemy's functionality, which is provided under the module name of `sqlalchemy`. For the purposes of this tutorial, we will import its full list of symbols into our own local namespace.
|
||||
|
||||
{python}
|
||||
>>> from sqlalchemy import *
|
||||
|
||||
Note that importing using the `*` operator pulls all the names from `sqlalchemy` into the local module namespace, which in a real application can produce name conflicts. Therefore its recommended in practice to either import the individual symbols desired (i.e. `from sqlalchemy import Table, Column`) or to import under a distinct namespace (i.e. `import sqlalchemy as sa`).
|
||||
|
||||
### Connecting to the Database
|
||||
|
||||
After our imports, the next thing we need is a handle to the desired database, represented by an `Engine` object. This object handles the business of managing connections and dealing with the specifics of a particular database. Below, we will make a SQLite connection to a file-based database called "tutorial.db".
|
||||
|
||||
{python}
|
||||
>>> db = create_engine('sqlite:///tutorial.db')
|
||||
|
||||
Technically, the above statement did not make an actual connection to the sqlite database just yet. As soon as we begine working with the engine, it will start creating connections. In the case of SQLite, the `tutorial.db` file will actually be created at the moment it is first used, if the file does not exist already.
|
||||
|
||||
For full information on creating database engines, including those for SQLite and others, see [dbengine](rel:dbengine).
|
||||
|
||||
SQLAlchemy is Two Libraries in One {@name=twoinone}
|
||||
----------------------------------------------------
|
||||
|
||||
Now that the basics of installing SQLAlchemy and connecting to our database are established, we can start getting in to actually doing something. But first, a little bit of explanation is required.
|
||||
|
||||
A central concept of SQLAlchemy is that it actually contains two distinct areas of functionality, one of which builds upon the other. One is a **SQL Construction Language** and the other is an **Object Relational Mapper** ("ORM" for short). The SQL construction language allows you to construct objects called `ClauseElements` which represent SQL expressions. These ClauseElements can then be executed against any database, where they are **compiled** into strings that are appropriate for the target database, and return an object called a `ResultProxy`, which is essentially a result set object that acts very much like a deluxe version of the dbapi `cursor` object.
|
||||
|
||||
The Object Relational Mapper (ORM) is a set of tools completely distinct from the SQL Construction Language which serve the purpose of mapping Python object instances into database rows, providing a rich selection interface with which to retrieve instances from tables as well as a comprehensive solution to persisting changes on those instances back into the database. When working with the ORM, its underlying workings as well as its public API make extensive use of the SQL Construction Language, however the general theory of operation is slightly different. Instead of working with database rows directly, you work with your own user-defined classes and object instances. Additionally, the method of issuing queries to the database is different, as the ORM handles the job of generating most of the SQL required, and instead requires more information about what kind of class instances you'd like to load and where you'd like to put them.
|
||||
|
||||
Where SA is somewhat unique, more powerful, and slightly more complicated is that the two areas of functionality can be mixed together in many ways. A key strategy to working with SA effectively is to have a solid awareness of these two distinct toolsets, and which concepts of SA belong to each - even some publications have confused the SQL Construction Language with the ORM. The key difference between the two is that when you're working with cursor-like result sets its the SQL Construction Language, and when working with collections of your own class instances its the Object Relational Mapper.
|
||||
|
||||
This tutorial will first focus on the basic configuration that is common to using both the SQL Construction Language as well as the ORM, which is to declare information about your database called **table metadata**. This will be followed by some constructed SQL examples, and then into usage of the ORM utilizing the same data we established in the SQL construction examples.
|
||||
|
||||
Working with Database Objects {@name=schemasql}
|
||||
-----------------------------------------------
|
||||
|
||||
### Defining Metadata, Binding to Engines {@name=metadata}
|
||||
|
||||
Configuring SQLAlchemy for your database consists of creating objects called `Tables`, each of which represent an actual table in the database. A collection of `Table` objects resides in a `MetaData` object which is essentially a table collection. We will create a `MetaData` and connect it to our `Engine` (connecting a schema object to an Engine is called *binding*):
|
||||
|
||||
{python}
|
||||
>>> metadata = MetaData()
|
||||
>>> metadata.bind = db
|
||||
|
||||
An equivalent operation is to create the `MetaData` object directly with the Engine:
|
||||
|
||||
{python}
|
||||
>>> metadata = MetaData(db)
|
||||
|
||||
Now, when we tell "metadata" about the tables in our database, we can issue CREATE statements for those tables, as well as execute SQL statements derived from them, without needing to open or close any connections; that will be all done automatically.
|
||||
|
||||
Note that SQLALchemy allows us to use explicit connection objects for everything, if we wanted to, and there are reasons why you might want to do this. But for the purposes of this tutorial, using `bind` removes the need for us to deal with explicit connections.
|
||||
|
||||
### Creating a Table {@name=table_creating}
|
||||
|
||||
With `metadata` as our established home for tables, lets make a Table for it:
|
||||
|
||||
{python}
|
||||
>>> users_table = Table('users', metadata,
|
||||
... Column('user_id', Integer, primary_key=True),
|
||||
... Column('user_name', String(40)),
|
||||
... Column('password', String(15))
|
||||
... )
|
||||
|
||||
As you might have guessed, we have just defined a table named `users` which has three columns: `user_id` (which is a primary key column), `user_name` and `password`. Currently it is just an object that doesn't necessarily correspond to an existing table in our database. To actually create the table, we use the `create()` method. To make it interesting, we will have SQLAlchemy echo the SQL statements it sends to the database, by setting the `echo` flag on the `Engine` associated with our `MetaData`:
|
||||
|
||||
{python}
|
||||
>>> metadata.bind.echo = True
|
||||
>>> users_table.create() # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE
|
||||
CREATE TABLE users (
|
||||
user_id INTEGER NOT NULL,
|
||||
user_name VARCHAR(40),
|
||||
password VARCHAR(15),
|
||||
PRIMARY KEY (user_id)
|
||||
)
|
||||
...
|
||||
|
||||
Alternatively, the `users` table might already exist (such as, if you're running examples from this tutorial for the second time), in which case you can just skip the `create()` method call. You can even skip defining the individual columns in the `users` table and ask SQLAlchemy to load its definition from the database:
|
||||
|
||||
{python}
|
||||
>>> users_table = Table('users', metadata, autoload=True)
|
||||
>>> list(users_table.columns)[0].name
|
||||
'user_id'
|
||||
|
||||
Loading a table's columns from the database is called **reflection**. Documentation on table metadata, including reflection, is available in [metadata](rel:metadata).
|
||||
|
||||
### Inserting Rows
|
||||
|
||||
Inserting is achieved via the `insert()` method, which defines a *clause object* (known as a `ClauseElement`) representing an INSERT statement:
|
||||
|
||||
{python}
|
||||
>>> i = users_table.insert()
|
||||
>>> i # doctest:+ELLIPSIS
|
||||
<sqlalchemy.sql.Insert object at 0x...>
|
||||
>>> # the string form of the Insert object is a generic SQL representation
|
||||
>>> print i
|
||||
INSERT INTO users (user_id, user_name, password) VALUES (?, ?, ?)
|
||||
|
||||
Since we created this insert statement object from the `users` table which is bound to our `Engine`, the statement itself is also bound to the `Engine`, and supports executing itself. The `execute()` method of the clause object will *compile* the object into a string according to the underlying *dialect* of the Engine to which the statement is bound, and will then execute the resulting statement.
|
||||
|
||||
{python}
|
||||
>>> # insert a single row
|
||||
>>> i.execute(user_name='Mary', password='secure') # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE
|
||||
INSERT INTO users (user_name, password) VALUES (?, ?)
|
||||
['Mary', 'secure']
|
||||
COMMIT
|
||||
<sqlalchemy.engine.base.ResultProxy object at 0x...>
|
||||
|
||||
>>> # insert multiple rows simultaneously
|
||||
>>> i.execute({'user_name':'Tom'}, {'user_name':'Fred'}, {'user_name':'Harry'}) # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE
|
||||
INSERT INTO users (user_name) VALUES (?)
|
||||
[['Tom'], ['Fred'], ['Harry']]
|
||||
COMMIT
|
||||
<sqlalchemy.engine.base.ResultProxy object at 0x...>
|
||||
|
||||
|
||||
Note that the `VALUES` clause of each `INSERT` statement was automatically adjusted to correspond to the parameters sent to the `execute()` method. This is because the compilation step of a `ClauseElement` takes into account not just the constructed SQL object and the specifics of the type of database being used, but the execution parameters sent along as well.
|
||||
|
||||
When constructing clause objects, SQLAlchemy will bind all literal values into bind parameters. On the construction side, bind parameters are always treated as named parameters. At compilation time, SQLAlchemy will convert them into their proper format, based on the paramstyle of the underlying DBAPI. This works equally well for all named and positional bind parameter formats described in the DBAPI specification.
|
||||
|
||||
Documentation on inserting: [sql_insert](rel:sql_insert).
|
||||
|
||||
### Selecting
|
||||
|
||||
Let's check that the data we have put into `users` table is actually there. The procedure is analogous to the insert example above, except you now call the `select()` method off the `users` table:
|
||||
|
||||
{python}
|
||||
>>> s = users_table.select()
|
||||
>>> print s
|
||||
SELECT users.user_id, users.user_name, users.password
|
||||
FROM users
|
||||
>>> r = s.execute()
|
||||
SELECT users.user_id, users.user_name, users.password
|
||||
FROM users
|
||||
[]
|
||||
|
||||
This time, we won't ignore the return value of `execute()`. Its an instance of `ResultProxy`, which is a result-holding object that behaves very similarly to the `cursor` object one deals with directly with a database API:
|
||||
|
||||
{python}
|
||||
>>> r # doctest:+ELLIPSIS
|
||||
<sqlalchemy.engine.base.ResultProxy object at 0x...>
|
||||
>>> r.fetchone()
|
||||
(1, u'Mary', u'secure')
|
||||
>>> r.fetchall()
|
||||
[(2, u'Tom', None), (3, u'Fred', None), (4, u'Harry', None)]
|
||||
|
||||
Query criterion for the select is specified using Python expressions, using the `Column` objects in the `Table` as a base. All expressions constructed from `Column` objects are themselves instances of `ClauseElements`, just like the `Select`, `Insert`, and `Table` objects themselves.
|
||||
|
||||
{python}
|
||||
>>> r = users_table.select(users_table.c.user_name=='Harry').execute()
|
||||
SELECT users.user_id, users.user_name, users.password
|
||||
FROM users
|
||||
WHERE users.user_name = ?
|
||||
['Harry']
|
||||
>>> row = r.fetchone()
|
||||
>>> print row
|
||||
(4, u'Harry', None)
|
||||
|
||||
Pretty much the full range of standard SQL operations are supported as constructed Python expressions, including joins, ordering, grouping, functions, correlated subqueries, unions, etc. Documentation on selecting: [sql_select](rel:sql_select).
|
||||
|
||||
### Working with Rows
|
||||
|
||||
You can see that when we print out the rows returned by an execution result, it prints the rows as tuples. These rows support both the list and dictionary interfaces. The dictionary interface allows the addressing of columns by string column name, or even the original `Column` object:
|
||||
|
||||
{python}
|
||||
>>> row.keys()
|
||||
[u'user_id', u'user_name', u'password']
|
||||
>>> row['user_id'], row[1], row[users_table.c.password]
|
||||
(4, u'Harry', None)
|
||||
|
||||
Addressing the columns in a row based on the original `Column` object is especially handy, as it eliminates the need to work with literal column names altogether.
|
||||
|
||||
Result sets also support iteration. We'll show this with a slightly different form of `select` that allows you to specify the specific columns to be selected:
|
||||
|
||||
{python}
|
||||
>>> for row in select([users_table.c.user_id, users_table.c.user_name]).execute(): # doctest:+NORMALIZE_WHITESPACE
|
||||
... print row
|
||||
SELECT users.user_id, users.user_name
|
||||
FROM users
|
||||
[]
|
||||
(1, u'Mary')
|
||||
(2, u'Tom')
|
||||
(3, u'Fred')
|
||||
(4, u'Harry')
|
||||
|
||||
### Table Relationships {@name=table_relationships}
|
||||
|
||||
Lets create a second table, `email_addresses`, which references the `users` table. To define the relationship between the two tables, we will use the `ForeignKey` construct. We will also issue the `CREATE` statement for the table:
|
||||
|
||||
{python}
|
||||
>>> email_addresses_table = Table('email_addresses', metadata,
|
||||
... Column('address_id', Integer, primary_key=True),
|
||||
... Column('email_address', String(100), nullable=False),
|
||||
... Column('user_id', Integer, ForeignKey('users.user_id')))
|
||||
>>> email_addresses_table.create() # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE
|
||||
CREATE TABLE email_addresses (
|
||||
address_id INTEGER NOT NULL,
|
||||
email_address VARCHAR(100) NOT NULL,
|
||||
user_id INTEGER,
|
||||
PRIMARY KEY (address_id),
|
||||
FOREIGN KEY(user_id) REFERENCES users (user_id)
|
||||
)
|
||||
...
|
||||
|
||||
Above, the `email_addresses` table is related to the `users` table via the `ForeignKey('users.user_id')`. The `ForeignKey` constructor can take a `Column` object or a string representing the table and column name. When using the string argument, the referenced table must exist within the same `MetaData` object; thats where it looks for the other table!
|
||||
|
||||
Next, lets put a few rows in:
|
||||
|
||||
{python}
|
||||
>>> email_addresses_table.insert().execute(
|
||||
... {'email_address':'tom@tom.com', 'user_id':2},
|
||||
... {'email_address':'mary@mary.com', 'user_id':1}) #doctest:+ELLIPSIS
|
||||
INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?)
|
||||
[['tom@tom.com', 2], ['mary@mary.com', 1]]
|
||||
COMMIT
|
||||
<sqlalchemy.engine.base.ResultProxy object at 0x...>
|
||||
|
||||
With two related tables, we can now construct a join amongst them using the `join` method:
|
||||
|
||||
{python}
|
||||
>>> r = users_table.join(email_addresses_table).select(order_by=users_table.c.user_id).execute()
|
||||
SELECT users.user_id, users.user_name, users.password, email_addresses.address_id, email_addresses.email_address, email_addresses.user_id
|
||||
FROM users JOIN email_addresses ON users.user_id = email_addresses.user_id ORDER BY users.user_id
|
||||
[]
|
||||
>>> print [row for row in r]
|
||||
[(1, u'Mary', u'secure', 2, u'mary@mary.com', 1), (2, u'Tom', None, 1, u'tom@tom.com', 2)]
|
||||
|
||||
The `join` method is also a standalone function in the `sqlalchemy` namespace. The join condition is figured out from the foreign keys of the Table objects given. The condition (also called the "ON clause") can be specified explicitly, such as in this example which creates a join representing all users that used their email address as their password:
|
||||
|
||||
{python}
|
||||
>>> print join(users_table, email_addresses_table,
|
||||
... and_(users_table.c.user_id==email_addresses_table.c.user_id,
|
||||
... users_table.c.password==email_addresses_table.c.email_address)
|
||||
... )
|
||||
users JOIN email_addresses ON users.user_id = email_addresses.user_id AND users.password = email_addresses.email_address
|
||||
|
||||
Working with Object Mappers {@name=orm}
|
||||
-----------------------------------------------
|
||||
|
||||
Now that we have a little bit of Table and SQL operations covered, lets look into SQLAlchemy's ORM (object relational mapper). With the ORM, you associate Tables (and other *Selectable* units, like queries and table aliases) with Python classes, into units called **Mappers**. Then you can execute queries that return lists of object instances, instead of result sets. The object instances themselves are associated with an object called a **Session**, which automatically tracks changes on each object and supports a "save all at once" operation called a **flush**.
|
||||
|
||||
To start, we will import the names necessary to use SQLAlchemy's ORM, again using `import *` for simplicities sake, even though we all know that in real life we should be importing individual names via "`from sqlalchemy.orm import symbol1, symbol2, ...`" or "`import sqlalchemy.orm as orm`":
|
||||
|
||||
{python}
|
||||
>>> from sqlalchemy.orm import *
|
||||
|
||||
It should be noted that the above step is technically not needed when working with the 0.3 series of SQLAlchemy; all symbols from the `orm` package are also included in the `sqlalchemy` package. However, a future release (most likely the 0.4 series) will make the separate `orm` import required in order to use the object relational mapper, so its a good practice for now.
|
||||
|
||||
### Creating a Mapper {@name=mapper}
|
||||
|
||||
A Mapper is usually created once per Python class, and at its core primarily means to say, "objects of this class are to be stored as rows in this table". Lets create a class called `User`, which will represent a user object that is stored in our `users` table:
|
||||
|
||||
{python}
|
||||
>>> class User(object):
|
||||
... def __repr__(self):
|
||||
... return "%s(%r,%r)" % (
|
||||
... self.__class__.__name__, self.user_name, self.password)
|
||||
|
||||
The class is a new style class (i.e. it extends `object`) and does not require a constructor (although one may be provided if desired). We just have one `__repr__` method on it which will display basic information about the User. Note that the `__repr__` method references the instance variables `user_name` and `password` which otherwise aren't defined. While we are free to explicitly define these attributes and treat them normally, this is optional; as SQLAlchemy's `Mapper` construct will manage them for us, since their names correspond to the names of columns in the `users` table. Lets create a mapper, and observe that these attributes are now defined:
|
||||
|
||||
{python}
|
||||
>>> mapper(User, users_table) # doctest: +ELLIPSIS
|
||||
<sqlalchemy.orm.mapper.Mapper object at 0x...>
|
||||
>>> u1 = User()
|
||||
>>> print u1.user_name
|
||||
None
|
||||
>>> print u1.password
|
||||
None
|
||||
|
||||
The `mapper` function returns a new instance of `Mapper`. As it is the first Mapper we have created for the `User` class, it is known as the classes' *primary mapper*. We generally don't need to hold onto the return value of the `mapper` function; SA can automatically locate this Mapper as needed when it deals with the `User` class.
|
||||
|
||||
### Obtaining a Session {@name=session}
|
||||
|
||||
After you create a Mapper, all operations with that Mapper require the usage of an important object called a `Session`. All objects loaded or saved by the Mapper must be *attached* to a `Session` object, which represents a kind of "workspace" of objects that are loaded into memory. A particular object instance can only be attached to one `Session` at a time (but of course can be moved around or detached altogether).
|
||||
|
||||
By default, you have to create a `Session` object explicitly before you can load or save objects. Theres several ways to manage sessions, but the most straightforward is to just create one, which we will do by saying, `create_session()`:
|
||||
|
||||
{python}
|
||||
>>> session = create_session()
|
||||
>>> session # doctest:+ELLIPSIS
|
||||
<sqlalchemy.orm.session.Session object at 0x...>
|
||||
|
||||
### The Query Object {@name=query}
|
||||
|
||||
The Session has all kinds of methods on it to manage and inspect its collection of objects. The Session also provides an easy interface which can be used to query the database, by giving you an instance to a `Query` object corresponding to a particular Python class:
|
||||
|
||||
{python}
|
||||
>>> query = session.query(User)
|
||||
>>> print query.filter_by(user_name='Harry').all()
|
||||
SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, users.password AS users_password
|
||||
FROM users
|
||||
WHERE users.user_name = ? ORDER BY users.oid
|
||||
['Harry']
|
||||
[User(u'Harry',None)]
|
||||
|
||||
All querying for objects is performed via an instance of `Query`. The various `select` methods on an instance of `Mapper` also use an underlying `Query` object to perform the operation. A `Query` is always bound to a specific `Session`.
|
||||
|
||||
Lets turn off the database echoing for a moment, and try out a few methods on `Query`. The two methods used to narrow results are `filter()` and `filter_by()`, and the two most common methods used to load results are `all()` and `first()`. The `get()` method is used for a quick lookup by primary key. `filter_by()` works with keyword arguments, and `filter()` works with `ClauseElement` objects, which are constructed by using `Column` objects inside of Python expressions, in the same way as we did with our SQL select example in the previous section of this tutorial. Using `ClauseElement` structures to query objects is more verbose but more flexible:
|
||||
|
||||
{python}
|
||||
>>> metadata.bind.echo = False
|
||||
>>> print query.filter(User.c.user_id==3).all()
|
||||
[User(u'Fred',None)]
|
||||
>>> print query.get(2)
|
||||
User(u'Tom',None)
|
||||
>>> print query.filter_by(user_name='Mary').first()
|
||||
User(u'Mary',u'secure')
|
||||
>>> print query.filter(User.c.password==None).first()
|
||||
User(u'Tom',None)
|
||||
>>> print query.count()
|
||||
4
|
||||
|
||||
Notice that our `User` class has a special attribute `c` attached to it. This 'c' represents the columns on the User's mapper's Table object. Saying `User.c.user_name` is synonymous with saying `users_table.c.user_name`, recalling that `User` is the Python class and `users_table` is our `Table` object.
|
||||
|
||||
### Making Changes {@name=changes}
|
||||
|
||||
With a little experience in loading objects, lets see what its like to make changes. First, lets create a new user "Ed". We do this by just constructing the new object. Then, we just add it to the session:
|
||||
|
||||
{python}
|
||||
>>> ed = User()
|
||||
>>> ed.user_name = 'Ed'
|
||||
>>> ed.password = 'edspassword'
|
||||
>>> session.save(ed)
|
||||
>>> ed in session
|
||||
True
|
||||
|
||||
Lets also make a few changes on some of the objects in the database. We will load them with our `Query` object, and then change some things.
|
||||
|
||||
{python}
|
||||
>>> mary = query.filter_by(user_name='Mary').first()
|
||||
>>> harry = query.filter_by(user_name='Harry').first()
|
||||
>>> mary.password = 'marysnewpassword'
|
||||
>>> harry.password = 'harrysnewpassword'
|
||||
|
||||
At the moment, nothing has been saved to the database; all of our changes are in memory only. What happens if some other part of the application also tries to load 'Mary' from the database and make some changes before we had a chance to save it ? Assuming that the same `Session` is used, loading 'Mary' from the database a second time will issue a second query in order locate the primary key of 'Mary', but will *return the same object instance as the one already loaded*. This behavior is due to an important property of the `Session` known as the **identity map**:
|
||||
|
||||
{python}
|
||||
>>> mary2 = query.filter_by(user_name='Mary').first()
|
||||
>>> mary is mary2
|
||||
True
|
||||
|
||||
With the identity map, a single `Session` can be relied upon to keep all loaded instances straight.
|
||||
|
||||
As far as the issue of the same object being modified in two different Sessions, that's an issue of concurrency detection; SQLAlchemy does some basic concurrency checks when saving objects, with the option for a stronger check using version ids. See [advdatamapping_arguments](rel:advdatamapping_arguments) for more details.
|
||||
|
||||
### Saving {@name=saving}
|
||||
|
||||
With a new user "ed" and some changes made on "Mary" and "Harry", lets also mark "Fred" as deleted:
|
||||
|
||||
{python}
|
||||
>>> fred = query.filter_by(user_name='Fred').first()
|
||||
>>> session.delete(fred)
|
||||
|
||||
Then to send all of our changes to the database, we `flush()` the Session. Lets turn echo back on to see this happen!:
|
||||
|
||||
{python}
|
||||
>>> metadata.bind.echo = True
|
||||
>>> session.flush()
|
||||
BEGIN
|
||||
UPDATE users SET password=? WHERE users.user_id = ?
|
||||
['marysnewpassword', 1]
|
||||
UPDATE users SET password=? WHERE users.user_id = ?
|
||||
['harrysnewpassword', 4]
|
||||
INSERT INTO users (user_name, password) VALUES (?, ?)
|
||||
['Ed', 'edspassword']
|
||||
DELETE FROM users WHERE users.user_id = ?
|
||||
[3]
|
||||
COMMIT
|
||||
|
||||
### Relationships
|
||||
|
||||
When our User object contains relationships to other kinds of information, such as a list of email addresses, we can indicate this by using a function when creating the `Mapper` called `relation()`. While there is a lot you can do with relations, we'll cover a simple one here. First, recall that our `users` table has a foreign key relationship to another table called `email_addresses`. A single row in `email_addresses` has a column `user_id` that references a row in the `users` table; since many rows in the `email_addresses` table can reference a single row in `users`, this is called a *one to many* relationship.
|
||||
|
||||
To illustrate this relationship, we will start with a new mapper configuration. Since our `User` class has a mapper assigned to it, we want to discard it and start over again. So we issue the `clear_mappers()` function first, which removes all mapping associations from classes:
|
||||
|
||||
{python}
|
||||
>>> clear_mappers()
|
||||
|
||||
When removing mappers, it is usually best to remove all mappings at the same time, since mappers usually have relationships to each other which will become invalid if only part of the mapper collection is removed. In practice, a particular mapping setup will usually remain throughout the lifetime of an application. Clearing out the mappers and making new ones is a practice that is generally limited to writing mapper unit tests and experimenting from the console.
|
||||
|
||||
Next, we want to create a class/mapping that corresponds to the `email_addresses` table. We will create a new class `Address` which represents a single row in the `email_addresses` table, and a corresponding `Mapper` which will associate the `Address` class with the `email_addresses` table:
|
||||
|
||||
{python}
|
||||
>>> class Address(object):
|
||||
... def __init__(self, email_address):
|
||||
... self.email_address = email_address
|
||||
... def __repr__(self):
|
||||
... return "%s(%r)" % (
|
||||
... self.__class__.__name__, self.email_address)
|
||||
>>> mapper(Address, email_addresses_table) # doctest: +ELLIPSIS
|
||||
<sqlalchemy.orm.mapper.Mapper object at 0x...>
|
||||
|
||||
We then create a mapper for the `User` class which contains a relationship to the `Address` class using the `relation()` function:
|
||||
|
||||
{python}
|
||||
>>> mapper(User, users_table, properties={ # doctest: +ELLIPSIS
|
||||
... 'addresses':relation(Address)
|
||||
... })
|
||||
<sqlalchemy.orm.mapper.Mapper object at 0x...>
|
||||
|
||||
Since we've made new mappers, we have to throw away the old `Query` object and get a new one:
|
||||
|
||||
>>> query = session.query(User)
|
||||
|
||||
The `relation()` function takes either a class or a Mapper as its first argument, and has many options to further control its behavior. When this mapping relationship is used, each new `User` instance will contain an attribute called `addresses`. SQLAlchemy will automatically determine that this relationship is a one-to-many relationship, and will subsequently create `addresses` as a list. When a new `User` is created, this list will begin as empty.
|
||||
|
||||
The order in which the mapping definitions for `User` and `Address` is created is *not significant*. When the `mapper()` function is called, it creates an *uncompiled* mapping record corresponding to the given class/table combination. When the mappers are first used, the entire collection of mappers created up until that point will be compiled, which involves the establishment of class instrumentation as well as the resolution of all mapping relationships.
|
||||
|
||||
Lets try out this new mapping configuration, and see what we get for the email addresses already in the database. Since we have made a new mapping configuration, its best that we clear out our `Session`, which is currently holding onto every `User` object we have already loaded:
|
||||
|
||||
{python}
|
||||
>>> session.clear()
|
||||
|
||||
We can then treat the `addresses` attribute on each `User` object like a regular list:
|
||||
|
||||
{python}
|
||||
>>> mary = query.filter_by(user_name='Mary').first() # doctest: +NORMALIZE_WHITESPACE
|
||||
SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, users.password AS users_password
|
||||
FROM users
|
||||
WHERE users.user_name = ? ORDER BY users.oid
|
||||
LIMIT 1 OFFSET 0
|
||||
['Mary']
|
||||
>>> print [a for a in mary.addresses]
|
||||
SELECT email_addresses.address_id AS email_addresses_address_id, email_addresses.email_address AS email_addresses_email_address, email_addresses.user_id AS email_addresses_user_id
|
||||
FROM email_addresses
|
||||
WHERE ? = email_addresses.user_id ORDER BY email_addresses.oid
|
||||
[1]
|
||||
[Address(u'mary@mary.com')]
|
||||
|
||||
Adding to the list is just as easy. New `Address` objects will be detected and saved when we `flush` the Session:
|
||||
|
||||
{python}
|
||||
>>> mary.addresses.append(Address('mary2@gmail.com'))
|
||||
>>> session.flush() # doctest: +NORMALIZE_WHITESPACE
|
||||
BEGIN
|
||||
INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?)
|
||||
['mary2@gmail.com', 1]
|
||||
COMMIT
|
||||
|
||||
|
||||
|
||||
Main documentation for using mappers: [datamapping](rel:datamapping)
|
||||
|
||||
### Transactions
|
||||
|
||||
You may have noticed from the example above that when we say `session.flush()`, SQLAlchemy indicates the names `BEGIN` and `COMMIT` to indicate a transaction with the database. The `flush()` method, since it may execute many statements in a row, will automatically use a transaction in order to execute these instructions. But what if we want to use `flush()` inside of a larger transaction? The easiest way is to use a "transactional" session; that is, when the session is created, you're automatically in a transaction which you can commit or rollback at any time. As a bonus, it offers the ability to call `flush()` for you, whenever a query is issued; that way whatever changes you've made can be returned right back (and since its all in a transaction, nothing gets committed until you tell it so).
|
||||
|
||||
Below, we create a session with `autoflush=True`, which implies that it's transactional. We can query for things as soon as they are created without the need for calling `flush()`. At the end, we call `commit()` to persist everything permanently.
|
||||
|
||||
{python}
|
||||
>>> metadata.bind.echo = False
|
||||
>>> session = create_session(autoflush=True)
|
||||
>>> (ed, harry, mary) = session.query(User).filter(
|
||||
... User.c.user_name.in_(['Ed', 'Harry', 'Mary'])
|
||||
... ).order_by(User.c.user_name).all() # doctest: +NORMALIZE_WHITESPACE
|
||||
>>> del mary.addresses[1]
|
||||
>>> harry_address = Address('harry2@gmail.com')
|
||||
>>> harry.addresses.append(harry_address)
|
||||
>>> session.query(User).join('addresses').filter_by(email_address='harry2@gmail.com').first() # doctest: +NORMALIZE_WHITESPACE
|
||||
User(u'Harry',u'harrysnewpassword')
|
||||
>>> session.commit()
|
||||
|
||||
Main documentation: [unitofwork](rel:unitofwork)
|
||||
|
||||
Next Steps
|
||||
----------
|
||||
|
||||
That covers a quick tour through the basic idea of SQLAlchemy, in its simplest form. Beyond that, one should familiarize oneself with the basics of Sessions, the various patterns that can be used to define different kinds of Mappers and relations among them, the rudimentary SQL types that are available when constructing Tables, and the basics of Engines, SQL statements, and database Connections.
|
||||
|
||||
Vendored
-155
@@ -1,155 +0,0 @@
|
||||
The Types System {@name=types}
|
||||
================
|
||||
|
||||
The package `sqlalchemy.types` defines the datatype identifiers which may be used when defining [metadata](rel:table metadata). This package includes a set of generic types, a set of SQL-specific subclasses of those types, and a small extension system used by specific database connectors to adapt these generic types into database-specific type objects.
|
||||
|
||||
### Built-in Types {@name=standard}
|
||||
|
||||
SQLAlchemy comes with a set of standard generic datatypes, which are defined as classes. Types are usually used when defining tables, and can be left as a class or instantiated, for example:
|
||||
|
||||
{python}
|
||||
mytable = Table('mytable', metadata,
|
||||
Column('myid', Integer, primary_key=True),
|
||||
Column('data', String(30)),
|
||||
Column('info', Unicode(100)),
|
||||
Column('value', Number(7,4))
|
||||
)
|
||||
|
||||
Following is a rundown of the standard types.
|
||||
|
||||
#### String
|
||||
|
||||
This type is the base type for all string and character types, such as `Unicode`, `TEXT`, `CLOB`, etc. By default it generates a VARCHAR in DDL. It includes an argument `length`, which indicates the length in characters of the type, as well as `convert_unicode` and `assert_unicode`, which are booleans. `length` will be used as the length argument when generating DDL. If `length` is omitted, the `String` type resolves into the `TEXT` type.
|
||||
|
||||
`convert_unicode=True` indicates that incoming strings, if they are Python `unicode` strings, will be encoded into a raw bytestring using the `encoding` attribute of the dialect (defaults to `utf-8`). Similarly, raw bytestrings coming back from the database will be decoded into `unicode` objects on the way back.
|
||||
|
||||
`assert_unicode` is set to `None` by default. When `True`, it indicates that incoming bind parameters will be checked that they are in fact `unicode` objects, else an error is raised. A value of `'warn'` instead raises a warning. Setting it to `None` indicates that the dialect-level `convert_unicode` setting should take place, whereas setting it to `False` disables it unconditionally (this flag is new as of version 0.4.2).
|
||||
|
||||
Both `convert_unicode` and `assert_unicode` may be set at the engine level as flags to `create_engine()`.
|
||||
|
||||
#### Unicode
|
||||
|
||||
The `Unicode` type is shorthand for `String` with `convert_unicode=True` and `assert_unicode='warn'`. When writing a unicode-aware appication, it is strongly recommended that this type is used, and that only unicode strings are used in the application. By "unicode string" we mean a string with a u, i.e. `u'hello'`. Otherwise, particularly when using the ORM, data will be converted to unicode when it returns from the database, but local data which was generated locally will not be in unicode format, which can create confusion.
|
||||
|
||||
#### Numeric
|
||||
|
||||
TODO
|
||||
|
||||
#### Float
|
||||
|
||||
TODO
|
||||
|
||||
#### Datetime/Date/Time
|
||||
|
||||
TODO
|
||||
|
||||
#### Interval
|
||||
|
||||
TODO
|
||||
|
||||
#### Binary
|
||||
|
||||
TODO
|
||||
|
||||
#### Boolean
|
||||
|
||||
TODO
|
||||
|
||||
#### PickleType
|
||||
|
||||
TODO
|
||||
|
||||
#### SQL-Specific Types {@name=sqlspecific}
|
||||
|
||||
These are subclasses of the generic types and include:
|
||||
|
||||
{python}
|
||||
class FLOAT(Numeric)
|
||||
class TEXT(String)
|
||||
class DECIMAL(Numeric)
|
||||
class INT(Integer)
|
||||
INTEGER = INT
|
||||
class TIMESTAMP(DateTime)
|
||||
class DATETIME(DateTime)
|
||||
class CLOB(String)
|
||||
class VARCHAR(String)
|
||||
class CHAR(String)
|
||||
class BLOB(Binary)
|
||||
class BOOLEAN(Boolean)
|
||||
|
||||
### Dialect Specific Types {@name=dialect}
|
||||
|
||||
Each dialect has its own set of types, many of which are available only within that dialect. For example, MySQL has a `BigInteger` type and Postgres has an `Inet` type. To use these, import them from the module explicitly:
|
||||
|
||||
{python}
|
||||
from sqlalchemy.databases.mysql import MSEnum, MSBigInteger
|
||||
|
||||
table = Table('foo', meta,
|
||||
Column('enumerates', MSEnum('a', 'b', 'c')),
|
||||
Column('id', MSBigInteger)
|
||||
)
|
||||
|
||||
Or some postgres types:
|
||||
|
||||
{python}
|
||||
from sqlalchemy.databases.postgres import PGInet, PGArray
|
||||
|
||||
table = Table('foo', meta,
|
||||
Column('ipaddress', PGInet),
|
||||
Column('elements', PGArray(str)) # PGArray is available in 0.4, and takes a type argument
|
||||
)
|
||||
|
||||
|
||||
### Creating your Own Types {@name=custom}
|
||||
|
||||
User-defined types can be created which can augment the bind parameter and result processing capabilities of the built in types. This is usually achieved using the `TypeDecorator` class, which "decorates" the behavior of any existing type. As of version 0.4.2, the new `process_bind_param()` and `process_result_value()` methods should be used:
|
||||
|
||||
{python}
|
||||
import sqlalchemy.types as types
|
||||
|
||||
class MyType(types.TypeDecorator):
|
||||
"""a type that decorates Unicode, prefixes values with "PREFIX:" on
|
||||
the way in and strips it off on the way out."""
|
||||
|
||||
impl = types.Unicode
|
||||
|
||||
def process_bind_param(self, value, engine):
|
||||
return "PREFIX:" + value
|
||||
|
||||
def process_result_value(self, value, engine):
|
||||
return value[7:]
|
||||
|
||||
def copy(self):
|
||||
return MyType(self.impl.length)
|
||||
|
||||
Note that the "old" way to process bind params and result values, the `convert_bind_param()` and `convert_result_value()` methods, are still available. The downside of these is that when using a type which already processes data such as the `Unicode` type, you need to call the superclass version of these methods directly. Using `process_bind_param()` and `process_result_value()`, user-defined code can return and receive the desired Python data directly.
|
||||
|
||||
As of version 0.4.2, `TypeDecorator` should generally be used for any user-defined type which redefines the behavior of another type, including other `TypeDecorator` subclasses such as `PickleType`, and the new `process_...()` methods described above should be used.
|
||||
|
||||
To build a type object from scratch, which will not have a corresponding database-specific implementation, subclass `TypeEngine`:
|
||||
|
||||
{python}
|
||||
import sqlalchemy.types as types
|
||||
|
||||
class MyType(types.TypeEngine):
|
||||
def __init__(self, precision = 8):
|
||||
self.precision = precision
|
||||
|
||||
def get_col_spec(self):
|
||||
return "MYTYPE(%s)" % self.precision
|
||||
|
||||
def convert_bind_param(self, value, engine):
|
||||
return value
|
||||
|
||||
def convert_result_value(self, value, engine):
|
||||
return value
|
||||
|
||||
Once you make your type, its immediately useable:
|
||||
|
||||
{python}
|
||||
table = Table('foo', meta,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('data', MyType(16))
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
Appendix: Copyright {@name=copyright}
|
||||
================
|
||||
====================
|
||||
Appendix: Copyright
|
||||
====================
|
||||
|
||||
This is the MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||
This is the MIT license: `<http://www.opensource.org/licenses/mit-license.php>`_
|
||||
|
||||
Copyright (c) 2005, 2006, 2007, 2008 Michael Bayer and contributors. SQLAlchemy is a trademark of Michael
|
||||
Bayer.
|
||||
Copyright (c) 2005, 2006, 2007, 2008, 2009, 2010 Michael Bayer and contributors.
|
||||
SQLAlchemy is a trademark of Michael Bayer.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
software and associated documentation files (the "Software"), to deal in the Software
|
||||
Vendored
+457
@@ -0,0 +1,457 @@
|
||||
.. _engines_toplevel:
|
||||
|
||||
================
|
||||
Database Engines
|
||||
================
|
||||
The **Engine** is the starting point for any SQLAlchemy application. It's "home base" for the actual database and its DBAPI, delivered to the SQLAlchemy application through a connection pool and a **Dialect**, which describes how to talk to a specific kind of database/DBAPI combination.
|
||||
|
||||
The general structure is this::
|
||||
|
||||
+-----------+ __________
|
||||
/---| Pool |---\ (__________)
|
||||
+-------------+ / +-----------+ \ +--------+ | |
|
||||
connect() <--| Engine |---x x----| DBAPI |---| database |
|
||||
+-------------+ \ +-----------+ / +--------+ | |
|
||||
\---| Dialect |---/ |__________|
|
||||
+-----------+ (__________)
|
||||
|
||||
Where above, a :class:`~sqlalchemy.engine.Engine` references both a :class:`~sqlalchemy.engine.Dialect` and :class:`~sqlalchemy.pool.Pool`, which together interpret the DBAPI's module functions as well as the behavior of the database.
|
||||
|
||||
Creating an engine is just a matter of issuing a single call, :func:`create_engine()`::
|
||||
|
||||
engine = create_engine('postgresql://scott:tiger@localhost:5432/mydatabase')
|
||||
|
||||
The above engine invokes the ``postgresql`` dialect and a connection pool which references ``localhost:5432``.
|
||||
|
||||
The engine can be used directly to issue SQL to the database. The most generic way is to use connections, which you get via the ``connect()`` method::
|
||||
|
||||
connection = engine.connect()
|
||||
result = connection.execute("select username from users")
|
||||
for row in result:
|
||||
print "username:", row['username']
|
||||
connection.close()
|
||||
|
||||
The connection is an instance of :class:`~sqlalchemy.engine.Connection`, which is a **proxy** object for an actual DBAPI connection. The returned result is an instance of :class:`~sqlalchemy.engine.ResultProxy`, which acts very much like a DBAPI cursor.
|
||||
|
||||
When you say ``engine.connect()``, a new ``Connection`` object is created, and a DBAPI connection is retrieved from the connection pool. Later, when you call ``connection.close()``, the DBAPI connection is returned to the pool; nothing is actually "closed" from the perspective of the database.
|
||||
|
||||
To execute some SQL more quickly, you can skip the ``Connection`` part and just say::
|
||||
|
||||
result = engine.execute("select username from users")
|
||||
for row in result:
|
||||
print "username:", row['username']
|
||||
result.close()
|
||||
|
||||
Where above, the ``execute()`` method on the ``Engine`` does the ``connect()`` part for you, and returns the ``ResultProxy`` directly. The actual ``Connection`` is *inside* the ``ResultProxy``, waiting for you to finish reading the result. In this case, when you ``close()`` the ``ResultProxy``, the underlying ``Connection`` is closed, which returns the DBAPI connection to the pool.
|
||||
|
||||
To summarize the above two examples, when you use a ``Connection`` object, it's known as **explicit execution**. When you don't see the ``Connection`` object, but you still use the ``execute()`` method on the ``Engine``, it's called **explicit, connectionless execution**. A third variant of execution also exists called **implicit execution**; this will be described later.
|
||||
|
||||
The ``Engine`` and ``Connection`` can do a lot more than what we illustrated above; SQL strings are only its most rudimentary function. Later chapters will describe how "constructed SQL" expressions can be used with engines; in many cases, you don't have to deal with the ``Engine`` at all after it's created. The Object Relational Mapper (ORM), an optional feature of SQLAlchemy, also uses the ``Engine`` in order to get at connections; that's also a case where you can often create the engine once, and then forget about it.
|
||||
|
||||
.. _supported_dbapis:
|
||||
|
||||
Supported Databases
|
||||
====================
|
||||
Recall that the ``Dialect`` is used to describe how to talk to a specific kind of database. Dialects are included with SQLAlchemy for many different backends; these can be seen as a Python package within the :mod:`~sqlalchemy.dialect` package. Each dialect requires the appropriate DBAPI drivers to be installed separately.
|
||||
|
||||
Dialects included with SQLAlchemy fall under one of three categories: supported, experimental, and third party. Supported drivers are those which work against the most common databases available in the open source world, including SQLite, PostgreSQL, MySQL, and Firebird. Very popular commercial databases which provide easy access to test platforms are also supported, these currently include MSSQL and Oracle. These dialects are tested frequently and the level of support should be close to 100% for each.
|
||||
|
||||
The experimental category is for drivers against less common database platforms, or commercial platforms for which no freely available and easily usable test platform is provided. These include Access, MaxDB, Informix, and Sybase at the time of this writing. These are not-yet-functioning
|
||||
or partially-functioning dialects for which the SQLAlchemy project is not able to provide regular test support. If you're interested in supporting one of these backends, contact the mailing list.
|
||||
|
||||
There are also third-party dialects available - currently IBM offers a DB2/Informix IDS dialect for SQLAlchemy.
|
||||
|
||||
Downloads for each DBAPI at the time of this writing are as follows:
|
||||
|
||||
* Supported Dialects
|
||||
- PostgreSQL: `psycopg2 <http://www.initd.org/tracker/psycopg/wiki/PsycopgTwo>`_ `pg8000 <http://pybrary.net/pg8000/>`_
|
||||
- PostgreSQL on Jython: `PostgreSQL JDBC Driver <http://jdbc.postgresql.org/>`_
|
||||
- SQLite: `sqlite3 <http://www.python.org/doc/2.5.2/lib/module-sqlite3.html>`_ (included in Python 2.5 or greater) `pysqlite <http://initd.org/tracker/pysqlite>`_
|
||||
- MySQL: `MySQLdb (a.k.a. mysql-python) <http://sourceforge.net/projects/mysql-python>`_ `MySQL Connector/Python <https://launchpad.net/myconnpy>`_
|
||||
- MySQL on Jython: `MySQL Connector/J JDBC driver <http://dev.mysql.com/downloads/connector/j/>`_
|
||||
- Oracle: `cx_Oracle <http://cx-oracle.sourceforge.net/>`_
|
||||
- Oracle on Jython: `Oracle JDBC Driver <http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html>`_
|
||||
- Firebird: `kinterbasdb <http://kinterbasdb.sourceforge.net/>`_
|
||||
- MS-SQL, MSAccess: `pyodbc <http://pyodbc.sourceforge.net/>`_ (recommended) `adodbapi <http://adodbapi.sourceforge.net/>`_ `pymssql <http://pymssql.sourceforge.net/>`_
|
||||
- MS-SQL on Jython: `jTDS JDBC Driver <http://jtds.sourceforge.net/>`_
|
||||
|
||||
* Experimental Dialects
|
||||
- MSAccess: `pyodbc <http://pyodbc.sourceforge.net/>`_
|
||||
- Informix: `informixdb <http://informixdb.sourceforge.net/>`_
|
||||
- Sybase: TODO
|
||||
- MAXDB: `sapdb <http://www.sapdb.org/sapdbapi.html>`_
|
||||
|
||||
* Third Party Dialects
|
||||
- DB2/Informix IDS: `ibm-db <http://code.google.com/p/ibm-db/>`_
|
||||
|
||||
The SQLAlchemy Wiki contains a page of database notes, describing whatever quirks and behaviors have been observed. Its a good place to check for issues with specific databases. `Database Notes <http://www.sqlalchemy.org/trac/wiki/DatabaseNotes>`_
|
||||
|
||||
create_engine() URL Arguments
|
||||
==============================
|
||||
|
||||
SQLAlchemy indicates the source of an Engine strictly via `RFC-1738 <http://rfc.net/rfc1738.html>`_ style URLs, combined with optional keyword arguments to specify options for the Engine. The form of the URL is:
|
||||
|
||||
dialect+driver://username:password@host:port/database
|
||||
|
||||
Dialect names include the identifying name of the SQLAlchemy dialect which include ``sqlite``, ``mysql``, ``postgresql``, ``oracle``, ``mssql``, and ``firebird``. The drivername is the name of the DBAPI to be used to connect to the database using all lowercase letters. If not specified, a "default" DBAPI will be imported if available - this default is typically the most widely known driver available for that backend (i.e. cx_oracle, pysqlite/sqlite3, psycopg2, mysqldb). For Jython connections, specify the `zxjdbc` driver, which is the JDBC-DBAPI bridge included with Jython.
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
# postgresql - psycopg2 is the default driver.
|
||||
pg_db = create_engine('postgresql://scott:tiger@localhost/mydatabase')
|
||||
pg_db = create_engine('postgresql+psycopg2://scott:tiger@localhost/mydatabase')
|
||||
pg_db = create_engine('postgresql+pg8000://scott:tiger@localhost/mydatabase')
|
||||
|
||||
# postgresql on Jython
|
||||
pg_db = create_engine('postgresql+zxjdbc://scott:tiger@localhost/mydatabase')
|
||||
|
||||
# mysql - MySQLdb (mysql-python) is the default driver
|
||||
mysql_db = create_engine('mysql://scott:tiger@localhost/foo')
|
||||
mysql_db = create_engine('mysql+mysqldb://scott:tiger@localhost/foo')
|
||||
|
||||
# mysql on Jython
|
||||
mysql_db = create_engine('mysql+zxjdbc://localhost/foo')
|
||||
|
||||
# mysql with pyodbc (buggy)
|
||||
mysql_db = create_engine('mysql+pyodbc://scott:tiger@some_dsn')
|
||||
|
||||
# oracle - cx_oracle is the default driver
|
||||
oracle_db = create_engine('oracle://scott:tiger@127.0.0.1:1521/sidname')
|
||||
|
||||
# oracle via TNS name
|
||||
oracle_db = create_engine('oracle+cx_oracle://scott:tiger@tnsname')
|
||||
|
||||
# mssql using ODBC datasource names. PyODBC is the default driver.
|
||||
mssql_db = create_engine('mssql://mydsn')
|
||||
mssql_db = create_engine('mssql+pyodbc://mydsn')
|
||||
mssql_db = create_engine('mssql+adodbapi://mydsn')
|
||||
mssql_db = create_engine('mssql+pyodbc://username:password@mydsn')
|
||||
|
||||
SQLite connects to file based databases. The same URL format is used, omitting the hostname, and using the "file" portion as the filename of the database. This has the effect of four slashes being present for an absolute file path::
|
||||
|
||||
# sqlite://<nohostname>/<path>
|
||||
# where <path> is relative:
|
||||
sqlite_db = create_engine('sqlite:///foo.db')
|
||||
|
||||
# or absolute, starting with a slash:
|
||||
sqlite_db = create_engine('sqlite:////absolute/path/to/foo.db')
|
||||
|
||||
To use a SQLite ``:memory:`` database, specify an empty URL::
|
||||
|
||||
sqlite_memory_db = create_engine('sqlite://')
|
||||
|
||||
The :class:`~sqlalchemy.engine.base.Engine` will ask the connection pool for a connection when the ``connect()`` or ``execute()`` methods are called. The default connection pool, :class:`~sqlalchemy.pool.QueuePool`, as well as the default connection pool used with SQLite, :class:`~sqlalchemy.pool.SingletonThreadPool`, will open connections to the database on an as-needed basis. As concurrent statements are executed, :class:`~sqlalchemy.pool.QueuePool` will grow its pool of connections to a default size of five, and will allow a default "overflow" of ten. Since the ``Engine`` is essentially "home base" for the connection pool, it follows that you should keep a single :class:`~sqlalchemy.engine.base.Engine` per database established within an application, rather than creating a new one for each connection.
|
||||
|
||||
Custom DBAPI connect() arguments
|
||||
--------------------------------
|
||||
|
||||
Custom arguments used when issuing the ``connect()`` call to the underlying DBAPI may be issued in three distinct ways. String-based arguments can be passed directly from the URL string as query arguments:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
db = create_engine('postgresql://scott:tiger@localhost/test?argument1=foo&argument2=bar')
|
||||
|
||||
If SQLAlchemy's database connector is aware of a particular query argument, it may convert its type from string to its proper type.
|
||||
|
||||
``create_engine`` also takes an argument ``connect_args`` which is an additional dictionary that will be passed to ``connect()``. This can be used when arguments of a type other than string are required, and SQLAlchemy's database connector has no type conversion logic present for that parameter:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
db = create_engine('postgresql://scott:tiger@localhost/test', connect_args = {'argument1':17, 'argument2':'bar'})
|
||||
|
||||
The most customizable connection method of all is to pass a ``creator`` argument, which specifies a callable that returns a DBAPI connection:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
def connect():
|
||||
return psycopg.connect(user='scott', host='localhost')
|
||||
|
||||
db = create_engine('postgresql://', creator=connect)
|
||||
|
||||
.. _create_engine_args:
|
||||
|
||||
Database Engine Options
|
||||
========================
|
||||
|
||||
Keyword options can also be specified to ``create_engine()``, following the string URL as follows:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
db = create_engine('postgresql://...', encoding='latin1', echo=True)
|
||||
|
||||
Options common to all database dialects are described at :func:`~sqlalchemy.create_engine`.
|
||||
|
||||
More On Connections
|
||||
====================
|
||||
|
||||
Recall from the beginning of this section that the Engine provides a ``connect()`` method which returns a ``Connection`` object. ``Connection`` is a *proxy* object which maintains a reference to a DBAPI connection instance. The ``close()`` method on ``Connection`` does not actually close the DBAPI connection, but instead returns it to the connection pool referenced by the ``Engine``. ``Connection`` will also automatically return its resources to the connection pool when the object is garbage collected, i.e. its ``__del__()`` method is called. When using the standard C implementation of Python, this method is usually called immediately as soon as the object is dereferenced. With other Python implementations such as Jython, this is not so guaranteed.
|
||||
|
||||
The ``execute()`` methods on both ``Engine`` and ``Connection`` can also receive SQL clause constructs as well::
|
||||
|
||||
connection = engine.connect()
|
||||
result = connection.execute(select([table1], table1.c.col1==5))
|
||||
for row in result:
|
||||
print row['col1'], row['col2']
|
||||
connection.close()
|
||||
|
||||
The above SQL construct is known as a ``select()``. The full range of SQL constructs available are described in :ref:`sqlexpression_toplevel`.
|
||||
|
||||
Both ``Connection`` and ``Engine`` fulfill an interface known as ``Connectable`` which specifies common functionality between the two objects, namely being able to call ``connect()`` to return a ``Connection`` object (``Connection`` just returns itself), and being able to call ``execute()`` to get a result set. Following this, most SQLAlchemy functions and objects which accept an ``Engine`` as a parameter or attribute with which to execute SQL will also accept a ``Connection``. This argument is named ``bind``::
|
||||
|
||||
engine = create_engine('sqlite:///:memory:')
|
||||
|
||||
# specify some Table metadata
|
||||
metadata = MetaData()
|
||||
table = Table('sometable', metadata, Column('col1', Integer))
|
||||
|
||||
# create the table with the Engine
|
||||
table.create(bind=engine)
|
||||
|
||||
# drop the table with a Connection off the Engine
|
||||
connection = engine.connect()
|
||||
table.drop(bind=connection)
|
||||
|
||||
.. index::
|
||||
single: thread safety; connections
|
||||
|
||||
Connection facts:
|
||||
|
||||
* the Connection object is **not thread-safe**. While a Connection can be shared among threads using properly synchronized access, this is also not recommended as many DBAPIs have issues with, if not outright disallow, sharing of connection state between threads.
|
||||
* The Connection object represents a single dbapi connection checked out from the connection pool. In this state, the connection pool has no affect upon the connection, including its expiration or timeout state. For the connection pool to properly manage connections, **connections should be returned to the connection pool (i.e. ``connection.close()``) whenever the connection is not in use**. If your application has a need for management of multiple connections or is otherwise long running (this includes all web applications, threaded or not), don't hold a single connection open at the module level.
|
||||
|
||||
Using Transactions with Connection
|
||||
===================================
|
||||
|
||||
The ``Connection`` object provides a ``begin()`` method which returns a ``Transaction`` object. This object is usually used within a try/except clause so that it is guaranteed to ``rollback()`` or ``commit()``::
|
||||
|
||||
trans = connection.begin()
|
||||
try:
|
||||
r1 = connection.execute(table1.select())
|
||||
connection.execute(table1.insert(), col1=7, col2='this is some data')
|
||||
trans.commit()
|
||||
except:
|
||||
trans.rollback()
|
||||
raise
|
||||
|
||||
The ``Transaction`` object also handles "nested" behavior by keeping track of the outermost begin/commit pair. In this example, two functions both issue a transaction on a Connection, but only the outermost Transaction object actually takes effect when it is committed.
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
# method_a starts a transaction and calls method_b
|
||||
def method_a(connection):
|
||||
trans = connection.begin() # open a transaction
|
||||
try:
|
||||
method_b(connection)
|
||||
trans.commit() # transaction is committed here
|
||||
except:
|
||||
trans.rollback() # this rolls back the transaction unconditionally
|
||||
raise
|
||||
|
||||
# method_b also starts a transaction
|
||||
def method_b(connection):
|
||||
trans = connection.begin() # open a transaction - this runs in the context of method_a's transaction
|
||||
try:
|
||||
connection.execute("insert into mytable values ('bat', 'lala')")
|
||||
connection.execute(mytable.insert(), col1='bat', col2='lala')
|
||||
trans.commit() # transaction is not committed yet
|
||||
except:
|
||||
trans.rollback() # this rolls back the transaction unconditionally
|
||||
raise
|
||||
|
||||
# open a Connection and call method_a
|
||||
conn = engine.connect()
|
||||
method_a(conn)
|
||||
conn.close()
|
||||
|
||||
Above, ``method_a`` is called first, which calls ``connection.begin()``. Then it calls ``method_b``. When ``method_b`` calls ``connection.begin()``, it just increments a counter that is decremented when it calls ``commit()``. If either ``method_a`` or ``method_b`` calls ``rollback()``, the whole transaction is rolled back. The transaction is not committed until ``method_a`` calls the ``commit()`` method. This "nesting" behavior allows the creation of functions which "guarantee" that a transaction will be used if one was not already available, but will automatically participate in an enclosing transaction if one exists.
|
||||
|
||||
Note that SQLAlchemy's Object Relational Mapper also provides a way to control transaction scope at a higher level; this is described in :ref:`unitofwork_transaction`.
|
||||
|
||||
.. index::
|
||||
single: thread safety; transactions
|
||||
|
||||
Transaction Facts:
|
||||
|
||||
* the Transaction object, just like its parent Connection, is **not thread-safe**.
|
||||
|
||||
Understanding Autocommit
|
||||
------------------------
|
||||
|
||||
|
||||
The above transaction example illustrates how to use ``Transaction`` so that several executions can take part in the same transaction. What happens when we issue an INSERT, UPDATE or DELETE call without using ``Transaction``? The answer is **autocommit**. While many DBAPIs implement a flag called ``autocommit``, the current SQLAlchemy behavior is such that it implements its own autocommit. This is achieved by detecting statements which represent data-changing operations, i.e. INSERT, UPDATE, DELETE, etc., and then issuing a COMMIT automatically if no transaction is in progress. The detection is based on compiled statement attributes, or in the case of a text-only statement via regular expressions.
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
conn = engine.connect()
|
||||
conn.execute("INSERT INTO users VALUES (1, 'john')") # autocommits
|
||||
|
||||
.. _dbengine_implicit:
|
||||
|
||||
Connectionless Execution, Implicit Execution
|
||||
=============================================
|
||||
|
||||
Recall from the first section we mentioned executing with and without a ``Connection``. ``Connectionless`` execution refers to calling the ``execute()`` method on an object which is not a ``Connection``, which could be on the ``Engine`` itself, or could be a constructed SQL object. When we say "implicit", we mean that we are calling the ``execute()`` method on an object which is neither a ``Connection`` nor an ``Engine`` object; this can only be used with constructed SQL objects which have their own ``execute()`` method, and can be "bound" to an ``Engine``. A description of "constructed SQL objects" may be found in :ref:`sqlexpression_toplevel`.
|
||||
|
||||
A summary of all three methods follows below. First, assume the usage of the following ``MetaData`` and ``Table`` objects; while we haven't yet introduced these concepts, for now you only need to know that we are representing a database table, and are creating an "executable" SQL construct which issues a statement to the database. These objects are described in :ref:`metadata_toplevel`.
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
meta = MetaData()
|
||||
users_table = Table('users', meta,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('name', String(50))
|
||||
)
|
||||
|
||||
Explicit execution delivers the SQL text or constructed SQL expression to the ``execute()`` method of ``Connection``:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
engine = create_engine('sqlite:///file.db')
|
||||
connection = engine.connect()
|
||||
result = connection.execute(users_table.select())
|
||||
for row in result:
|
||||
# ....
|
||||
connection.close()
|
||||
|
||||
Explicit, connectionless execution delivers the expression to the ``execute()`` method of ``Engine``:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
engine = create_engine('sqlite:///file.db')
|
||||
result = engine.execute(users_table.select())
|
||||
for row in result:
|
||||
# ....
|
||||
result.close()
|
||||
|
||||
Implicit execution is also connectionless, and calls the ``execute()`` method on the expression itself, utilizing the fact that either an ``Engine`` or ``Connection`` has been *bound* to the expression object (binding is discussed further in the next section, :ref:`metadata_toplevel`):
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
engine = create_engine('sqlite:///file.db')
|
||||
meta.bind = engine
|
||||
result = users_table.select().execute()
|
||||
for row in result:
|
||||
# ....
|
||||
result.close()
|
||||
|
||||
In both "connectionless" examples, the ``Connection`` is created behind the scenes; the ``ResultProxy`` returned by the ``execute()`` call references the ``Connection`` used to issue the SQL statement. When we issue ``close()`` on the ``ResultProxy``, or if the result set object falls out of scope and is garbage collected, the underlying ``Connection`` is closed for us, resulting in the DBAPI connection being returned to the pool.
|
||||
|
||||
.. _threadlocal_strategy:
|
||||
|
||||
Using the Threadlocal Execution Strategy
|
||||
-----------------------------------------
|
||||
|
||||
The "threadlocal" engine strategy is used by non-ORM applications which wish to bind a transaction to the current thread, such that all parts of the application can participate in that transaction implicitly without the need to explicitly reference a ``Connection``. "threadlocal" is designed for a very specific pattern of use, and is not appropriate unless this very specfic pattern, described below, is what's desired. It has **no impact** on the "thread safety" of SQLAlchemy components or one's application. It also should not be used when using an ORM ``Session`` object, as the ``Session`` itself represents an ongoing transaction and itself handles the job of maintaining connection and transactional resources.
|
||||
|
||||
Enabling ``threadlocal`` is achieved as follows:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
db = create_engine('mysql://localhost/test', strategy='threadlocal')
|
||||
|
||||
When the engine above is used in a "connectionless" style, meaning ``engine.execute()`` is called, a DBAPI connection is retrieved from the connection pool and then associated with the current thread. Subsequent operations on the ``Engine`` while the DBAPI connection remains checked out will make use of the *same* DBAPI connection object. The connection stays allocated until all returned ``ResultProxy`` objects are closed, which occurs for a particular ``ResultProxy`` after all pending results are fetched, or immediately for an operation which returns no rows (such as an INSERT).
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
# execute one statement and receive results. r1 now references a DBAPI connection resource.
|
||||
r1 = db.execute("select * from table1")
|
||||
|
||||
# execute a second statement and receive results. r2 now references the *same* resource as r1
|
||||
r2 = db.execute("select * from table2")
|
||||
|
||||
# fetch a row on r1 (assume more results are pending)
|
||||
row1 = r1.fetchone()
|
||||
|
||||
# fetch a row on r2 (same)
|
||||
row2 = r2.fetchone()
|
||||
|
||||
# close r1. the connection is still held by r2.
|
||||
r1.close()
|
||||
|
||||
# close r2. with no more references to the underlying connection resources, they
|
||||
# are returned to the pool.
|
||||
r2.close()
|
||||
|
||||
The above example does not illustrate any pattern that is particularly useful, as it is not a frequent occurence that two execute/result fetching operations "leapfrog" one another. There is a slight savings of connection pool checkout overhead between the two operations, and an implicit sharing of the same transactional context, but since there is no explicitly declared transaction, this association is short lived.
|
||||
|
||||
The real usage of "threadlocal" comes when we want several operations to occur within the scope of a shared transaction. The ``Engine`` now has ``begin()``, ``commit()`` and ``rollback()`` methods which will retrieve a connection resource from the pool and establish a new transaction, maintaining the connection against the current thread until the transaction is committed or rolled back:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
db.begin()
|
||||
try:
|
||||
call_operation1()
|
||||
call_operation2()
|
||||
db.commit()
|
||||
except:
|
||||
db.rollback()
|
||||
|
||||
``call_operation1()`` and ``call_operation2()`` can make use of the ``Engine`` as a global variable, using the "connectionless" execution style, and their operations will participate in the same transaction:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
def call_operation1():
|
||||
engine.execute("insert into users values (?, ?)", 1, "john")
|
||||
|
||||
def call_operation2():
|
||||
users.update(users.c.user_id==5).execute(name='ed')
|
||||
|
||||
When using threadlocal, operations that do call upon the ``engine.connect()`` method will receive a ``Connection`` that is **outside** the scope of the transaction. This can be used for operations such as logging the status of an operation regardless of transaction success:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
db.begin()
|
||||
conn = db.connect()
|
||||
try:
|
||||
conn.execute(log_table.insert(), message="Operation started")
|
||||
call_operation1()
|
||||
call_operation2()
|
||||
db.commit()
|
||||
conn.execute(log_table.insert(), message="Operation succeeded")
|
||||
except:
|
||||
db.rollback()
|
||||
conn.execute(log_table.insert(), message="Operation failed")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
Functions which are written to use an explicit ``Connection`` object, but wish to participate in the threadlocal transaction, can receive their ``Connection`` object from the ``contextual_connect()`` method, which returns a ``Connection`` that is **inside** the scope of the transaction:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
conn = db.contextual_connect()
|
||||
call_operation3(conn)
|
||||
conn.close()
|
||||
|
||||
Calling ``close()`` on the "contextual" connection does not release the connection resources to the pool if other resources are making use of it. A resource-counting mechanism is employed so that the connection is released back to the pool only when all users of that connection, including the transaction established by ``engine.begin()``, have been completed.
|
||||
|
||||
So remember - if you're not sure if you need to use ``strategy="threadlocal"`` or not, the answer is **no** ! It's driven by a specific programming pattern that is generally not the norm.
|
||||
|
||||
Configuring Logging
|
||||
====================
|
||||
|
||||
Python's standard `logging <http://www.python.org/doc/lib/module-logging.html>`_ module is used to implement informational and debug log output with SQLAlchemy. This allows SQLAlchemy's logging to integrate in a standard way with other applications and libraries. The ``echo`` and ``echo_pool`` flags that are present on ``create_engine()``, as well as the ``echo_uow`` flag used on ``Session``, all interact with regular loggers.
|
||||
|
||||
This section assumes familiarity with the above linked logging module. All logging performed by SQLAlchemy exists underneath the ``sqlalchemy`` namespace, as used by ``logging.getLogger('sqlalchemy')``. When logging has been configured (i.e. such as via ``logging.basicConfig()``), the general namespace of SA loggers that can be turned on is as follows:
|
||||
|
||||
* ``sqlalchemy.engine`` - controls SQL echoing. set to ``logging.INFO`` for SQL query output, ``logging.DEBUG`` for query + result set output.
|
||||
* ``sqlalchemy.pool`` - controls connection pool logging. set to ``logging.INFO`` or lower to log connection pool checkouts/checkins.
|
||||
* ``sqlalchemy.orm`` - controls logging of various ORM functions. set to ``logging.INFO`` for configurational logging as well as unit of work dumps, ``logging.DEBUG`` for extensive logging during query and flush() operations. Subcategories of ``sqlalchemy.orm`` include:
|
||||
* ``sqlalchemy.orm.attributes`` - logs certain instrumented attribute operations, such as triggered callables
|
||||
* ``sqlalchemy.orm.mapper`` - logs Mapper configuration and operations
|
||||
* ``sqlalchemy.orm.unitofwork`` - logs flush() operations, including dependency sort graphs and other operations
|
||||
* ``sqlalchemy.orm.strategies`` - logs relation loader operations (i.e. lazy and eager loads)
|
||||
* ``sqlalchemy.orm.sync`` - logs synchronization of attributes from parent to child instances during a flush()
|
||||
|
||||
For example, to log SQL queries as well as unit of work debugging:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
import logging
|
||||
|
||||
logging.basicConfig()
|
||||
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
|
||||
logging.getLogger('sqlalchemy.orm.unitofwork').setLevel(logging.DEBUG)
|
||||
|
||||
By default, the log level is set to ``logging.ERROR`` within the entire ``sqlalchemy`` namespace so that no log operations occur, even within an application that has logging enabled otherwise.
|
||||
|
||||
The ``echo`` flags present as keyword arguments to ``create_engine()`` and others as well as the ``echo`` property on ``Engine``, when set to ``True``, will first attempt to ensure that logging is enabled. Unfortunately, the ``logging`` module provides no way of determining if output has already been configured (note we are referring to if a logging configuration has been set up, not just that the logging level is set). For this reason, any ``echo=True`` flags will result in a call to ``logging.basicConfig()`` using sys.stdout as the destination. It also sets up a default format using the level name, timestamp, and logger name. Note that this configuration has the affect of being configured **in addition** to any existing logger configurations. Therefore, **when using Python logging, ensure all echo flags are set to False at all times**, to avoid getting duplicate log lines.
|
||||
Vendored
+121
@@ -0,0 +1,121 @@
|
||||
.. _examples_toplevel:
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
The SQLAlchemy distribution includes a variety of code examples illustrating a select set of patterns, some typical and some not so typical. All are runnable and can be found in the ``/examples`` directory of the distribution. Each example contains a README in its ``__init__.py`` file, each of which are listed below.
|
||||
|
||||
Additional SQLAlchemy examples, some user contributed, are available on the wiki at `<http://www.sqlalchemy.org/trac/wiki/UsageRecipes>`_.
|
||||
|
||||
Adjacency List
|
||||
--------------
|
||||
|
||||
Location: /examples/adjacency_list/
|
||||
|
||||
.. automodule:: adjacency_list
|
||||
|
||||
Associations
|
||||
------------
|
||||
|
||||
Location: /examples/association/
|
||||
|
||||
.. automodule:: association
|
||||
|
||||
Attribute Instrumentation
|
||||
-------------------------
|
||||
|
||||
Location: /examples/custom_attributes/
|
||||
|
||||
.. automodule:: custom_attributes
|
||||
|
||||
Beaker Caching
|
||||
--------------
|
||||
|
||||
Location: /examples/beaker_caching/
|
||||
|
||||
.. automodule:: beaker_caching
|
||||
|
||||
Derived Attributes
|
||||
------------------
|
||||
|
||||
Location: /examples/derived_attributes/
|
||||
|
||||
.. automodule:: derived_attributes
|
||||
|
||||
|
||||
Directed Graphs
|
||||
---------------
|
||||
|
||||
Location: /examples/graphs/
|
||||
|
||||
.. automodule:: graphs
|
||||
|
||||
Dynamic Relations as Dictionaries
|
||||
----------------------------------
|
||||
|
||||
Location: /examples/dynamic_dict/
|
||||
|
||||
.. automodule:: dynamic_dict
|
||||
|
||||
Horizontal Sharding
|
||||
-------------------
|
||||
|
||||
Location: /examples/sharding
|
||||
|
||||
.. automodule:: sharding
|
||||
|
||||
Inheritance Mappings
|
||||
--------------------
|
||||
|
||||
Location: /examples/inheritance/
|
||||
|
||||
.. automodule:: inheritance
|
||||
|
||||
Large Collections
|
||||
-----------------
|
||||
|
||||
Location: /examples/large_collection/
|
||||
|
||||
.. automodule:: large_collection
|
||||
|
||||
Nested Sets
|
||||
-----------
|
||||
|
||||
Location: /examples/nested_sets/
|
||||
|
||||
.. automodule:: nested_sets
|
||||
|
||||
Polymorphic Associations
|
||||
------------------------
|
||||
|
||||
Location: /examples/poly_assoc/
|
||||
|
||||
.. automodule:: poly_assoc
|
||||
|
||||
PostGIS Integration
|
||||
-------------------
|
||||
|
||||
Location: /examples/postgis
|
||||
|
||||
.. automodule:: postgis
|
||||
|
||||
Versioned Objects
|
||||
-----------------
|
||||
|
||||
Location: /examples/versioning
|
||||
|
||||
.. automodule:: versioning
|
||||
|
||||
Vertical Attribute Mapping
|
||||
--------------------------
|
||||
|
||||
Location: /examples/vertical
|
||||
|
||||
.. automodule:: vertical
|
||||
|
||||
XML Persistence
|
||||
---------------
|
||||
|
||||
Location: /examples/elementtree/
|
||||
|
||||
.. automodule:: elementtree
|
||||
Vendored
-111
@@ -1,111 +0,0 @@
|
||||
from toc import TOCElement
|
||||
import docstring
|
||||
import re
|
||||
|
||||
from sqlalchemy import schema, types, engine, sql, pool, orm, exceptions, databases, interfaces
|
||||
from sqlalchemy.sql import compiler, expression
|
||||
from sqlalchemy.engine import default, strategies, threadlocal, url
|
||||
import sqlalchemy.orm.shard
|
||||
import sqlalchemy.ext.sessioncontext as sessioncontext
|
||||
import sqlalchemy.ext.selectresults as selectresults
|
||||
import sqlalchemy.ext.orderinglist as orderinglist
|
||||
import sqlalchemy.ext.associationproxy as associationproxy
|
||||
import sqlalchemy.ext.assignmapper as assignmapper
|
||||
import sqlalchemy.ext.sqlsoup as sqlsoup
|
||||
|
||||
def make_doc(obj, classes=None, functions=None, **kwargs):
|
||||
"""generate a docstring.ObjectDoc structure for an individual module, list of classes, and list of functions."""
|
||||
obj = docstring.ObjectDoc(obj, classes=classes, functions=functions, **kwargs)
|
||||
return (obj.name, obj)
|
||||
|
||||
def make_all_docs():
|
||||
"""generate a docstring.AbstractDoc structure."""
|
||||
print "generating docstrings"
|
||||
objects = [
|
||||
make_doc(obj=engine),
|
||||
make_doc(obj=default),
|
||||
make_doc(obj=strategies),
|
||||
make_doc(obj=threadlocal),
|
||||
make_doc(obj=url),
|
||||
make_doc(obj=exceptions),
|
||||
make_doc(obj=interfaces),
|
||||
make_doc(obj=pool),
|
||||
make_doc(obj=schema),
|
||||
#make_doc(obj=sql,include_all_classes=True),
|
||||
make_doc(obj=compiler),
|
||||
make_doc(obj=expression,include_all_classes=True),
|
||||
make_doc(obj=types),
|
||||
make_doc(obj=orm),
|
||||
make_doc(obj=orm.collections, classes=[orm.collections.collection,
|
||||
orm.collections.MappedCollection,
|
||||
orm.collections.CollectionAdapter]),
|
||||
make_doc(obj=orm.interfaces),
|
||||
make_doc(obj=orm.mapperlib, classes=[orm.mapperlib.Mapper]),
|
||||
make_doc(obj=orm.properties),
|
||||
make_doc(obj=orm.query, classes=[orm.query.Query]),
|
||||
make_doc(obj=orm.session, classes=[orm.session.Session, orm.session.SessionExtension]),
|
||||
make_doc(obj=orm.shard),
|
||||
make_doc(obj=associationproxy, classes=[associationproxy.AssociationProxy]),
|
||||
make_doc(obj=orderinglist, classes=[orderinglist.OrderingList]),
|
||||
make_doc(obj=sqlsoup),
|
||||
] + [make_doc(getattr(__import__('sqlalchemy.databases.%s' % m).databases, m)) for m in databases.__all__]
|
||||
return objects
|
||||
|
||||
def create_docstring_toc(data, root):
|
||||
"""given a docstring.AbstractDoc structure, create new TOCElement nodes corresponding
|
||||
to the elements and cross-reference them back to the doc structure."""
|
||||
root = TOCElement("docstrings", name="docstrings", description="API Documentation", parent=root, requires_paged=True)
|
||||
files = []
|
||||
def create_obj_toc(obj, toc):
|
||||
if obj.isclass:
|
||||
s = []
|
||||
for elem in obj.inherits:
|
||||
if isinstance(elem, docstring.ObjectDoc):
|
||||
s.append(elem.name)
|
||||
else:
|
||||
s.append(str(elem))
|
||||
description = "class " + obj.classname + "(%s)" % (','.join(s))
|
||||
filename = toc.filename
|
||||
else:
|
||||
description = obj.description
|
||||
filename = re.sub(r'\W', '_', obj.name)
|
||||
|
||||
toc = TOCElement(filename, obj.name, description, parent=toc, requires_paged=True)
|
||||
obj.toc_path = toc.path
|
||||
if not obj.isclass:
|
||||
create_module_file(obj, toc)
|
||||
files.append(filename)
|
||||
|
||||
if not obj.isclass and obj.functions:
|
||||
functoc = TOCElement(toc.filename, name="modfunc", description="Module Functions", parent=toc)
|
||||
obj.mod_path = functoc.path
|
||||
for func in obj.functions:
|
||||
t = TOCElement(toc.filename, name=func.name, description=func.name + "()", parent=functoc)
|
||||
func.toc_path = t.path
|
||||
#elif obj.functions:
|
||||
# for func in obj.functions:
|
||||
# t = TOCElement(toc.filename, name=func.name, description=func.name, parent=toc)
|
||||
# func.toc_path = t.path
|
||||
|
||||
if obj.classes:
|
||||
for class_ in obj.classes:
|
||||
create_obj_toc(class_, toc)
|
||||
|
||||
for key, obj in data:
|
||||
create_obj_toc(obj, root)
|
||||
return files
|
||||
|
||||
def create_module_file(obj, toc):
|
||||
outname = 'output/%s.html' % toc.filename
|
||||
print "->", outname
|
||||
header = """# -*- coding: utf-8 -*-
|
||||
<%%inherit file="module.html"/>
|
||||
<%%def name="title()">%s - %s</%%def>
|
||||
## This file is generated. Edit the .txt files instead of this one.
|
||||
<%%!
|
||||
filename = '%s'
|
||||
docstring = '%s'
|
||||
%%>
|
||||
""" % (toc.root.doctitle, obj.description, toc.filename, obj.name)
|
||||
file(outname, 'w').write(header)
|
||||
return outname
|
||||
Vendored
-106
@@ -1,106 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import sys,re,os,shutil
|
||||
from os import path
|
||||
import cPickle as pickle
|
||||
|
||||
sys.path = ['../../lib', './lib'] + sys.path
|
||||
|
||||
import sqlalchemy
|
||||
import gen_docstrings, read_markdown, toc
|
||||
from mako.lookup import TemplateLookup
|
||||
from mako import exceptions, runtime
|
||||
import time
|
||||
import optparse
|
||||
|
||||
files = [
|
||||
'index',
|
||||
'documentation',
|
||||
'intro',
|
||||
'ormtutorial',
|
||||
'sqlexpression',
|
||||
'mappers',
|
||||
'session',
|
||||
'dbengine',
|
||||
'metadata',
|
||||
'types',
|
||||
'pooling',
|
||||
'plugins',
|
||||
'docstrings',
|
||||
]
|
||||
|
||||
post_files = [
|
||||
'copyright'
|
||||
]
|
||||
|
||||
v = open(path.join(path.dirname(__file__), '..', '..', 'VERSION'))
|
||||
VERSION = v.readline().strip()
|
||||
v.close()
|
||||
|
||||
parser = optparse.OptionParser(usage = "usage: %prog [options] [tests...]")
|
||||
parser.add_option("--file", action="store", dest="file", help="only generate file <file>")
|
||||
parser.add_option("--docstrings", action="store_true", dest="docstrings", help="only generate docstrings")
|
||||
parser.add_option("--version", action="store", dest="version", default=VERSION, help="version string")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
if options.file:
|
||||
to_gen = [options.file]
|
||||
else:
|
||||
to_gen = files + post_files
|
||||
|
||||
title='SQLAlchemy 0.4 Documentation'
|
||||
version = options.version
|
||||
|
||||
|
||||
root = toc.TOCElement('', 'root', '', version=version, doctitle=title)
|
||||
|
||||
shutil.copy('./content/index.html', './output/index.html')
|
||||
shutil.copy('./content/docstrings.html', './output/docstrings.html')
|
||||
shutil.copy('./content/documentation.html', './output/documentation.html')
|
||||
|
||||
if not options.docstrings:
|
||||
read_markdown.parse_markdown_files(root, [f for f in files if f in to_gen])
|
||||
|
||||
if not options.file or options.docstrings:
|
||||
docstrings = gen_docstrings.make_all_docs()
|
||||
doc_files = gen_docstrings.create_docstring_toc(docstrings, root)
|
||||
|
||||
pickle.dump(docstrings, file('./output/compiled_docstrings.pickle', 'w'))
|
||||
|
||||
if not options.docstrings:
|
||||
read_markdown.parse_markdown_files(root, [f for f in post_files if f in to_gen])
|
||||
|
||||
if not options.file or options.docstrings:
|
||||
pickle.dump(root, file('./output/table_of_contents.pickle', 'w'))
|
||||
|
||||
template_dirs = ['./templates', './output']
|
||||
output = os.path.dirname(os.getcwd())
|
||||
|
||||
lookup = TemplateLookup(template_dirs, output_encoding='utf-8', module_directory='./modules')
|
||||
|
||||
def genfile(name, outname):
|
||||
infile = name + ".html"
|
||||
outfile = file(outname, 'w')
|
||||
print infile, '->', outname
|
||||
t = lookup.get_template(infile)
|
||||
outfile.write(t.render(attributes={}))
|
||||
|
||||
if not options.docstrings:
|
||||
for filename in to_gen:
|
||||
try:
|
||||
genfile(filename, os.path.join(os.getcwd(), '../', filename + ".html"))
|
||||
except:
|
||||
print exceptions.text_error_template().render()
|
||||
|
||||
if not options.file or options.docstrings:
|
||||
for filename in doc_files:
|
||||
try:
|
||||
genfile(filename, os.path.join(os.getcwd(), '../', os.path.basename(filename) + ".html"))
|
||||
except:
|
||||
print exceptions.text_error_template().render()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
Table of Contents
|
||||
=================
|
||||
|
||||
.. toctree::
|
||||
|
||||
intro
|
||||
ormtutorial
|
||||
sqlexpression
|
||||
mappers
|
||||
session
|
||||
dbengine
|
||||
metadata
|
||||
examples
|
||||
reference/index
|
||||
|
||||
Indices and tables
|
||||
------------------
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`search`
|
||||
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
.. _overview_toplevel:
|
||||
|
||||
=======================
|
||||
Overview / Installation
|
||||
=======================
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
|
||||
The SQLAlchemy SQL Toolkit and Object Relational Mapper is a comprehensive set of tools for working with databases and Python. It has several distinct areas of functionality which can be used individually or combined together. Its major components are illustrated below. The arrows represent the general dependencies of components:
|
||||
|
||||
.. image:: sqla_arch_small.jpg
|
||||
|
||||
Above, the two most significant front-facing portions of SQLAlchemy are the **Object Relational Mapper** and the **SQL Expression Language**. SQL Expressions can be used independently of the ORM. When using the ORM, the SQL Expression language remains part of the public facing API as it is used within object-relational configurations and queries.
|
||||
|
||||
Tutorials
|
||||
=========
|
||||
|
||||
* :ref:`ormtutorial_toplevel` - This describes the richest feature of SQLAlchemy, its object relational mapper. If you want to work with higher-level SQL which is constructed automatically for you, as well as management of Python objects, proceed to this tutorial.
|
||||
* :ref:`sqlexpression_toplevel` - The core of SQLAlchemy is its SQL expression language. The SQL Expression Language is a toolkit all its own, independent of the ORM package, which can be used to construct manipulable SQL expressions which can be programmatically constructed, modified, and executed, returning cursor-like result sets. It's a lot more lightweight than the ORM and is appropriate for higher scaling SQL operations. It's also heavily present within the ORM's public facing API, so advanced ORM users will want to master this language as well.
|
||||
|
||||
Main Documentation
|
||||
==================
|
||||
|
||||
* :ref:`datamapping_toplevel` - A comprehensive walkthrough of major ORM patterns and techniques.
|
||||
* :ref:`session_toplevel` - A detailed description of SQLAlchemy's Session object
|
||||
* :ref:`engines_toplevel` - Describes SQLAlchemy's database-connection facilities, including connection documentation and working with connections and transactions.
|
||||
* :ref:`metadata_toplevel` - All about schema management using ``MetaData`` and ``Table`` objects; reading database schemas into your application, creating and dropping tables, constraints, defaults, sequences, indexes.
|
||||
* :ref:`pooling_toplevel` - Further detail about SQLAlchemy's connection pool library.
|
||||
* :ref:`types` - Datatypes included with SQLAlchemy, their functions, as well as how to create your own types.
|
||||
* :ref:`plugins` - Included addons for SQLAlchemy
|
||||
|
||||
API Reference
|
||||
=============
|
||||
|
||||
An organized section of all SQLAlchemy APIs is at :ref:`api_reference_toplevel`.
|
||||
|
||||
Installing SQLAlchemy
|
||||
======================
|
||||
|
||||
Installing SQLAlchemy from scratch is most easily achieved with `setuptools <http://pypi.python.org/pypi/setuptools/>`_. Assuming it's installed, just run this from the command-line:
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
# easy_install SQLAlchemy
|
||||
|
||||
This command will download the latest version of SQLAlchemy from the `Python Cheese Shop <http://pypi.python.org/pypi/SQLAlchemy>`_ and install it to your system.
|
||||
|
||||
* `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
|
||||
* `install setuptools <http://peak.telecommunity.com/DevCenter/EasyInstall#installation-instructions>`_
|
||||
* `pypi <http://pypi.python.org/pypi/SQLAlchemy>`_
|
||||
|
||||
Otherwise, you can install from the distribution using the ``setup.py`` script:
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
# python setup.py install
|
||||
|
||||
Installing a Database API
|
||||
==========================
|
||||
|
||||
SQLAlchemy is designed to operate with a `DB-API <http://www.python.org/doc/peps/pep-0249/>`_ implementation built for a particular database, and includes support for the most popular databases. The current list is at :ref:`supported_dbapis`.
|
||||
|
||||
Checking the Installed SQLAlchemy Version
|
||||
=========================================
|
||||
|
||||
This documentation covers SQLAlchemy version 0.6. If you're working on a system that already has SQLAlchemy installed, check the version from your Python prompt like this:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
>>> import sqlalchemy
|
||||
>>> sqlalchemy.__version__ # doctest: +SKIP
|
||||
0.6.0
|
||||
|
||||
0.5 to 0.6 Migration
|
||||
=====================
|
||||
|
||||
Notes on what's changed from 0.5 to 0.6 is available on the SQLAlchemy wiki at `06Migration <http://www.sqlalchemy.org/trac/wiki/06Migration>`_.
|
||||
Vendored
-181
@@ -1,181 +0,0 @@
|
||||
"""
|
||||
defines a pickleable, recursive "generated python documentation" datastructure.
|
||||
"""
|
||||
|
||||
import re, types, string, inspect
|
||||
|
||||
allobjects = {}
|
||||
|
||||
class AbstractDoc(object):
|
||||
def __init__(self, obj):
|
||||
allobjects[id(obj)] = self
|
||||
self.id = id(obj)
|
||||
self.allobjects = allobjects
|
||||
self.toc_path = None
|
||||
|
||||
class ObjectDoc(AbstractDoc):
|
||||
def __init__(self, obj, functions=None, classes=None, include_all_classes=False):
|
||||
super(ObjectDoc, self).__init__(obj)
|
||||
self.isclass = isinstance(obj, types.ClassType) or isinstance(obj, types.TypeType)
|
||||
self.name= obj.__name__
|
||||
self.include_all_classes = include_all_classes
|
||||
functions = functions
|
||||
classes= classes
|
||||
|
||||
if not self.isclass:
|
||||
if not include_all_classes and hasattr(obj, '__all__'):
|
||||
objects = obj.__all__
|
||||
sort = True
|
||||
else:
|
||||
objects = obj.__dict__.keys()
|
||||
sort = True
|
||||
if functions is None:
|
||||
functions = [getattr(obj, x, None)
|
||||
for x in objects
|
||||
if getattr(obj,x,None) is not None and
|
||||
(isinstance(getattr(obj,x), types.FunctionType))
|
||||
and not self._is_private_name(getattr(obj,x).__name__)
|
||||
]
|
||||
if sort:
|
||||
functions.sort(lambda a, b: cmp(a.__name__, b.__name__))
|
||||
if classes is None:
|
||||
classes = [getattr(obj, x, None) for x in objects
|
||||
if getattr(obj,x,None) is not None and
|
||||
(isinstance(getattr(obj,x), types.TypeType)
|
||||
or isinstance(getattr(obj,x), types.ClassType))
|
||||
and (self.include_all_classes or not self._is_private_name(getattr(obj,x).__name__))
|
||||
]
|
||||
classes = list(set(classes))
|
||||
if sort:
|
||||
classes.sort(lambda a, b: cmp(a.__name__.replace('_', ''), b.__name__.replace('_', '')))
|
||||
else:
|
||||
if functions is None:
|
||||
functions = (
|
||||
[getattr(obj, x).im_func for x in obj.__dict__.keys() if isinstance(getattr(obj,x), types.MethodType)
|
||||
and
|
||||
(getattr(obj, x).__name__ == '__init__' or not self._is_private_name(getattr(obj,x).__name__))
|
||||
] +
|
||||
[(x, getattr(obj, x)) for x in obj.__dict__.keys() if _is_property(getattr(obj,x))
|
||||
and
|
||||
not self._is_private_name(x)
|
||||
]
|
||||
)
|
||||
functions.sort(_method_sort)
|
||||
if classes is None:
|
||||
classes = []
|
||||
|
||||
if self.isclass:
|
||||
self.description = "class " + self.name
|
||||
self.classname = self.name
|
||||
if hasattr(obj, '__mro__'):
|
||||
l = []
|
||||
mro = list(obj.__mro__[1:])
|
||||
mro.reverse()
|
||||
for x in mro:
|
||||
for y in x.__mro__[1:]:
|
||||
if y in l:
|
||||
del l[l.index(y)]
|
||||
l.insert(0, x)
|
||||
self.description += "(" + string.join([x.__name__ for x in l], ',') + ")"
|
||||
self._inherits = [(id(x), x.__name__) for x in l]
|
||||
else:
|
||||
self._inherits = []
|
||||
else:
|
||||
self.description = "module " + self.name
|
||||
|
||||
self.doc = obj.__doc__
|
||||
|
||||
self.functions = []
|
||||
if not self.isclass:
|
||||
for func in functions:
|
||||
self.functions.append(FunctionDoc(func))
|
||||
else:
|
||||
for func in functions:
|
||||
if isinstance(func, types.FunctionType):
|
||||
self.functions.append(MethodDoc(func, self))
|
||||
elif isinstance(func, tuple):
|
||||
self.functions.append(PropertyDoc(func[0], func[1]))
|
||||
|
||||
self.classes = []
|
||||
for class_ in classes:
|
||||
self.classes.append(ObjectDoc(class_))
|
||||
|
||||
def _is_private_name(self, name):
|
||||
if name in ('__weakref__', '__repr__','__str__', '__unicode__',
|
||||
'__getstate__', '__setstate__', '__reduce__',
|
||||
'__reduce_ex__', '__hash__'):
|
||||
return True
|
||||
elif re.match(r'^__.*__$', name):
|
||||
return False
|
||||
elif name.startswith('_'):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _get_inherits(self):
|
||||
for item in self._inherits:
|
||||
if item[0] in self.allobjects:
|
||||
yield self.allobjects[item[0]]
|
||||
else:
|
||||
yield item[1]
|
||||
inherits = property(_get_inherits)
|
||||
def accept_visitor(self, visitor):
|
||||
visitor.visit_object(self)
|
||||
|
||||
def _is_property(elem):
|
||||
return isinstance(elem, property) or (hasattr(elem, '__get__') and hasattr(elem, '__set__'))
|
||||
|
||||
class FunctionDoc(AbstractDoc):
|
||||
def __init__(self, func):
|
||||
super(FunctionDoc, self).__init__(func)
|
||||
argspec = inspect.getargspec(func)
|
||||
argnames = argspec[0]
|
||||
varargs = argspec[1]
|
||||
varkw = argspec[2]
|
||||
defaults = argspec[3] or ()
|
||||
argstrings = []
|
||||
for i in range(0, len(argnames)):
|
||||
if i >= len(argnames) - len(defaults):
|
||||
argstrings.append("%s=%s" % (argnames[i], repr(defaults[i - (len(argnames) - len(defaults))])))
|
||||
else:
|
||||
argstrings.append(argnames[i])
|
||||
if varargs is not None:
|
||||
argstrings.append("*%s" % varargs)
|
||||
if varkw is not None:
|
||||
argstrings.append("**%s" % varkw)
|
||||
self.argstrings = self.arglist = argstrings
|
||||
self.name = func.__name__
|
||||
self.link = func.__name__
|
||||
self.doc = func.__doc__
|
||||
def accept_visitor(self, visitor):
|
||||
visitor.visit_function(self)
|
||||
|
||||
class MethodDoc(FunctionDoc):
|
||||
def __init__(self, func, owner):
|
||||
super(MethodDoc, self).__init__(func)
|
||||
if self.name == '__init__' and not self.doc:
|
||||
self.doc = "Construct a new ``%s``." % owner.name
|
||||
|
||||
class PropertyDoc(AbstractDoc):
|
||||
def __init__(self, name, prop):
|
||||
super(PropertyDoc, self).__init__(prop)
|
||||
self.doc = prop.__doc__
|
||||
self.name = name
|
||||
self.link = name
|
||||
def accept_visitor(self, visitor):
|
||||
visitor.visit_property(self)
|
||||
|
||||
def _method_sort(fna, fnb):
|
||||
a = getattr(fna, '__name__', None) or fna[0]
|
||||
b = getattr(fnb, '__name__', None) or fnb[0]
|
||||
|
||||
if a == '__init__': return -1
|
||||
if b == '__init__': return 1
|
||||
|
||||
a_u = a.startswith('__') and a.endswith('__')
|
||||
b_u = b.startswith('__') and b.endswith('__')
|
||||
|
||||
if a_u and not b_u: return 1
|
||||
if b_u and not a_u: return -1
|
||||
|
||||
return cmp(a, b)
|
||||
Vendored
-389
@@ -1,389 +0,0 @@
|
||||
# $Id$
|
||||
# highlight.py - syntax highlighting functions for Myghty
|
||||
# Copyright (C) 2004 Michael Bayer mike_mp@zzzcomputing.com
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
|
||||
|
||||
import re, StringIO, sys, string, os
|
||||
import token, tokenize, keyword
|
||||
|
||||
# Highlighter - highlights Myghty and Python source code
|
||||
|
||||
__all__ = ['highlight', 'PythonHighlighter', 'MyghtyHighlighter']
|
||||
|
||||
pystyles = {
|
||||
token.ENDMARKER : 'python_operator' ,
|
||||
token.NAME : 'python_name' ,
|
||||
token.NUMBER : 'python_number' ,
|
||||
token.STRING : 'python_literal' ,
|
||||
token.NEWLINE : 'python_operator' ,
|
||||
token.INDENT : 'python_operator' ,
|
||||
token.DEDENT : 'python_operator' ,
|
||||
token.LPAR : 'python_enclosure' ,
|
||||
token.RPAR : 'python_enclosure' ,
|
||||
token.LSQB : 'python_enclosure' ,
|
||||
token.RSQB : 'python_enclosure' ,
|
||||
token.COLON : 'python_operator' ,
|
||||
token.COMMA : 'python_operator' ,
|
||||
token.SEMI : 'python_operator' ,
|
||||
token.PLUS : 'python_operator' ,
|
||||
token.MINUS : 'python_operator' ,
|
||||
token.STAR : 'python_operator' ,
|
||||
token.SLASH : 'python_operator' ,
|
||||
token.VBAR : 'python_operator' ,
|
||||
token.AMPER : 'python_operator' ,
|
||||
token.LESS : 'python_operator' ,
|
||||
token.GREATER : 'python_operator' ,
|
||||
token.EQUAL : 'python_operator' ,
|
||||
token.DOT : 'python_operator' ,
|
||||
token.PERCENT : 'python_operator' ,
|
||||
token.BACKQUOTE : 'python_operator' ,
|
||||
token.LBRACE : 'python_enclosure',
|
||||
token.RBRACE : 'python_enclosure' ,
|
||||
token.EQEQUAL : 'python_operator' ,
|
||||
token.NOTEQUAL : 'python_operator' ,
|
||||
token.LESSEQUAL : 'python_operator' ,
|
||||
token.GREATEREQUAL : 'python_operator' ,
|
||||
token.TILDE : 'python_operator' ,
|
||||
token.CIRCUMFLEX : 'python_operator' ,
|
||||
token.LEFTSHIFT : 'python_operator' ,
|
||||
token.RIGHTSHIFT : 'python_operator' ,
|
||||
token.DOUBLESTAR : 'python_operator' ,
|
||||
token.PLUSEQUAL : 'python_operator' ,
|
||||
token.MINEQUAL : 'python_operator' ,
|
||||
token.STAREQUAL : 'python_operator' ,
|
||||
token.SLASHEQUAL : 'python_operator' ,
|
||||
token.PERCENTEQUAL : 'python_operator' ,
|
||||
token.AMPEREQUAL : 'python_operator' ,
|
||||
token.VBAREQUAL : 'python_operator' ,
|
||||
token.CIRCUMFLEXEQUAL : 'python_operator' ,
|
||||
token.LEFTSHIFTEQUAL : 'python_operator' ,
|
||||
token.RIGHTSHIFTEQUAL : 'python_operator' ,
|
||||
token.DOUBLESTAREQUAL : 'python_operator' ,
|
||||
token.DOUBLESLASH : 'python_operator' ,
|
||||
token.DOUBLESLASHEQUAL : 'python_operator' ,
|
||||
token.OP : 'python_operator' ,
|
||||
token.ERRORTOKEN : 'python_operator' ,
|
||||
token.N_TOKENS : 'python_operator' ,
|
||||
token.NT_OFFSET : 'python_operator' ,
|
||||
tokenize.COMMENT: 'python_comment',
|
||||
}
|
||||
|
||||
html_escapes = {
|
||||
'&' : '&',
|
||||
'>' : '>',
|
||||
'<' : '<',
|
||||
'"' : '"'
|
||||
}
|
||||
|
||||
def do_html_escape(string):
|
||||
#return "@" + re.sub(r"([&<>])", lambda m: html_escapes[m.group()], string) + "+"
|
||||
return re.sub(r"([&<>])", lambda m: html_escapes[m.group()], string)
|
||||
|
||||
def highlight(source, filename = None, syntaxtype = None, html_escape = True):
|
||||
if syntaxtype is not None:
|
||||
highlighter = highlighters.get(syntaxtype, None)
|
||||
elif filename is not None:
|
||||
(root, filename) = os.path.split(filename)
|
||||
highlighter = highlighters.get(filename, None)
|
||||
if highlighter is None:
|
||||
(root, ext) = os.path.splitext(filename)
|
||||
highlighter = highlighters.get(ext, None)
|
||||
else:
|
||||
highlighter = None
|
||||
|
||||
if highlighter is None:
|
||||
if html_escape:
|
||||
return do_html_escape(source)
|
||||
else:
|
||||
return source
|
||||
else:
|
||||
return highlighter(source, html_escape = html_escape).highlight()
|
||||
|
||||
class Highlighter:
|
||||
def __init__(self, source, output = None, html_escape = True):
|
||||
self.source = source
|
||||
self.pos = 0
|
||||
self.html_escape = html_escape
|
||||
if output is None:
|
||||
self.output = StringIO.StringIO()
|
||||
else:
|
||||
self.output = output
|
||||
|
||||
def content(self):
|
||||
return self.output.getvalue()
|
||||
|
||||
def highlight(self):raise NotImplementedError()
|
||||
|
||||
|
||||
def colorize(self, tokens):
|
||||
for pair in tokens:
|
||||
if pair[1] is None:
|
||||
if self.html_escape:
|
||||
self.output.write(do_html_escape(pair[0]))
|
||||
else:
|
||||
self.output.write(pair[0])
|
||||
else:
|
||||
if self.html_escape:
|
||||
self.output.write('<span class="%s">%s</span>' % (pair[1], do_html_escape(pair[0])))
|
||||
else:
|
||||
self.output.write('<span class="%s">%s</span>' % (pair[1], pair[0]))
|
||||
|
||||
|
||||
class PythonHighlighter(Highlighter):
|
||||
|
||||
def _line_grid(self, str, start, end):
|
||||
lines = re.findall(re.compile(r'[^\n]*\n?', re.S), str)
|
||||
r = 0
|
||||
for l in lines[0 : end[0] - start[0]]:
|
||||
r += len(l)
|
||||
r += end[1]
|
||||
return (start, (start[0], r))
|
||||
|
||||
def highlight(self):
|
||||
buf = StringIO.StringIO(self.source)
|
||||
|
||||
# tokenize module not too good at getting the
|
||||
# whitespace at the end of a python block
|
||||
trailingspace = re.search(r"\n([ \t]+$)", self.source, re.S)
|
||||
if trailingspace:
|
||||
trailingspace = trailingspace.group(1)
|
||||
|
||||
curl = -1
|
||||
tokens = []
|
||||
curstyle = None
|
||||
line = None
|
||||
|
||||
for t in tokenize.generate_tokens(lambda: buf.readline()):
|
||||
if t[2][0] != curl:
|
||||
curl = t[2][0]
|
||||
curc = 0
|
||||
|
||||
line = t[4]
|
||||
|
||||
# pick up whitespace and output
|
||||
if t[2][1] > curc:
|
||||
tokens.append(line[curc : t[2][1]])
|
||||
curc = t[2][1]
|
||||
|
||||
if self.get_style(t[0], t[1]) != curstyle:
|
||||
if tokens:
|
||||
self.colorize([(string.join(tokens, ''), curstyle)])
|
||||
tokens = []
|
||||
curstyle = self.get_style(t[0], t[1])
|
||||
|
||||
(start, end) = self._line_grid(line, t[2], t[3])
|
||||
text = line[start[1]:end[1]]
|
||||
|
||||
# special hardcoded rule to allow "interactive" demos without
|
||||
# >>> getting sucked in as >> , > operators
|
||||
if text == '">>>"':
|
||||
text = '>>>'
|
||||
tokens.append(text)
|
||||
curc = t[3][1]
|
||||
curl = t[3][0]
|
||||
|
||||
# any remaining content to output, output it
|
||||
if tokens:
|
||||
self.colorize([(string.join(tokens, ''), curstyle)])
|
||||
|
||||
if trailingspace:
|
||||
self.output.write(trailingspace)
|
||||
|
||||
return self.content()
|
||||
|
||||
def get_style(self, tokenid, str):
|
||||
if tokenid == token.NAME:
|
||||
if keyword.iskeyword(str):
|
||||
return "python_keyword"
|
||||
else:
|
||||
return "python_name"
|
||||
elif tokenid == token.OP:
|
||||
if "()[]{}".find(str) != -1:
|
||||
return "python_enclosure"
|
||||
else:
|
||||
return "python_operator"
|
||||
else:
|
||||
return pystyles.get(tokenid, None)
|
||||
|
||||
class MyghtyHighlighter(Highlighter):
|
||||
|
||||
def _match(self, regexp):
|
||||
|
||||
match = regexp.match(self.source, self.pos)
|
||||
if match:
|
||||
(start, end) = match.span()
|
||||
self.output.write(self.source[self.pos:start])
|
||||
|
||||
if start == end:
|
||||
self.pos = end + 1
|
||||
else:
|
||||
self.pos = end
|
||||
|
||||
return match
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def highlight(self):
|
||||
|
||||
while (self.pos < len(self.source)):
|
||||
if self.match_named_block():
|
||||
continue
|
||||
|
||||
if self.match_block():
|
||||
continue
|
||||
|
||||
if self.match_comp_call():
|
||||
continue
|
||||
|
||||
if self.match_comp_content_call():
|
||||
continue
|
||||
|
||||
if self.match_substitution():
|
||||
continue
|
||||
|
||||
if self.match_line():
|
||||
continue
|
||||
|
||||
if self.match_text():
|
||||
continue;
|
||||
|
||||
break
|
||||
|
||||
return self.content()
|
||||
|
||||
|
||||
def pythonize(self, text):
|
||||
py = PythonHighlighter(text, output = self.output)
|
||||
py.highlight()
|
||||
|
||||
def match_text(self):
|
||||
textmatch = re.compile(r"""
|
||||
(.*?) # anything, followed by:
|
||||
(
|
||||
(?<=\n)(?=[%#]) # an eval or comment line
|
||||
|
|
||||
(?=</?[%&]) # a substitution or block or call start or end
|
||||
# - don't consume
|
||||
|
|
||||
(\\\n) # an escaped newline
|
||||
|
|
||||
\Z # end of string
|
||||
)""", re.X | re.S)
|
||||
|
||||
match = self._match(textmatch)
|
||||
if match:
|
||||
self.colorize([(match.group(1), 'text')])
|
||||
if match.group(3):
|
||||
self.colorize([(match.group(3), 'python_operator')])
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def match_named_block(self):
|
||||
namedmatch = re.compile(r"(<%(def|method))(.*?)(>)(.*?)(</%\2>)", re.M | re.S)
|
||||
|
||||
match = self._match(namedmatch)
|
||||
if match:
|
||||
self.colorize([(match.group(1), 'deftag')])
|
||||
self.colorize([(match.group(3), 'compname')])
|
||||
self.colorize([(match.group(4), 'deftag')])
|
||||
MyghtyHighlighter(match.group(5), self.output).highlight()
|
||||
self.colorize([(match.group(6), 'deftag')])
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def match_block(self):
|
||||
blockmatch = re.compile(r"(<%(\w+).*?>)(.*?)(</%\2\s*>)", re.M | re.S)
|
||||
match = self._match(blockmatch)
|
||||
|
||||
|
||||
if match:
|
||||
style = {
|
||||
'doc': 'doctag',
|
||||
'args': 'argstag',
|
||||
}.setdefault(match.group(2), "blocktag")
|
||||
|
||||
self.colorize([(match.group(1), style)])
|
||||
if style == 'doctag':
|
||||
self.colorize([(match.group(3), 'doctag_text')])
|
||||
|
||||
else:
|
||||
self.pythonize(match.group(3))
|
||||
self.colorize([(match.group(4), style)])
|
||||
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def match_comp_call(self):
|
||||
compmatch = re.compile(r"(<&[^|])(.*?)(,.*?)?(&>)", re.M)
|
||||
match = self._match(compmatch)
|
||||
if match:
|
||||
self.colorize([(match.group(1), 'compcall')])
|
||||
self.colorize([(match.group(2), 'compname')])
|
||||
if match.group(3) is not None:
|
||||
self.pythonize(match.group(3))
|
||||
self.colorize([(match.group(4), 'compcall')])
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def match_substitution(self):
|
||||
submatch = re.compile(r"(<%)(.*?)(%>)", re.M)
|
||||
match = self._match(submatch)
|
||||
if match:
|
||||
self.colorize([(match.group(1), 'substitution')])
|
||||
self.pythonize(match.group(2))
|
||||
self.colorize([(match.group(3), 'substitution')])
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def match_comp_content_call(self):
|
||||
compcontmatch = re.compile(r"(<&\|)(.*?)(,.*?)?(&>)|(</&>)", re.M | re.S)
|
||||
match = self._match(compcontmatch)
|
||||
if match:
|
||||
if match.group(5) is not None:
|
||||
self.colorize([(match.group(5), 'compcall')])
|
||||
else:
|
||||
self.colorize([(match.group(1), 'compcall')])
|
||||
self.colorize([(match.group(2), 'compname')])
|
||||
if match.group(3) is not None:
|
||||
self.pythonize(match.group(3))
|
||||
self.colorize([(match.group(4), 'compcall')])
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def match_line(self):
|
||||
linematch = re.compile(r"(?<=^)([%#])([^\n]*)(\n|\Z)", re.M)
|
||||
match = self._match(linematch)
|
||||
if match:
|
||||
if match.group(1) == '#':
|
||||
self.colorize([(match.group(0), 'doctag')])
|
||||
else:
|
||||
#self.colorize([(match.group(0), 'doctag')])
|
||||
self.colorize([(match.group(1), 'controlline')])
|
||||
self.pythonize(match.group(2))
|
||||
self.output.write(match.group(3))
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
highlighters = {
|
||||
'.myt': MyghtyHighlighter,
|
||||
'.myc': MyghtyHighlighter,
|
||||
'autohandler' : MyghtyHighlighter,
|
||||
'dhandler': MyghtyHighlighter,
|
||||
'.py': PythonHighlighter,
|
||||
'myghty': MyghtyHighlighter,
|
||||
'python' : PythonHighlighter
|
||||
}
|
||||
Vendored
-1671
File diff suppressed because it is too large
Load Diff
Vendored
-83
@@ -1,83 +0,0 @@
|
||||
"""
|
||||
defines a pickleable, recursive "table of contents" datastructure.
|
||||
|
||||
TOCElements define a name, a description, and also a uniquely-identifying "path" which is
|
||||
used to generate hyperlinks between document sections.
|
||||
"""
|
||||
import time, re
|
||||
|
||||
toc_by_file = {}
|
||||
toc_by_path = {}
|
||||
filenames = []
|
||||
|
||||
class TOCElement(object):
|
||||
def __init__(self, filename, name, description, parent=None, version=None, last_updated=None, doctitle=None, requires_paged=False, **kwargs):
|
||||
self.filename = filename
|
||||
self.name = re.sub(r'[<>&;%]', '', name)
|
||||
self.description = description
|
||||
self.parent = parent
|
||||
self.content = None
|
||||
self.filenames = filenames
|
||||
self.toc_by_path = toc_by_path
|
||||
self.toc_by_file = toc_by_file
|
||||
self.last_updated = time.time()
|
||||
self.version = version
|
||||
self.doctitle = doctitle
|
||||
self.requires_paged = requires_paged
|
||||
(self.path, self.depth) = self._create_path()
|
||||
#print "NEW TOC:", self.path
|
||||
for key, value in kwargs.iteritems():
|
||||
setattr(self, key, value)
|
||||
|
||||
toc_by_path[self.path] = self
|
||||
|
||||
self.is_top = (self.parent is not None and self.parent.filename != self.filename) or self.parent is None
|
||||
if self.is_top:
|
||||
toc_by_file[self.filename] = self
|
||||
if self.filename:
|
||||
filenames.append(self.filename)
|
||||
|
||||
self.root = self.parent and self.parent.root or self
|
||||
|
||||
self.content = None
|
||||
self.previous = None
|
||||
self.next = None
|
||||
self.children = []
|
||||
if parent:
|
||||
if parent.children:
|
||||
self.previous = parent.children[-1]
|
||||
parent.children[-1].next = self
|
||||
parent.children.append(self)
|
||||
if parent is not parent.root:
|
||||
self.up = parent
|
||||
else:
|
||||
self.up = None
|
||||
|
||||
def get_page_root(self):
|
||||
return self.toc_by_file[self.filename]
|
||||
|
||||
def get_by_path(self, path):
|
||||
return self.toc_by_path.get(path)
|
||||
|
||||
def get_by_file(self, filename):
|
||||
return self.toc_by_file[filename]
|
||||
|
||||
def get_link(self, extension='html', anchor=True, usefilename=True):
|
||||
if usefilename or self.requires_paged:
|
||||
if anchor:
|
||||
return "%s.%s#%s" % (self.filename, extension, self.path)
|
||||
else:
|
||||
return "%s.%s" % (self.filename, extension)
|
||||
else:
|
||||
return "#%s" % (self.path)
|
||||
|
||||
|
||||
def _create_path(self):
|
||||
elem = self
|
||||
tokens = []
|
||||
depth = 0
|
||||
while elem.parent is not None:
|
||||
tokens.insert(0, elem.name)
|
||||
elem = elem.parent
|
||||
depth +=1
|
||||
return ('_'.join(tokens), depth)
|
||||
Vendored
+1794
File diff suppressed because it is too large
Load Diff
Vendored
+844
@@ -0,0 +1,844 @@
|
||||
.. _metadata_toplevel:
|
||||
|
||||
==================
|
||||
Database Meta Data
|
||||
==================
|
||||
|
||||
Describing Databases with MetaData
|
||||
==================================
|
||||
|
||||
The core of SQLAlchemy's query and object mapping operations are supported by *database metadata*, which is comprised of Python objects that describe tables and other schema-level objects. These objects are at the core of three major types of operations - issuing CREATE and DROP statements (known as *DDL*), constructing SQL queries, and expressing information about structures that already exist within the database.
|
||||
|
||||
Database metadata can be expressed by explicitly naming the various components and their properties, using constructs such as ``Table``, ``Column``, ``ForeignKey`` and ``Sequence``, all of which are imported from the ``sqlalchemy.schema`` package. It can also be generated by SQLAlchemy using a process called *reflection*, which means you start with a single object such as ``Table``, assign it a name, and then instruct SQLAlchemy to load all the additional information related to that name from a particular engine source.
|
||||
|
||||
A key feature of SQLAlchemy's database metadata constructs is that they are designed to be used in a *declarative* style which closely resembles that of real DDL. They are therefore most intuitive to those who have some background in creating real schema generation scripts.
|
||||
|
||||
A collection of metadata entities is stored in an object aptly named ``MetaData``::
|
||||
|
||||
from sqlalchemy import *
|
||||
|
||||
metadata = MetaData()
|
||||
|
||||
``MetaData`` is a container object that keeps together many different features of a database (or multiple databases) being described.
|
||||
|
||||
To represent a table, use the ``Table`` class. Its two primary arguments are the table name, then the ``MetaData`` object which it will be associated with. The remaining positional arguments are mostly ``Column`` objects describing each column::
|
||||
|
||||
user = Table('user', metadata,
|
||||
Column('user_id', Integer, primary_key = True),
|
||||
Column('user_name', String(16), nullable = False),
|
||||
Column('email_address', String(60)),
|
||||
Column('password', String(20), nullable = False)
|
||||
)
|
||||
|
||||
Above, a table called ``user`` is described, which contains four columns. The primary key of the table consists of the ``user_id`` column. Multiple columns may be assigned the ``primary_key=True`` flag which denotes a multi-column primary key, known as a *composite* primary key.
|
||||
|
||||
Note also that each column describes its datatype using objects corresponding to genericized types, such as ``Integer`` and ``String``. SQLAlchemy features dozens of types of varying levels of specificity as well as the ability to create custom types. Documentation on the type system can be found at :ref:`types`.
|
||||
|
||||
Accessing Tables and Columns
|
||||
----------------------------
|
||||
|
||||
The ``MetaData`` object contains all of the schema constructs we've associated with it. It supports a few methods of accessing these table objects, such as the ``sorted_tables`` accessor which returns a list of each ``Table`` object in order of foreign key dependency (that is, each table is preceded by all tables which it references)::
|
||||
|
||||
>>> for t in metadata.sorted_tables:
|
||||
... print t.name
|
||||
user
|
||||
user_preference
|
||||
invoice
|
||||
invoice_item
|
||||
|
||||
In most cases, individual ``Table`` objects have been explicitly declared, and these objects are typically accessed directly as module-level variables in an application.
|
||||
Once a ``Table`` has been defined, it has a full set of accessors which allow inspection of its properties. Given the following ``Table`` definition::
|
||||
|
||||
employees = Table('employees', metadata,
|
||||
Column('employee_id', Integer, primary_key=True),
|
||||
Column('employee_name', String(60), nullable=False),
|
||||
Column('employee_dept', Integer, ForeignKey("departments.department_id"))
|
||||
)
|
||||
|
||||
Note the ``ForeignKey`` object used in this table - this construct defines a reference to a remote table, and is fully described in :ref:`metadata_foreignkeys`. Methods of accessing information about this table include::
|
||||
|
||||
# access the column "EMPLOYEE_ID":
|
||||
employees.columns.employee_id
|
||||
|
||||
# or just
|
||||
employees.c.employee_id
|
||||
|
||||
# via string
|
||||
employees.c['employee_id']
|
||||
|
||||
# iterate through all columns
|
||||
for c in employees.c:
|
||||
print c
|
||||
|
||||
# get the table's primary key columns
|
||||
for primary_key in employees.primary_key:
|
||||
print primary_key
|
||||
|
||||
# get the table's foreign key objects:
|
||||
for fkey in employees.foreign_keys:
|
||||
print fkey
|
||||
|
||||
# access the table's MetaData:
|
||||
employees.metadata
|
||||
|
||||
# access the table's bound Engine or Connection, if its MetaData is bound:
|
||||
employees.bind
|
||||
|
||||
# access a column's name, type, nullable, primary key, foreign key
|
||||
employees.c.employee_id.name
|
||||
employees.c.employee_id.type
|
||||
employees.c.employee_id.nullable
|
||||
employees.c.employee_id.primary_key
|
||||
employees.c.employee_dept.foreign_key
|
||||
|
||||
# get the "key" of a column, which defaults to its name, but can
|
||||
# be any user-defined string:
|
||||
employees.c.name.key
|
||||
|
||||
# access a column's table:
|
||||
employees.c.employee_id.table is employees
|
||||
|
||||
# get the table related by a foreign key
|
||||
fcolumn = employees.c.employee_dept.foreign_key.column.table
|
||||
|
||||
.. _metadata_binding:
|
||||
|
||||
|
||||
Creating and Dropping Database Tables
|
||||
-------------------------------------
|
||||
|
||||
Once you've defined some ``Table`` objects, assuming you're working with a brand new database one thing you might want to do is issue CREATE statements for those tables and their related constructs (as an aside, it's also quite possible that you *don't* want to do this, if you already have some preferred methodology such as tools included with your database or an existing scripting system - if that's the case, feel free to skip this section - SQLAlchemy has no requirement that it be used to create your tables).
|
||||
|
||||
The usual way to issue CREATE is to use ``create_all()`` on the ``MetaData`` object. This method will issue queries that first check for the existence of each individual table, and if not found will issue the CREATE statements:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
engine = create_engine('sqlite:///:memory:')
|
||||
|
||||
metadata = MetaData()
|
||||
|
||||
user = Table('user', metadata,
|
||||
Column('user_id', Integer, primary_key = True),
|
||||
Column('user_name', String(16), nullable = False),
|
||||
Column('email_address', String(60), key='email'),
|
||||
Column('password', String(20), nullable = False)
|
||||
)
|
||||
|
||||
user_prefs = Table('user_prefs', metadata,
|
||||
Column('pref_id', Integer, primary_key=True),
|
||||
Column('user_id', Integer, ForeignKey("user.user_id"), nullable=False),
|
||||
Column('pref_name', String(40), nullable=False),
|
||||
Column('pref_value', String(100))
|
||||
)
|
||||
|
||||
{sql}metadata.create_all(engine)
|
||||
PRAGMA table_info(user){}
|
||||
CREATE TABLE user(
|
||||
user_id INTEGER NOT NULL PRIMARY KEY,
|
||||
user_name VARCHAR(16) NOT NULL,
|
||||
email_address VARCHAR(60),
|
||||
password VARCHAR(20) NOT NULL
|
||||
)
|
||||
PRAGMA table_info(user_prefs){}
|
||||
CREATE TABLE user_prefs(
|
||||
pref_id INTEGER NOT NULL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES user(user_id),
|
||||
pref_name VARCHAR(40) NOT NULL,
|
||||
pref_value VARCHAR(100)
|
||||
)
|
||||
|
||||
``create_all()`` creates foreign key constraints between tables usually inline with the table definition itself, and for this reason it also generates the tables in order of their dependency. There are options to change this behavior such that ``ALTER TABLE`` is used instead.
|
||||
|
||||
Dropping all tables is similarly achieved using the ``drop_all()`` method. This method does the exact opposite of ``create_all()`` - the presence of each table is checked first, and tables are dropped in reverse order of dependency.
|
||||
|
||||
Creating and dropping individual tables can be done via the ``create()`` and ``drop()`` methods of ``Table``. These methods by default issue the CREATE or DROP regardless of the table being present:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
engine = create_engine('sqlite:///:memory:')
|
||||
|
||||
meta = MetaData()
|
||||
|
||||
employees = Table('employees', meta,
|
||||
Column('employee_id', Integer, primary_key=True),
|
||||
Column('employee_name', String(60), nullable=False, key='name'),
|
||||
Column('employee_dept', Integer, ForeignKey("departments.department_id"))
|
||||
)
|
||||
{sql}employees.create(engine)
|
||||
CREATE TABLE employees(
|
||||
employee_id SERIAL NOT NULL PRIMARY KEY,
|
||||
employee_name VARCHAR(60) NOT NULL,
|
||||
employee_dept INTEGER REFERENCES departments(department_id)
|
||||
)
|
||||
{}
|
||||
|
||||
``drop()`` method:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
{sql}employees.drop(engine)
|
||||
DROP TABLE employees
|
||||
{}
|
||||
|
||||
To enable the "check first for the table existing" logic, add the ``checkfirst=True`` argument to ``create()`` or ``drop()``::
|
||||
|
||||
employees.create(engine, checkfirst=True)
|
||||
employees.drop(engine, checkfirst=False)
|
||||
|
||||
|
||||
Binding MetaData to an Engine or Connection
|
||||
--------------------------------------------
|
||||
|
||||
Notice in the previous section the creator/dropper methods accept an argument for the database engine in use. When a schema construct is combined with an ``Engine`` object, or an individual ``Connection`` object, we call this the *bind*. In the above examples the bind is associated with the schema construct only for the duration of the operation. However, the option exists to persistently associate a bind with a set of schema constructs via the ``MetaData`` object's ``bind`` attribute::
|
||||
|
||||
engine = create_engine('sqlite://')
|
||||
|
||||
# create MetaData
|
||||
meta = MetaData()
|
||||
|
||||
# bind to an engine
|
||||
meta.bind = engine
|
||||
|
||||
We can now call methods like ``create_all()`` without needing to pass the ``Engine``::
|
||||
|
||||
meta.create_all()
|
||||
|
||||
The MetaData's bind is used for anything that requires an active connection, such as loading the definition of a table from the database automatically (called *reflection*)::
|
||||
|
||||
# describe a table called 'users', query the database for its columns
|
||||
users_table = Table('users', meta, autoload=True)
|
||||
|
||||
As well as for executing SQL constructs that are derived from that MetaData's table objects::
|
||||
|
||||
# generate a SELECT statement and execute
|
||||
result = users_table.select().execute()
|
||||
|
||||
Binding the MetaData to the Engine is a **completely optional** feature. The above operations can be achieved without the persistent bind using parameters::
|
||||
|
||||
# describe a table called 'users', query the database for its columns
|
||||
users_table = Table('users', meta, autoload=True, autoload_with=engine)
|
||||
|
||||
# generate a SELECT statement and execute
|
||||
result = engine.execute(users_table.select())
|
||||
|
||||
Should you use bind ? It's probably best to start without it. If you find yourself constantly needing to specify the same ``Engine`` object throughout the entire application, consider binding as a convenience feature which is applicable to applications that don't have multiple engines in use and don't have the need to reference connections explicitly. It should also be noted that an application which is focused on using the SQLAlchemy ORM will not be dealing explicitly with ``Engine`` or ``Connection`` objects very much in any case, so it's probably less confusing and more "future proof" to not use the `bind` attribute.
|
||||
|
||||
Reflecting Tables
|
||||
-----------------
|
||||
|
||||
A ``Table`` object can be instructed to load information about itself from the corresponding database schema object already existing within the database. This process is called *reflection*. Most simply you need only specify the table name, a ``MetaData`` object, and the ``autoload=True`` flag. If the ``MetaData`` is not persistently bound, also add the ``autoload_with`` argument::
|
||||
|
||||
>>> messages = Table('messages', meta, autoload=True, autoload_with=engine)
|
||||
>>> [c.name for c in messages.columns]
|
||||
['message_id', 'message_name', 'date']
|
||||
|
||||
The above operation will use the given engine to query the database for information about the ``messages`` table, and will then generate ``Column``, ``ForeignKey``, and other objects corresponding to this information as though the ``Table`` object were hand-constructed in Python.
|
||||
|
||||
When tables are reflected, if a given table references another one via foreign key, a second ``Table`` object is created within the ``MetaData`` object representing the connection. Below, assume the table ``shopping_cart_items`` references a table named ``shopping_carts``. Reflecting the ``shopping_cart_items`` table has the effect such that the ``shopping_carts`` table will also be loaded::
|
||||
|
||||
>>> shopping_cart_items = Table('shopping_cart_items', meta, autoload=True, autoload_with=engine)
|
||||
>>> 'shopping_carts' in meta.tables:
|
||||
True
|
||||
|
||||
The ``MetaData`` has an interesting "singleton-like" behavior such that if you requested both tables individually, ``MetaData`` will ensure that exactly one ``Table`` object is created for each distinct table name. The ``Table`` constructor actually returns to you the already-existing ``Table`` object if one already exists with the given name. Such as below, we can access the already generated ``shopping_carts`` table just by naming it::
|
||||
|
||||
shopping_carts = Table('shopping_carts', meta)
|
||||
|
||||
Of course, it's a good idea to use ``autoload=True`` with the above table regardless. This is so that the table's attributes will be loaded if they have not been already. The autoload operation only occurs for the table if it hasn't already been loaded; once loaded, new calls to ``Table`` with the same name will not re-issue any reflection queries.
|
||||
|
||||
Overriding Reflected Columns
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Individual columns can be overridden with explicit values when reflecting tables; this is handy for specifying custom datatypes, constraints such as primary keys that may not be configured within the database, etc.::
|
||||
|
||||
>>> mytable = Table('mytable', meta,
|
||||
... Column('id', Integer, primary_key=True), # override reflected 'id' to have primary key
|
||||
... Column('mydata', Unicode(50)), # override reflected 'mydata' to be Unicode
|
||||
... autoload=True)
|
||||
|
||||
Reflecting Views
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The reflection system can also reflect views. Basic usage is the same as that of a table::
|
||||
|
||||
my_view = Table("some_view", metadata, autoload=True)
|
||||
|
||||
Above, ``my_view`` is a ``Table`` object with ``Column`` objects representing the names and types
|
||||
of each column within the view "some_view".
|
||||
|
||||
Usually, it's desired to have at least a primary key constraint when reflecting a view, if not
|
||||
foreign keys as well. View reflection doesn't extrapolate these constraints.
|
||||
|
||||
Use the "override" technique for this, specifying explicitly those columns
|
||||
which are part of the primary key or have foreign key constraints::
|
||||
|
||||
my_view = Table("some_view", metadata,
|
||||
Column("view_id", Integer, primary_key=True),
|
||||
Column("related_thing", Integer, ForeignKey("othertable.thing_id")),
|
||||
autoload=True
|
||||
)
|
||||
|
||||
Reflecting All Tables at Once
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``MetaData`` object can also get a listing of tables and reflect the full set. This is achieved by using the ``reflect()`` method. After calling it, all located tables are present within the ``MetaData`` object's dictionary of tables::
|
||||
|
||||
meta = MetaData()
|
||||
meta.reflect(bind=someengine)
|
||||
users_table = meta.tables['users']
|
||||
addresses_table = meta.tables['addresses']
|
||||
|
||||
``metadata.reflect()`` also provides a handy way to clear or delete all the rows in a database::
|
||||
|
||||
meta = MetaData()
|
||||
meta.reflect(bind=someengine)
|
||||
for table in reversed(meta.sorted_tables):
|
||||
someengine.execute(table.delete())
|
||||
|
||||
Specifying the Schema Name
|
||||
---------------------------
|
||||
|
||||
Some databases support the concept of multiple schemas. A ``Table`` can reference this by specifying the ``schema`` keyword argument::
|
||||
|
||||
financial_info = Table('financial_info', meta,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('value', String(100), nullable=False),
|
||||
schema='remote_banks'
|
||||
)
|
||||
|
||||
Within the ``MetaData`` collection, this table will be identified by the combination of ``financial_info`` and ``remote_banks``. If another table called ``financial_info`` is referenced without the ``remote_banks`` schema, it will refer to a different ``Table``. ``ForeignKey`` objects can specify references to columns in this table using the form ``remote_banks.financial_info.id``.
|
||||
|
||||
The ``schema`` argument should be used for any name qualifiers required, including Oracle's "owner" attribute and similar. It also can accommodate a dotted name for longer schemes::
|
||||
|
||||
schema="dbo.scott"
|
||||
|
||||
Backend-Specific Options
|
||||
------------------------
|
||||
|
||||
``Table`` supports database-specific options. For example, MySQL has different table backend types, including "MyISAM" and "InnoDB". This can be expressed with ``Table`` using ``mysql_engine``::
|
||||
|
||||
addresses = Table('engine_email_addresses', meta,
|
||||
Column('address_id', Integer, primary_key = True),
|
||||
Column('remote_user_id', Integer, ForeignKey(users.c.user_id)),
|
||||
Column('email_address', String(20)),
|
||||
mysql_engine='InnoDB'
|
||||
)
|
||||
|
||||
Other backends may support table-level options as well. See the API documentation for each backend for further details.
|
||||
|
||||
Column Insert/Update Defaults
|
||||
==============================
|
||||
|
||||
SQLAlchemy provides a very rich featureset regarding column level events which take place during INSERT and UPDATE statements. Options include:
|
||||
|
||||
* Scalar values used as defaults during INSERT and UPDATE operations
|
||||
* Python functions which execute upon INSERT and UPDATE operations
|
||||
* SQL expressions which are embedded in INSERT statements (or in some cases execute beforehand)
|
||||
* SQL expressions which are embedded in UPDATE statements
|
||||
* Server side default values used during INSERT
|
||||
* Markers for server-side triggers used during UPDATE
|
||||
|
||||
The general rule for all insert/update defaults is that they only take effect if no value for a particular column is passed as an ``execute()`` parameter; otherwise, the given value is used.
|
||||
|
||||
Scalar Defaults
|
||||
---------------
|
||||
|
||||
The simplest kind of default is a scalar value used as the default value of a column::
|
||||
|
||||
Table("mytable", meta,
|
||||
Column("somecolumn", Integer, default=12)
|
||||
)
|
||||
|
||||
Above, the value "12" will be bound as the column value during an INSERT if no other value is supplied.
|
||||
|
||||
A scalar value may also be associated with an UPDATE statement, though this is not very common (as UPDATE statements are usually looking for dynamic defaults)::
|
||||
|
||||
Table("mytable", meta,
|
||||
Column("somecolumn", Integer, onupdate=25)
|
||||
)
|
||||
|
||||
|
||||
Python-Executed Functions
|
||||
-------------------------
|
||||
|
||||
The ``default`` and ``onupdate`` keyword arguments also accept Python functions. These functions are invoked at the time of insert or update if no other value for that column is supplied, and the value returned is used for the column's value. Below illustrates a crude "sequence" that assigns an incrementing counter to a primary key column::
|
||||
|
||||
# a function which counts upwards
|
||||
i = 0
|
||||
def mydefault():
|
||||
global i
|
||||
i += 1
|
||||
return i
|
||||
|
||||
t = Table("mytable", meta,
|
||||
Column('id', Integer, primary_key=True, default=mydefault),
|
||||
)
|
||||
|
||||
It should be noted that for real "incrementing sequence" behavior, the built-in capabilities of the database should normally be used, which may include sequence objects or other autoincrementing capabilities. For primary key columns, SQLAlchemy will in most cases use these capabilities automatically. See the API documentation for ``Column`` including the ``autoincrement`` flag, as well as the section on ``Sequence`` later in this chapter for background on standard primary key generation techniques.
|
||||
|
||||
To illustrate onupdate, we assign the Python ``datetime`` function ``now`` to the ``onupdate`` attribute::
|
||||
|
||||
import datetime
|
||||
|
||||
t = Table("mytable", meta,
|
||||
Column('id', Integer, primary_key=True),
|
||||
|
||||
# define 'last_updated' to be populated with datetime.now()
|
||||
Column('last_updated', DateTime, onupdate=datetime.datetime.now),
|
||||
)
|
||||
|
||||
When an update statement executes and no value is passed for ``last_updated``, the ``datetime.datetime.now()`` Python function is executed and its return value used as the value for ``last_updated``. Notice that we provide ``now`` as the function itself without calling it (i.e. there are no parenthesis following) - SQLAlchemy will execute the function at the time the statement executes.
|
||||
|
||||
Context-Sensitive Default Functions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Python functions used by ``default`` and ``onupdate`` may also make use of the current statement's context in order to determine a value. The `context` of a statement is an internal SQLAlchemy object which contains all information about the statement being executed, including its source expression, the parameters associated with it and the cursor. The typical use case for this context with regards to default generation is to have access to the other values being inserted or updated on the row. To access the context, provide a function that accepts a single ``context`` argument::
|
||||
|
||||
def mydefault(context):
|
||||
return context.current_parameters['counter'] + 12
|
||||
|
||||
t = Table('mytable', meta,
|
||||
Column('counter', Integer),
|
||||
Column('counter_plus_twelve', Integer, default=mydefault, onupdate=mydefault)
|
||||
)
|
||||
|
||||
Above we illustrate a default function which will execute for all INSERT and UPDATE statements where a value for ``counter_plus_twelve`` was otherwise not provided, and the value will be that of whatever value is present in the execution for the ``counter`` column, plus the number 12.
|
||||
|
||||
While the context object passed to the default function has many attributes, the ``current_parameters`` member is a special member provided only during the execution of a default function for the purposes of deriving defaults from its existing values. For a single statement that is executing many sets of bind parameters, the user-defined function is called for each set of parameters, and ``current_parameters`` will be provided with each individual parameter set for each execution.
|
||||
|
||||
SQL Expressions
|
||||
---------------
|
||||
|
||||
The "default" and "onupdate" keywords may also be passed SQL expressions, including select statements or direct function calls::
|
||||
|
||||
t = Table("mytable", meta,
|
||||
Column('id', Integer, primary_key=True),
|
||||
|
||||
# define 'create_date' to default to now()
|
||||
Column('create_date', DateTime, default=func.now()),
|
||||
|
||||
# define 'key' to pull its default from the 'keyvalues' table
|
||||
Column('key', String(20), default=keyvalues.select(keyvalues.c.type='type1', limit=1)),
|
||||
|
||||
# define 'last_modified' to use the current_timestamp SQL function on update
|
||||
Column('last_modified', DateTime, onupdate=func.utc_timestamp())
|
||||
)
|
||||
|
||||
Above, the ``create_date`` column will be populated with the result of the ``now()`` SQL function (which, depending on backend, compiles into ``NOW()`` or ``CURRENT_TIMESTAMP`` in most cases) during an INSERT statement, and the ``key`` column with the result of a SELECT subquery from another table. The ``last_modified`` column will be populated with the value of ``UTC_TIMESTAMP()``, a function specific to MySQL, when an UPDATE statement is emitted for this table.
|
||||
|
||||
Note that when using ``func`` functions, unlike when using Python `datetime` functions we *do* call the function, i.e. with parenthesis "()" - this is because what we want in this case is the return value of the function, which is the SQL expression construct that will be rendered into the INSERT or UPDATE statement.
|
||||
|
||||
The above SQL functions are usually executed "inline" with the INSERT or UPDATE statement being executed, meaning, a single statement is executed which embeds the given expressions or subqueries within the VALUES or SET clause of the statement. Although in some cases, the function is "pre-executed" in a SELECT statement of its own beforehand. This happens when all of the following is true:
|
||||
|
||||
* the column is a primary key column
|
||||
|
||||
* the database dialect does not support a usable ``cursor.lastrowid`` accessor (or equivalent); this currently includes PostgreSQL, Oracle, and Firebird, as well as some MySQL dialects.
|
||||
|
||||
* the dialect does not support the "RETURNING" clause or similar, or the ``implicit_returning`` flag is set to ``False`` for the dialect. Dialects which support RETURNING currently include Postgresql, Oracle, Firebird, and MS-SQL.
|
||||
|
||||
* the statement is a single execution, i.e. only supplies one set of parameters and doesn't use "executemany" behavior
|
||||
|
||||
* the ``inline=True`` flag is not set on the ``Insert()`` or ``Update()`` construct, and the statement has not defined an explicit `returning()` clause.
|
||||
|
||||
Whether or not the default generation clause "pre-executes" is not something that normally needs to be considered, unless it is being addressed for performance reasons.
|
||||
|
||||
When the statement is executed with a single set of parameters (that is, it is not an "executemany" style execution), the returned ``ResultProxy`` will contain a collection accessible via ``result.postfetch_cols()`` which contains a list of all ``Column`` objects which had an inline-executed default. Similarly, all parameters which were bound to the statement, including all Python and SQL expressions which were pre-executed, are present in the ``last_inserted_params()`` or ``last_updated_params()`` collections on ``ResultProxy``. The ``inserted_primary_key`` collection contains a list of primary key values for the row inserted (a list so that single-column and composite-column primary keys are represented in the same format).
|
||||
|
||||
Server Side Defaults
|
||||
--------------------
|
||||
|
||||
A variant on the SQL expression default is the ``server_default``, which gets placed in the CREATE TABLE statement during a ``create()`` operation:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
t = Table('test', meta,
|
||||
Column('abc', String(20), server_default='abc'),
|
||||
Column('created_at', DateTime, server_default=text("sysdate"))
|
||||
)
|
||||
|
||||
A create call for the above table will produce::
|
||||
|
||||
CREATE TABLE test (
|
||||
abc varchar(20) default 'abc',
|
||||
created_at datetime default sysdate
|
||||
)
|
||||
|
||||
The behavior of ``server_default`` is similar to that of a regular SQL default; if it's placed on a primary key column for a database which doesn't have a way to "postfetch" the ID, and the statement is not "inlined", the SQL expression is pre-executed; otherwise, SQLAlchemy lets the default fire off on the database side normally.
|
||||
|
||||
Triggered Columns
|
||||
------------------
|
||||
|
||||
Columns with values set by a database trigger or other external process may be called out with a marker::
|
||||
|
||||
t = Table('test', meta,
|
||||
Column('abc', String(20), server_default=FetchedValue()),
|
||||
Column('def', String(20), server_onupdate=FetchedValue())
|
||||
)
|
||||
|
||||
These markers do not emit a "default" clause when the table is created, however they do set the same internal flags as a static ``server_default`` clause, providing hints to higher-level tools that a "post-fetch" of these rows should be performed after an insert or update.
|
||||
|
||||
Defining Sequences
|
||||
-------------------
|
||||
|
||||
SQLAlchemy represents database sequences using the ``Sequence`` object, which is considered to be a special case of "column default". It only has an effect on databases which have explicit support for sequences, which currently includes Postgresql, Oracle, and Firebird. The ``Sequence`` object is otherwise ignored.
|
||||
|
||||
The ``Sequence`` may be placed on any column as a "default" generator to be used during INSERT operations, and can also be configured to fire off during UPDATE operations if desired. It is most commonly used in conjunction with a single integer primary key column::
|
||||
|
||||
table = Table("cartitems", meta,
|
||||
Column("cart_id", Integer, Sequence('cart_id_seq'), primary_key=True),
|
||||
Column("description", String(40)),
|
||||
Column("createdate", DateTime())
|
||||
)
|
||||
|
||||
Where above, the table "cartitems" is associated with a sequence named "cart_id_seq". When INSERT statements take place for "cartitems", and no value is passed for the "cart_id" column, the "cart_id_seq" sequence will be used to generate a value.
|
||||
|
||||
When the ``Sequence`` is associated with a table, CREATE and DROP statements issued for that table will also issue CREATE/DROP for the sequence object as well, thus "bundling" the sequence object with its parent table.
|
||||
|
||||
The ``Sequence`` object also implements special functionality to accommodate Postgresql's SERIAL datatype. The SERIAL type in PG automatically generates a sequence that is used implicitly during inserts. This means that if a ``Table`` object defines a ``Sequence`` on its primary key column so that it works with Oracle and Firebird, the ``Sequence`` would get in the way of the "implicit" sequence that PG would normally use. For this use case, add the flag ``optional=True`` to the ``Sequence`` object - this indicates that the ``Sequence`` should only be used if the database provides no other option for generating primary key identifiers.
|
||||
|
||||
The ``Sequence`` object also has the ability to be executed standalone like a SQL expression, which has the effect of calling its "next value" function::
|
||||
|
||||
seq = Sequence('some_sequence')
|
||||
nextid = connection.execute(seq)
|
||||
|
||||
Defining Constraints and Indexes
|
||||
=================================
|
||||
|
||||
.. _metadata_foreignkeys:
|
||||
|
||||
Defining Foreign Keys
|
||||
---------------------
|
||||
|
||||
A *foreign key* in SQL is a table-level construct that constrains one or more columns in that table to only allow values that are present in a different set of columns, typically but not always located on a different table. We call the columns which are constrained the *foreign key* columns and the columns which they are constrained towards the *referenced* columns. The referenced columns almost always define the primary key for their owning table, though there are exceptions to this. The foreign key is the "joint" that connects together pairs of rows which have a relationship with each other, and SQLAlchemy assigns very deep importance to this concept in virtually every area of its operation.
|
||||
|
||||
In SQLAlchemy as well as in DDL, foreign key constraints can be defined as additional attributes within the table clause, or for single-column foreign keys they may optionally be specified within the definition of a single column. The single column foreign key is more common, and at the column level is specified by constructing a ``ForeignKey`` object as an argument to a ``Column`` object::
|
||||
|
||||
user_preference = Table('user_preference', metadata,
|
||||
Column('pref_id', Integer, primary_key=True),
|
||||
Column('user_id', Integer, ForeignKey("user.user_id"), nullable=False),
|
||||
Column('pref_name', String(40), nullable=False),
|
||||
Column('pref_value', String(100))
|
||||
)
|
||||
|
||||
Above, we define a new table ``user_preference`` for which each row must contain a value in the ``user_id`` column that also exists in the ``user`` table's ``user_id`` column.
|
||||
|
||||
The argument to ``ForeignKey`` is most commonly a string of the form *<tablename>.<columnname>*, or for a table in a remote schema or "owner" of the form *<schemaname>.<tablename>.<columnname>*. It may also be an actual ``Column`` object, which as we'll see later is accessed from an existing ``Table`` object via its ``c`` collection::
|
||||
|
||||
ForeignKey(user.c.user_id)
|
||||
|
||||
The advantage to using a string is that the in-python linkage between ``user`` and ``user_preference`` is resolved only when first needed, so that table objects can be easily spread across multiple modules and defined in any order.
|
||||
|
||||
Foreign keys may also be defined at the table level, using the ``ForeignKeyConstraint`` object. This object can describe a single- or multi-column foreign key. A multi-column foreign key is known as a *composite* foreign key, and almost always references a table that has a composite primary key. Below we define a table ``invoice`` which has a composite primary key::
|
||||
|
||||
invoice = Table('invoice', metadata,
|
||||
Column('invoice_id', Integer, primary_key=True),
|
||||
Column('ref_num', Integer, primary_key=True),
|
||||
Column('description', String(60), nullable=False)
|
||||
)
|
||||
|
||||
And then a table ``invoice_item`` with a composite foreign key referencing ``invoice``::
|
||||
|
||||
invoice_item = Table('invoice_item', metadata,
|
||||
Column('item_id', Integer, primary_key=True),
|
||||
Column('item_name', String(60), nullable=False),
|
||||
Column('invoice_id', Integer, nullable=False),
|
||||
Column('ref_num', Integer, nullable=False),
|
||||
ForeignKeyConstraint(['invoice_id', 'ref_num'], ['invoice.invoice_id', 'invoice.ref_num'])
|
||||
)
|
||||
|
||||
It's important to note that the ``ForeignKeyConstraint`` is the only way to define a composite foreign key. While we could also have placed individual ``ForeignKey`` objects on both the ``invoice_item.invoice_id`` and ``invoice_item.ref_num`` columns, SQLAlchemy would not be aware that these two values should be paired together - it would be two individual foreign key constraints instead of a single composite foreign key referencing two columns.
|
||||
|
||||
Creating/Dropping Foreign Key Constraints via ALTER
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In all the above examples, the ``ForeignKey`` object causes the "REFERENCES" keyword to be added inline to a column definition within a "CREATE TABLE" statement when ``create_all()`` is issued, and ``ForeignKeyConstraint`` invokes the "CONSTRAINT" keyword inline with "CREATE TABLE". There are some cases where this is undesireable, particularly when two tables reference each other mutually, each with a foreign key referencing the other. In such a situation at least one of the foreign key constraints must be generated after both tables have been built. To support such a scheme, ``ForeignKey`` and ``ForeignKeyConstraint`` offer the flag ``use_alter=True``. When using this flag, the constraint will be generated using a definition similar to "ALTER TABLE <tablename> ADD CONSTRAINT <name> ...". Since a name is required, the ``name`` attribute must also be specified. For example::
|
||||
|
||||
node = Table('node', meta,
|
||||
Column('node_id', Integer, primary_key=True),
|
||||
Column('primary_element', Integer,
|
||||
ForeignKey('element.element_id', use_alter=True, name='fk_node_element_id')
|
||||
)
|
||||
)
|
||||
|
||||
element = Table('element', meta,
|
||||
Column('element_id', Integer, primary_key=True),
|
||||
Column('parent_node_id', Integer),
|
||||
ForeignKeyConstraint(
|
||||
['parent_node_id'],
|
||||
['node.node_id'],
|
||||
use_alter=True,
|
||||
name='fk_element_parent_node_id'
|
||||
)
|
||||
)
|
||||
|
||||
ON UPDATE and ON DELETE
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Most databases support *cascading* of foreign key values, that is the when a parent row is updated the new value is placed in child rows, or when the parent row is deleted all corresponding child rows are set to null or deleted. In data definition language these are specified using phrases like "ON UPDATE CASCADE", "ON DELETE CASCADE", and "ON DELETE SET NULL", corresponding to foreign key constraints. The phrase after "ON UPDATE" or "ON DELETE" may also other allow other phrases that are specific to the database in use. The ``ForeignKey`` and ``ForeignKeyConstraint`` objects support the generation of this clause via the ``onupdate`` and ``ondelete`` keyword arguments. The value is any string which will be output after the appropriate "ON UPDATE" or "ON DELETE" phrase::
|
||||
|
||||
child = Table('child', meta,
|
||||
Column('id', Integer,
|
||||
ForeignKey('parent.id', onupdate="CASCADE", ondelete="CASCADE"),
|
||||
primary_key=True
|
||||
)
|
||||
)
|
||||
|
||||
composite = Table('composite', meta,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('rev_id', Integer),
|
||||
Column('note_id', Integer),
|
||||
ForeignKeyConstraint(
|
||||
['rev_id', 'note_id'],
|
||||
['revisions.id', 'revisions.note_id'],
|
||||
onupdate="CASCADE", ondelete="SET NULL"
|
||||
)
|
||||
)
|
||||
|
||||
Note that these clauses are not supported on SQLite, and require ``InnoDB`` tables when used with MySQL. They may also not be supported on other databases.
|
||||
|
||||
UNIQUE Constraint
|
||||
-----------------
|
||||
|
||||
Unique constraints can be created anonymously on a single column using the ``unique`` keyword on ``Column``. Explicitly named unique constraints and/or those with multiple columns are created via the ``UniqueConstraint`` table-level construct.
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
meta = MetaData()
|
||||
mytable = Table('mytable', meta,
|
||||
|
||||
# per-column anonymous unique constraint
|
||||
Column('col1', Integer, unique=True),
|
||||
|
||||
Column('col2', Integer),
|
||||
Column('col3', Integer),
|
||||
|
||||
# explicit/composite unique constraint. 'name' is optional.
|
||||
UniqueConstraint('col2', 'col3', name='uix_1')
|
||||
)
|
||||
|
||||
CHECK Constraint
|
||||
----------------
|
||||
|
||||
Check constraints can be named or unnamed and can be created at the Column or Table level, using the ``CheckConstraint`` construct. The text of the check constraint is passed directly through to the database, so there is limited "database independent" behavior. Column level check constraints generally should only refer to the column to which they are placed, while table level constraints can refer to any columns in the table.
|
||||
|
||||
Note that some databases do not actively support check constraints such as MySQL and SQLite.
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
meta = MetaData()
|
||||
mytable = Table('mytable', meta,
|
||||
|
||||
# per-column CHECK constraint
|
||||
Column('col1', Integer, CheckConstraint('col1>5')),
|
||||
|
||||
Column('col2', Integer),
|
||||
Column('col3', Integer),
|
||||
|
||||
# table level CHECK constraint. 'name' is optional.
|
||||
CheckConstraint('col2 > col3 + 5', name='check1')
|
||||
)
|
||||
|
||||
{sql}mytable.create(engine)
|
||||
CREATE TABLE mytable (
|
||||
col1 INTEGER CHECK (col1>5),
|
||||
col2 INTEGER,
|
||||
col3 INTEGER,
|
||||
CONSTRAINT check1 CHECK (col2 > col3 + 5)
|
||||
){stop}
|
||||
|
||||
Indexes
|
||||
-------
|
||||
|
||||
Indexes can be created anonymously (using an auto-generated name ``ix_<column label>``) for a single column using the inline ``index`` keyword on ``Column``, which also modifies the usage of ``unique`` to apply the uniqueness to the index itself, instead of adding a separate UNIQUE constraint. For indexes with specific names or which encompass more than one column, use the ``Index`` construct, which requires a name.
|
||||
|
||||
Note that the ``Index`` construct is created **externally** to the table which it corresponds, using ``Column`` objects and not strings.
|
||||
|
||||
Below we illustrate a ``Table`` with several ``Index`` objects associated. The DDL for "CREATE INDEX" is issued right after the create statements for the table:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
meta = MetaData()
|
||||
mytable = Table('mytable', meta,
|
||||
# an indexed column, with index "ix_mytable_col1"
|
||||
Column('col1', Integer, index=True),
|
||||
|
||||
# a uniquely indexed column with index "ix_mytable_col2"
|
||||
Column('col2', Integer, index=True, unique=True),
|
||||
|
||||
Column('col3', Integer),
|
||||
Column('col4', Integer),
|
||||
|
||||
Column('col5', Integer),
|
||||
Column('col6', Integer),
|
||||
)
|
||||
|
||||
# place an index on col3, col4
|
||||
Index('idx_col34', mytable.c.col3, mytable.c.col4)
|
||||
|
||||
# place a unique index on col5, col6
|
||||
Index('myindex', mytable.c.col5, mytable.c.col6, unique=True)
|
||||
|
||||
{sql}mytable.create(engine)
|
||||
CREATE TABLE mytable (
|
||||
col1 INTEGER,
|
||||
col2 INTEGER,
|
||||
col3 INTEGER,
|
||||
col4 INTEGER,
|
||||
col5 INTEGER,
|
||||
col6 INTEGER
|
||||
)
|
||||
CREATE INDEX ix_mytable_col1 ON mytable (col1)
|
||||
CREATE UNIQUE INDEX ix_mytable_col2 ON mytable (col2)
|
||||
CREATE UNIQUE INDEX myindex ON mytable (col5, col6)
|
||||
CREATE INDEX idx_col34 ON mytable (col3, col4){stop}
|
||||
|
||||
The ``Index`` object also supports its own ``create()`` method:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
i = Index('someindex', mytable.c.col5)
|
||||
{sql}i.create(engine)
|
||||
CREATE INDEX someindex ON mytable (col5){stop}
|
||||
|
||||
Customizing DDL
|
||||
===============
|
||||
|
||||
In the preceding sections we've discussed a variety of schema constructs including ``Table``, ``ForeignKeyConstraint``, ``CheckConstraint``, and ``Sequence``. Throughout, we've relied upon the ``create()`` and ``create_all()`` methods of ``Table`` and ``MetaData`` in order to issue data definition language (DDL) for all constructs. When issued, a pre-determined order of operations is invoked, and DDL to create each table is created unconditionally including all constraints and other objects associated with it. For more complex scenarios where database-specific DDL is required, SQLAlchemy offers two techniques which can be used to add any DDL based on any condition, either accompanying the standard generation of tables or by itself.
|
||||
|
||||
Controlling DDL Sequences
|
||||
-------------------------
|
||||
|
||||
The ``sqlalchemy.schema`` package contains SQL expression constructs that provide DDL expressions. For example, to produce a ``CREATE TABLE`` statement:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
from sqlalchemy.schema import CreateTable
|
||||
{sql}engine.execute(CreateTable(mytable))
|
||||
CREATE TABLE mytable (
|
||||
col1 INTEGER,
|
||||
col2 INTEGER,
|
||||
col3 INTEGER,
|
||||
col4 INTEGER,
|
||||
col5 INTEGER,
|
||||
col6 INTEGER
|
||||
){stop}
|
||||
|
||||
Above, the ``CreateTable`` construct works like any other expression construct (such as ``select()``, ``table.insert()``, etc.). A full reference of available constructs is in :ref:`schema_api_ddl`.
|
||||
|
||||
The DDL constructs all extend a common base class which provides the capability to be associated with an individual ``Table`` or ``MetaData`` object, to be invoked upon create/drop events. Consider the example of a table which contains a CHECK constraint:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
users = Table('users', metadata,
|
||||
Column('user_id', Integer, primary_key=True),
|
||||
Column('user_name', String(40), nullable=False),
|
||||
CheckConstraint('length(user_name) >= 8',name="cst_user_name_length")
|
||||
)
|
||||
|
||||
{sql}users.create(engine)
|
||||
CREATE TABLE users (
|
||||
user_id SERIAL NOT NULL,
|
||||
user_name VARCHAR(40) NOT NULL,
|
||||
PRIMARY KEY (user_id),
|
||||
CONSTRAINT cst_user_name_length CHECK (length(user_name) >= 8)
|
||||
){stop}
|
||||
|
||||
The above table contains a column "user_name" which is subject to a CHECK constraint that validates that the length of the string is at least eight characters. When a ``create()`` is issued for this table, DDL for the ``CheckConstraint`` will also be issued inline within the table definition.
|
||||
|
||||
The ``CheckConstraint`` construct can also be constructed externally and associated with the ``Table`` afterwards::
|
||||
|
||||
constraint = CheckConstraint('length(user_name) >= 8',name="cst_user_name_length")
|
||||
users.append_constraint(constraint)
|
||||
|
||||
So far, the effect is the same. However, if we create DDL elements corresponding to the creation and removal of this constraint, and associate them with the ``Table`` as events, these new events will take over the job of issuing DDL for the constraint. Additionally, the constraint will be added via ALTER:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
AddConstraint(constraint).execute_at("after-create", users)
|
||||
DropConstraint(constraint).execute_at("before-drop", users)
|
||||
|
||||
{sql}users.create(engine)
|
||||
CREATE TABLE users (
|
||||
user_id SERIAL NOT NULL,
|
||||
user_name VARCHAR(40) NOT NULL,
|
||||
PRIMARY KEY (user_id)
|
||||
)
|
||||
|
||||
ALTER TABLE users ADD CONSTRAINT cst_user_name_length CHECK (length(user_name) >= 8){stop}
|
||||
|
||||
{sql}users.drop(engine)
|
||||
ALTER TABLE users DROP CONSTRAINT cst_user_name_length
|
||||
DROP TABLE users{stop}
|
||||
|
||||
The real usefulness of the above becomes clearer once we illustrate the ``on`` attribute of a DDL event. The ``on`` parameter is part of the constructor, and may be a string name of a database dialect name, a tuple containing dialect names, or a Python callable. This will limit the execution of the item to just those dialects, or when the return value of the callable is ``True``. So if our ``CheckConstraint`` was only supported by Postgresql and not other databases, we could limit it to just that dialect::
|
||||
|
||||
AddConstraint(constraint, on='postgresql').execute_at("after-create", users)
|
||||
DropConstraint(constraint, on='postgresql').execute_at("before-drop", users)
|
||||
|
||||
Or to any set of dialects::
|
||||
|
||||
AddConstraint(constraint, on=('postgresql', 'mysql')).execute_at("after-create", users)
|
||||
DropConstraint(constraint, on=('postgresql', 'mysql')).execute_at("before-drop", users)
|
||||
|
||||
When using a callable, the callable is passed the ddl element, event name, the ``Table`` or ``MetaData`` object whose "create" or "drop" event is in progress, and the ``Connection`` object being used for the operation, as well as additional information as keyword arguments. The callable can perform checks, such as whether or not a given item already exists. Below we define ``should_create()`` and ``should_drop()`` callables that check for the presence of our named constraint:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
def should_create(ddl, event, target, connection, **kw):
|
||||
row = connection.execute("select conname from pg_constraint where conname='%s'" % ddl.element.name).scalar()
|
||||
return not bool(row)
|
||||
|
||||
def should_drop(ddl, event, target, connection, **kw):
|
||||
return not should_create(ddl, event, target, connection, **kw)
|
||||
|
||||
AddConstraint(constraint, on=should_create).execute_at("after-create", users)
|
||||
DropConstraint(constraint, on=should_drop).execute_at("before-drop", users)
|
||||
|
||||
{sql}users.create(engine)
|
||||
CREATE TABLE users (
|
||||
user_id SERIAL NOT NULL,
|
||||
user_name VARCHAR(40) NOT NULL,
|
||||
PRIMARY KEY (user_id)
|
||||
)
|
||||
|
||||
select conname from pg_constraint where conname='cst_user_name_length'
|
||||
ALTER TABLE users ADD CONSTRAINT cst_user_name_length CHECK (length(user_name) >= 8){stop}
|
||||
|
||||
{sql}users.drop(engine)
|
||||
select conname from pg_constraint where conname='cst_user_name_length'
|
||||
ALTER TABLE users DROP CONSTRAINT cst_user_name_length
|
||||
DROP TABLE users{stop}
|
||||
|
||||
Custom DDL
|
||||
----------
|
||||
|
||||
Custom DDL phrases are most easily achieved using the :class:`~sqlalchemy.schema.DDL` construct. This construct works like all the other DDL elements except it accepts a string which is the
|
||||
text to be emitted:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
DDL("ALTER TABLE users ADD CONSTRAINT "
|
||||
"cst_user_name_length "
|
||||
" CHECK (length(user_name) >= 8)").execute_at("after-create", metadata)
|
||||
|
||||
A more comprehensive method of creating libraries of DDL constructs is to use the :ref:`sqlalchemy.ext.compiler_toplevel` extension. See that chapter for full details.
|
||||
|
||||
Adapting Tables to Alternate Metadata
|
||||
======================================
|
||||
|
||||
|
||||
A ``Table`` object created against a specific ``MetaData`` object can be re-created against a new MetaData using the ``tometadata`` method:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
# create two metadata
|
||||
meta1 = MetaData('sqlite:///querytest.db')
|
||||
meta2 = MetaData()
|
||||
|
||||
# load 'users' from the sqlite engine
|
||||
users_table = Table('users', meta1, autoload=True)
|
||||
|
||||
# create the same Table object for the plain metadata
|
||||
users_table_2 = users_table.tometadata(meta2)
|
||||
|
||||
|
||||
Vendored
+1341
File diff suppressed because it is too large
Load Diff
Vendored
-244
@@ -1,244 +0,0 @@
|
||||
"""loads Markdown files, converts each one to HTML and parses the HTML into an ElementTree structure.
|
||||
The collection of ElementTrees are further parsed to generate a table of contents structure, and are
|
||||
manipulated to replace various markdown-generated HTML with specific Mako tags before being written
|
||||
to Mako templates, which then re-access the table of contents structure at runtime.
|
||||
|
||||
Much thanks to Alexey Shamrin, who came up with the original idea and did all the heavy Markdown/Elementtree
|
||||
lifting for this module.
|
||||
"""
|
||||
|
||||
import sys, re, os
|
||||
from toc import TOCElement
|
||||
|
||||
try:
|
||||
import xml.etree.ElementTree as et
|
||||
except ImportError:
|
||||
try:
|
||||
import elementtree.ElementTree as et
|
||||
except:
|
||||
raise "This module requires ElementTree to run (http://effbot.org/zone/element-index.htm)"
|
||||
|
||||
import markdown
|
||||
|
||||
def dump_tree(elem, stream):
|
||||
if elem.tag.startswith('MAKO:'):
|
||||
dump_mako_tag(elem, stream)
|
||||
else:
|
||||
if elem.tag != 'html':
|
||||
if elem.attrib:
|
||||
stream.write("<%s %s>" % (elem.tag, " ".join(["%s=%s" % (key, repr(val)) for key, val in elem.attrib.iteritems()])))
|
||||
else:
|
||||
stream.write("<%s>" % elem.tag)
|
||||
if elem.text:
|
||||
stream.write(elem.text)
|
||||
for child in elem:
|
||||
dump_tree(child, stream)
|
||||
if child.tail:
|
||||
stream.write(child.tail)
|
||||
if elem.tag != 'html':
|
||||
stream.write("</%s>" % elem.tag)
|
||||
|
||||
def dump_mako_tag(elem, stream):
|
||||
tag = elem.tag[5:]
|
||||
params = ','.join(['%s=%s' % i for i in elem.items()])
|
||||
stream.write('<%%call expr="%s(%s)">' % (tag, params))
|
||||
if elem.text:
|
||||
stream.write(elem.text)
|
||||
for n in elem:
|
||||
dump_tree(n, stream)
|
||||
if n.tail:
|
||||
stream.write(n.tail)
|
||||
stream.write("</%call>")
|
||||
|
||||
def create_toc(filename, tree, tocroot):
|
||||
title = [None]
|
||||
current = [tocroot]
|
||||
level = [0]
|
||||
def process(tree):
|
||||
while True:
|
||||
i = find_header_index(tree)
|
||||
if i is None:
|
||||
return
|
||||
node = tree[i]
|
||||
taglevel = int(node.tag[1])
|
||||
start, end = i, end_of_header(tree, taglevel, i+1)
|
||||
content = tree[start+1:end]
|
||||
description = node.text.strip()
|
||||
if title[0] is None:
|
||||
title[0] = description
|
||||
name = node.get('name')
|
||||
if name is None:
|
||||
name = description.split()[0].lower()
|
||||
|
||||
taglevel = node.tag[1]
|
||||
if taglevel > level[0]:
|
||||
current[0] = TOCElement(filename, name, description, current[0])
|
||||
elif taglevel == level[0]:
|
||||
current[0] = TOCElement(filename, name, description, current[0].parent)
|
||||
else:
|
||||
current[0] = TOCElement(filename, name, description, current[0].parent.parent)
|
||||
|
||||
level[0] = taglevel
|
||||
|
||||
tag = et.Element("MAKO:formatting.section", path=repr(current[0].path), paged='paged', extension='extension', toc='toc')
|
||||
tag.text = (node.tail or "") + '\n'
|
||||
tag.tail = '\n'
|
||||
tag[:] = content
|
||||
tree[start:end] = [tag]
|
||||
|
||||
process(tag)
|
||||
|
||||
process(tree)
|
||||
return (title[0], tocroot.get_by_file(filename))
|
||||
|
||||
def literal(s):
|
||||
return '"%s"' % s
|
||||
|
||||
def index(parent, item):
|
||||
for n, i in enumerate(parent):
|
||||
if i is item:
|
||||
return n
|
||||
|
||||
def find_header_index(tree):
|
||||
for i, node in enumerate(tree):
|
||||
if is_header(node):
|
||||
return i
|
||||
|
||||
def is_header(node):
|
||||
t = node.tag
|
||||
return (isinstance(t, str) and len(t) == 2 and t[0] == 'h'
|
||||
and t[1] in '123456789')
|
||||
|
||||
def end_of_header(tree, level, start):
|
||||
for i, node in enumerate(tree[start:]):
|
||||
if is_header(node) and int(node.tag[1]) <= level:
|
||||
return start + i
|
||||
return len(tree)
|
||||
|
||||
def process_rel_href(tree):
|
||||
parent = get_parent_map(tree)
|
||||
for a in tree.findall('.//a'):
|
||||
m = re.match(r'(bold)?rel\:(.+)', a.get('href'))
|
||||
if m:
|
||||
(bold, path) = m.group(1,2)
|
||||
text = a.text
|
||||
if text == path:
|
||||
tag = et.Element("MAKO:nav.toclink", path=repr(path), extension='extension', paged='paged', toc='toc')
|
||||
else:
|
||||
tag = et.Element("MAKO:nav.toclink", path=repr(path), description=repr(text), extension='extension', paged='paged', toc='toc')
|
||||
a_parent = parent[a]
|
||||
if bold:
|
||||
bold = et.Element('strong')
|
||||
bold.tail = a.tail
|
||||
bold.append(tag)
|
||||
a_parent[index(a_parent, a)] = bold
|
||||
else:
|
||||
tag.tail = a.tail
|
||||
a_parent[index(a_parent, a)] = tag
|
||||
|
||||
def replace_pre_with_mako(tree):
|
||||
def splice_code_tag(pre, text, code=None, title=None):
|
||||
doctest_directives = re.compile(r'#\s*doctest:\s*[+-]\w+(,[+-]\w+)*\s*$', re.M)
|
||||
text = re.sub(doctest_directives, '', text)
|
||||
# process '>>>' to have quotes around it, to work with the pygments
|
||||
# syntax highlighter which uses the tokenize module
|
||||
text = re.sub(r'>>> ', r'">>>" ', text)
|
||||
|
||||
sqlre = re.compile(r'{sql}(.*?)\n((?:PRAGMA|BEGIN|SELECT|INSERT|DELETE|ROLLBACK|COMMIT|UPDATE|CREATE|DROP|PRAGMA|DESCRIBE).*?)\n\s*((?:{stop})|\n|$)', re.S)
|
||||
if sqlre.search(text) is not None:
|
||||
use_sliders = False
|
||||
else:
|
||||
use_sliders = True
|
||||
|
||||
text = sqlre.sub(r"""${formatting.poplink()}\1<%call expr="formatting.codepopper()">\2</%call>""", text)
|
||||
|
||||
#sqlre2 = re.compile(r'{opensql}(.*?\n)((?:PRAGMA|BEGIN|SELECT|INSERT|DELETE|UPDATE|ROLLBACK|COMMIT|CREATE|DROP).*?)\n\s*((?:{stop})|\n|$)', re.S)
|
||||
sqlre2 = re.compile(r'{opensql}(.*?)\n?((?:PRAGMA|BEGIN|SELECT|INSERT|DELETE|ROLLBACK|COMMIT|UPDATE|CREATE|DROP|PRAGMA|DESCRIBE).*?)\n\s*((?:{stop})|\n|$)', re.S)
|
||||
text = sqlre2.sub(r"\1<%call expr='formatting.poppedcode()' >\2</%call>\n\n", text)
|
||||
|
||||
tag = et.Element("MAKO:formatting.code", extension='extension', paged='paged', toc='toc')
|
||||
if code:
|
||||
tag.attrib["syntaxtype"] = repr(code)
|
||||
if title:
|
||||
tag.attrib["title"] = repr(title)
|
||||
if use_sliders:
|
||||
tag.attrib['use_sliders'] = True
|
||||
tag.text = text
|
||||
|
||||
pre_parent = parents[pre]
|
||||
tag.tail = pre.tail
|
||||
pre_parent[reverse_parent(pre_parent, pre)] = tag
|
||||
|
||||
parents = get_parent_map(tree)
|
||||
|
||||
for precode in tree.findall('.//pre/code'):
|
||||
reg = re.compile(r'\{(python|code|diagram)(?: title="(.*?)"){0,1}\}(.*)', re.S)
|
||||
m = reg.match(precode[0].text.lstrip())
|
||||
if m:
|
||||
code = m.group(1)
|
||||
title = m.group(2)
|
||||
text = m.group(3)
|
||||
text = re.sub(r'{(python|code|diagram).*?}(\n\s*)?', '', text)
|
||||
text = re.sub(r'\\\n', r'${r"\\\\" + "\\n\\n"}', text)
|
||||
splice_code_tag(parents[precode], text, code=code, title=title)
|
||||
elif precode.text.lstrip().startswith('>>> '):
|
||||
splice_code_tag(parents[precode], precode.text)
|
||||
|
||||
def safety_code(tree):
|
||||
parents = get_parent_map(tree)
|
||||
for code in tree.findall('.//code'):
|
||||
tag = et.Element('%text')
|
||||
if parents[code].tag != 'pre':
|
||||
tag.attrib["filter"] = "h"
|
||||
tag.text = code.text
|
||||
code.append(tag)
|
||||
code.text = ""
|
||||
|
||||
def reverse_parent(parent, item):
|
||||
for n, i in enumerate(parent):
|
||||
if i is item:
|
||||
return n
|
||||
|
||||
def get_parent_map(tree):
|
||||
return dict([(c, p) for p in tree.getiterator() for c in p])
|
||||
|
||||
def header(toc, title, filename):
|
||||
return \
|
||||
"""# -*- coding: utf-8 -*-
|
||||
<%%inherit file="content_layout.html"/>
|
||||
<%%page args="toc, extension, paged"/>
|
||||
<%%namespace name="formatting" file="formatting.html"/>
|
||||
<%%namespace name="nav" file="nav.html"/>
|
||||
<%%def name="title()">%s - %s</%%def>
|
||||
<%%!
|
||||
filename = '%s'
|
||||
%%>
|
||||
## This file is generated. Edit the .txt files instead of this one.
|
||||
""" % (toc.root.doctitle, title, filename)
|
||||
|
||||
class utf8stream(object):
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
def write(self, str):
|
||||
self.stream.write(str.encode('utf8'))
|
||||
|
||||
def parse_markdown_files(toc, files):
|
||||
for inname in files:
|
||||
infile = 'content/%s.txt' % inname
|
||||
if not os.access(infile, os.F_OK):
|
||||
continue
|
||||
html = markdown.markdown(file(infile).read())
|
||||
#foo = file('foo', 'w')
|
||||
#foo.write(html)
|
||||
tree = et.fromstring("<html>" + html + "</html>")
|
||||
(title, toc_element) = create_toc(inname, tree, toc)
|
||||
safety_code(tree)
|
||||
replace_pre_with_mako(tree)
|
||||
process_rel_href(tree)
|
||||
outname = 'output/%s.html' % inname
|
||||
print infile, '->', outname
|
||||
outfile = utf8stream(file(outname, 'w'))
|
||||
outfile.write(header(toc, title, inname))
|
||||
dump_tree(tree, outfile)
|
||||
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
Microsoft Access
|
||||
================
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.access.base
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
Firebird
|
||||
========
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.firebird.base
|
||||
|
||||
.. _kinterbasdb:
|
||||
|
||||
kinterbasdb
|
||||
-----------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.firebird.kinterbasdb
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
.. _sqlalchemy.dialects:
|
||||
|
||||
sqlalchemy.dialects
|
||||
====================
|
||||
|
||||
Supported Databases
|
||||
-------------------
|
||||
|
||||
These backends are fully operational with
|
||||
current versions of SQLAlchemy.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
firebird
|
||||
mssql
|
||||
mysql
|
||||
oracle
|
||||
postgresql
|
||||
sqlite
|
||||
|
||||
Unsupported Databases
|
||||
---------------------
|
||||
|
||||
These backends are untested and may not be completely
|
||||
ported to current versions of SQLAlchemy.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
access
|
||||
informix
|
||||
maxdb
|
||||
sybase
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
Informix
|
||||
========
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.informix.base
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
MaxDB
|
||||
=====
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.maxdb.base
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
Microsoft SQL Server
|
||||
====================
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.mssql.base
|
||||
|
||||
PyODBC
|
||||
------
|
||||
.. automodule:: sqlalchemy.dialects.mssql.pyodbc
|
||||
|
||||
AdoDBAPI
|
||||
--------
|
||||
.. automodule:: sqlalchemy.dialects.mssql.adodbapi
|
||||
|
||||
pymssql
|
||||
-------
|
||||
.. automodule:: sqlalchemy.dialects.mssql.pymssql
|
||||
|
||||
zxjdbc Notes
|
||||
--------------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.mssql.zxjdbc
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
MySQL
|
||||
=====
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.mysql.base
|
||||
|
||||
MySQL Column Types
|
||||
------------------
|
||||
|
||||
.. autoclass:: NUMERIC
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DECIMAL
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DOUBLE
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: REAL
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: FLOAT
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: INTEGER
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: BIGINT
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: MEDIUMINT
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: TINYINT
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: SMALLINT
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: BIT
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DATETIME
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DATE
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: TIME
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: TIMESTAMP
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: YEAR
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: TEXT
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: TINYTEXT
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: MEDIUMTEXT
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: LONGTEXT
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: VARCHAR
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: CHAR
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: NVARCHAR
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: NCHAR
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: VARBINARY
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: BINARY
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: BLOB
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: TINYBLOB
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: MEDIUMBLOB
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: LONGBLOB
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: ENUM
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: SET
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: BOOLEAN
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
MySQL-Python Notes
|
||||
--------------------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.mysql.mysqldb
|
||||
|
||||
OurSQL Notes
|
||||
--------------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.mysql.oursql
|
||||
|
||||
MySQL-Connector Notes
|
||||
----------------------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.mysql.mysqlconnector
|
||||
|
||||
pyodbc Notes
|
||||
--------------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.mysql.pyodbc
|
||||
|
||||
zxjdbc Notes
|
||||
--------------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.mysql.zxjdbc
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
Oracle
|
||||
======
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.oracle.base
|
||||
|
||||
cx_Oracle Notes
|
||||
---------------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.oracle.cx_oracle
|
||||
|
||||
zxjdbc Notes
|
||||
--------------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.oracle.zxjdbc
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
PostgreSQL
|
||||
==========
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.postgresql.base
|
||||
|
||||
PostgresSQL Column Types
|
||||
------------------------
|
||||
|
||||
.. autoclass:: ARRAY
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: BIT
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: BYTEA
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: CIDR
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DOUBLE_PRECISION
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: ENUM
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: INET
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: INTERVAL
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: MACADDR
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: REAL
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: UUID
|
||||
:members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
psycopg2 Notes
|
||||
--------------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.postgresql.psycopg2
|
||||
|
||||
|
||||
pg8000 Notes
|
||||
--------------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.postgresql.pg8000
|
||||
|
||||
zxjdbc Notes
|
||||
--------------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.postgresql.zxjdbc
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
SQLite
|
||||
======
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.sqlite.base
|
||||
|
||||
Pysqlite
|
||||
--------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.sqlite.pysqlite
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
Sybase
|
||||
======
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.sybase.base
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
.. _associationproxy:
|
||||
|
||||
associationproxy
|
||||
================
|
||||
|
||||
.. module:: sqlalchemy.ext.associationproxy
|
||||
|
||||
``associationproxy`` is used to create a simplified, read/write view of a
|
||||
relationship. It can be used to cherry-pick fields from a collection of
|
||||
related objects or to greatly simplify access to associated objects in an
|
||||
association relationship.
|
||||
|
||||
Simplifying Relations
|
||||
---------------------
|
||||
|
||||
Consider this "association object" mapping::
|
||||
|
||||
users_table = Table('users', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('name', String(64)),
|
||||
)
|
||||
|
||||
keywords_table = Table('keywords', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('keyword', String(64))
|
||||
)
|
||||
|
||||
userkeywords_table = Table('userkeywords', metadata,
|
||||
Column('user_id', Integer, ForeignKey("users.id"),
|
||||
primary_key=True),
|
||||
Column('keyword_id', Integer, ForeignKey("keywords.id"),
|
||||
primary_key=True)
|
||||
)
|
||||
|
||||
class User(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
class Keyword(object):
|
||||
def __init__(self, keyword):
|
||||
self.keyword = keyword
|
||||
|
||||
mapper(User, users_table, properties={
|
||||
'kw': relation(Keyword, secondary=userkeywords_table)
|
||||
})
|
||||
mapper(Keyword, keywords_table)
|
||||
|
||||
Above are three simple tables, modeling users, keywords and a many-to-many
|
||||
relationship between the two. These ``Keyword`` objects are little more
|
||||
than a container for a name, and accessing them via the relation is
|
||||
awkward::
|
||||
|
||||
user = User('jek')
|
||||
user.kw.append(Keyword('cheese inspector'))
|
||||
print user.kw
|
||||
# [<__main__.Keyword object at 0xb791ea0c>]
|
||||
print user.kw[0].keyword
|
||||
# 'cheese inspector'
|
||||
print [keyword.keyword for keyword in user.kw]
|
||||
# ['cheese inspector']
|
||||
|
||||
With ``association_proxy`` you have a "view" of the relation that contains
|
||||
just the ``.keyword`` of the related objects. The proxy is a Python
|
||||
property, and unlike the mapper relation, is defined in your class::
|
||||
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
|
||||
class User(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
# proxy the 'keyword' attribute from the 'kw' relation
|
||||
keywords = association_proxy('kw', 'keyword')
|
||||
|
||||
# ...
|
||||
>>> user.kw
|
||||
[<__main__.Keyword object at 0xb791ea0c>]
|
||||
>>> user.keywords
|
||||
['cheese inspector']
|
||||
>>> user.keywords.append('snack ninja')
|
||||
>>> user.keywords
|
||||
['cheese inspector', 'snack ninja']
|
||||
>>> user.kw
|
||||
[<__main__.Keyword object at 0x9272a4c>, <__main__.Keyword object at 0xb7b396ec>]
|
||||
|
||||
The proxy is read/write. New associated objects are created on demand when
|
||||
values are added to the proxy, and modifying or removing an entry through
|
||||
the proxy also affects the underlying collection.
|
||||
|
||||
- The association proxy property is backed by a mapper-defined relation,
|
||||
either a collection or scalar.
|
||||
|
||||
- You can access and modify both the proxy and the backing
|
||||
relation. Changes in one are immediate in the other.
|
||||
|
||||
- The proxy acts like the type of the underlying collection. A list gets a
|
||||
list-like proxy, a dict a dict-like proxy, and so on.
|
||||
|
||||
- Multiple proxies for the same relation are fine.
|
||||
|
||||
- Proxies are lazy, and won't trigger a load of the backing relation until
|
||||
they are accessed.
|
||||
|
||||
- The relation is inspected to determine the type of the related objects.
|
||||
|
||||
- To construct new instances, the type is called with the value being
|
||||
assigned, or key and value for dicts.
|
||||
|
||||
- A ````creator```` function can be used to create instances instead.
|
||||
|
||||
Above, the ``Keyword.__init__`` takes a single argument ``keyword``, which
|
||||
maps conveniently to the value being set through the proxy. A ``creator``
|
||||
function could have been used instead if more flexibility was required.
|
||||
|
||||
Because the proxies are backed by a regular relation collection, all of the
|
||||
usual hooks and patterns for using collections are still in effect. The
|
||||
most convenient behavior is the automatic setting of "parent"-type
|
||||
relationships on assignment. In the example above, nothing special had to
|
||||
be done to associate the Keyword to the User. Simply adding it to the
|
||||
collection is sufficient.
|
||||
|
||||
Simplifying Association Object Relations
|
||||
----------------------------------------
|
||||
|
||||
Association proxies are also useful for keeping ``association objects`` out
|
||||
the way during regular use. For example, the ``userkeywords`` table
|
||||
might have a bunch of auditing columns that need to get updated when changes
|
||||
are made- columns that are updated but seldom, if ever, accessed in your
|
||||
application. A proxy can provide a very natural access pattern for the
|
||||
relation.
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
|
||||
# users_table and keywords_table tables as above, then:
|
||||
|
||||
def get_current_uid():
|
||||
"""Return the uid of the current user."""
|
||||
return 1 # hardcoded for this example
|
||||
|
||||
userkeywords_table = Table('userkeywords', metadata,
|
||||
Column('user_id', Integer, ForeignKey("users.id"), primary_key=True),
|
||||
Column('keyword_id', Integer, ForeignKey("keywords.id"), primary_key=True),
|
||||
# add some auditing columns
|
||||
Column('updated_at', DateTime, default=datetime.now),
|
||||
Column('updated_by', Integer, default=get_current_uid, onupdate=get_current_uid),
|
||||
)
|
||||
|
||||
def _create_uk_by_keyword(keyword):
|
||||
"""A creator function."""
|
||||
return UserKeyword(keyword=keyword)
|
||||
|
||||
class User(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
keywords = association_proxy('user_keywords', 'keyword', creator=_create_uk_by_keyword)
|
||||
|
||||
class Keyword(object):
|
||||
def __init__(self, keyword):
|
||||
self.keyword = keyword
|
||||
def __repr__(self):
|
||||
return 'Keyword(%s)' % repr(self.keyword)
|
||||
|
||||
class UserKeyword(object):
|
||||
def __init__(self, user=None, keyword=None):
|
||||
self.user = user
|
||||
self.keyword = keyword
|
||||
|
||||
mapper(User, users_table)
|
||||
mapper(Keyword, keywords_table)
|
||||
mapper(UserKeyword, userkeywords_table, properties={
|
||||
'user': relation(User, backref='user_keywords'),
|
||||
'keyword': relation(Keyword),
|
||||
})
|
||||
|
||||
user = User('log')
|
||||
kw1 = Keyword('new_from_blammo')
|
||||
|
||||
# Creating a UserKeyword association object will add a Keyword.
|
||||
# the "user" reference assignment in the UserKeyword() constructor
|
||||
# populates "user_keywords" via backref.
|
||||
UserKeyword(user, kw1)
|
||||
|
||||
# Accessing Keywords requires traversing UserKeywords
|
||||
print user.user_keywords[0]
|
||||
# <__main__.UserKeyword object at 0xb79bbbec>
|
||||
|
||||
print user.user_keywords[0].keyword
|
||||
# Keyword('new_from_blammo')
|
||||
|
||||
# Lots of work.
|
||||
|
||||
# It's much easier to go through the association proxy!
|
||||
for kw in (Keyword('its_big'), Keyword('its_heavy'), Keyword('its_wood')):
|
||||
user.keywords.append(kw)
|
||||
|
||||
print user.keywords
|
||||
# [Keyword('new_from_blammo'), Keyword('its_big'), Keyword('its_heavy'), Keyword('its_wood')]
|
||||
|
||||
|
||||
Building Complex Views
|
||||
----------------------
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
stocks_table = Table("stocks", meta,
|
||||
Column('symbol', String(10), primary_key=True),
|
||||
Column('last_price', Numeric)
|
||||
)
|
||||
|
||||
brokers_table = Table("brokers", meta,
|
||||
Column('id', Integer,primary_key=True),
|
||||
Column('name', String(100), nullable=False)
|
||||
)
|
||||
|
||||
holdings_table = Table("holdings", meta,
|
||||
Column('broker_id', Integer, ForeignKey('brokers.id'), primary_key=True),
|
||||
Column('symbol', String(10), ForeignKey('stocks.symbol'), primary_key=True),
|
||||
Column('shares', Integer)
|
||||
)
|
||||
|
||||
Above are three tables, modeling stocks, their brokers and the number of
|
||||
shares of a stock held by each broker. This situation is quite different
|
||||
from the association example above. ``shares`` is a *property of the
|
||||
relation*, an important one that we need to use all the time.
|
||||
|
||||
For this example, it would be very convenient if ``Broker`` objects had a
|
||||
dictionary collection that mapped ``Stock`` instances to the shares held for
|
||||
each. That's easy::
|
||||
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||
|
||||
def _create_holding(stock, shares):
|
||||
"""A creator function, constructs Holdings from Stock and share quantity."""
|
||||
return Holding(stock=stock, shares=shares)
|
||||
|
||||
class Broker(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
holdings = association_proxy('by_stock', 'shares', creator=_create_holding)
|
||||
|
||||
class Stock(object):
|
||||
def __init__(self, symbol):
|
||||
self.symbol = symbol
|
||||
self.last_price = 0
|
||||
|
||||
class Holding(object):
|
||||
def __init__(self, broker=None, stock=None, shares=0):
|
||||
self.broker = broker
|
||||
self.stock = stock
|
||||
self.shares = shares
|
||||
|
||||
mapper(Stock, stocks_table)
|
||||
mapper(Broker, brokers_table, properties={
|
||||
'by_stock': relation(Holding,
|
||||
collection_class=attribute_mapped_collection('stock'))
|
||||
})
|
||||
mapper(Holding, holdings_table, properties={
|
||||
'stock': relation(Stock),
|
||||
'broker': relation(Broker)
|
||||
})
|
||||
|
||||
Above, we've set up the ``by_stock`` relation collection to act as a
|
||||
dictionary, using the ``.stock`` property of each Holding as a key.
|
||||
|
||||
Populating and accessing that dictionary manually is slightly inconvenient
|
||||
because of the complexity of the Holdings association object::
|
||||
|
||||
stock = Stock('ZZK')
|
||||
broker = Broker('paj')
|
||||
|
||||
broker.by_stock[stock] = Holding(broker, stock, 10)
|
||||
print broker.by_stock[stock].shares
|
||||
# 10
|
||||
|
||||
The ``holdings`` proxy we've added to the ``Broker`` class hides the details
|
||||
of the ``Holding`` while also giving access to ``.shares``::
|
||||
|
||||
for stock in (Stock('JEK'), Stock('STPZ')):
|
||||
broker.holdings[stock] = 123
|
||||
|
||||
for stock, shares in broker.holdings.items():
|
||||
print stock, shares
|
||||
|
||||
session.add(broker)
|
||||
session.commit()
|
||||
|
||||
# lets take a peek at that holdings_table after committing changes to the db
|
||||
print list(holdings_table.select().execute())
|
||||
# [(1, 'ZZK', 10), (1, 'JEK', 123), (1, 'STEPZ', 123)]
|
||||
|
||||
Further examples can be found in the ``examples/`` directory in the
|
||||
SQLAlchemy distribution.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
.. autofunction:: association_proxy
|
||||
|
||||
.. autoclass:: AssociationProxy
|
||||
:members:
|
||||
:undoc-members:
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
.. _sqlalchemy.ext.compiler_toplevel:
|
||||
|
||||
compiler
|
||||
========
|
||||
|
||||
.. automodule:: sqlalchemy.ext.compiler
|
||||
:members:
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
declarative
|
||||
===========
|
||||
|
||||
.. automodule:: sqlalchemy.ext.declarative
|
||||
:members:
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
.. _plugins:
|
||||
.. _sqlalchemy.ext:
|
||||
|
||||
sqlalchemy.ext
|
||||
==============
|
||||
|
||||
SQLAlchemy has a variety of extensions available which provide extra
|
||||
functionality to SA, either via explicit usage or by augmenting the
|
||||
core behavior.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
declarative
|
||||
associationproxy
|
||||
orderinglist
|
||||
serializer
|
||||
sqlsoup
|
||||
compiler
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
orderinglist
|
||||
============
|
||||
|
||||
.. module: sqlalchemy.ext.orderinglist
|
||||
|
||||
:author: Jason Kirtland
|
||||
|
||||
``orderinglist`` is a helper for mutable ordered relations. It will intercept
|
||||
list operations performed on a relation collection and automatically
|
||||
synchronize changes in list position with an attribute on the related objects.
|
||||
(See :ref:`advdatamapping_entitycollections` for more information on the general pattern.)
|
||||
|
||||
Example: Two tables that store slides in a presentation. Each slide
|
||||
has a number of bullet points, displayed in order by the 'position'
|
||||
column on the bullets table. These bullets can be inserted and re-ordered
|
||||
by your end users, and you need to update the 'position' column of all
|
||||
affected rows when changes are made.
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
slides_table = Table('Slides', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('name', String))
|
||||
|
||||
bullets_table = Table('Bullets', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('slide_id', Integer, ForeignKey('Slides.id')),
|
||||
Column('position', Integer),
|
||||
Column('text', String))
|
||||
|
||||
class Slide(object):
|
||||
pass
|
||||
class Bullet(object):
|
||||
pass
|
||||
|
||||
mapper(Slide, slides_table, properties={
|
||||
'bullets': relation(Bullet, order_by=[bullets_table.c.position])
|
||||
})
|
||||
mapper(Bullet, bullets_table)
|
||||
|
||||
The standard relation mapping will produce a list-like attribute on each Slide
|
||||
containing all related Bullets, but coping with changes in ordering is totally
|
||||
your responsibility. If you insert a Bullet into that list, there is no
|
||||
magic- it won't have a position attribute unless you assign it it one, and
|
||||
you'll need to manually renumber all the subsequent Bullets in the list to
|
||||
accommodate the insert.
|
||||
|
||||
An ``orderinglist`` can automate this and manage the 'position' attribute on all
|
||||
related bullets for you.
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
mapper(Slide, slides_table, properties={
|
||||
'bullets': relation(Bullet,
|
||||
collection_class=ordering_list('position'),
|
||||
order_by=[bullets_table.c.position])
|
||||
})
|
||||
mapper(Bullet, bullets_table)
|
||||
|
||||
s = Slide()
|
||||
s.bullets.append(Bullet())
|
||||
s.bullets.append(Bullet())
|
||||
s.bullets[1].position
|
||||
>>> 1
|
||||
s.bullets.insert(1, Bullet())
|
||||
s.bullets[2].position
|
||||
>>> 2
|
||||
|
||||
Use the ``ordering_list`` function to set up the ``collection_class`` on relations
|
||||
(as in the mapper example above). This implementation depends on the list
|
||||
starting in the proper order, so be SURE to put an order_by on your relation.
|
||||
|
||||
``ordering_list`` takes the name of the related object's ordering attribute as
|
||||
an argument. By default, the zero-based integer index of the object's
|
||||
position in the ``ordering_list`` is synchronized with the ordering attribute:
|
||||
index 0 will get position 0, index 1 position 1, etc. To start numbering at 1
|
||||
or some other integer, provide ``count_from=1``.
|
||||
|
||||
Ordering values are not limited to incrementing integers. Almost any scheme
|
||||
can implemented by supplying a custom ``ordering_func`` that maps a Python list
|
||||
index to any value you require. See the [module
|
||||
documentation](rel:docstrings_sqlalchemy.ext.orderinglist) for more
|
||||
information, and also check out the unit tests for examples of stepped
|
||||
numbering, alphabetical and Fibonacci numbering.
|
||||
|
||||
.. automodule:: sqlalchemy.ext.orderinglist
|
||||
:members:
|
||||
:undoc-members:
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
serializer
|
||||
==========
|
||||
|
||||
:author: Mike Bayer
|
||||
|
||||
.. automodule:: sqlalchemy.ext.serializer
|
||||
:members:
|
||||
:undoc-members:
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
SqlSoup
|
||||
=======
|
||||
|
||||
.. automodule:: sqlalchemy.ext.sqlsoup
|
||||
:members:
|
||||
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
.. _api_reference_toplevel:
|
||||
|
||||
API Reference
|
||||
=============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
sqlalchemy/index
|
||||
orm/index
|
||||
dialects/index
|
||||
ext/index
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
Collection Mapping
|
||||
==================
|
||||
|
||||
This is an in-depth discussion of collection mechanics. For simple examples, see :ref:`alternate_collection_implementations`.
|
||||
|
||||
.. automodule:: sqlalchemy.orm.collections
|
||||
|
||||
.. autofunction:: attribute_mapped_collection
|
||||
|
||||
.. autoclass:: collection
|
||||
|
||||
.. autoclass:: sqlalchemy.orm.collections.MappedCollection
|
||||
:members:
|
||||
|
||||
.. autofunction:: collection_adapter
|
||||
|
||||
.. autofunction:: column_mapped_collection
|
||||
|
||||
.. autofunction:: mapped_collection
|
||||
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
.. _sqlalchemy_orm_toplevel:
|
||||
|
||||
sqlalchemy.orm
|
||||
==============
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
mapping
|
||||
collections
|
||||
query
|
||||
sessions
|
||||
interfaces
|
||||
utilities
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
Interfaces
|
||||
==========
|
||||
|
||||
.. automodule:: sqlalchemy.orm.interfaces
|
||||
:members: AttributeExtension, InstrumentationManager, MapperExtension, PropComparator, SessionExtension
|
||||
:undoc-members:
|
||||
|
||||
Vendored
+91
@@ -0,0 +1,91 @@
|
||||
Class Mapping
|
||||
=============
|
||||
|
||||
.. module:: sqlalchemy.orm
|
||||
|
||||
Defining Mappings
|
||||
-----------------
|
||||
|
||||
Python classes are mapped to the database using the :func:`mapper` function.
|
||||
|
||||
.. autofunction:: mapper
|
||||
|
||||
Mapper Properties
|
||||
-----------------
|
||||
|
||||
A basic mapping of a class will simply make the columns of the
|
||||
database table or selectable available as attributes on the class.
|
||||
**Mapper properties** allow you to customize and add additional
|
||||
properties to your classes, for example making the results one-to-many
|
||||
join available as a Python list of :func:`related <relation>` objects.
|
||||
|
||||
Mapper properties are most commonly included in the :func:`mapper`
|
||||
call::
|
||||
|
||||
mapper(Parent, properties={
|
||||
'children': relation(Children)
|
||||
}
|
||||
|
||||
.. autofunction:: backref
|
||||
|
||||
.. autofunction:: column_property
|
||||
|
||||
.. autofunction:: comparable_property
|
||||
|
||||
.. autofunction:: composite
|
||||
|
||||
.. autofunction:: deferred
|
||||
|
||||
.. autofunction:: dynamic_loader
|
||||
|
||||
.. autofunction:: relation
|
||||
|
||||
.. autofunction:: synonym
|
||||
|
||||
Decorators
|
||||
----------
|
||||
|
||||
.. autofunction:: reconstructor
|
||||
|
||||
.. autofunction:: validates
|
||||
|
||||
Utilities
|
||||
---------
|
||||
|
||||
.. autofunction:: object_mapper
|
||||
|
||||
.. autofunction:: class_mapper
|
||||
|
||||
.. autofunction:: compile_mappers
|
||||
|
||||
.. autofunction:: clear_mappers
|
||||
|
||||
Attribute Utilities
|
||||
-------------------
|
||||
.. autofunction:: sqlalchemy.orm.attributes.del_attribute
|
||||
|
||||
.. autofunction:: sqlalchemy.orm.attributes.get_attribute
|
||||
|
||||
.. autofunction:: sqlalchemy.orm.attributes.get_history
|
||||
|
||||
.. autofunction:: sqlalchemy.orm.attributes.init_collection
|
||||
|
||||
.. function:: sqlalchemy.orm.attributes.instance_state
|
||||
|
||||
Return the :class:`InstanceState` for a given object.
|
||||
|
||||
.. autofunction:: sqlalchemy.orm.attributes.is_instrumented
|
||||
|
||||
.. function:: sqlalchemy.orm.attributes.manager_of_class
|
||||
|
||||
Return the :class:`ClassManager` for a given class.
|
||||
|
||||
.. autofunction:: sqlalchemy.orm.attributes.set_attribute
|
||||
|
||||
.. autofunction:: sqlalchemy.orm.attributes.set_committed_value
|
||||
|
||||
Internals
|
||||
---------
|
||||
|
||||
.. autoclass:: sqlalchemy.orm.mapper.Mapper
|
||||
:members:
|
||||
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
.. _query_api_toplevel:
|
||||
|
||||
Querying
|
||||
========
|
||||
|
||||
.. module:: sqlalchemy.orm
|
||||
|
||||
The Query Object
|
||||
----------------
|
||||
|
||||
:class:`~sqlalchemy.orm.query.Query` is produced in terms of a given :class:`~sqlalchemy.orm.session.Session`, using the :func:`~sqlalchemy.orm.query.Query.query` function::
|
||||
|
||||
q = session.query(SomeMappedClass)
|
||||
|
||||
Following is the full interface for the :class:`Query` object.
|
||||
|
||||
.. autoclass:: sqlalchemy.orm.query.Query
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
ORM-Specific Query Constructs
|
||||
-----------------------------
|
||||
|
||||
.. autoclass:: aliased
|
||||
|
||||
.. autofunction:: join
|
||||
|
||||
.. autofunction:: outerjoin
|
||||
|
||||
Query Options
|
||||
-------------
|
||||
|
||||
Options which are passed to ``query.options()``, to affect the behavior of loading.
|
||||
|
||||
.. autofunction:: contains_eager
|
||||
|
||||
.. autofunction:: defer
|
||||
|
||||
.. autofunction:: eagerload
|
||||
|
||||
.. autofunction:: eagerload_all
|
||||
|
||||
.. autofunction:: extension
|
||||
|
||||
.. autofunction:: lazyload
|
||||
|
||||
.. autofunction:: undefer
|
||||
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
Sessions
|
||||
========
|
||||
|
||||
.. module:: sqlalchemy.orm
|
||||
|
||||
|
||||
.. autofunction:: create_session
|
||||
|
||||
.. autofunction:: scoped_session
|
||||
|
||||
.. autofunction:: sessionmaker
|
||||
|
||||
.. autoclass:: sqlalchemy.orm.session.Session
|
||||
:members:
|
||||
|
||||
.. autoclass:: sqlalchemy.orm.scoping.ScopedSession
|
||||
:members:
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
Utilities
|
||||
=========
|
||||
|
||||
.. automodule:: sqlalchemy.orm.util
|
||||
:members: identity_key, Validator, with_parent, polymorphic_union
|
||||
:undoc-members:
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
Connections
|
||||
===========
|
||||
|
||||
Creating Engines
|
||||
----------------
|
||||
|
||||
.. autofunction:: sqlalchemy.create_engine
|
||||
|
||||
.. autofunction:: sqlalchemy.engine_from_config
|
||||
|
||||
.. autoclass:: sqlalchemy.engine.url.URL
|
||||
:members:
|
||||
|
||||
Connectables
|
||||
------------
|
||||
|
||||
.. currentmodule:: sqlalchemy.engine.base
|
||||
|
||||
.. autoclass:: Engine
|
||||
:members:
|
||||
|
||||
.. autoclass:: Connection
|
||||
:members:
|
||||
|
||||
.. autoclass:: Connectable
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
Result Objects
|
||||
--------------
|
||||
|
||||
.. autoclass:: sqlalchemy.engine.base.ResultProxy
|
||||
:members:
|
||||
|
||||
.. autoclass:: sqlalchemy.engine.base.RowProxy
|
||||
:members:
|
||||
|
||||
Transactions
|
||||
------------
|
||||
|
||||
.. autoclass:: Transaction
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
Internals
|
||||
---------
|
||||
|
||||
.. autofunction:: connection_memoize
|
||||
|
||||
.. autoclass:: Dialect
|
||||
:members:
|
||||
|
||||
.. autoclass:: sqlalchemy.engine.default.DefaultDialect
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: sqlalchemy.engine.default.DefaultExecutionContext
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: ExecutionContext
|
||||
:members:
|
||||
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
SQL Statements and Expressions
|
||||
==============================
|
||||
|
||||
.. module:: sqlalchemy.sql.expression
|
||||
|
||||
Functions
|
||||
---------
|
||||
|
||||
The expression package uses functions to construct SQL expressions. The return value of each function is an object instance which is a subclass of :class:`~sqlalchemy.sql.expression.ClauseElement`.
|
||||
|
||||
.. autofunction:: alias
|
||||
|
||||
.. autofunction:: and_
|
||||
|
||||
.. autofunction:: asc
|
||||
|
||||
.. autofunction:: between
|
||||
|
||||
.. autofunction:: bindparam
|
||||
|
||||
.. autofunction:: case
|
||||
|
||||
.. autofunction:: cast
|
||||
|
||||
.. autofunction:: column
|
||||
|
||||
.. autofunction:: collate
|
||||
|
||||
.. autofunction:: delete
|
||||
|
||||
.. autofunction:: desc
|
||||
|
||||
.. autofunction:: distinct
|
||||
|
||||
.. autofunction:: except_
|
||||
|
||||
.. autofunction:: except_all
|
||||
|
||||
.. autofunction:: exists
|
||||
|
||||
.. autofunction:: extract
|
||||
|
||||
.. attribute:: func
|
||||
|
||||
Generate SQL function expressions.
|
||||
|
||||
``func`` is a special object instance which generates SQL functions based on name-based attributes, e.g.::
|
||||
|
||||
>>> print func.count(1)
|
||||
count(:param_1)
|
||||
|
||||
Any name can be given to `func`. If the function name is unknown to SQLAlchemy, it will be rendered exactly as is. For common SQL functions which SQLAlchemy is aware of, the name may be interpreted as a *generic function* which will be compiled appropriately to the target database::
|
||||
|
||||
>>> print func.current_timestamp()
|
||||
CURRENT_TIMESTAMP
|
||||
|
||||
To call functions which are present in dot-separated packages, specify them in the same manner::
|
||||
|
||||
>>> print func.stats.yield_curve(5, 10)
|
||||
stats.yield_curve(:yield_curve_1, :yield_curve_2)
|
||||
|
||||
SQLAlchemy can be made aware of the return type of functions to enable type-specific lexical and result-based behavior. For example, to ensure that a string-based function returns a Unicode value and is similarly treated as a string in expressions, specify :class:`~sqlalchemy.types.Unicode` as the type:
|
||||
|
||||
>>> print func.my_string(u'hi', type_=Unicode) + ' ' + \
|
||||
... func.my_string(u'there', type_=Unicode)
|
||||
my_string(:my_string_1) || :my_string_2 || my_string(:my_string_3)
|
||||
|
||||
Functions which are interpreted as "generic" functions know how to calculate their return type automatically. For a listing of known generic functions, see :ref:`generic_functions`.
|
||||
|
||||
.. autofunction:: insert
|
||||
|
||||
.. autofunction:: intersect
|
||||
|
||||
.. autofunction:: intersect_all
|
||||
|
||||
.. autofunction:: join
|
||||
|
||||
.. autofunction:: label
|
||||
|
||||
.. autofunction:: literal
|
||||
|
||||
.. autofunction:: literal_column
|
||||
|
||||
.. autofunction:: not_
|
||||
|
||||
.. autofunction:: null
|
||||
|
||||
.. autofunction:: or_
|
||||
|
||||
.. autofunction:: outparam
|
||||
|
||||
.. autofunction:: outerjoin
|
||||
|
||||
.. autofunction:: select
|
||||
|
||||
.. autofunction:: subquery
|
||||
|
||||
.. autofunction:: table
|
||||
|
||||
.. autofunction:: text
|
||||
|
||||
.. autofunction:: tuple_
|
||||
|
||||
.. autofunction:: union
|
||||
|
||||
.. autofunction:: union_all
|
||||
|
||||
.. autofunction:: update
|
||||
|
||||
Classes
|
||||
-------
|
||||
|
||||
.. autoclass:: Alias
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: _BindParamClause
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: ClauseElement
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: ColumnClause
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: ColumnCollection
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: ColumnElement
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: _CompareMixin
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: ColumnOperators
|
||||
:members:
|
||||
:undoc-members:
|
||||
:inherited-members:
|
||||
|
||||
.. autoclass:: CompoundSelect
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Delete
|
||||
:members: where
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: FunctionElement
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Function
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: FromClause
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Insert
|
||||
:members: prefix_with, values
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Join
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Select
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Selectable
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: _SelectBaseMixin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: TableClause
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Update
|
||||
:members: where, values
|
||||
:show-inheritance:
|
||||
|
||||
.. _generic_functions:
|
||||
|
||||
Generic Functions
|
||||
-----------------
|
||||
|
||||
SQL functions which are known to SQLAlchemy with regards to database-specific rendering, return types and argument behavior. Generic functions are invoked like all SQL functions, using the :attr:`func` attribute::
|
||||
|
||||
select([func.count()]).select_from(sometable)
|
||||
|
||||
.. automodule:: sqlalchemy.sql.functions
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
sqlalchemy
|
||||
==========
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
connections
|
||||
pooling
|
||||
expressions
|
||||
schema
|
||||
types
|
||||
interfaces
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Interfaces
|
||||
----------
|
||||
|
||||
.. automodule:: sqlalchemy.interfaces
|
||||
:members:
|
||||
:undoc-members:
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
.. _pooling_toplevel:
|
||||
|
||||
Connection Pooling
|
||||
==================
|
||||
|
||||
.. module:: sqlalchemy.pool
|
||||
|
||||
SQLAlchemy ships with a connection pooling framework that integrates
|
||||
with the Engine system and can also be used on its own to manage plain
|
||||
DB-API connections.
|
||||
|
||||
At the base of any database helper library is a system for efficiently
|
||||
acquiring connections to the database. Since the establishment of a
|
||||
database connection is typically a somewhat expensive operation, an
|
||||
application needs a way to get at database connections repeatedly
|
||||
without incurring the full overhead each time. Particularly for
|
||||
server-side web applications, a connection pool is the standard way to
|
||||
maintain a group or "pool" of active database connections which are
|
||||
reused from request to request in a single server process.
|
||||
|
||||
Connection Pool Configuration
|
||||
-----------------------------
|
||||
|
||||
The :class:`~sqlalchemy.engine.Engine` returned by the
|
||||
:func:`~sqlalchemy.create_engine` function in most cases has a :class:`QueuePool`
|
||||
integrated, pre-configured with reasonable pooling defaults. If
|
||||
you're reading this section to simply enable pooling- congratulations!
|
||||
You're already done.
|
||||
|
||||
The most common :class:`QueuePool` tuning parameters can be passed
|
||||
directly to :func:`~sqlalchemy.create_engine` as keyword arguments:
|
||||
``pool_size``, ``max_overflow``, ``pool_recycle`` and
|
||||
``pool_timeout``. For example::
|
||||
|
||||
engine = create_engine('postgresql://me@localhost/mydb',
|
||||
pool_size=20, max_overflow=0)
|
||||
|
||||
In the case of SQLite, a :class:`SingletonThreadPool` is provided instead,
|
||||
to provide compatibility with SQLite's restricted threading model.
|
||||
|
||||
|
||||
Custom Pool Construction
|
||||
------------------------
|
||||
|
||||
:class:`Pool` instances may be created directly for your own use or to
|
||||
supply to :func:`sqlalchemy.create_engine` via the ``pool=``
|
||||
keyword argument.
|
||||
|
||||
Constructing your own pool requires supplying a callable function the
|
||||
Pool can use to create new connections. The function will be called
|
||||
with no arguments.
|
||||
|
||||
Through this method, custom connection schemes can be made, such as a
|
||||
using connections from another library's pool, or making a new
|
||||
connection that automatically executes some initialization commands::
|
||||
|
||||
import sqlalchemy.pool as pool
|
||||
import psycopg2
|
||||
|
||||
def getconn():
|
||||
c = psycopg2.connect(username='ed', host='127.0.0.1', dbname='test')
|
||||
# execute an initialization function on the connection before returning
|
||||
c.cursor.execute("setup_encodings()")
|
||||
return c
|
||||
|
||||
p = pool.QueuePool(getconn, max_overflow=10, pool_size=5)
|
||||
|
||||
Or with SingletonThreadPool::
|
||||
|
||||
import sqlalchemy.pool as pool
|
||||
import sqlite
|
||||
|
||||
p = pool.SingletonThreadPool(lambda: sqlite.connect(filename='myfile.db'))
|
||||
|
||||
|
||||
Builtin Pool Implementations
|
||||
----------------------------
|
||||
|
||||
.. autoclass:: AssertionPool
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: NullPool
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: sqlalchemy.pool.Pool
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
:inherited-members:
|
||||
|
||||
.. autoclass:: sqlalchemy.pool.QueuePool
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: SingletonThreadPool
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: StaticPool
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Pooling Plain DB-API Connections
|
||||
--------------------------------
|
||||
|
||||
Any :pep:`249` DB-API module can be "proxied" through the connection
|
||||
pool transparently. Usage of the DB-API is exactly as before, except
|
||||
the ``connect()`` method will consult the pool. Below we illustrate
|
||||
this with ``psycopg2``::
|
||||
|
||||
import sqlalchemy.pool as pool
|
||||
import psycopg2 as psycopg
|
||||
|
||||
psycopg = pool.manage(psycopg)
|
||||
|
||||
# then connect normally
|
||||
connection = psycopg.connect(database='test', username='scott',
|
||||
password='tiger')
|
||||
|
||||
This produces a :class:`_DBProxy` object which supports the same
|
||||
``connect()`` function as the original DB-API module. Upon
|
||||
connection, a connection proxy object is returned, which delegates its
|
||||
calls to a real DB-API connection object. This connection object is
|
||||
stored persistently within a connection pool (an instance of
|
||||
:class:`Pool`) that corresponds to the exact connection arguments sent
|
||||
to the ``connect()`` function.
|
||||
|
||||
The connection proxy supports all of the methods on the original
|
||||
connection object, most of which are proxied via ``__getattr__()``.
|
||||
The ``close()`` method will return the connection to the pool, and the
|
||||
``cursor()`` method will return a proxied cursor object. Both the
|
||||
connection proxy and the cursor proxy will also return the underlying
|
||||
connection to the pool after they have both been garbage collected,
|
||||
which is detected via weakref callbacks (``__del__`` is not used).
|
||||
|
||||
Additionally, when connections are returned to the pool, a
|
||||
``rollback()`` is issued on the connection unconditionally. This is
|
||||
to release any locks still held by the connection that may have
|
||||
resulted from normal activity.
|
||||
|
||||
By default, the ``connect()`` method will return the same connection
|
||||
that is already checked out in the current thread. This allows a
|
||||
particular connection to be used in a given thread without needing to
|
||||
pass it around between functions. To disable this behavior, specify
|
||||
``use_threadlocal=False`` to the ``manage()`` function.
|
||||
|
||||
.. autofunction:: sqlalchemy.pool.manage
|
||||
|
||||
.. autofunction:: sqlalchemy.pool.clear_managers
|
||||
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
.. _schema_api_toplevel:
|
||||
|
||||
Database Schema
|
||||
===============
|
||||
|
||||
.. module:: sqlalchemy.schema
|
||||
|
||||
SQLAlchemy schema definition language. For more usage examples, see :ref:`metadata_toplevel`.
|
||||
|
||||
Tables and Columns
|
||||
------------------
|
||||
|
||||
.. autoclass:: Column
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: MetaData
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Table
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: ThreadLocalMetaData
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Constraints
|
||||
-----------
|
||||
|
||||
.. autoclass:: CheckConstraint
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Constraint
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: ForeignKey
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: ForeignKeyConstraint
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Index
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: PrimaryKeyConstraint
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: UniqueConstraint
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Default Generators and Markers
|
||||
------------------------------
|
||||
|
||||
.. autoclass:: ColumnDefault
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DefaultClause
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DefaultGenerator
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: FetchedValue
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: PassiveDefault
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Sequence
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _schema_api_ddl:
|
||||
|
||||
DDL Generation
|
||||
--------------
|
||||
|
||||
.. autoclass:: DDLElement
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DDL
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: CreateTable
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DropTable
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: CreateSequence
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DropSequence
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: CreateIndex
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DropIndex
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: AddConstraint
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DropConstraint
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Internals
|
||||
---------
|
||||
|
||||
.. autoclass:: SchemaItem
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: SchemaVisitor
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
.. _types:
|
||||
|
||||
Column and Data Types
|
||||
=====================
|
||||
|
||||
.. module:: sqlalchemy.types
|
||||
|
||||
SQLAlchemy provides abstractions for most common database data types,
|
||||
and a mechanism for specifying your own custom data types.
|
||||
|
||||
The methods and attributes of type objects are rarely used directly.
|
||||
Type objects are supplied to :class:`~sqlalchemy.Table` definitions
|
||||
and can be supplied as type hints to `functions` for occasions where
|
||||
the database driver returns an incorrect type.
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> users = Table('users', metadata,
|
||||
... Column('id', Integer, primary_key=True)
|
||||
... Column('login', String(32))
|
||||
... )
|
||||
|
||||
|
||||
SQLAlchemy will use the ``Integer`` and ``String(32)`` type
|
||||
information when issuing a ``CREATE TABLE`` statement and will use it
|
||||
again when reading back rows ``SELECTed`` from the database.
|
||||
Functions that accept a type (such as :func:`~sqlalchemy.Column`) will
|
||||
typically accept a type class or instance; ``Integer`` is equivalent
|
||||
to ``Integer()`` with no construction arguments in this case.
|
||||
|
||||
Generic Types
|
||||
-------------
|
||||
|
||||
Generic types specify a column that can read, write and store a
|
||||
particular type of Python data. SQLAlchemy will choose the best
|
||||
database column type available on the target database when issuing a
|
||||
``CREATE TABLE`` statement. For complete control over which column
|
||||
type is emitted in ``CREATE TABLE``, such as ``VARCHAR`` see `SQL
|
||||
Standard Types`_ and the other sections of this chapter.
|
||||
|
||||
.. autoclass:: Boolean
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Date
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DateTime
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Enum
|
||||
:show-inheritance:
|
||||
:members:
|
||||
|
||||
.. autoclass:: Float
|
||||
:show-inheritance:
|
||||
:members:
|
||||
|
||||
.. autoclass:: Integer
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Interval
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: LargeBinary
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Numeric
|
||||
:show-inheritance:
|
||||
:members:
|
||||
|
||||
.. autoclass:: PickleType
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: SchemaType
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. autoclass:: SmallInteger
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: String
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Text
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Time
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Unicode
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: UnicodeText
|
||||
:show-inheritance:
|
||||
|
||||
SQL Standard Types
|
||||
------------------
|
||||
|
||||
The SQL standard types always create database column types of the same
|
||||
name when ``CREATE TABLE`` is issued. Some types may not be supported
|
||||
on all databases.
|
||||
|
||||
.. autoclass:: BINARY
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: BLOB
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: BOOLEAN
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: CHAR
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: CLOB
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DATE
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DATETIME
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DECIMAL
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: FLOAT
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: INT
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: sqlalchemy.types.INTEGER
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: NCHAR
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: NUMERIC
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: SMALLINT
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: TEXT
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: TIME
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: TIMESTAMP
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: VARBINARY
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: VARCHAR
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Vendor-Specific Types
|
||||
---------------------
|
||||
|
||||
Database-specific types are also available for import from each
|
||||
database's dialect module. See the :ref:`sqlalchemy.dialects`
|
||||
reference for the database you're interested in.
|
||||
|
||||
For example, MySQL has a ``BIGINTEGER`` type and PostgreSQL has an
|
||||
``INET`` type. To use these, import them from the module explicitly::
|
||||
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
table = Table('foo', meta,
|
||||
Column('id', mysql.BIGINTEGER),
|
||||
Column('enumerates', mysql.ENUM('a', 'b', 'c'))
|
||||
)
|
||||
|
||||
Or some PostgreSQL types::
|
||||
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
table = Table('foo', meta,
|
||||
Column('ipaddress', postgresql.INET),
|
||||
Column('elements', postgresql.ARRAY(str))
|
||||
)
|
||||
|
||||
Each dialect provides the full set of typenames supported by
|
||||
that backend within its `__all__` collection, so that a simple
|
||||
`import *` or similar will import all supported types as
|
||||
implemented for that backend::
|
||||
|
||||
from sqlalchemy.dialects.postgresql import *
|
||||
|
||||
t = Table('mytable', metadata,
|
||||
Column('id', INTEGER, primary_key=True),
|
||||
Column('name', VARCHAR(300)),
|
||||
Column('inetaddr', INET)
|
||||
)
|
||||
|
||||
Where above, the INTEGER and VARCHAR types are ultimately from
|
||||
sqlalchemy.types, and INET is specific to the Postgresql dialect.
|
||||
|
||||
Some dialect level types have the same name as the SQL standard type,
|
||||
but also provide additional arguments. For example, MySQL implements
|
||||
the full range of character and string types including additional arguments
|
||||
such as `collation` and `charset`::
|
||||
|
||||
from sqlalchemy.dialects.mysql import VARCHAR, TEXT
|
||||
|
||||
table = Table('foo', meta,
|
||||
Column('col1', VARCHAR(200, collation='binary')),
|
||||
Column('col2', TEXT(charset='latin1'))
|
||||
)
|
||||
|
||||
Custom Types
|
||||
------------
|
||||
|
||||
User-defined types may be created to match special capabilities of a
|
||||
particular database or simply for implementing custom processing logic
|
||||
in Python.
|
||||
|
||||
The simplest method is implementing a :class:`TypeDecorator`, a helper
|
||||
class that makes it easy to augment the bind parameter and result
|
||||
processing capabilities of one of the built in types.
|
||||
|
||||
To build a type object from scratch, subclass `:class:UserDefinedType`.
|
||||
|
||||
.. autoclass:: TypeDecorator
|
||||
:members:
|
||||
:undoc-members:
|
||||
:inherited-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: UserDefinedType
|
||||
:members:
|
||||
:undoc-members:
|
||||
:inherited-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: TypeEngine
|
||||
:members:
|
||||
:undoc-members:
|
||||
:inherited-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: AbstractType
|
||||
:members:
|
||||
:undoc-members:
|
||||
:inherited-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: MutableType
|
||||
:members:
|
||||
:undoc-members:
|
||||
:inherited-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: Concatenable
|
||||
:members:
|
||||
:undoc-members:
|
||||
:inherited-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: NullType
|
||||
:show-inheritance:
|
||||
|
||||
Vendored
+708
@@ -0,0 +1,708 @@
|
||||
.. _session_toplevel:
|
||||
|
||||
=================
|
||||
Using the Session
|
||||
=================
|
||||
|
||||
The `Mapper` is the entrypoint to the configurational API of the SQLAlchemy object relational mapper. But the primary object one works with when using the ORM is the :class:`~sqlalchemy.orm.session.Session`.
|
||||
|
||||
What does the Session do ?
|
||||
==========================
|
||||
|
||||
In the most general sense, the ``Session`` establishes all conversations with the database and represents a "holding zone" for all the mapped instances which you've loaded or created during its lifespan. It implements the `Unit of Work <http://martinfowler.com/eaaCatalog/unitOfWork.html>`_ pattern, which means it keeps track of all changes which occur, and is capable of **flushing** those changes to the database as appropriate. Another important facet of the ``Session`` is that it's also maintaining **unique** copies of each instance, where "unique" means "only one object with a particular primary key" - this pattern is called the `Identity Map <http://martinfowler.com/eaaCatalog/identityMap.html>`_.
|
||||
|
||||
Beyond that, the ``Session`` implements an interface which lets you move objects in or out of the session in a variety of ways, it provides the entryway to a ``Query`` object which is used to query the database for data, and it also provides a transactional context for SQL operations which rides on top of the transactional capabilities of ``Engine`` and ``Connection`` objects.
|
||||
|
||||
Getting a Session
|
||||
=================
|
||||
|
||||
``Session`` is a regular Python class which can be directly instantiated. However, to standardize how sessions are configured and acquired, the ``sessionmaker()`` function is normally used to create a top level ``Session`` configuration which can then be used throughout an application without the need to repeat the configurational arguments.
|
||||
|
||||
Using a sessionmaker() Configuration
|
||||
------------------------------------
|
||||
|
||||
The usage of ``sessionmaker()`` is illustrated below:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
# create a configured "Session" class
|
||||
Session = sessionmaker(bind=some_engine)
|
||||
|
||||
# create a Session
|
||||
session = Session()
|
||||
|
||||
# work with sess
|
||||
myobject = MyObject('foo', 'bar')
|
||||
session.add(myobject)
|
||||
session.commit()
|
||||
|
||||
# close when finished
|
||||
session.close()
|
||||
|
||||
Above, the ``sessionmaker`` call creates a class for us, which we assign to the name ``Session``. This class is a subclass of the actual ``sqlalchemy.orm.session.Session`` class, which will instantiate with a particular bound engine.
|
||||
|
||||
When you write your application, place the call to ``sessionmaker()`` somewhere global, and then make your new ``Session`` class available to the rest of your application.
|
||||
|
||||
Binding Session to an Engine
|
||||
----------------------------
|
||||
|
||||
In our previous example regarding ``sessionmaker()``, we specified a ``bind`` for a particular ``Engine``. If we'd like to construct a ``sessionmaker()`` without an engine available and bind it later on, or to specify other options to an existing ``sessionmaker()``, we may use the ``configure()`` method::
|
||||
|
||||
# configure Session class with desired options
|
||||
Session = sessionmaker()
|
||||
|
||||
# later, we create the engine
|
||||
engine = create_engine('postgresql://...')
|
||||
|
||||
# associate it with our custom Session class
|
||||
Session.configure(bind=engine)
|
||||
|
||||
# work with the session
|
||||
session = Session()
|
||||
|
||||
It's actually entirely optional to bind a Session to an engine. If the underlying mapped ``Table`` objects use "bound" metadata, the ``Session`` will make use of the bound engine instead (or will even use multiple engines if multiple binds are present within the mapped tables). "Bound" metadata is described at :ref:`metadata_binding`.
|
||||
|
||||
The ``Session`` also has the ability to be bound to multiple engines explicitly. Descriptions of these scenarios are described in :ref:`session_partitioning`.
|
||||
|
||||
Binding Session to a Connection
|
||||
-------------------------------
|
||||
|
||||
The ``Session`` can also be explicitly bound to an individual database ``Connection``. Reasons for doing this may include to join a ``Session`` with an ongoing transaction local to a specific ``Connection`` object, or to bypass connection pooling by just having connections persistently checked out and associated with distinct, long running sessions::
|
||||
|
||||
# global application scope. create Session class, engine
|
||||
Session = sessionmaker()
|
||||
|
||||
engine = create_engine('postgresql://...')
|
||||
|
||||
...
|
||||
|
||||
# local scope, such as within a controller function
|
||||
|
||||
# connect to the database
|
||||
connection = engine.connect()
|
||||
|
||||
# bind an individual Session to the connection
|
||||
session = Session(bind=connection)
|
||||
|
||||
Using create_session()
|
||||
----------------------
|
||||
|
||||
As an alternative to ``sessionmaker()``, ``create_session()`` is a function which calls the normal ``Session`` constructor directly. All arguments are passed through and the new ``Session`` object is returned::
|
||||
|
||||
session = create_session(bind=myengine, autocommit=True, autoflush=False)
|
||||
|
||||
Note that ``create_session()`` disables all optional "automation" by default. Called with no arguments, the session produced is not autoflushing, does not auto-expire, and does not maintain a transaction (i.e. it begins and commits a new transaction for each ``flush()``). SQLAlchemy uses ``create_session()`` extensively within its own unit tests.
|
||||
|
||||
Configurational Arguments
|
||||
-------------------------
|
||||
|
||||
Configurational arguments accepted by ``sessionmaker()`` and ``create_session()`` are the same as that of the ``Session`` class itself, and are described at :func:`sqlalchemy.orm.sessionmaker`.
|
||||
|
||||
Note that the defaults of ``create_session()`` are the opposite of that of ``sessionmaker()``: autoflush and expire_on_commit are False, autocommit is True. It is recommended to use the ``sessionmaker()`` function instead of ``create_session()``. ``create_session()`` is used to get a session with no automation turned on and is useful for testing.
|
||||
|
||||
Using the Session
|
||||
==================
|
||||
|
||||
Quickie Intro to Object States
|
||||
------------------------------
|
||||
|
||||
It's helpful to know the states which an instance can have within a session:
|
||||
|
||||
* *Transient* - an instance that's not in a session, and is not saved to the database; i.e. it has no database identity. The only relationship such an object has to the ORM is that its class has a ``mapper()`` associated with it.
|
||||
|
||||
* *Pending* - when you ``add()`` a transient instance, it becomes pending. It still wasn't actually flushed to the database yet, but it will be when the next flush occurs.
|
||||
|
||||
* *Persistent* - An instance which is present in the session and has a record in the database. You get persistent instances by either flushing so that the pending instances become persistent, or by querying the database for existing instances (or moving persistent instances from other sessions into your local session).
|
||||
|
||||
* *Detached* - an instance which has a record in the database, but is not in any session. There's nothing wrong with this, and you can use objects normally when they're detached, **except** they will not be able to issue any SQL in order to load collections or attributes which are not yet loaded, or were marked as "expired".
|
||||
|
||||
Knowing these states is important, since the ``Session`` tries to be strict about ambiguous operations (such as trying to save the same object to two different sessions at the same time).
|
||||
|
||||
Frequently Asked Questions
|
||||
--------------------------
|
||||
|
||||
* When do I make a ``sessionmaker`` ?
|
||||
|
||||
Just one time, somewhere in your application's global scope. It should be looked upon as part of your application's configuration. If your application has three .py files in a package, you could, for example, place the ``sessionmaker`` line in your ``__init__.py`` file; from that point on your other modules say "from mypackage import Session". That way, everyone else just uses ``Session()``, and the configuration of that session is controlled by that central point.
|
||||
|
||||
If your application starts up, does imports, but does not know what database it's going to be connecting to, you can bind the ``Session`` at the "class" level to the engine later on, using ``configure()``.
|
||||
|
||||
In the examples in this section, we will frequently show the ``sessionmaker`` being created right above the line where we actually invoke ``Session()``. But that's just for example's sake ! In reality, the ``sessionmaker`` would be somewhere at the module level, and your individual ``Session()`` calls would be sprinkled all throughout your app, such as in a web application within each controller method.
|
||||
|
||||
* When do I make a ``Session`` ?
|
||||
|
||||
You typically invoke ``Session()`` when you first need to talk to your database, and want to save some objects or load some existing ones. Then, you work with it, save your changes, and then dispose of it....or at the very least ``close()`` it. It's not a "global" kind of object, and should be handled more like a "local variable", as it's generally **not** safe to use with concurrent threads. Sessions are very inexpensive to make, and don't use any resources whatsoever until they are first used...so create some !
|
||||
|
||||
There is also a pattern whereby you're using a **contextual session**, this is described later in :ref:`unitofwork_contextual`. In this pattern, a helper object is maintaining a ``Session`` for you, most commonly one that is local to the current thread (and sometimes also local to an application instance). SQLAlchemy has worked this pattern out such that it still *looks* like you're creating a new session as you need one...so in that case, it's still a guaranteed win to just say ``Session()`` whenever you want a session.
|
||||
|
||||
* Is the Session a cache ?
|
||||
|
||||
Yeee...no. It's somewhat used as a cache, in that it implements the identity map pattern, and stores objects keyed to their primary key. However, it doesn't do any kind of query caching. This means, if you say ``session.query(Foo).filter_by(name='bar')``, even if ``Foo(name='bar')`` is right there, in the identity map, the session has no idea about that. It has to issue SQL to the database, get the rows back, and then when it sees the primary key in the row, *then* it can look in the local identity map and see that the object is already there. It's only when you say ``query.get({some primary key})`` that the ``Session`` doesn't have to issue a query.
|
||||
|
||||
Additionally, the Session stores object instances using a weak reference by default. This also defeats the purpose of using the Session as a cache, unless the ``weak_identity_map`` flag is set to ``False``.
|
||||
|
||||
The ``Session`` is not designed to be a global object from which everyone consults as a "registry" of objects. That is the job of a **second level cache**. A good library for implementing second level caching is `Memcached <http://www.danga.com/memcached/>`_. It *is* possible to "sort of" use the ``Session`` in this manner, if you set it to be non-transactional and it never flushes any SQL, but it's not a terrific solution, since if concurrent threads load the same objects at the same time, you may have multiple copies of the same objects present in collections.
|
||||
|
||||
* How can I get the ``Session`` for a certain object ?
|
||||
|
||||
Use the ``object_session()`` classmethod available on ``Session``::
|
||||
|
||||
session = Session.object_session(someobject)
|
||||
|
||||
.. index::
|
||||
single: thread safety; sessions
|
||||
single: thread safety; Session
|
||||
|
||||
* Is the session thread-safe?
|
||||
|
||||
Nope. It has no thread synchronization of any kind built in, and particularly when you do a flush operation, it definitely is not open to concurrent threads accessing it, because it holds onto a single database connection at that point. If you use a session which is non-transactional for read operations only, it's still not thread-"safe", but you also wont get any catastrophic failures either, since it opens and closes connections on an as-needed basis; it's just that different threads might load the same objects independently of each other, but only one will wind up in the identity map (however, the other one might still live in a collection somewhere).
|
||||
|
||||
But the bigger point here is, you should not *want* to use the session with multiple concurrent threads. That would be like having everyone at a restaurant all eat from the same plate. The session is a local "workspace" that you use for a specific set of tasks; you don't want to, or need to, share that session with other threads who are doing some other task. If, on the other hand, there are other threads participating in the same task you are, such as in a desktop graphical application, then you would be sharing the session with those threads, but you also will have implemented a proper locking scheme (or your graphical framework does) so that those threads do not collide.
|
||||
|
||||
Querying
|
||||
--------
|
||||
|
||||
The ``query()`` function takes one or more *entities* and returns a new ``Query`` object which will issue mapper queries within the context of this Session. An entity is defined as a mapped class, a ``Mapper`` object, an orm-enabled *descriptor*, or an ``AliasedClass`` object::
|
||||
|
||||
# query from a class
|
||||
session.query(User).filter_by(name='ed').all()
|
||||
|
||||
# query with multiple classes, returns tuples
|
||||
session.query(User, Address).join('addresses').filter_by(name='ed').all()
|
||||
|
||||
# query using orm-enabled descriptors
|
||||
session.query(User.name, User.fullname).all()
|
||||
|
||||
# query from a mapper
|
||||
user_mapper = class_mapper(User)
|
||||
session.query(user_mapper)
|
||||
|
||||
When ``Query`` returns results, each object instantiated is stored within the identity map. When a row matches an object which is already present, the same object is returned. In the latter case, whether or not the row is populated onto an existing object depends upon whether the attributes of the instance have been *expired* or not. A default-configured ``Session`` automatically expires all instances along transaction boundaries, so that with a normally isolated transaction, there shouldn't be any issue of instances representing data which is stale with regards to the current transaction.
|
||||
|
||||
Adding New or Existing Items
|
||||
----------------------------
|
||||
|
||||
``add()`` is used to place instances in the session. For *transient* (i.e. brand new) instances, this will have the effect of an INSERT taking place for those instances upon the next flush. For instances which are *persistent* (i.e. were loaded by this session), they are already present and do not need to be added. Instances which are *detached* (i.e. have been removed from a session) may be re-associated with a session using this method::
|
||||
|
||||
user1 = User(name='user1')
|
||||
user2 = User(name='user2')
|
||||
session.add(user1)
|
||||
session.add(user2)
|
||||
|
||||
session.commit() # write changes to the database
|
||||
|
||||
To add a list of items to the session at once, use ``add_all()``::
|
||||
|
||||
session.add_all([item1, item2, item3])
|
||||
|
||||
The ``add()`` operation **cascades** along the ``save-update`` cascade. For more details see the section :ref:`unitofwork_cascades`.
|
||||
|
||||
Merging
|
||||
-------
|
||||
|
||||
``merge()`` reconciles the current state of an instance and its associated children with existing data in the database, and returns a copy of the instance associated with the session. Usage is as follows::
|
||||
|
||||
merged_object = session.merge(existing_object)
|
||||
|
||||
When given an instance, it follows these steps:
|
||||
|
||||
* It examines the primary key of the instance. If it's present, it attempts to load an instance with that primary key (or pulls from the local identity map).
|
||||
* If there's no primary key on the given instance, or the given primary key does not exist in the database, a new instance is created.
|
||||
* The state of the given instance is then copied onto the located/newly created instance.
|
||||
* The operation is cascaded to associated child items along the ``merge`` cascade. Note that all changes present on the given instance, including changes to collections, are merged.
|
||||
* The new instance is returned.
|
||||
|
||||
With ``merge()``, the given instance is not placed within the session, and can be associated with a different session or detached. ``merge()`` is very useful for taking the state of any kind of object structure without regard for its origins or current session associations and placing that state within a session. Here's two examples:
|
||||
|
||||
* An application which reads an object structure from a file and wishes to save it to the database might parse the file, build up the structure, and then use ``merge()`` to save it to the database, ensuring that the data within the file is used to formulate the primary key of each element of the structure. Later, when the file has changed, the same process can be re-run, producing a slightly different object structure, which can then be ``merged()`` in again, and the ``Session`` will automatically update the database to reflect those changes.
|
||||
* A web application stores mapped entities within an HTTP session object. When each request starts up, the serialized data can be merged into the session, so that the original entity may be safely shared among requests and threads.
|
||||
|
||||
``merge()`` is frequently used by applications which implement their own second level caches. This refers to an application which uses an in memory dictionary, or an tool like Memcached to store objects over long running spans of time. When such an object needs to exist within a ``Session``, ``merge()`` is a good choice since it leaves the original cached object untouched. For this use case, merge provides a keyword option called ``load=False``. When this boolean flag is set to ``False``, ``merge()`` will not issue any SQL to reconcile the given object against the current state of the database, thereby reducing query overhead. The limitation is that the given object and all of its children may not contain any pending changes, and it's also of course possible that newer information in the database will not be present on the merged object, since no load is issued.
|
||||
|
||||
Deleting
|
||||
--------
|
||||
|
||||
The ``delete`` method places an instance into the Session's list of objects to be marked as deleted::
|
||||
|
||||
# mark two objects to be deleted
|
||||
session.delete(obj1)
|
||||
session.delete(obj2)
|
||||
|
||||
# commit (or flush)
|
||||
session.commit()
|
||||
|
||||
The big gotcha with ``delete()`` is that **nothing is removed from collections**. Such as, if a ``User`` has a collection of three ``Addresses``, deleting an ``Address`` will not remove it from ``user.addresses``::
|
||||
|
||||
>>> address = user.addresses[1]
|
||||
>>> session.delete(address)
|
||||
>>> session.flush()
|
||||
>>> address in user.addresses
|
||||
True
|
||||
|
||||
The solution is to use proper cascading::
|
||||
|
||||
mapper(User, users_table, properties={
|
||||
'addresses':relation(Address, cascade="all, delete, delete-orphan")
|
||||
})
|
||||
del user.addresses[1]
|
||||
session.flush()
|
||||
|
||||
Deleting based on Filter Criterion
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The caveat with ``Session.delete()`` is that you need to have an object handy already in order to delete. The Query includes a ``delete()`` method which deletes based on filtering criteria::
|
||||
|
||||
session.query(User).filter(User.id==7).delete()
|
||||
|
||||
The ``Query.delete()`` method includes functionality to "expire" objects already in the session which
|
||||
match the criteria. However it does have some caveats, including that "delete" and "delete-orphan"
|
||||
cascades won't be fully expressed for collections which are already loaded. See the API docs for :meth:`~sqlalchemy.orm.query.Query.delete` for more details.
|
||||
|
||||
Flushing
|
||||
--------
|
||||
|
||||
When the ``Session`` is used with its default configuration, the flush step is nearly always done transparently. Specifically, the flush occurs before any individual ``Query`` is issued, as well as within the ``commit()`` call before the transaction is committed. It also occurs before a SAVEPOINT is issued when ``begin_nested()`` is used.
|
||||
|
||||
Regardless of the autoflush setting, a flush can always be forced by issuing ``flush()``::
|
||||
|
||||
session.flush()
|
||||
|
||||
The "flush-on-Query" aspect of the behavior can be disabled by constructing ``sessionmaker()`` with the flag ``autoflush=False``::
|
||||
|
||||
Session = sessionmaker(autoflush=False)
|
||||
|
||||
Additionally, autoflush can be temporarily disabled by setting the ``autoflush`` flag at any time::
|
||||
|
||||
mysession = Session()
|
||||
mysession.autoflush = False
|
||||
|
||||
Some autoflush-disable recipes are available at `DisableAutoFlush <http://www.sqlalchemy.org/trac/wiki/UsageRecipes/DisableAutoflush>`_.
|
||||
|
||||
The flush process *always* occurs within a transaction, even if the ``Session`` has been configured with ``autocommit=True``, a setting that disables the session's persistent transactional state. If no transaction is present, ``flush()`` creates its own transaction and commits it. Any failures during flush will always result in a rollback of whatever transaction is present. If the Session is not in ``autocommit=True`` mode, an explicit call to ``rollback()`` is required after a flush fails, even though the underlying transaction will have been rolled back already - this is so that the overall nesting pattern of so-called "subtransactions" is consistently maintained.
|
||||
|
||||
Committing
|
||||
----------
|
||||
|
||||
``commit()`` is used to commit the current transaction. It always issues ``flush()`` beforehand to flush any remaining state to the database; this is independent of the "autoflush" setting. If no transaction is present, it raises an error. Note that the default behavior of the ``Session`` is that a transaction is always present; this behavior can be disabled by setting ``autocommit=True``. In autocommit mode, a transaction can be initiated by calling the ``begin()`` method.
|
||||
|
||||
Another behavior of ``commit()`` is that by default it expires the state of all instances present after the commit is complete. This is so that when the instances are next accessed, either through attribute access or by them being present in a ``Query`` result set, they receive the most recent state. To disable this behavior, configure ``sessionmaker()`` with ``expire_on_commit=False``.
|
||||
|
||||
Normally, instances loaded into the ``Session`` are never changed by subsequent queries; the assumption is that the current transaction is isolated so the state most recently loaded is correct as long as the transaction continues. Setting ``autocommit=True`` works against this model to some degree since the ``Session`` behaves in exactly the same way with regard to attribute state, except no transaction is present.
|
||||
|
||||
Rolling Back
|
||||
------------
|
||||
|
||||
``rollback()`` rolls back the current transaction. With a default configured session, the post-rollback state of the session is as follows:
|
||||
|
||||
* All connections are rolled back and returned to the connection pool, unless the Session was bound directly to a Connection, in which case the connection is still maintained (but still rolled back).
|
||||
* Objects which were initially in the *pending* state when they were added to the ``Session`` within the lifespan of the transaction are expunged, corresponding to their INSERT statement being rolled back. The state of their attributes remains unchanged.
|
||||
* Objects which were marked as *deleted* within the lifespan of the transaction are promoted back to the *persistent* state, corresponding to their DELETE statement being rolled back. Note that if those objects were first *pending* within the transaction, that operation takes precedence instead.
|
||||
* All objects not expunged are fully expired.
|
||||
|
||||
With that state understood, the ``Session`` may safely continue usage after a rollback occurs.
|
||||
|
||||
When a ``flush()`` fails, typically for reasons like primary key, foreign key, or "not nullable" constraint violations, a ``rollback()`` is issued automatically (it's currently not possible for a flush to continue after a partial failure). However, the flush process always uses its own transactional demarcator called a *subtransaction*, which is described more fully in the docstrings for ``Session``. What it means here is that even though the database transaction has been rolled back, the end user must still issue ``rollback()`` to fully reset the state of the ``Session``.
|
||||
|
||||
Expunging
|
||||
---------
|
||||
|
||||
Expunge removes an object from the Session, sending persistent instances to the detached state, and pending instances to the transient state:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
session.expunge(obj1)
|
||||
|
||||
To remove all items, call ``session.expunge_all()`` (this method was formerly known as ``clear()``).
|
||||
|
||||
Closing
|
||||
-------
|
||||
|
||||
The ``close()`` method issues a ``expunge_all()``, and releases any transactional/connection resources. When connections are returned to the connection pool, transactional state is rolled back as well.
|
||||
|
||||
Refreshing / Expiring
|
||||
---------------------
|
||||
|
||||
To assist with the Session's "sticky" behavior of instances which are present, individual objects can have all of their attributes immediately re-loaded from the database, or marked as "expired" which will cause a re-load to occur upon the next access of any of the object's mapped attributes. This includes all relationships, so lazy-loaders will be re-initialized, eager relationships will be repopulated. Any changes marked on the object are discarded::
|
||||
|
||||
# immediately re-load attributes on obj1, obj2
|
||||
session.refresh(obj1)
|
||||
session.refresh(obj2)
|
||||
|
||||
# expire objects obj1, obj2, attributes will be reloaded
|
||||
# on the next access:
|
||||
session.expire(obj1)
|
||||
session.expire(obj2)
|
||||
|
||||
``refresh()`` and ``expire()`` also support being passed a list of individual attribute names in which to be refreshed. These names can reference any attribute, column-based or relation based::
|
||||
|
||||
# immediately re-load the attributes 'hello', 'world' on obj1, obj2
|
||||
session.refresh(obj1, ['hello', 'world'])
|
||||
session.refresh(obj2, ['hello', 'world'])
|
||||
|
||||
# expire the attributes 'hello', 'world' objects obj1, obj2, attributes will be reloaded
|
||||
# on the next access:
|
||||
session.expire(obj1, ['hello', 'world'])
|
||||
session.expire(obj2, ['hello', 'world'])
|
||||
|
||||
The full contents of the session may be expired at once using ``expire_all()``::
|
||||
|
||||
session.expire_all()
|
||||
|
||||
``refresh()`` and ``expire()`` are usually not needed when working with a default-configured ``Session``. The usual need is when an UPDATE or DELETE has been issued manually within the transaction using ``Session.execute()``.
|
||||
|
||||
Session Attributes
|
||||
------------------
|
||||
|
||||
The ``Session`` itself acts somewhat like a set-like collection. All items present may be accessed using the iterator interface::
|
||||
|
||||
for obj in session:
|
||||
print obj
|
||||
|
||||
And presence may be tested for using regular "contains" semantics::
|
||||
|
||||
if obj in session:
|
||||
print "Object is present"
|
||||
|
||||
The session is also keeping track of all newly created (i.e. pending) objects, all objects which have had changes since they were last loaded or saved (i.e. "dirty"), and everything that's been marked as deleted::
|
||||
|
||||
# pending objects recently added to the Session
|
||||
session.new
|
||||
|
||||
# persistent objects which currently have changes detected
|
||||
# (this collection is now created on the fly each time the property is called)
|
||||
session.dirty
|
||||
|
||||
# persistent objects that have been marked as deleted via session.delete(obj)
|
||||
session.deleted
|
||||
|
||||
Note that objects within the session are by default *weakly referenced*. This means that when they are dereferenced in the outside application, they fall out of scope from within the ``Session`` as well and are subject to garbage collection by the Python interpreter. The exceptions to this include objects which are pending, objects which are marked as deleted, or persistent objects which have pending changes on them. After a full flush, these collections are all empty, and all objects are again weakly referenced. To disable the weak referencing behavior and force all objects within the session to remain until explicitly expunged, configure ``sessionmaker()`` with the ``weak_identity_map=False`` setting.
|
||||
|
||||
.. _unitofwork_cascades:
|
||||
|
||||
Cascades
|
||||
========
|
||||
|
||||
Mappers support the concept of configurable *cascade* behavior on :func:`~sqlalchemy.orm.relation()` constructs. This behavior controls how the Session should treat the instances that have a parent-child relationship with another instance that is operated upon by the Session. Cascade is indicated as a comma-separated list of string keywords, with the possible values ``all``, ``delete``, ``save-update``, ``refresh-expire``, ``merge``, ``expunge``, and ``delete-orphan``.
|
||||
|
||||
Cascading is configured by setting the ``cascade`` keyword argument on a ``relation()``::
|
||||
|
||||
mapper(Order, order_table, properties={
|
||||
'items' : relation(Item, items_table, cascade="all, delete-orphan"),
|
||||
'customer' : relation(User, users_table, user_orders_table, cascade="save-update"),
|
||||
})
|
||||
|
||||
The above mapper specifies two relations, ``items`` and ``customer``. The ``items`` relationship specifies "all, delete-orphan" as its ``cascade`` value, indicating that all ``add``, ``merge``, ``expunge``, ``refresh`` ``delete`` and ``expire`` operations performed on a parent ``Order`` instance should also be performed on the child ``Item`` instances attached to it. The ``delete-orphan`` cascade value additionally indicates that if an ``Item`` instance is no longer associated with an ``Order``, it should also be deleted. The "all, delete-orphan" cascade argument allows a so-called *lifecycle* relationship between an ``Order`` and an ``Item`` object.
|
||||
|
||||
The ``customer`` relationship specifies only the "save-update" cascade value, indicating most operations will not be cascaded from a parent ``Order`` instance to a child ``User`` instance except for the ``add()`` operation. "save-update" cascade indicates that an ``add()`` on the parent will cascade to all child items, and also that items added to a parent which is already present in the session will also be added. "save-update" cascade also cascades the *pending history* of a relation()-based attribute, meaning that objects which were removed from a scalar or collection attribute whose changes have not yet been flushed are also placed into the new session - this so that foreign key clear operations and deletions will take place (new in 0.6).
|
||||
|
||||
Note that the ``delete-orphan`` cascade only functions for relationships where the target object can have a single parent at a time, meaning it is only appropriate for one-to-one or one-to-many relationships. For a :func:`~sqlalchemy.orm.relation` which establishes one-to-one via a local foreign key, i.e. a many-to-one that stores only a single parent, or one-to-one/one-to-many via a "secondary" (association) table, a warning will be issued if ``delete-orphan`` is configured. To disable this warning, also specify the ``single_parent=True`` flag on the relationship, which constrains objects to allow attachment to only one parent at a time.
|
||||
|
||||
The default value for ``cascade`` on :func:`~sqlalchemy.orm.relation()` is ``save-update, merge``.
|
||||
|
||||
.. _unitofwork_transaction:
|
||||
|
||||
Managing Transactions
|
||||
=====================
|
||||
|
||||
The ``Session`` manages transactions across all engines associated with it. As the ``Session`` receives requests to execute SQL statements using a particular ``Engine`` or ``Connection``, it adds each individual ``Engine`` encountered to its transactional state and maintains an open connection for each one (note that a simple application normally has just one ``Engine``). At commit time, all unflushed data is flushed, and each individual transaction is committed. If the underlying databases support two-phase semantics, this may be used by the Session as well if two-phase transactions are enabled.
|
||||
|
||||
Normal operation ends the transactional state using the ``rollback()`` or ``commit()`` methods. After either is called, the ``Session`` starts a new transaction::
|
||||
|
||||
Session = sessionmaker()
|
||||
session = Session()
|
||||
try:
|
||||
item1 = session.query(Item).get(1)
|
||||
item2 = session.query(Item).get(2)
|
||||
item1.foo = 'bar'
|
||||
item2.bar = 'foo'
|
||||
|
||||
# commit- will immediately go into a new transaction afterwards
|
||||
session.commit()
|
||||
except:
|
||||
# rollback - will immediately go into a new transaction afterwards.
|
||||
session.rollback()
|
||||
|
||||
A session which is configured with ``autocommit=True`` may be placed into a transaction using ``begin()``. With an ``autocommit=True`` session that's been placed into a transaction using ``begin()``, the session releases all connection resources after a ``commit()`` or ``rollback()`` and remains transaction-less (with the exception of flushes) until the next ``begin()`` call::
|
||||
|
||||
Session = sessionmaker(autocommit=True)
|
||||
session = Session()
|
||||
session.begin()
|
||||
try:
|
||||
item1 = session.query(Item).get(1)
|
||||
item2 = session.query(Item).get(2)
|
||||
item1.foo = 'bar'
|
||||
item2.bar = 'foo'
|
||||
session.commit()
|
||||
except:
|
||||
session.rollback()
|
||||
raise
|
||||
|
||||
The ``begin()`` method also returns a transactional token which is compatible with the Python 2.6 ``with`` statement::
|
||||
|
||||
Session = sessionmaker(autocommit=True)
|
||||
session = Session()
|
||||
with session.begin():
|
||||
item1 = session.query(Item).get(1)
|
||||
item2 = session.query(Item).get(2)
|
||||
item1.foo = 'bar'
|
||||
item2.bar = 'foo'
|
||||
|
||||
Using SAVEPOINT
|
||||
---------------
|
||||
|
||||
SAVEPOINT transactions, if supported by the underlying engine, may be delineated using the ``begin_nested()`` method::
|
||||
|
||||
Session = sessionmaker()
|
||||
session = Session()
|
||||
session.add(u1)
|
||||
session.add(u2)
|
||||
|
||||
session.begin_nested() # establish a savepoint
|
||||
session.add(u3)
|
||||
session.rollback() # rolls back u3, keeps u1 and u2
|
||||
|
||||
session.commit() # commits u1 and u2
|
||||
|
||||
``begin_nested()`` may be called any number of times, which will issue a new SAVEPOINT with a unique identifier for each call. For each ``begin_nested()`` call, a corresponding ``rollback()`` or ``commit()`` must be issued.
|
||||
|
||||
When ``begin_nested()`` is called, a ``flush()`` is unconditionally issued (regardless of the ``autoflush`` setting). This is so that when a ``rollback()`` occurs, the full state of the session is expired, thus causing all subsequent attribute/instance access to reference the full state of the ``Session`` right before ``begin_nested()`` was called.
|
||||
|
||||
Enabling Two-Phase Commit
|
||||
-------------------------
|
||||
|
||||
Finally, for MySQL, PostgreSQL, and soon Oracle as well, the session can be instructed to use two-phase commit semantics. This will coordinate the committing of transactions across databases so that the transaction is either committed or rolled back in all databases. You can also ``prepare()`` the session for interacting with transactions not managed by SQLAlchemy. To use two phase transactions set the flag ``twophase=True`` on the session::
|
||||
|
||||
engine1 = create_engine('postgresql://db1')
|
||||
engine2 = create_engine('postgresql://db2')
|
||||
|
||||
Session = sessionmaker(twophase=True)
|
||||
|
||||
# bind User operations to engine 1, Account operations to engine 2
|
||||
Session.configure(binds={User:engine1, Account:engine2})
|
||||
|
||||
session = Session()
|
||||
|
||||
# .... work with accounts and users
|
||||
|
||||
# commit. session will issue a flush to all DBs, and a prepare step to all DBs,
|
||||
# before committing both transactions
|
||||
session.commit()
|
||||
|
||||
Embedding SQL Insert/Update Expressions into a Flush
|
||||
=====================================================
|
||||
|
||||
This feature allows the value of a database column to be set to a SQL expression instead of a literal value. It's especially useful for atomic updates, calling stored procedures, etc. All you do is assign an expression to an attribute::
|
||||
|
||||
class SomeClass(object):
|
||||
pass
|
||||
mapper(SomeClass, some_table)
|
||||
|
||||
someobject = session.query(SomeClass).get(5)
|
||||
|
||||
# set 'value' attribute to a SQL expression adding one
|
||||
someobject.value = some_table.c.value + 1
|
||||
|
||||
# issues "UPDATE some_table SET value=value+1"
|
||||
session.commit()
|
||||
|
||||
This technique works both for INSERT and UPDATE statements. After the flush/commit operation, the ``value`` attribute on ``someobject`` above is expired, so that when next accessed the newly generated value will be loaded from the database.
|
||||
|
||||
Using SQL Expressions with Sessions
|
||||
====================================
|
||||
|
||||
SQL expressions and strings can be executed via the ``Session`` within its transactional context. This is most easily accomplished using the ``execute()`` method, which returns a ``ResultProxy`` in the same manner as an ``Engine`` or ``Connection``::
|
||||
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
# execute a string statement
|
||||
result = session.execute("select * from table where id=:id", {'id':7})
|
||||
|
||||
# execute a SQL expression construct
|
||||
result = session.execute(select([mytable]).where(mytable.c.id==7))
|
||||
|
||||
The current ``Connection`` held by the ``Session`` is accessible using the ``connection()`` method::
|
||||
|
||||
connection = session.connection()
|
||||
|
||||
The examples above deal with a ``Session`` that's bound to a single ``Engine`` or ``Connection``. To execute statements using a ``Session`` which is bound either to multiple engines, or none at all (i.e. relies upon bound metadata), both ``execute()`` and ``connection()`` accept a ``mapper`` keyword argument, which is passed a mapped class or ``Mapper`` instance, which is used to locate the proper context for the desired engine::
|
||||
|
||||
Session = sessionmaker()
|
||||
session = Session()
|
||||
|
||||
# need to specify mapper or class when executing
|
||||
result = session.execute("select * from table where id=:id", {'id':7}, mapper=MyMappedClass)
|
||||
|
||||
result = session.execute(select([mytable], mytable.c.id==7), mapper=MyMappedClass)
|
||||
|
||||
connection = session.connection(MyMappedClass)
|
||||
|
||||
Joining a Session into an External Transaction
|
||||
===============================================
|
||||
|
||||
If a ``Connection`` is being used which is already in a transactional state (i.e. has a ``Transaction``), a ``Session`` can be made to participate within that transaction by just binding the ``Session`` to that ``Connection``::
|
||||
|
||||
Session = sessionmaker()
|
||||
|
||||
# non-ORM connection + transaction
|
||||
conn = engine.connect()
|
||||
trans = conn.begin()
|
||||
|
||||
# create a Session, bind to the connection
|
||||
session = Session(bind=conn)
|
||||
|
||||
# ... work with session
|
||||
|
||||
session.commit() # commit the session
|
||||
session.close() # close it out, prohibit further actions
|
||||
|
||||
trans.commit() # commit the actual transaction
|
||||
|
||||
Note that above, we issue a ``commit()`` both on the ``Session`` as well as the ``Transaction``. This is an example of where we take advantage of ``Connection``'s ability to maintain *subtransactions*, or nested begin/commit pairs. The ``Session`` is used exactly as though it were managing the transaction on its own; its ``commit()`` method issues its ``flush()``, and commits the subtransaction. The subsequent transaction the ``Session`` starts after commit will not begin until it's next used. Above we issue a ``close()`` to prevent this from occurring. Finally, the actual transaction is committed using ``Transaction.commit()``.
|
||||
|
||||
When using the ``threadlocal`` engine context, the process above is simplified; the ``Session`` uses the same connection/transaction as everyone else in the current thread, whether or not you explicitly bind it::
|
||||
|
||||
engine = create_engine('postgresql://mydb', strategy="threadlocal")
|
||||
engine.begin()
|
||||
|
||||
session = Session() # session takes place in the transaction like everyone else
|
||||
|
||||
# ... go nuts
|
||||
|
||||
engine.commit() # commit the transaction
|
||||
|
||||
.. _unitofwork_contextual:
|
||||
|
||||
Contextual/Thread-local Sessions
|
||||
=================================
|
||||
|
||||
A common need in applications, particularly those built around web frameworks, is the ability to "share" a ``Session`` object among disparate parts of an application, without needing to pass the object explicitly to all method and function calls. What you're really looking for is some kind of "global" session object, or at least "global" to all the parts of an application which are tasked with servicing the current request. For this pattern, SQLAlchemy provides the ability to enhance the ``Session`` class generated by ``sessionmaker()`` to provide auto-contextualizing support. This means that whenever you create a ``Session`` instance with its constructor, you get an *existing* ``Session`` object which is bound to some "context". By default, this context is the current thread. This feature is what previously was accomplished using the ``sessioncontext`` SQLAlchemy extension.
|
||||
|
||||
Creating a Thread-local Context
|
||||
-------------------------------
|
||||
|
||||
The ``scoped_session()`` function wraps around the ``sessionmaker()`` function, and produces an object which behaves the same as the ``Session`` subclass returned by ``sessionmaker()``::
|
||||
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
Session = scoped_session(sessionmaker())
|
||||
|
||||
However, when you instantiate this ``Session`` "class", in reality the object is pulled from a threadlocal variable, or if it doesn't exist yet, it's created using the underlying class generated by ``sessionmaker()``::
|
||||
|
||||
>>> # call Session() the first time. the new Session instance is created.
|
||||
>>> session = Session()
|
||||
|
||||
>>> # later, in the same application thread, someone else calls Session()
|
||||
>>> session2 = Session()
|
||||
|
||||
>>> # the two Session objects are *the same* object
|
||||
>>> session is session2
|
||||
True
|
||||
|
||||
Since the ``Session()`` constructor now returns the same ``Session`` object every time within the current thread, the object returned by ``scoped_session()`` also implements most of the ``Session`` methods and properties at the "class" level, such that you don't even need to instantiate ``Session()``::
|
||||
|
||||
# create some objects
|
||||
u1 = User()
|
||||
u2 = User()
|
||||
|
||||
# save to the contextual session, without instantiating
|
||||
Session.add(u1)
|
||||
Session.add(u2)
|
||||
|
||||
# view the "new" attribute
|
||||
assert u1 in Session.new
|
||||
|
||||
# commit changes
|
||||
Session.commit()
|
||||
|
||||
The contextual session may be disposed of by calling ``Session.remove()``::
|
||||
|
||||
# remove current contextual session
|
||||
Session.remove()
|
||||
|
||||
After ``remove()`` is called, the next operation with the contextual session will start a new ``Session`` for the current thread.
|
||||
|
||||
.. _session_lifespan:
|
||||
|
||||
Lifespan of a Contextual Session
|
||||
--------------------------------
|
||||
|
||||
A (really, really) common question is when does the contextual session get created, when does it get disposed ? We'll consider a typical lifespan as used in a web application::
|
||||
|
||||
Web Server Web Framework User-defined Controller Call
|
||||
-------------- -------------- ------------------------------
|
||||
web request ->
|
||||
call controller -> # call Session(). this establishes a new,
|
||||
# contextual Session.
|
||||
session = Session()
|
||||
|
||||
# load some objects, save some changes
|
||||
objects = session.query(MyClass).all()
|
||||
|
||||
# some other code calls Session, it's the
|
||||
# same contextual session as "sess"
|
||||
session2 = Session()
|
||||
session2.add(foo)
|
||||
session2.commit()
|
||||
|
||||
# generate content to be returned
|
||||
return generate_content()
|
||||
Session.remove() <-
|
||||
web response <-
|
||||
|
||||
The above example illustrates an explicit call to ``Session.remove()``. This has the effect such that each web request starts fresh with a brand new session. When integrating with a web framework, there's actually many options on how to proceed for this step:
|
||||
|
||||
* Session.remove() - this is the most cut and dry approach; the ``Session`` is thrown away, all of its transactional/connection resources are closed out, everything within it is explicitly gone. A new ``Session`` will be used on the next request.
|
||||
* Session.close() - Similar to calling ``remove()``, in that all objects are explicitly expunged and all transactional/connection resources closed, except the actual ``Session`` object hangs around. It doesn't make too much difference here unless the start of the web request would like to pass specific options to the initial construction of ``Session()``, such as a specific ``Engine`` to bind to.
|
||||
* Session.commit() - In this case, the behavior is that any remaining changes pending are flushed, and the transaction is committed. The full state of the session is expired, so that when the next web request is started, all data will be reloaded. In reality, the contents of the ``Session`` are weakly referenced anyway so its likely that it will be empty on the next request in any case.
|
||||
* Session.rollback() - Similar to calling commit, except we assume that the user would have called commit explicitly if that was desired; the ``rollback()`` ensures that no transactional state remains and expires all data, in the case that the request was aborted and did not roll back itself.
|
||||
* do nothing - this is a valid option as well. The controller code is responsible for doing one of the above steps at the end of the request.
|
||||
|
||||
Scoped Session API docs: :func:`sqlalchemy.orm.scoped_session`
|
||||
|
||||
.. _session_partitioning:
|
||||
|
||||
Partitioning Strategies
|
||||
=======================
|
||||
|
||||
Vertical Partitioning
|
||||
---------------------
|
||||
|
||||
Vertical partitioning places different kinds of objects, or different tables, across multiple databases::
|
||||
|
||||
engine1 = create_engine('postgresql://db1')
|
||||
engine2 = create_engine('postgresql://db2')
|
||||
|
||||
Session = sessionmaker(twophase=True)
|
||||
|
||||
# bind User operations to engine 1, Account operations to engine 2
|
||||
Session.configure(binds={User:engine1, Account:engine2})
|
||||
|
||||
session = Session()
|
||||
|
||||
Horizontal Partitioning
|
||||
-----------------------
|
||||
|
||||
Horizontal partitioning partitions the rows of a single table (or a set of tables) across multiple databases.
|
||||
|
||||
See the "sharding" example in `attribute_shard.py <http://www.sqlalchemy.org/trac/browser/sqlalchemy/trunk/examples/sharding/attribute_shard.py>`_
|
||||
|
||||
Extending Session
|
||||
=================
|
||||
|
||||
Extending the session can be achieved through subclassing as well as through a simple extension class, which resembles the style of :ref:`extending_mapper` called :class:`~sqlalchemy.orm.interfaces.SessionExtension`. See the docstrings for more information on this class' methods.
|
||||
|
||||
Basic usage is similar to :class:`~sqlalchemy.orm.interfaces.MapperExtension`::
|
||||
|
||||
class MySessionExtension(SessionExtension):
|
||||
def before_commit(self, session):
|
||||
print "before commit!"
|
||||
|
||||
Session = sessionmaker(extension=MySessionExtension())
|
||||
|
||||
or with :func:`~sqlalchemy.orm.create_session()`::
|
||||
|
||||
session = create_session(extension=MySessionExtension())
|
||||
|
||||
The same ``SessionExtension`` instance can be used with any number of sessions.
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
+395
-240
File diff suppressed because it is too large
Load Diff
Vendored
+294
@@ -0,0 +1,294 @@
|
||||
/* documentation section styles */
|
||||
|
||||
body, td {
|
||||
font-family: verdana, sans-serif;
|
||||
font-size:.95em;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #FDFBFC;
|
||||
margin:20px 20px 20px 20px;
|
||||
}
|
||||
|
||||
form {
|
||||
display:inline;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top:10px;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
|
||||
a {font-weight:normal; text-decoration:underline;}
|
||||
a:link {color:#0000FF;}
|
||||
a:visited {color:#0000FF;}
|
||||
a:active {color:#0000FF;}
|
||||
a:hover {color:#700000;}
|
||||
|
||||
|
||||
strong a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#search {
|
||||
float:right;
|
||||
}
|
||||
|
||||
#searchform {
|
||||
padding:20px;
|
||||
}
|
||||
|
||||
#pagecontrol {
|
||||
float:right;
|
||||
}
|
||||
|
||||
.topnav
|
||||
{
|
||||
background-color: #fbfbee;
|
||||
border: solid 1px #ccc;
|
||||
padding:10px;
|
||||
margin:10px 0px 10px 0px;
|
||||
}
|
||||
|
||||
.document {
|
||||
border: solid 1px #ccc;
|
||||
}
|
||||
|
||||
.topnav .prevnext {
|
||||
padding: 5px 0px 0px 0px;
|
||||
font-size: 0.8em
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
font-family:arial,helvetica,sans-serif;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
.document h1, .document h2, .document h3, .document h4, .document h5 {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
.document img {
|
||||
display:block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.document h1 {
|
||||
display:none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font: normal 20px/22px arial,helvetica,sans-serif;
|
||||
color: #222;
|
||||
padding:0px;
|
||||
margin:0px;
|
||||
}
|
||||
|
||||
.topnav h2 {
|
||||
margin:26px 4px 0px 5px;
|
||||
font-family:arial,helvetica,sans-serif;
|
||||
font-size:1.6em;
|
||||
font-weight:normal;
|
||||
line-height:1.6em;
|
||||
}
|
||||
|
||||
.topnav h3 {
|
||||
font-weight: bold;
|
||||
font-size: 1.4em;
|
||||
margin:0px;
|
||||
display:inline;
|
||||
font-family:verdana,sans-serif;
|
||||
}
|
||||
|
||||
.topnav li,
|
||||
li.toctree-l1,
|
||||
li.toctree-l1 li
|
||||
{
|
||||
list-style-type:disc;
|
||||
margin:0px;
|
||||
padding:1px 8px;
|
||||
}
|
||||
|
||||
|
||||
.topnav li ul,
|
||||
li.toctree-l1 ul
|
||||
{
|
||||
padding:0px 0px 0px 20px;
|
||||
}
|
||||
|
||||
.topnav li ul li li,
|
||||
li.toctree-l1 ul li li
|
||||
{
|
||||
/*font-size:.90em;*/
|
||||
}
|
||||
|
||||
.sourcelink {
|
||||
font-size:.8em;
|
||||
text-align:right;
|
||||
padding-top:10px;
|
||||
}
|
||||
|
||||
.section {
|
||||
line-height: 1.5em;
|
||||
padding:8px 10px 20px 10px;
|
||||
margin:10px 0px 0px;
|
||||
}
|
||||
|
||||
.section .section {
|
||||
margin:0px 0px 0px 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.section .section .section {
|
||||
margin:0px 0px 0px 20px;
|
||||
}
|
||||
|
||||
.section .section .section .section {
|
||||
margin:0px 0px 0px 20px;
|
||||
}
|
||||
|
||||
|
||||
.bottomnav {
|
||||
background-color:#FBFBEE;
|
||||
border:1px solid #CCCCCC;
|
||||
float:right;
|
||||
margin: 1em 0 1em 5px;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
.totoc {
|
||||
|
||||
}
|
||||
|
||||
.doc_copyright {
|
||||
font-size:.85em;
|
||||
padding:10px 0px 10px 0px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #f0f0f0;
|
||||
border: solid 1px #ccc;
|
||||
padding:10px;
|
||||
margin: 5px 5px 5px 5px;
|
||||
overflow:auto;
|
||||
line-height:1.3em;
|
||||
}
|
||||
|
||||
.popup_sql, .show_sql
|
||||
{
|
||||
background-color: #fbfbee;
|
||||
padding:0px 10px;
|
||||
margin:0px -10px;
|
||||
}
|
||||
|
||||
.sql_link
|
||||
{
|
||||
font-weight:normal;
|
||||
font-family: arial, sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.9em;
|
||||
color:#666;
|
||||
border:1px solid;
|
||||
padding:1px 2px 1px 2px;
|
||||
margin:0px 10px 0px 15px;
|
||||
float:right;
|
||||
line-height:1.2em;
|
||||
}
|
||||
|
||||
#docs a.sql_link, .sql_link
|
||||
{
|
||||
text-decoration: none;
|
||||
padding:1px 2px;
|
||||
}
|
||||
|
||||
#docs a.sql_link:hover {
|
||||
text-decoration: none;
|
||||
color:#fff;
|
||||
border:1px solid #900;
|
||||
background-color: #900;
|
||||
}
|
||||
|
||||
.versionheader {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
.versionnum {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.prerelease {
|
||||
border: solid #c25757 2px;
|
||||
border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
background-color: #c21a1a;
|
||||
color: white;
|
||||
padding: 0.05em 0.2em;
|
||||
}
|
||||
|
||||
dl.function > dt,
|
||||
dl.class > dt
|
||||
{
|
||||
background-color:#F0F0F0;
|
||||
margin:0px -10px;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
dt:target, span.highlight {
|
||||
background-color:#FBE54E;
|
||||
}
|
||||
|
||||
a.headerlink {
|
||||
font-size: 0.8em;
|
||||
padding: 0 4px 0 4px;
|
||||
text-decoration: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
h1:hover > a.headerlink,
|
||||
h2:hover > a.headerlink,
|
||||
h3:hover > a.headerlink,
|
||||
h4:hover > a.headerlink,
|
||||
h5:hover > a.headerlink,
|
||||
h6:hover > a.headerlink,
|
||||
dt:hover > a.headerlink {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
background-color: #00f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.clearboth {
|
||||
clear:both;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
background-color:transparent;
|
||||
font-size:1.2em;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
tt.descclassname {
|
||||
background-color:transparent;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color:#ECF0F3;
|
||||
padding:0 1px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
#nav { display: none; }
|
||||
#pagecontrol { display: none; }
|
||||
.topnav .prevnext { display: none; }
|
||||
.bottomnav { display: none; }
|
||||
.totoc { display: none; }
|
||||
.topnav ul li a { text-decoration: none; color: #000; }
|
||||
}
|
||||
|
||||
/* syntax highlighting overrides */
|
||||
.k, .kn {color:#0908CE;}
|
||||
.o {color:#BF0005;}
|
||||
.go {color:#804049;}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
$(document).ready(function(){
|
||||
$('div.popup_sql').hide();
|
||||
$('a.sql_link').click(function() {
|
||||
$(this).nextAll('div.popup_sql:first').toggle();
|
||||
return false;
|
||||
})
|
||||
});
|
||||
Vendored
-30
@@ -1,30 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<title>${self.title()}</title>
|
||||
${self.style()}
|
||||
<%def name="style()">
|
||||
</%def>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
${next.body()}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<%def name="style()">
|
||||
<link rel="stylesheet" href="style.css"></link>
|
||||
<link rel="stylesheet" href="docs.css"></link>
|
||||
<link href="syntaxhighlight.css" rel="stylesheet" type="text/css"></link>
|
||||
<script src="scripts.js"></script>
|
||||
% if parent:
|
||||
${parent.style()}
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="title()">
|
||||
Documentation
|
||||
</%def>
|
||||
|
||||
|
||||
Vendored
-41
@@ -1,41 +0,0 @@
|
||||
<%!
|
||||
from mako.ext.autohandler import autohandler
|
||||
%>
|
||||
<%inherit file="${autohandler(template, context)}"/>
|
||||
<%page cached="True" cache_key="${self.filename}"/>
|
||||
|
||||
<%doc>
|
||||
base.html - common to all documentation pages. intentionally separate
|
||||
from autohandler, which can be swapped out for a different one
|
||||
</%doc>
|
||||
|
||||
<%
|
||||
# bootstrap TOC structure from request args, or pickled file if not present.
|
||||
import cPickle as pickle
|
||||
import os, time
|
||||
#print "%s generating from table of contents for file %s" % (local.filename, self.filename)
|
||||
filename = os.path.join(os.path.dirname(self.filename), 'table_of_contents.pickle')
|
||||
toc = pickle.load(file(filename))
|
||||
version = toc.version
|
||||
last_updated = toc.last_updated
|
||||
|
||||
kwargs = context.kwargs
|
||||
kwargs.setdefault('extension', 'html')
|
||||
extension = kwargs['extension']
|
||||
kwargs.setdefault('paged', True)
|
||||
kwargs.setdefault('toc', toc)
|
||||
%>
|
||||
|
||||
<div id="topanchor"><a name="top"> </a></div>
|
||||
|
||||
|
||||
<h1>${toc.root.doctitle}</h1>
|
||||
|
||||
<div id="pagecontrol"><a href="index.${extension}">Multiple Pages</a> | <a href="documentation.${extension}">One Page</a></div>
|
||||
|
||||
<div class="versionheader">Version: ${version} Last Updated: ${time.strftime('%x %X', time.localtime(last_updated))}</div>
|
||||
|
||||
${next.body(**kwargs)}
|
||||
|
||||
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
## defines the default layout for normal documentation pages (not including the index)
|
||||
<%inherit file="base.html"/>
|
||||
<%page args="toc, extension, paged"/>
|
||||
<%namespace file="nav.html" import="topnav, pagenav, bottomnav"/>
|
||||
|
||||
<%
|
||||
current = toc.get_by_file(self.template.module.filename)
|
||||
%>
|
||||
|
||||
<A name="<% current.path %>"></a>
|
||||
|
||||
${topnav(item=current, toc=toc, extension=extension, paged=paged)}
|
||||
|
||||
${next.body(toc=toc, extension=extension, paged=paged)}
|
||||
|
||||
${bottomnav(item=current, extension=extension, paged=paged)}
|
||||
Vendored
-152
@@ -1,152 +0,0 @@
|
||||
## formatting.myt - Provides section formatting elements, syntax-highlighted code blocks, and other special filters.
|
||||
<%!
|
||||
import string, re, cgi
|
||||
from mako import filters
|
||||
import highlight
|
||||
|
||||
def plainfilter(f):
|
||||
f = re.sub(r'\n[\s\t]*\n[\s\t]*', '</p>\n<p>', f)
|
||||
f = "<p>" + f + "</p>"
|
||||
return f
|
||||
|
||||
%>
|
||||
|
||||
<%namespace name="nav" file="nav.html"/>
|
||||
|
||||
<%def name="section(toc, path, paged, extension, description=None)">
|
||||
## Main section formatting element.
|
||||
<%
|
||||
content = capture(caller.body)
|
||||
re2 = re.compile(r"'''PYESC(.+?)PYESC'''", re.S)
|
||||
content = re2.sub(lambda m: filters.url_unescape(m.group(1)), content)
|
||||
|
||||
item = toc.get_by_path(path)
|
||||
subsection = item.depth > 1
|
||||
level = min(item.depth, 4)
|
||||
%>
|
||||
<A name="${item.path}"></a>
|
||||
|
||||
<div class="${'sectionL%d' % level}">
|
||||
|
||||
% if (subsection):
|
||||
<h3>${description or item.description}</h3>
|
||||
% endif
|
||||
|
||||
${content}
|
||||
|
||||
% if len(item.children) == 0:
|
||||
% if paged:
|
||||
<a href="#top">back to section top</a>
|
||||
% else:
|
||||
<a href="#${item.get_page_root().path}">back to section top</a>
|
||||
% endif
|
||||
% endif
|
||||
</div>
|
||||
|
||||
</%def>
|
||||
|
||||
|
||||
<%def name="formatplain()" filter="plainfilter">
|
||||
${ caller.body() | h}
|
||||
</%def>
|
||||
|
||||
|
||||
<%def name="codeline()" filter="trim,h">
|
||||
<span class="codeline">${ caller.body() }</span>
|
||||
</%def>
|
||||
|
||||
<%def name="code(toc, paged, extension, title=None, syntaxtype='mako', html_escape=True, use_sliders=False)">
|
||||
<%
|
||||
def fix_indent(f):
|
||||
f =string.expandtabs(f, 4)
|
||||
g = ''
|
||||
lines = string.split(f, "\n")
|
||||
whitespace = None
|
||||
for line in lines:
|
||||
if whitespace is None:
|
||||
match = re.match(r"^([ ]*).+", line)
|
||||
if match is not None:
|
||||
whitespace = match.group(1)
|
||||
|
||||
if whitespace is not None:
|
||||
line = re.sub(r"^%s" % whitespace, "", line)
|
||||
|
||||
if whitespace is not None or re.search(r"\w", line) is not None:
|
||||
g += (line + "\n")
|
||||
else:
|
||||
g += "\n"
|
||||
|
||||
return g[:-1] #.rstrip()
|
||||
|
||||
p = re.compile(r'<pre>(.*?)</pre>', re.S)
|
||||
|
||||
def hlight(match):
|
||||
try:
|
||||
return "<pre>" + highlight.highlight(fix_indent(match.group(1)), html_escape = html_escape, syntaxtype = syntaxtype) + "</pre>"
|
||||
except:
|
||||
print "TEXT IS", fix_indent(match.group(1))
|
||||
|
||||
def link(match):
|
||||
return capture(nav.toclink, toc, match.group(2), extension, paged, description=match.group(1))
|
||||
|
||||
content = re.sub(r'\[(.+?)\]\(rel:(.+?)\)', link, capture(caller.body))
|
||||
if syntaxtype != 'diagram':
|
||||
content = p.sub(hlight, "<pre>" + content + "</pre>")
|
||||
else:
|
||||
content = "<pre>" + content + "</pre>"
|
||||
%>
|
||||
|
||||
<div class="${ use_sliders and "sliding_code" or "code" }">
|
||||
% if title is not None:
|
||||
<div class="codetitle">${title}</div>
|
||||
% endif
|
||||
${ content }
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
|
||||
<%def name="popboxlink(name=None, show='show', hide='hide')" filter="trim">
|
||||
<%
|
||||
if name is None:
|
||||
name = attributes.setdefault('popbox_name', 0)
|
||||
name += 1
|
||||
attributes['popbox_name'] = name
|
||||
name = "popbox_" + repr(name)
|
||||
%>
|
||||
javascript:togglePopbox('${name}', '${show}', '${hide}')
|
||||
</%def>
|
||||
|
||||
<%def name="popbox(name=None, class_=None)" filter="trim">
|
||||
<%
|
||||
if name is None:
|
||||
name = 'popbox_' + repr(attributes['popbox_name'])
|
||||
%>
|
||||
<div id="${name}_div" class="${class_}" style="display:none;">${capture(caller.body) | trim}</div>
|
||||
</%def>
|
||||
|
||||
<%def name="poplink(link='sql')" filter="trim">
|
||||
<%
|
||||
href = capture(popboxlink)
|
||||
%>
|
||||
'''PYESC${capture(nav.link, href=href, text=link, class_="codepoplink") | u}PYESC'''
|
||||
</%def>
|
||||
|
||||
<%def name="codepopper()" filter="trim">
|
||||
<%
|
||||
c = capture(caller.body)
|
||||
c = re.sub(r'\n', '<br/>\n', filters.html_escape(c.strip()))
|
||||
%>
|
||||
</pre><%call expr="popbox(class_='codepop')">${c}</%call><pre>
|
||||
</%def>
|
||||
|
||||
<%def name="poppedcode()" filter="trim">
|
||||
<%
|
||||
c = capture(caller.body)
|
||||
c = re.sub(r'\n', '<br/>\n', filters.html_escape(c.strip()))
|
||||
%>
|
||||
</pre><div class="codepop">${c}</div><pre>
|
||||
</%def>
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
<%inherit file="layout.mako"/>
|
||||
|
||||
<%def name="show_title()">${_('Index')}</%def>
|
||||
|
||||
<h1 id="index">${_('Index')}</h1>
|
||||
|
||||
% for i, (key, dummy) in enumerate(genindexentries):
|
||||
${i != 0 and '| ' or ''}<a href="#${key}"><strong>${key}</strong></a>
|
||||
% endfor
|
||||
|
||||
<hr />
|
||||
|
||||
% for i, (key, entries) in enumerate(genindexentries):
|
||||
<h2 id="${key}">${key}</h2>
|
||||
<table width="100%" class="indextable"><tr><td width="33%" valign="top">
|
||||
<dl>
|
||||
<%
|
||||
breakat = genindexcounts[i] // 2
|
||||
numcols = 1
|
||||
numitems = 0
|
||||
%>
|
||||
% for entryname, (links, subitems) in entries:
|
||||
|
||||
<dt>
|
||||
% if links:
|
||||
<a href="${links[0]}">${entryname|h}</a>
|
||||
% for link in links[1:]:
|
||||
, <a href="${link}">[${i}]</a>
|
||||
% endfor
|
||||
% else:
|
||||
${entryname|h}
|
||||
% endif
|
||||
|
||||
% if subitems:
|
||||
<dd><dl>
|
||||
% for subentryname, subentrylinks in subitems:
|
||||
<dt><a href="${subentrylinks[0]}">${subentryname|h}</a>
|
||||
% for j, link in enumerate(subentrylinks[1:]):
|
||||
<a href="${link}">[${j}]</a>
|
||||
% endfor
|
||||
</dt>
|
||||
% endfor
|
||||
</dl></dd>
|
||||
% endif
|
||||
<%
|
||||
numitems = numitems + 1 + len(subitems)
|
||||
%>
|
||||
% if numcols <2 and numitems > breakat:
|
||||
<%
|
||||
numcols = numcols + 1
|
||||
%>
|
||||
</dl></td><td width="33%" valign="top"><dl>
|
||||
% endif
|
||||
|
||||
% endfor
|
||||
</dl></td></tr></table>
|
||||
% endfor
|
||||
|
||||
<%def name="sidebarrel()">
|
||||
% if split_index:
|
||||
<h4>${_('Index')}</h4>
|
||||
<p>
|
||||
% for i, (key, dummy) in enumerate(genindexentries):
|
||||
${i > 0 and '| ' or ''}
|
||||
<a href="${pathto('genindex-' + key)}"><strong>${key}</strong></a>
|
||||
% endfor
|
||||
</p>
|
||||
|
||||
<p><a href="${pathto('genindex-all')}"><strong>${_('Full index on one page')}</strong></a></p>
|
||||
% endif
|
||||
${parent.sidebarrel()}
|
||||
</%def>
|
||||
Vendored
+132
@@ -0,0 +1,132 @@
|
||||
## coding: utf-8
|
||||
<%inherit file="${context['mako_layout']}"/>
|
||||
|
||||
<%def name="headers()">
|
||||
<link rel="stylesheet" href="${pathto('_static/pygments.css', 1)}" type="text/css" />
|
||||
<link rel="stylesheet" href="${pathto('_static/docs.css', 1)}" type="text/css" />
|
||||
|
||||
<script type="text/javascript">
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: '${pathto("", 1)}',
|
||||
VERSION: '${release|h}',
|
||||
COLLAPSE_MODINDEX: false,
|
||||
FILE_SUFFIX: '${file_suffix}'
|
||||
};
|
||||
</script>
|
||||
% for scriptfile in script_files + self.attr.local_script_files:
|
||||
<script type="text/javascript" src="${pathto(scriptfile, 1)}"></script>
|
||||
% endfor
|
||||
<script type="text/javascript" src="${pathto('_static/init.js', 1)}"></script>
|
||||
% if hasdoc('about'):
|
||||
<link rel="author" title="${_('About these documents')}" href="${pathto('about')}" />
|
||||
% endif
|
||||
<link rel="index" title="${_('Index')}" href="${pathto('genindex')}" />
|
||||
<link rel="search" title="${_('Search')}" href="${pathto('search')}" />
|
||||
% if hasdoc('copyright'):
|
||||
<link rel="copyright" title="${_('Copyright')}" href="${pathto('copyright')}" />
|
||||
% endif
|
||||
<link rel="top" title="${docstitle|h}" href="${pathto('index')}" />
|
||||
% if parents:
|
||||
<link rel="up" title="${parents[-1]['title']|util.striptags}" href="${parents[-1]['link']|h}" />
|
||||
% endif
|
||||
% if nexttopic:
|
||||
<link rel="next" title="${nexttopic['title']|util.striptags}" href="${nexttopic['link']|h}" />
|
||||
% endif
|
||||
% if prevtopic:
|
||||
<link rel="prev" title="${prevtopic['title']|util.striptags}" href="${prevtopic['link']|h}" />
|
||||
% endif
|
||||
${self.extrahead()}
|
||||
</%def>
|
||||
<%def name="extrahead()"></%def>
|
||||
|
||||
<h1>${docstitle|h}</h1>
|
||||
|
||||
<div id="search">
|
||||
Search:
|
||||
<form class="search" action="${pathto('search')}" method="get">
|
||||
<input type="text" name="q" size="18" /> <input type="submit" value="${_('Search')}" />
|
||||
<input type="hidden" name="check_keywords" value="yes" />
|
||||
<input type="hidden" name="area" value="default" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="versionheader">
|
||||
Version: <span class="versionnum">${release}</span> Last Updated: ${last_updated}
|
||||
</div>
|
||||
<div class="clearboth"></div>
|
||||
|
||||
<div class="topnav">
|
||||
<div id="pagecontrol">
|
||||
<a href="${pathto('reference/index')}">API Reference</a>
|
||||
|
|
||||
<a href="${pathto('genindex')}">Index</a>
|
||||
|
||||
% if sourcename:
|
||||
<div class="sourcelink">(<a href="${pathto('_sources/' + sourcename, True)|h}">${_('view source')})</div>
|
||||
% endif
|
||||
</div>
|
||||
|
||||
<div class="navbanner">
|
||||
<a class="totoc" href="${pathto(master_doc)}">Table of Contents</a>
|
||||
% if parents:
|
||||
% for parent in parents:
|
||||
» <a href="${parent['link']|h}" title="${parent['title']}">${parent['title']}</a>
|
||||
% endfor
|
||||
% endif
|
||||
% if current_page_name != master_doc:
|
||||
» ${self.show_title()}
|
||||
% endif
|
||||
|
||||
${prevnext()}
|
||||
<h2>
|
||||
${self.show_title()}
|
||||
</h2>
|
||||
</div>
|
||||
% if display_toc and not current_page_name.startswith('index'):
|
||||
${toc}
|
||||
% endif
|
||||
<div class="clearboth"></div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="body">
|
||||
${next.body()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%def name="footer()">
|
||||
<div class="bottomnav">
|
||||
${prevnext()}
|
||||
<div class="doc_copyright">
|
||||
% if hasdoc('copyright'):
|
||||
© <a href="${pathto('copyright')}">Copyright</a> ${copyright|h}.
|
||||
% else:
|
||||
© Copyright ${copyright|h}.
|
||||
% endif
|
||||
% if show_sphinx:
|
||||
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> ${sphinx_version|h}.
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</%def>
|
||||
${self.footer()}
|
||||
|
||||
<%def name="prevnext()">
|
||||
<div class="prevnext">
|
||||
% if prevtopic:
|
||||
Previous:
|
||||
<a href="${prevtopic['link']|h}" title="${_('previous chapter')}">${prevtopic['title']}</a>
|
||||
% endif
|
||||
% if nexttopic:
|
||||
Next:
|
||||
<a href="${nexttopic['link']|h}" title="${_('next chapter')}">${nexttopic['title']}</a>
|
||||
% endif
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="show_title()">
|
||||
% if title:
|
||||
${title}
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
Vendored
-27
@@ -1,27 +0,0 @@
|
||||
<%inherit file="base.html"/>
|
||||
<%page args="toc, extension, paged"/>
|
||||
<%namespace name="formatting" file="formatting.html"/>
|
||||
<%namespace name="nav" file="nav.html"/>
|
||||
<%namespace name="pydoc" file="pydoc.html"/>
|
||||
<%!
|
||||
import cPickle as pickle
|
||||
import os
|
||||
%>
|
||||
<%
|
||||
current = toc.get_by_file(self.template.module.filename)
|
||||
docfile = os.path.join(os.path.dirname(self.filename), 'compiled_docstrings.pickle')
|
||||
data = dict(pickle.load(file(docfile)))
|
||||
data = data[self.template.module.docstring]
|
||||
%>
|
||||
|
||||
<%def name="style()">
|
||||
${parent.style()}
|
||||
<link rel="stylesheet" href="docutil.css"></link>
|
||||
</%def>
|
||||
|
||||
${nav.topnav(item=current, toc=toc, extension=extension, paged=True)}
|
||||
|
||||
${pydoc.obj_doc(obj=data, toc=toc, extension=extension, paged=True)}
|
||||
|
||||
${nav.bottomnav(item=current, extension=extension, paged=True)}
|
||||
|
||||
Vendored
-76
@@ -1,76 +0,0 @@
|
||||
## nav.myt - Provides page navigation elements that are derived from toc.TOCElement structures, including
|
||||
## individual hyperlinks as well as navigational toolbars and table-of-content listings.
|
||||
<%namespace name="tocns" file="toc.html"/>
|
||||
|
||||
<%def name="itemlink(item, paged, extension, anchor=True)" filter="trim">
|
||||
<a href="${ item.get_link(anchor=anchor, usefilename=paged, extension=extension) }">${ item.description }</a>
|
||||
</%def>
|
||||
|
||||
<%def name="toclink(toc, path, extension, paged, description=None)" filter="trim">
|
||||
<%
|
||||
item = toc.get_by_path(path)
|
||||
if description is None:
|
||||
if item:
|
||||
description = item.description
|
||||
else:
|
||||
description = path
|
||||
if item:
|
||||
anchor = not paged or item.depth > 1
|
||||
else:
|
||||
anchor = False
|
||||
%>
|
||||
% if item:
|
||||
<a href="${ item.get_link(extension=extension, anchor=anchor, usefilename=paged) }">${ description }</a>
|
||||
% else:
|
||||
<%
|
||||
#raise Exception("Can't find TOC link for '%s'" % path)
|
||||
%>
|
||||
<b>${ description }</b>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
|
||||
<%def name="link(href, text, class_)" filter="trim">
|
||||
<a href="${ href }" ${ class_ and (('class=\"%s\"' % class_) or '')}>${ text }</a>
|
||||
</%def>
|
||||
|
||||
<%def name="topnav(item, toc, extension, paged)">
|
||||
<div class="topnav">
|
||||
|
||||
${pagenav(item, extension=extension, paged=paged)}
|
||||
|
||||
${tocns.printtoc(root=item, current=None, anchor_toplevel=True, paged=paged, extension=extension)}
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="pagenav(item, paged, extension)">
|
||||
<div class="navbanner">
|
||||
<a href="${paged and 'index' or 'documentation'}.${ extension }">Table of Contents</a>
|
||||
${prevnext(item, paged, extension)}
|
||||
<h2>${item.description}</h2>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="bottomnav(item, paged, extension)">
|
||||
<div class="bottomnav">
|
||||
${prevnext(item, paged, extension)}
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="prevnext(item, paged, extension)">
|
||||
<div class="prevnext">
|
||||
% if item.up:
|
||||
Up: ${itemlink(item=item.up, paged=paged, anchor=not paged, extension=extension)}
|
||||
% endif
|
||||
|
||||
% if item.previous is not None:
|
||||
${item.up is not None and " | " or ""}
|
||||
Previous: ${itemlink(item=item.previous, paged=paged, anchor=not paged, extension=extension)}
|
||||
% endif
|
||||
|
||||
% if item.next is not None:
|
||||
${item.previous is not None and " | " or ""}
|
||||
Next: ${itemlink(item=item.next, paged=paged, anchor=not paged, extension=extension)}
|
||||
% endif
|
||||
</div>
|
||||
</%def>
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
<%inherit file="layout.mako"/>
|
||||
${body| util.strip_toplevel_anchors}
|
||||
Vendored
-128
@@ -1,128 +0,0 @@
|
||||
<%doc>pydoc.myt - provides formatting functions for printing docstring.AbstractDoc generated python documentation objects.</%doc>
|
||||
<%!
|
||||
import docstring
|
||||
from docutils.core import publish_parts
|
||||
import re, sys
|
||||
|
||||
def whitespace(content):
|
||||
"""trim left whitespace."""
|
||||
if not content:
|
||||
return ''
|
||||
# Convert tabs to spaces (following the normal Python rules)
|
||||
# and split into a list of lines:
|
||||
lines = content.expandtabs().splitlines()
|
||||
# Determine minimum indentation (first line doesn't count):
|
||||
indent = sys.maxint
|
||||
for line in lines[1:]:
|
||||
stripped = line.lstrip()
|
||||
if stripped:
|
||||
indent = min(indent, len(line) - len(stripped))
|
||||
# Remove indentation (first line is special):
|
||||
trimmed = [lines[0].strip()]
|
||||
if indent < sys.maxint:
|
||||
for line in lines[1:]:
|
||||
trimmed.append(line[indent:].rstrip())
|
||||
# Strip off trailing and leading blank lines:
|
||||
while trimmed and not trimmed[-1]:
|
||||
trimmed.pop()
|
||||
while trimmed and not trimmed[0]:
|
||||
trimmed.pop(0)
|
||||
# Return a single string:
|
||||
return '\n'.join(trimmed)
|
||||
|
||||
def formatdocstring(content):
|
||||
return publish_parts(whitespace(content), writer_name='html')['body']
|
||||
%>
|
||||
|
||||
<%def name="inline_links(toc, extension, paged)"><%
|
||||
def link(match):
|
||||
(module, desc) = match.group(1,2)
|
||||
if not desc:
|
||||
path = "docstrings_" + module
|
||||
elif desc.endswith('()'):
|
||||
path = "docstrings_" + module + "_modfunc_" + desc[:-2]
|
||||
else:
|
||||
path = "docstrings_" + module + "_" + desc
|
||||
return capture(nav.toclink, toc=toc, path=path, description=desc or None, extension=extension, paged=paged)
|
||||
return lambda content: re.sub('\[(.+?)#(.*?)\]', link, content)
|
||||
%></%def>
|
||||
|
||||
<%namespace name="formatting" file="formatting.html"/>
|
||||
<%namespace name="nav" file="nav.html"/>
|
||||
|
||||
<%def name="obj_doc(obj, toc, extension, paged)">
|
||||
<%
|
||||
if obj.isclass:
|
||||
links = []
|
||||
for elem in obj.inherits:
|
||||
if isinstance(elem, docstring.ObjectDoc):
|
||||
links.append(capture(nav.toclink, toc=toc, path=elem.toc_path, extension=extension, description=elem.name, paged=paged))
|
||||
else:
|
||||
links.append(str(elem))
|
||||
htmldescription = "class " + obj.classname + "(%s)" % (','.join(links))
|
||||
else:
|
||||
htmldescription = obj.description
|
||||
|
||||
%>
|
||||
|
||||
<%call expr="formatting.section(toc=toc, path=obj.toc_path, description=htmldescription, paged=paged, extension=extension)">
|
||||
% if obj.doc:
|
||||
<div class="darkcell">${obj.doc or '' | formatdocstring, inline_links(toc, extension, paged)}</div>
|
||||
% endif
|
||||
|
||||
% if not obj.isclass and obj.functions:
|
||||
|
||||
<%call expr="formatting.section(toc=toc, path=obj.mod_path, paged=paged, extension=extension)">
|
||||
% for func in obj.functions:
|
||||
${function_doc(func=func,toc=toc, extension=extension, paged=paged)}
|
||||
% endfor
|
||||
</%call>
|
||||
|
||||
% else:
|
||||
|
||||
% if obj.functions:
|
||||
% for func in obj.functions:
|
||||
% if isinstance(func, docstring.FunctionDoc):
|
||||
${function_doc(func=func, toc=toc, extension=extension, paged=paged)}
|
||||
% elif isinstance(func, docstring.PropertyDoc):
|
||||
${property_doc(prop=func, toc=toc, extension=extension, paged=paged)}
|
||||
% endif
|
||||
% endfor
|
||||
% endif
|
||||
% endif
|
||||
|
||||
% if obj.classes:
|
||||
% for class_ in obj.classes:
|
||||
${obj_doc(obj=class_, toc=toc, extension=extension, paged=paged)}
|
||||
% endfor
|
||||
% endif
|
||||
</%call>
|
||||
</%def>
|
||||
|
||||
<%def name="function_doc(func, toc, extension, paged)">
|
||||
<div class="darkcell">
|
||||
<%
|
||||
if hasattr(func, 'toc_path'):
|
||||
item = toc.get_by_path(func.toc_path)
|
||||
else:
|
||||
item = None
|
||||
%>
|
||||
<A name="${item and item.path or ''}"></a>
|
||||
<b>def ${func.name}(${", ".join(map(lambda k: "<i>%s</i>" % k, func.arglist))})</b>
|
||||
<div class="docstring">
|
||||
${func.doc or '' | formatdocstring, inline_links(toc, extension, paged)}
|
||||
</div>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="property_doc(prop, toc, extension, paged)">
|
||||
<div class="darkcell">
|
||||
<A name=""></a>
|
||||
<b>${prop.name} = property()</b>
|
||||
<div class="docstring">
|
||||
${prop.doc or '' | formatdocstring, inline_links(toc, extension, paged)}
|
||||
</div>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
<%inherit file="layout.mako"/>
|
||||
|
||||
<%!
|
||||
local_script_files = ['_static/searchtools.js']
|
||||
%>
|
||||
<%def name="show_title()">${_('Search')}</%def>
|
||||
|
||||
<div id="searchform">
|
||||
<h3>Enter Search Terms:</h3>
|
||||
<form class="search" action="${pathto('search')}" method="get">
|
||||
<input type="text" name="q" size="18" /> <input type="submit" value="${_('Search')}" />
|
||||
<input type="hidden" name="check_keywords" value="yes" />
|
||||
<input type="hidden" name="area" value="default" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="search-results"></div>
|
||||
|
||||
<%def name="footer()">
|
||||
${parent.footer()}
|
||||
<script type="text/javascript" src="searchindex.js"></script>
|
||||
</%def>
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
<%text>#coding:utf-8
|
||||
<%inherit file="/base.html"/>
|
||||
<%page cache_type="file" cached="True"/>
|
||||
<%!
|
||||
in_docs=True
|
||||
%>
|
||||
</%text>
|
||||
|
||||
<div style="text-align:right">
|
||||
<b>Quick Select:</b> <a href="/docs/06/">0.6</a> | <a href="/docs/05/">0.5</a> | <a href="/docs/04/">0.4</a><br/>
|
||||
<b>PDF Download:</b> <a href="${pathto('sqlalchemy_' + release.replace('.', '_') + '.pdf', 1)}">download</a>
|
||||
</div>
|
||||
|
||||
${'<%text>'}
|
||||
${next.body()}
|
||||
${'</%text>'}
|
||||
|
||||
<%text><%def name="style()"></%text>
|
||||
${self.headers()}
|
||||
<%text>${parent.style()}</%text>
|
||||
<link href="/css/site_docs.css" rel="stylesheet" type="text/css"></link>
|
||||
<%text></%def></%text>
|
||||
|
||||
<%text><%def name="title()"></%text>${capture(self.show_title)|util.striptags} — ${docstitle|h}<%text></%def></%text>
|
||||
|
||||
<%!
|
||||
local_script_files = []
|
||||
%>
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
${metatags and metatags or ''}
|
||||
<title>${capture(self.show_title)|util.striptags} — ${docstitle|h}</title>
|
||||
${self.headers()}
|
||||
</head>
|
||||
<body>
|
||||
${next.body()}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
<%!
|
||||
local_script_files = []
|
||||
%>
|
||||
Vendored
-44
@@ -1,44 +0,0 @@
|
||||
## toc.myt - prints table of contents listings given toc.TOCElement strucures
|
||||
|
||||
<%def name="toc(toc, paged, extension)">
|
||||
<div class="topnav">
|
||||
|
||||
<a name="table_of_contents"></a>
|
||||
<h3>Table of Contents</h3>
|
||||
|
||||
<a href="#full_index">(view full table)</a>
|
||||
<br/><br/>
|
||||
${printtoc(root=toc,paged=paged, extension=extension, current=None,children=False,anchor_toplevel=False)}
|
||||
|
||||
<a name="full_index"></a>
|
||||
<h3>Table of Contents: Full</h3>
|
||||
|
||||
<a href="#table_of_contents">(view brief table)</a>
|
||||
|
||||
${printtoc(root=toc,paged=paged, extension=extension, current=None,children=True,anchor_toplevel=False)}
|
||||
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
|
||||
<%def name="printtoc(root, paged, extension, current=None, children=True, anchor_toplevel=False)">
|
||||
% if root.children:
|
||||
<ul>
|
||||
% for item in root.children:
|
||||
<%
|
||||
anchor = anchor_toplevel
|
||||
if paged and item.filename != root.filename:
|
||||
anchor = False
|
||||
%>
|
||||
<li><a style="${item is current and "font-weight:bold;" or "" }" href="${item.get_link(extension=extension,anchor=anchor, usefilename=paged) }">${item.description}</a></li>
|
||||
|
||||
% if children and item.children:
|
||||
<li>
|
||||
${printtoc(item, current=current, children=True,anchor_toplevel=True, paged=paged, extension=extension)}
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</ul>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
Vendored
+3
-3
@@ -5,7 +5,7 @@ import os
|
||||
import re
|
||||
import doctest
|
||||
import sqlalchemy.util as util
|
||||
import sqlalchemy.logging as salog
|
||||
import sqlalchemy.log as salog
|
||||
import logging
|
||||
|
||||
salog.default_enabled=True
|
||||
@@ -55,7 +55,7 @@ def teststring(s, name, globs=None, verbose=None, report=True,
|
||||
return runner.failures, runner.tries
|
||||
|
||||
def replace_file(s, newfile):
|
||||
engine = r"'(sqlite|postgres|mysql):///.*'"
|
||||
engine = r"'(sqlite|postgresql|mysql):///.*'"
|
||||
engine = re.compile(engine, re.MULTILINE)
|
||||
s, n = re.subn(engine, "'sqlite:///" + newfile + "'", s)
|
||||
if not n:
|
||||
@@ -63,7 +63,7 @@ def replace_file(s, newfile):
|
||||
return s
|
||||
|
||||
for filename in ('ormtutorial', 'sqlexpression'):
|
||||
filename = 'content/%s.txt' % filename
|
||||
filename = '%s.rst' % filename
|
||||
s = open(filename).read()
|
||||
#s = replace_file(s, ':memory:')
|
||||
s = re.sub(r'{(?:stop|sql|opensql)}', '', s)
|
||||
|
||||
Vendored
+711
@@ -0,0 +1,711 @@
|
||||
%
|
||||
% sphinx.sty
|
||||
%
|
||||
% Adapted from the old python.sty, mostly written by Fred Drake,
|
||||
% by Georg Brandl.
|
||||
%
|
||||
|
||||
\NeedsTeXFormat{LaTeX2e}[1995/12/01]
|
||||
\ProvidesPackage{sphinx}[2008/05/01 LaTeX package (Sphinx markup)]
|
||||
|
||||
\RequirePackage{textcomp}
|
||||
\RequirePackage{fancyhdr}
|
||||
\RequirePackage{fancybox}
|
||||
\RequirePackage{titlesec}
|
||||
\RequirePackage{tabulary}
|
||||
\RequirePackage{amsmath} % for \text
|
||||
\RequirePackage{makeidx}
|
||||
\RequirePackage{framed}
|
||||
\RequirePackage{color}
|
||||
\RequirePackage{fancyvrb}
|
||||
\RequirePackage{threeparttable}
|
||||
|
||||
% Redefine these colors to your liking in the preamble.
|
||||
\definecolor{TitleColor}{rgb}{0.126,0.263,0.361}
|
||||
\definecolor{InnerLinkColor}{rgb}{0.208,0.374,0.486}
|
||||
\definecolor{OuterLinkColor}{rgb}{0.216,0.439,0.388}
|
||||
% Redefine these colors to something not white if you want to have colored
|
||||
% background and border for code examples.
|
||||
\definecolor{VerbatimColor}{rgb}{1,1,1}
|
||||
\definecolor{VerbatimBorderColor}{rgb}{1,1,1}
|
||||
|
||||
% Uncomment these two lines to ignore the paper size and make the page
|
||||
% size more like a typical published manual.
|
||||
%\renewcommand{\paperheight}{9in}
|
||||
%\renewcommand{\paperwidth}{8.5in} % typical squarish manual
|
||||
%\renewcommand{\paperwidth}{7in} % O'Reilly ``Programmming Python''
|
||||
|
||||
% For graphicx, check if we are compiling under latex or pdflatex.
|
||||
\ifx\pdftexversion\undefined
|
||||
\usepackage{graphicx}
|
||||
\else
|
||||
\usepackage[pdftex]{graphicx}
|
||||
\fi
|
||||
|
||||
% for PDF output, use colors and maximal compression
|
||||
\newif\ifsphinxpdfoutput\sphinxpdfoutputfalse
|
||||
\ifx\pdfoutput\undefined\else\ifcase\pdfoutput
|
||||
\let\py@NormalColor\relax
|
||||
\let\py@TitleColor\relax
|
||||
\else
|
||||
\sphinxpdfoutputtrue
|
||||
\input{pdfcolor}
|
||||
\def\py@NormalColor{\color[rgb]{0.0,0.0,0.0}}
|
||||
\def\py@TitleColor{\color{TitleColor}}
|
||||
\pdfcompresslevel=9
|
||||
\fi\fi
|
||||
|
||||
% XeLaTeX can do colors, too
|
||||
\ifx\XeTeXrevision\undefined\else
|
||||
\def\py@NormalColor{\color[rgb]{0.0,0.0,0.0}}
|
||||
\def\py@TitleColor{\color{TitleColor}}
|
||||
\fi
|
||||
|
||||
% Increase printable page size (copied from fullpage.sty)
|
||||
\topmargin 0pt
|
||||
\advance \topmargin by -\headheight
|
||||
\advance \topmargin by -\headsep
|
||||
|
||||
% attempt to work a little better for A4 users
|
||||
\textheight \paperheight
|
||||
\advance\textheight by -2in
|
||||
|
||||
\oddsidemargin 0pt
|
||||
\evensidemargin 0pt
|
||||
%\evensidemargin -.25in % for ``manual size'' documents
|
||||
\marginparwidth 0.5in
|
||||
|
||||
\textwidth \paperwidth
|
||||
\advance\textwidth by -2in
|
||||
|
||||
|
||||
% Style parameters and macros used by most documents here
|
||||
\raggedbottom
|
||||
\sloppy
|
||||
\parindent = 0mm
|
||||
\parskip = 2mm
|
||||
\hbadness = 5000 % don't print trivial gripes
|
||||
|
||||
\pagestyle{empty} % start this way; change for
|
||||
\pagenumbering{roman} % ToC & chapters
|
||||
|
||||
% Use this to set the font family for headers and other decor:
|
||||
\newcommand{\py@HeaderFamily}{\sffamily\bfseries}
|
||||
|
||||
% Redefine the 'normal' header/footer style when using "fancyhdr" package:
|
||||
\@ifundefined{fancyhf}{}{
|
||||
% Use \pagestyle{normal} as the primary pagestyle for text.
|
||||
\fancypagestyle{normal}{
|
||||
\fancyhf{}
|
||||
\fancyfoot[LE,RO]{{\py@HeaderFamily\thepage}}
|
||||
\fancyfoot[LO]{{\py@HeaderFamily\nouppercase{\rightmark}}}
|
||||
\fancyfoot[RE]{{\py@HeaderFamily\nouppercase{\leftmark}}}
|
||||
\fancyhead[LE,RO]{{\py@HeaderFamily \@title, \py@release}}
|
||||
\renewcommand{\headrulewidth}{0.4pt}
|
||||
\renewcommand{\footrulewidth}{0.4pt}
|
||||
}
|
||||
% Update the plain style so we get the page number & footer line,
|
||||
% but not a chapter or section title. This is to keep the first
|
||||
% page of a chapter and the blank page between chapters `clean.'
|
||||
\fancypagestyle{plain}{
|
||||
\fancyhf{}
|
||||
\fancyfoot[LE,RO]{{\py@HeaderFamily\thepage}}
|
||||
\renewcommand{\headrulewidth}{0pt}
|
||||
\renewcommand{\footrulewidth}{0.4pt}
|
||||
}
|
||||
}
|
||||
|
||||
% Some custom font markup commands.
|
||||
%
|
||||
\newcommand{\strong}[1]{{\bf #1}}
|
||||
\newcommand{\code}[1]{\texttt{#1}}
|
||||
\newcommand{\bfcode}[1]{\code{\bfseries#1}}
|
||||
\newcommand{\samp}[1]{`\code{#1}'}
|
||||
\newcommand{\email}[1]{\textsf{#1}}
|
||||
|
||||
\newcommand{\py@modulebadkey}{{--just-some-junk--}}
|
||||
|
||||
% Redefine the Verbatim environment to allow border and background colors.
|
||||
% The original environment is still used for verbatims within tables.
|
||||
\let\OriginalVerbatim=\Verbatim
|
||||
\let\endOriginalVerbatim=\endVerbatim
|
||||
|
||||
% Play with vspace to be able to keep the indentation.
|
||||
\newlength\distancetoright
|
||||
\newlength\leftsidespace
|
||||
\def\mycolorbox#1{%
|
||||
\setlength\leftsidespace{\@totalleftmargin}%
|
||||
\setlength\distancetoright{\textwidth}%
|
||||
\advance\distancetoright -\@totalleftmargin %
|
||||
\noindent\hspace*{\@totalleftmargin}%
|
||||
\fcolorbox{VerbatimBorderColor}{VerbatimColor}{%
|
||||
\begin{minipage}{\distancetoright}%
|
||||
\smallskip%
|
||||
\noindent\hspace*{-\leftsidespace}%
|
||||
#1
|
||||
\end{minipage}%
|
||||
}%
|
||||
}
|
||||
\def\FrameCommand{\mycolorbox}
|
||||
|
||||
\renewcommand{\Verbatim}[1][1]{%
|
||||
\OriginalVerbatim[#1]%
|
||||
}
|
||||
\renewcommand{\endVerbatim}{%
|
||||
\endOriginalVerbatim%
|
||||
}
|
||||
|
||||
|
||||
% Index-entry generation support.
|
||||
%
|
||||
|
||||
% Command to generate two index entries (using subentries)
|
||||
\newcommand{\indexii}[2]{\index{#1!#2}\index{#2!#1}}
|
||||
|
||||
% And three entries (using only one level of subentries)
|
||||
\newcommand{\indexiii}[3]{\index{#1!#2 #3}\index{#2!#3, #1}\index{#3!#1 #2}}
|
||||
|
||||
% And four (again, using only one level of subentries)
|
||||
\newcommand{\indexiv}[4]{
|
||||
\index{#1!#2 #3 #4}
|
||||
\index{#2!#3 #4, #1}
|
||||
\index{#3!#4, #1 #2}
|
||||
\index{#4!#1 #2 #3}
|
||||
}
|
||||
|
||||
% support for the module index
|
||||
\newif\ifpy@UseModuleIndex
|
||||
\py@UseModuleIndexfalse
|
||||
|
||||
\newcommand{\makemodindex}{
|
||||
\newwrite\modindexfile
|
||||
\openout\modindexfile=mod\jobname.idx
|
||||
\py@UseModuleIndextrue
|
||||
}
|
||||
|
||||
\newcommand{\printmodindex}{
|
||||
\@input@{mod\jobname.ind}
|
||||
}
|
||||
|
||||
% Add the defining entry for a module
|
||||
\newcommand{\py@modindex}[2]{%
|
||||
\renewcommand{\py@thismodule}{#1}
|
||||
\ifpy@UseModuleIndex%
|
||||
\@ifundefined{py@modplat@\py@thismodulekey}{
|
||||
\write\modindexfile{\protect\indexentry{#1@{\texttt{#1}}|hyperpage}{\thepage}}%
|
||||
}{\write\modindexfile{\protect\indexentry{#1@{\texttt{#1 }%
|
||||
\emph{(\platformof{\py@thismodulekey})}}|hyperpage}{\thepage}}%
|
||||
}
|
||||
\fi%
|
||||
}
|
||||
|
||||
% "Current" keys
|
||||
\newcommand{\py@thisclass}{}
|
||||
\newcommand{\py@thismodule}{}
|
||||
\newcommand{\py@thismodulekey}{}
|
||||
\newcommand{\py@thismoduletype}{}
|
||||
\newcommand{\py@emptymodule}{}
|
||||
|
||||
% \declaremodule[key]{type}{name}
|
||||
\newcommand{\declaremodule}[3][\py@modulebadkey]{
|
||||
\renewcommand{\py@thismoduletype}{#2}
|
||||
\ifx\py@modulebadkey#1
|
||||
\renewcommand{\py@thismodulekey}{#3}
|
||||
\else
|
||||
\renewcommand{\py@thismodulekey}{#1}
|
||||
\fi
|
||||
\py@modindex{#3}{}
|
||||
%\label{module-\py@thismodulekey}
|
||||
}
|
||||
|
||||
% Record module platforms for the Module Index
|
||||
\newif\ifpy@ModPlatformFileIsOpen \py@ModPlatformFileIsOpenfalse
|
||||
\long\def\py@writeModPlatformFile#1{%
|
||||
\protected@write\py@ModPlatformFile%
|
||||
{\let\label\@gobble \let\index\@gobble \let\glossary\@gobble}%
|
||||
{\string#1}%
|
||||
}
|
||||
\newcommand{\py@ModPlatformFilename}{\jobname.pla}
|
||||
\newcommand{\platform}[1]{
|
||||
\ifpy@ModPlatformFileIsOpen\else
|
||||
\newwrite\py@ModPlatformFile
|
||||
\openout\py@ModPlatformFile=\py@ModPlatformFilename
|
||||
\py@ModPlatformFileIsOpentrue
|
||||
\fi
|
||||
\py@writeModPlatformFile{\py@defplatform{\py@thismodulekey}{#1}}
|
||||
}
|
||||
\newcommand{\py@defplatform}[2]{\expandafter\def\csname py@modplat@#1\endcsname{#2}}
|
||||
\newcommand{\platformof}[1]{\csname py@modplat@#1\endcsname}
|
||||
|
||||
\InputIfFileExists{\jobname.pla}{}{}
|
||||
|
||||
% \moduleauthor{name}{email}
|
||||
\newcommand{\moduleauthor}[2]{}
|
||||
|
||||
% \sectionauthor{name}{email}
|
||||
\newcommand{\sectionauthor}[2]{}
|
||||
|
||||
% Ignore module synopsis.
|
||||
\newcommand{\modulesynopsis}[1]{}
|
||||
|
||||
% Reset "current" objects.
|
||||
\newcommand{\resetcurrentobjects}{
|
||||
\renewcommand{\py@thisclass}{}
|
||||
\renewcommand{\py@thismodule}{}
|
||||
\renewcommand{\py@thismodulekey}{}
|
||||
\renewcommand{\py@thismoduletype}{}
|
||||
}
|
||||
|
||||
% Augment the sectioning commands used to get our own font family in place,
|
||||
% and reset some internal data items:
|
||||
\titleformat{\section}{\Large\py@HeaderFamily}%
|
||||
{\py@TitleColor\thesection}{0.5em}{\py@TitleColor}{\py@NormalColor}
|
||||
\titleformat{\subsection}{\large\py@HeaderFamily}%
|
||||
{\py@TitleColor\thesubsection}{0.5em}{\py@TitleColor}{\py@NormalColor}
|
||||
\titleformat{\subsubsection}{\py@HeaderFamily}%
|
||||
{\py@TitleColor\thesubsubsection}{0.5em}{\py@TitleColor}{\py@NormalColor}
|
||||
\titleformat{\paragraph}{\large\py@HeaderFamily}%
|
||||
{\py@TitleColor}{0em}{\py@TitleColor}{\py@NormalColor}
|
||||
|
||||
|
||||
% Now for a lot of semantically-loaded environments that do a ton of magical
|
||||
% things to get the right formatting and index entries for the stuff in
|
||||
% Python modules and C API.
|
||||
|
||||
|
||||
% {fulllineitems} is used in one place in libregex.tex, but is really for
|
||||
% internal use in this file.
|
||||
%
|
||||
\newcommand{\py@itemnewline}[1]{%
|
||||
\@tempdima\linewidth%
|
||||
\advance\@tempdima \leftmargin\makebox[\@tempdima][l]{#1}%
|
||||
}
|
||||
|
||||
\newenvironment{fulllineitems}{
|
||||
\begin{list}{}{\labelwidth \leftmargin \labelsep 0pt
|
||||
\rightmargin 0pt \topsep -\parskip \partopsep \parskip
|
||||
\itemsep -\parsep
|
||||
\let\makelabel=\py@itemnewline}
|
||||
}{\end{list}}
|
||||
|
||||
% \optional is mostly for use in the arguments parameters to the various
|
||||
% {*desc} environments defined below, but may be used elsewhere. Known to
|
||||
% be used in the debugger chapter.
|
||||
%
|
||||
% Typical usage:
|
||||
%
|
||||
% \begin{funcdesc}{myfunc}{reqparm\optional{, optparm}}
|
||||
% ^^^ ^^^
|
||||
% No space here No space here
|
||||
%
|
||||
% When a function has multiple optional parameters, \optional should be
|
||||
% nested, not chained. This is right:
|
||||
%
|
||||
% \begin{funcdesc}{myfunc}{\optional{parm1\optional{, parm2}}}
|
||||
%
|
||||
\let\py@badkey=\@undefined
|
||||
|
||||
\newcommand{\optional}[1]{%
|
||||
{\textnormal{\Large[}}{#1}\hspace{0.5mm}{\textnormal{\Large]}}}
|
||||
|
||||
% This can be used when a function or method accepts an varying number
|
||||
% of arguments, such as by using the *args syntax in the parameter list.
|
||||
\newcommand{\py@moreargs}{...}
|
||||
|
||||
% This can be used when you don't want to document the parameters to a
|
||||
% function or method, but simply state that it's an alias for
|
||||
% something else.
|
||||
\newcommand{\py@unspecified}{...}
|
||||
|
||||
\newcommand{\py@varvars}[1]{{%
|
||||
{\let\unspecified=\py@unspecified%
|
||||
\let\moreargs=\py@moreargs%
|
||||
\emph{#1}}}}
|
||||
|
||||
\newlength{\py@argswidth}
|
||||
\newcommand{\py@sigparams}[1]{%
|
||||
\parbox[t]{\py@argswidth}{\py@varvars{#1}\code{)}}}
|
||||
\newcommand{\py@sigline}[2]{%
|
||||
\settowidth{\py@argswidth}{#1\code{(}}%
|
||||
\addtolength{\py@argswidth}{-2\py@argswidth}%
|
||||
\addtolength{\py@argswidth}{\textwidth}%
|
||||
\item[#1\code{(}\py@sigparams{#2}]}
|
||||
|
||||
% C functions ------------------------------------------------------------
|
||||
% \begin{cfuncdesc}[refcount]{type}{name}{arglist}
|
||||
% Note that the [refcount] slot should only be filled in by
|
||||
% tools/anno-api.py; it pulls the value from the refcounts database.
|
||||
\newcommand{\cfuncline}[3]{
|
||||
\py@sigline{\code{#1 \bfcode{#2}}}{#3}%
|
||||
}
|
||||
\newenvironment{cfuncdesc}[3]{
|
||||
\begin{fulllineitems}
|
||||
\cfuncline{#1}{#2}{#3}
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% C variables ------------------------------------------------------------
|
||||
% \begin{cvardesc}{type}{name}
|
||||
\newenvironment{cvardesc}[2]{
|
||||
\begin{fulllineitems}
|
||||
\item[\code{#1 \bfcode{#2}}]
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% C data types -----------------------------------------------------------
|
||||
% \begin{ctypedesc}[index name]{typedef name}
|
||||
\newenvironment{ctypedesc}[2][\py@badkey]{
|
||||
\begin{fulllineitems}
|
||||
\item[\bfcode{#2}]
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% C type fields ----------------------------------------------------------
|
||||
% \begin{cmemberdesc}{container type}{ctype}{membername}
|
||||
\newcommand{\cmemberline}[3]{
|
||||
\item[\code{#2 \bfcode{#3}}]
|
||||
}
|
||||
\newenvironment{cmemberdesc}[3]{
|
||||
\begin{fulllineitems}
|
||||
\cmemberline{#1}{#2}{#3}
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% Funky macros -----------------------------------------------------------
|
||||
% \begin{csimplemacrodesc}{name}
|
||||
% -- "simple" because it has no args; NOT for constant definitions!
|
||||
\newenvironment{csimplemacrodesc}[1]{
|
||||
\begin{fulllineitems}
|
||||
\item[\bfcode{#1}]
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% simple functions (not methods) -----------------------------------------
|
||||
% \begin{funcdesc}{name}{args}
|
||||
\newcommand{\funcline}[2]{%
|
||||
\py@sigline{\bfcode{#1}}{#2}}
|
||||
\newenvironment{funcdesc}[2]{
|
||||
\begin{fulllineitems}
|
||||
\funcline{#1}{#2}
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% classes ----------------------------------------------------------------
|
||||
% \begin{classdesc}{name}{constructor args}
|
||||
\newcommand{\classline}[2]{
|
||||
\py@sigline{\strong{class }\bfcode{#1}}{#2}}
|
||||
\newenvironment{classdesc}[2]{
|
||||
% Using \renewcommand doesn't work for this, for unknown reasons:
|
||||
\global\def\py@thisclass{#1}
|
||||
\begin{fulllineitems}
|
||||
\classline{#1}{#2}
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% \begin{excclassdesc}{name}{constructor args}
|
||||
% but indexes as an exception
|
||||
\newenvironment{excclassdesc}[2]{
|
||||
% Using \renewcommand doesn't work for this, for unknown reasons:
|
||||
\global\def\py@thisclass{#1}
|
||||
\begin{fulllineitems}
|
||||
\py@sigline{\strong{exception }\bfcode{#1}}{#2}%
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% There is no corresponding {excclassdesc*} environment. To describe
|
||||
% a class exception without parameters, use the {excdesc} environment.
|
||||
|
||||
|
||||
\let\py@classbadkey=\@undefined
|
||||
|
||||
% object method ----------------------------------------------------------
|
||||
% \begin{methoddesc}[classname]{methodname}{args}
|
||||
\newcommand{\methodline}[3][\@undefined]{
|
||||
\py@sigline{\bfcode{#2}}{#3}}
|
||||
\newenvironment{methoddesc}[3][\@undefined]{
|
||||
\begin{fulllineitems}
|
||||
\ifx\@undefined#1\relax
|
||||
\methodline{#2}{#3}
|
||||
\else
|
||||
\def\py@thisclass{#1}
|
||||
\methodline{#2}{#3}
|
||||
\fi
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% static method ----------------------------------------------------------
|
||||
% \begin{staticmethoddesc}[classname]{methodname}{args}
|
||||
\newcommand{\staticmethodline}[3][\@undefined]{
|
||||
\py@sigline{static \bfcode{#2}}{#3}}
|
||||
\newenvironment{staticmethoddesc}[3][\@undefined]{
|
||||
\begin{fulllineitems}
|
||||
\ifx\@undefined#1\relax
|
||||
\staticmethodline{#2}{#3}
|
||||
\else
|
||||
\def\py@thisclass{#1}
|
||||
\staticmethodline{#2}{#3}
|
||||
\fi
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% class method ----------------------------------------------------------
|
||||
% \begin{classmethoddesc}[classname]{methodname}{args}
|
||||
\newcommand{\classmethodline}[3][\@undefined]{
|
||||
\py@sigline{class \bfcode{#2}}{#3}}
|
||||
\newenvironment{classmethoddesc}[3][\@undefined]{
|
||||
\begin{fulllineitems}
|
||||
\ifx\@undefined#1\relax
|
||||
\classmethodline{#2}{#3}
|
||||
\else
|
||||
\def\py@thisclass{#1}
|
||||
\classmethodline{#2}{#3}
|
||||
\fi
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
|
||||
% object data attribute --------------------------------------------------
|
||||
% \begin{memberdesc}[classname]{membername}
|
||||
\newcommand{\memberline}[2][\py@classbadkey]{%
|
||||
\ifx\@undefined#1\relax
|
||||
\item[\bfcode{#2}]
|
||||
\else
|
||||
\item[\bfcode{#2}]
|
||||
\fi
|
||||
}
|
||||
\newenvironment{memberdesc}[2][\py@classbadkey]{
|
||||
\begin{fulllineitems}
|
||||
\ifx\@undefined#1\relax
|
||||
\memberline{#2}
|
||||
\else
|
||||
\def\py@thisclass{#1}
|
||||
\memberline{#2}
|
||||
\fi
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% For exceptions: --------------------------------------------------------
|
||||
% \begin{excdesc}{name}
|
||||
% -- for constructor information, use excclassdesc instead
|
||||
\newenvironment{excdesc}[1]{
|
||||
\begin{fulllineitems}
|
||||
\item[\strong{exception }\bfcode{#1}]
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% Module data or constants: ----------------------------------------------
|
||||
% \begin{datadesc}{name}
|
||||
\newcommand{\dataline}[1]{%
|
||||
\item[\bfcode{#1}]\nopagebreak}
|
||||
\newenvironment{datadesc}[1]{
|
||||
\begin{fulllineitems}
|
||||
\dataline{#1}
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% bytecode instruction ---------------------------------------------------
|
||||
% \begin{opcodedesc}{name}{var}
|
||||
% -- {var} may be {}
|
||||
\newenvironment{opcodedesc}[2]{
|
||||
\begin{fulllineitems}
|
||||
\item[\bfcode{#1}\quad\emph{#2}]
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% generic description ----------------------------------------------------
|
||||
\newcommand{\descline}[1]{%
|
||||
\item[\bfcode{#1}]\nopagebreak%
|
||||
}
|
||||
\newenvironment{describe}[1]{
|
||||
\begin{fulllineitems}
|
||||
\descline{#1}
|
||||
}{\end{fulllineitems}}
|
||||
|
||||
% This version is being checked in for the historical record; it shows
|
||||
% how I've managed to get some aspects of this to work. It will not
|
||||
% be used in practice, so a subsequent revision will change things
|
||||
% again. This version has problems, but shows how to do something
|
||||
% that proved more tedious than I'd expected, so I don't want to lose
|
||||
% the example completely.
|
||||
%
|
||||
\newcommand{\grammartoken}[1]{\texttt{#1}}
|
||||
\newenvironment{productionlist}[1][\py@badkey]{
|
||||
\def\optional##1{{\Large[}##1{\Large]}}
|
||||
\def\production##1##2{\code{##1}&::=&\code{##2}\\}
|
||||
\def\productioncont##1{& &\code{##1}\\}
|
||||
\def\token##1{##1}
|
||||
\let\grammartoken=\token
|
||||
\parindent=2em
|
||||
\indent
|
||||
\begin{tabular}{lcl}
|
||||
}{%
|
||||
\end{tabular}
|
||||
}
|
||||
|
||||
% Notices / Admonitions
|
||||
%
|
||||
\newlength{\py@noticelength}
|
||||
|
||||
\newcommand{\py@heavybox}{
|
||||
\setlength{\fboxrule}{1pt}
|
||||
\setlength{\fboxsep}{7pt}
|
||||
\setlength{\py@noticelength}{\linewidth}
|
||||
\addtolength{\py@noticelength}{-2\fboxsep}
|
||||
\addtolength{\py@noticelength}{-2\fboxrule}
|
||||
\setlength{\shadowsize}{3pt}
|
||||
\Sbox
|
||||
\minipage{\py@noticelength}
|
||||
}
|
||||
\newcommand{\py@endheavybox}{
|
||||
\endminipage
|
||||
\endSbox
|
||||
\fbox{\TheSbox}
|
||||
}
|
||||
|
||||
% Some are quite plain:
|
||||
\newcommand{\py@noticestart@note}{}
|
||||
\newcommand{\py@noticeend@note}{}
|
||||
\newcommand{\py@noticestart@hint}{}
|
||||
\newcommand{\py@noticeend@hint}{}
|
||||
\newcommand{\py@noticestart@important}{}
|
||||
\newcommand{\py@noticeend@important}{}
|
||||
\newcommand{\py@noticestart@tip}{}
|
||||
\newcommand{\py@noticeend@tip}{}
|
||||
|
||||
% Others gets more visible distinction:
|
||||
\newcommand{\py@noticestart@warning}{\py@heavybox}
|
||||
\newcommand{\py@noticeend@warning}{\py@endheavybox}
|
||||
\newcommand{\py@noticestart@caution}{\py@heavybox}
|
||||
\newcommand{\py@noticeend@caution}{\py@endheavybox}
|
||||
\newcommand{\py@noticestart@attention}{\py@heavybox}
|
||||
\newcommand{\py@noticeend@attention}{\py@endheavybox}
|
||||
\newcommand{\py@noticestart@danger}{\py@heavybox}
|
||||
\newcommand{\py@noticeend@danger}{\py@endheavybox}
|
||||
\newcommand{\py@noticestart@error}{\py@heavybox}
|
||||
\newcommand{\py@noticeend@error}{\py@endheavybox}
|
||||
|
||||
\newenvironment{notice}[2]{
|
||||
\def\py@noticetype{#1}
|
||||
\csname py@noticestart@#1\endcsname
|
||||
\par\strong{#2}
|
||||
}{\csname py@noticeend@\py@noticetype\endcsname}
|
||||
|
||||
% Allow the release number to be specified independently of the
|
||||
% \date{}. This allows the date to reflect the document's date and
|
||||
% release to specify the release that is documented.
|
||||
%
|
||||
\newcommand{\py@release}{}
|
||||
\newcommand{\version}{}
|
||||
\newcommand{\shortversion}{}
|
||||
\newcommand{\releaseinfo}{}
|
||||
\newcommand{\releasename}{Release}
|
||||
\newcommand{\release}[1]{%
|
||||
\renewcommand{\py@release}{\releasename\space\version}%
|
||||
\renewcommand{\version}{#1}}
|
||||
\newcommand{\setshortversion}[1]{%
|
||||
\renewcommand{\shortversion}{#1}}
|
||||
\newcommand{\setreleaseinfo}[1]{%
|
||||
\renewcommand{\releaseinfo}{#1}}
|
||||
|
||||
% Allow specification of the author's address separately from the
|
||||
% author's name. This can be used to format them differently, which
|
||||
% is a good thing.
|
||||
%
|
||||
\newcommand{\py@authoraddress}{}
|
||||
\newcommand{\authoraddress}[1]{\renewcommand{\py@authoraddress}{#1}}
|
||||
|
||||
% This sets up the fancy chapter headings that make the documents look
|
||||
% at least a little better than the usual LaTeX output.
|
||||
%
|
||||
\@ifundefined{ChTitleVar}{}{
|
||||
\ChNameVar{\raggedleft\normalsize\py@HeaderFamily}
|
||||
\ChNumVar{\raggedleft \bfseries\Large\py@HeaderFamily}
|
||||
\ChTitleVar{\raggedleft \rm\Huge\py@HeaderFamily}
|
||||
% This creates chapter heads without the leading \vspace*{}:
|
||||
\def\@makechapterhead#1{%
|
||||
{\parindent \z@ \raggedright \normalfont
|
||||
\ifnum \c@secnumdepth >\m@ne
|
||||
\DOCH
|
||||
\fi
|
||||
\interlinepenalty\@M
|
||||
\DOTI{#1}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
% Redefine description environment so that it is usable inside fulllineitems.
|
||||
%
|
||||
\renewcommand{\description}{%
|
||||
\list{}{\labelwidth\z@%
|
||||
\itemindent-\leftmargin%
|
||||
\labelsep5pt%
|
||||
\let\makelabel=\descriptionlabel}}
|
||||
|
||||
% Definition lists; requested by AMK for HOWTO documents. Probably useful
|
||||
% elsewhere as well, so keep in in the general style support.
|
||||
%
|
||||
\newenvironment{definitions}{%
|
||||
\begin{description}%
|
||||
\def\term##1{\item[##1]\mbox{}\\*[0mm]}
|
||||
}{%
|
||||
\end{description}%
|
||||
}
|
||||
|
||||
% Tell TeX about pathological hyphenation cases:
|
||||
\hyphenation{Base-HTTP-Re-quest-Hand-ler}
|
||||
|
||||
|
||||
% The following is stuff copied from docutils' latex writer.
|
||||
%
|
||||
\newcommand{\optionlistlabel}[1]{\bf #1 \hfill}
|
||||
\newenvironment{optionlist}[1]
|
||||
{\begin{list}{}
|
||||
{\setlength{\labelwidth}{#1}
|
||||
\setlength{\rightmargin}{1cm}
|
||||
\setlength{\leftmargin}{\rightmargin}
|
||||
\addtolength{\leftmargin}{\labelwidth}
|
||||
\addtolength{\leftmargin}{\labelsep}
|
||||
\renewcommand{\makelabel}{\optionlistlabel}}
|
||||
}{\end{list}}
|
||||
|
||||
\newlength{\lineblockindentation}
|
||||
\setlength{\lineblockindentation}{2.5em}
|
||||
\newenvironment{lineblock}[1]
|
||||
{\begin{list}{}
|
||||
{\setlength{\partopsep}{\parskip}
|
||||
\addtolength{\partopsep}{\baselineskip}
|
||||
\topsep0pt\itemsep0.15\baselineskip\parsep0pt
|
||||
\leftmargin#1}
|
||||
\raggedright}
|
||||
{\end{list}}
|
||||
|
||||
% Redefine includgraphics for avoiding images larger than the screen size
|
||||
% If the size is not specified.
|
||||
\let\py@Oldincludegraphics\includegraphics
|
||||
|
||||
\newbox\image@box%
|
||||
\newdimen\image@width%
|
||||
\renewcommand\includegraphics[2][\@empty]{%
|
||||
\ifx#1\@empty%
|
||||
\setbox\image@box=\hbox{\py@Oldincludegraphics{#2}}%
|
||||
\image@width\wd\image@box%
|
||||
\ifdim \image@width>\linewidth%
|
||||
\setbox\image@box=\hbox{\py@Oldincludegraphics[width=\linewidth]{#2}}%
|
||||
\fi%
|
||||
\box\image@box%
|
||||
\else%
|
||||
\py@Oldincludegraphics[#1]{#2}%
|
||||
\fi%
|
||||
}
|
||||
|
||||
|
||||
% Fix the index and bibliography environments to add an entry to the Table of
|
||||
% Contents; this is much nicer than just having to jump to the end of the book
|
||||
% and flip around, especially with multiple indexes.
|
||||
%
|
||||
\let\py@OldTheindex=\theindex
|
||||
\renewcommand{\theindex}{
|
||||
\cleardoublepage
|
||||
\phantomsection
|
||||
\py@OldTheindex
|
||||
\addcontentsline{toc}{chapter}{\indexname}
|
||||
}
|
||||
|
||||
\let\py@OldThebibliography=\thebibliography
|
||||
\renewcommand{\thebibliography}[1]{
|
||||
\cleardoublepage
|
||||
\phantomsection
|
||||
\py@OldThebibliography{1}
|
||||
\addcontentsline{toc}{chapter}{\bibname}
|
||||
}
|
||||
|
||||
% Include hyperref last.
|
||||
\RequirePackage[colorlinks,breaklinks,
|
||||
linkcolor=InnerLinkColor,filecolor=OuterLinkColor,
|
||||
menucolor=OuterLinkColor,pagecolor=OuterLinkColor,
|
||||
urlcolor=OuterLinkColor]{hyperref}
|
||||
-200
@@ -1,200 +0,0 @@
|
||||
/* documentation section styles */
|
||||
|
||||
#topanchor {position:absolute;left:0px;top:0px;width:0px;height:0px;}
|
||||
#pagecontrol {float:right;}
|
||||
|
||||
.topnav {
|
||||
background-color: #fbfbee;
|
||||
border: solid 1px #ccc;
|
||||
padding:10px 10px 0px 10px;
|
||||
margin:10px 0px 10px 0px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin:0px;
|
||||
padding:0px;
|
||||
}
|
||||
|
||||
.prevnext {
|
||||
padding: 5px 0px 0px 0px;
|
||||
}
|
||||
|
||||
.codetitle {
|
||||
font-family: verdana, sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-decoration:underline;
|
||||
padding:5px;
|
||||
}
|
||||
|
||||
.codeline {
|
||||
font-family: courier, "courier new", serif;
|
||||
font-size:1.1em;
|
||||
color: #960;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-family:arial,helvetica,sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font: normal 20px/22px arial,helvetica,sans-serif;
|
||||
color: #222;
|
||||
padding:0px;
|
||||
margin:0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family:arial,helvetica,sans-serif;
|
||||
font-size:20px;
|
||||
font-weight:normal;
|
||||
line-height:24px;
|
||||
margin:0px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-family: arial, sans-serif;
|
||||
font-size:16px;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
.topnav h3 {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
margin:0px;
|
||||
display:inline;
|
||||
font-family:verdana,sans-serif;
|
||||
}
|
||||
|
||||
.topnav h2 {
|
||||
margin:26px 4px 0px 5px;
|
||||
}
|
||||
|
||||
.sectionL1 {
|
||||
line-height: 1.5em;
|
||||
padding:8px 10px 20px 10px;
|
||||
margin:10px 0px 0px;
|
||||
}
|
||||
|
||||
.sectionL2 {
|
||||
margin:0px 0px 0px 0px;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.sectionL3 {
|
||||
margin:0px 0px 0px 20px;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.sectionL4 {
|
||||
margin:0px 0px 0px 20px;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
|
||||
.topnav li {
|
||||
font-size:12px;
|
||||
list-style-type:none;
|
||||
padding:0px 0px 3px 8px;
|
||||
margin:0px;
|
||||
}
|
||||
|
||||
.topnav ul ul {
|
||||
padding:0px 0px 0px 8px;
|
||||
}
|
||||
|
||||
.topnav ul ul li {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.bottomnav {
|
||||
background-color:#FBFBEE;
|
||||
border:1px solid #CCCCCC;
|
||||
float:right;
|
||||
margin:0px 0px 15px 5px;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
.toclink {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
padding:0px 0px 3px 8px;
|
||||
/*border:1px solid;*/
|
||||
}
|
||||
|
||||
.smalltoclink {
|
||||
font-size: 11px;
|
||||
padding:0px 0px 3px 0px;
|
||||
}
|
||||
|
||||
.docstring {
|
||||
margin-left:15px;
|
||||
margin-bottom:5px;
|
||||
margin-top:5px;
|
||||
}
|
||||
|
||||
.darkcell {
|
||||
/*font-family: courier, "courier new", serif;*/
|
||||
margin:0px 0px 10px 0px;
|
||||
padding:4px 4px 4px 4px;
|
||||
background-color: #f0f0f0;
|
||||
border: solid 1px #ccc;
|
||||
}
|
||||
|
||||
.sliding_code {
|
||||
font-family: courier, "courier new", serif;
|
||||
font-size:12px;
|
||||
background-color: #f0f0f0;
|
||||
border: solid 1px #ccc;
|
||||
padding:10px;
|
||||
margin: 5px 5px 5px 5px;
|
||||
overflow:auto;
|
||||
}
|
||||
|
||||
.code {
|
||||
font-family: courier, "courier new", serif;
|
||||
font-size:12px;
|
||||
background-color: #f0f0f0;
|
||||
border: solid 1px #ccc;
|
||||
padding:10px; /*2px 2px 2px 10px;*/
|
||||
margin: 5px 5px 5px 5px;
|
||||
line-height:1.2em;
|
||||
}
|
||||
|
||||
.codepop
|
||||
{
|
||||
font-family: courier, "courier new", serif;
|
||||
color:#000;
|
||||
background-color: #fbfbee;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-right: 1px solid #999;
|
||||
border-bottom: 1px solid #999;
|
||||
padding:10px;
|
||||
width:95%;
|
||||
/*margin:5px 10px 5px 0px;*/
|
||||
/*clear:right;*/
|
||||
}
|
||||
|
||||
.codepoplink,
|
||||
#docs a.codepoplink
|
||||
{
|
||||
font-weight:normal;
|
||||
font-family: arial, sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size:11px;
|
||||
color:#666;
|
||||
border:1px solid;
|
||||
padding:1px 2px 1px 2px;
|
||||
margin:0px 10px 0px 15px;
|
||||
float:right;
|
||||
}
|
||||
#docs a.codepoplink {
|
||||
text-decoration: none;
|
||||
}
|
||||
#docs a.codepoplink:hover {
|
||||
text-decoration: none;
|
||||
color:#fff;
|
||||
border:1px solid #900;
|
||||
background-color: #900;
|
||||
}
|
||||
|
||||
-274
@@ -1,274 +0,0 @@
|
||||
/*
|
||||
:Author: David Goodger <goodger@python.org>
|
||||
:Id: $Id: html4css1.css 4993 2007-03-04 21:21:49Z fwiemann $
|
||||
:Copyright: This stylesheet has been placed in the public domain.
|
||||
|
||||
Default cascading style sheet for the HTML output of Docutils.
|
||||
|
||||
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
|
||||
customize this style sheet.
|
||||
*/
|
||||
|
||||
/* used to remove borders from tables and images */
|
||||
.borderless, table.borderless td, table.borderless th {
|
||||
border: 0 }
|
||||
|
||||
table.borderless td, table.borderless th {
|
||||
/* Override padding for "table.docutils td" with "! important".
|
||||
The right padding separates the table cells. */
|
||||
padding: 0 0.5em 0 0 ! important }
|
||||
|
||||
.first {
|
||||
/* Override more specific margin styles with "! important". */
|
||||
margin-top: 0 ! important }
|
||||
|
||||
.last, .with-subtitle {
|
||||
margin-bottom: 0 ! important }
|
||||
|
||||
.hidden {
|
||||
display: none }
|
||||
|
||||
a.toc-backref {
|
||||
text-decoration: none ;
|
||||
color: black }
|
||||
|
||||
blockquote.epigraph {
|
||||
margin: 2em 5em ; }
|
||||
|
||||
dl.docutils dd {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
/* Uncomment (and remove this text!) to get bold-faced definition list terms
|
||||
dl.docutils dt {
|
||||
font-weight: bold }
|
||||
*/
|
||||
|
||||
div.abstract {
|
||||
margin: 2em 5em }
|
||||
|
||||
div.abstract p.topic-title {
|
||||
font-weight: bold ;
|
||||
text-align: center }
|
||||
|
||||
div.admonition, div.attention, div.caution, div.danger, div.error,
|
||||
div.hint, div.important, div.note, div.tip, div.warning {
|
||||
margin: 2em ;
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.admonition p.admonition-title, div.hint p.admonition-title,
|
||||
div.important p.admonition-title, div.note p.admonition-title,
|
||||
div.tip p.admonition-title {
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
div.attention p.admonition-title, div.caution p.admonition-title,
|
||||
div.danger p.admonition-title, div.error p.admonition-title,
|
||||
div.warning p.admonition-title {
|
||||
color: red ;
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
/* Uncomment (and remove this text!) to get reduced vertical space in
|
||||
compound paragraphs.
|
||||
div.compound .compound-first, div.compound .compound-middle {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
div.compound .compound-last, div.compound .compound-middle {
|
||||
margin-top: 0.5em }
|
||||
*/
|
||||
|
||||
div.dedication {
|
||||
margin: 2em 5em ;
|
||||
text-align: center ;
|
||||
font-style: italic }
|
||||
|
||||
div.dedication p.topic-title {
|
||||
font-weight: bold ;
|
||||
font-style: normal }
|
||||
|
||||
div.figure {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
div.footer, div.header {
|
||||
clear: both;
|
||||
font-size: smaller }
|
||||
|
||||
div.line-block {
|
||||
display: block ;
|
||||
margin-top: 1em ;
|
||||
margin-bottom: 1em }
|
||||
|
||||
div.line-block div.line-block {
|
||||
margin-top: 0 ;
|
||||
margin-bottom: 0 ;
|
||||
margin-left: 1.5em }
|
||||
|
||||
div.sidebar {
|
||||
margin-left: 1em ;
|
||||
border: medium outset ;
|
||||
padding: 1em ;
|
||||
background-color: #ffffee ;
|
||||
width: 40% ;
|
||||
float: right ;
|
||||
clear: right }
|
||||
|
||||
div.sidebar p.rubric {
|
||||
font-family: sans-serif ;
|
||||
font-size: medium }
|
||||
|
||||
div.system-messages {
|
||||
margin: 5em }
|
||||
|
||||
div.system-messages h1 {
|
||||
color: red }
|
||||
|
||||
div.system-message {
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.system-message p.system-message-title {
|
||||
color: red ;
|
||||
font-weight: bold }
|
||||
|
||||
div.topic {
|
||||
margin: 2em }
|
||||
|
||||
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
|
||||
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
|
||||
margin-top: 0.4em }
|
||||
|
||||
h1.title {
|
||||
text-align: center }
|
||||
|
||||
h2.subtitle {
|
||||
text-align: center }
|
||||
|
||||
hr.docutils {
|
||||
width: 75% }
|
||||
|
||||
img.align-left {
|
||||
clear: left }
|
||||
|
||||
img.align-right {
|
||||
clear: right }
|
||||
|
||||
ol.simple, ul.simple {
|
||||
margin-bottom: 1em }
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal }
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha }
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha }
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman }
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman }
|
||||
|
||||
p.attribution {
|
||||
text-align: right ;
|
||||
margin-left: 50% }
|
||||
|
||||
p.caption {
|
||||
font-style: italic }
|
||||
|
||||
p.credits {
|
||||
font-style: italic ;
|
||||
font-size: smaller }
|
||||
|
||||
p.label {
|
||||
white-space: nowrap }
|
||||
|
||||
p.rubric {
|
||||
font-weight: bold ;
|
||||
font-size: larger ;
|
||||
color: maroon ;
|
||||
text-align: center }
|
||||
|
||||
p.sidebar-title {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold ;
|
||||
font-size: larger }
|
||||
|
||||
p.sidebar-subtitle {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
p.topic-title {
|
||||
font-weight: bold }
|
||||
|
||||
pre.address {
|
||||
margin-bottom: 0 ;
|
||||
margin-top: 0 ;
|
||||
font-family: serif ;
|
||||
font-size: 100% }
|
||||
|
||||
pre.literal-block, pre.doctest-block {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
span.classifier {
|
||||
font-family: sans-serif ;
|
||||
font-style: oblique }
|
||||
|
||||
span.classifier-delimiter {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
span.interpreted {
|
||||
font-family: sans-serif }
|
||||
|
||||
span.option {
|
||||
white-space: nowrap }
|
||||
|
||||
span.pre {
|
||||
white-space: pre }
|
||||
|
||||
span.problematic {
|
||||
color: red }
|
||||
|
||||
span.section-subtitle {
|
||||
/* font-size relative to parent (h1..h6 element) */
|
||||
font-size: 80% }
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docinfo {
|
||||
margin: 2em 4em }
|
||||
|
||||
table.docutils {
|
||||
margin-top: 0.5em ;
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
table.footnote {
|
||||
border-left: solid 1px black;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docutils td, table.docutils th,
|
||||
table.docinfo td, table.docinfo th {
|
||||
padding-left: 0.5em ;
|
||||
padding-right: 0.5em ;
|
||||
vertical-align: top }
|
||||
|
||||
table.docutils th.field-name, table.docinfo th.docinfo-name {
|
||||
font-weight: bold ;
|
||||
text-align: left ;
|
||||
white-space: nowrap ;
|
||||
padding-left: 0 }
|
||||
|
||||
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
|
||||
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
|
||||
font-size: 100% }
|
||||
|
||||
ul.auto-toc {
|
||||
list-style-type: none }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user