Compare commits
599 Commits
0.0.3
...
sqlalchemy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1dff79a88 | ||
|
|
95c6a1e171 | ||
|
|
ce22f2a50b | ||
|
|
ac9d1a514a | ||
|
|
8a35352bc5 | ||
|
|
a1fdc57271 | ||
|
|
b8ded9b9ca | ||
|
|
759220d592 | ||
|
|
ee16ba4dc3 | ||
|
|
8daa54443d | ||
|
|
68963e7013 | ||
|
|
10231263f2 | ||
|
|
cd05bfeaf2 | ||
|
|
ab965f0113 | ||
|
|
525373da31 | ||
|
|
d0d0288797 | ||
|
|
ccbb92aeaf | ||
|
|
5d1bf5c2f3 | ||
|
|
b1ed8c36ca | ||
|
|
88670a5bbb | ||
|
|
dbd0101c40 | ||
|
|
7d00768456 | ||
|
|
073e4f9fd1 | ||
|
|
fa5895c6d1 | ||
|
|
c889139491 | ||
|
|
cc0e6d6146 | ||
|
|
b6bc7de263 | ||
|
|
5699c43ee5 | ||
|
|
7dbc1c934a | ||
|
|
6680b3c26f | ||
|
|
5100200bea | ||
|
|
0c65fed61b | ||
|
|
3d064c4542 | ||
|
|
84d739e0e4 | ||
|
|
7173b44a5c | ||
|
|
8c275289f2 | ||
|
|
b34a20448b | ||
|
|
0c78e0759a | ||
|
|
b8a6a1f869 | ||
|
|
2b268d135c | ||
|
|
9cc1772b64 | ||
|
|
33b9179424 | ||
|
|
74496c25f6 | ||
|
|
811fe9ffc9 | ||
|
|
aacf9ec19b | ||
|
|
94d6b0ecd6 | ||
|
|
b1be043006 | ||
|
|
0efd1bfaa0 | ||
|
|
66c1d93cfe | ||
|
|
015601cd5b | ||
|
|
a3044bbf68 | ||
|
|
4aade03081 | ||
|
|
277953a614 | ||
|
|
e6ad74d50a | ||
|
|
48f2a88752 | ||
|
|
4071b0fc64 | ||
|
|
589237b80f | ||
|
|
6d93a46fe0 | ||
|
|
e86b5fcc84 | ||
|
|
ab3d19f0ac | ||
|
|
893f8bd039 | ||
|
|
4f9b2ea9eb | ||
|
|
06de217329 | ||
|
|
a3cfa7618d | ||
|
|
3d8a870da4 | ||
|
|
45bc96e8a3 | ||
|
|
1406429fdf | ||
|
|
1999c77481 | ||
|
|
059970f024 | ||
|
|
87257b08bf | ||
|
|
4aef35e81d | ||
|
|
313783e294 | ||
|
|
74c53203fc | ||
|
|
f1d5262ca6 | ||
|
|
abbc92bc20 | ||
|
|
aa814e24bc | ||
|
|
79ef8d0675 | ||
|
|
c92fe5018e | ||
|
|
368bd664db | ||
|
|
772b1a6fa1 | ||
|
|
ad5b10f4e0 | ||
|
|
fed464a41c | ||
|
|
321cd93e61 | ||
|
|
1eb70dfec9 | ||
|
|
64276fcefb | ||
|
|
5d682eda33 | ||
|
|
7dbd1a96fc | ||
|
|
97fa93f587 | ||
|
|
e6794c6c7f | ||
|
|
fa0ec972ce | ||
|
|
b5ecb184cf | ||
|
|
4173ba34e5 | ||
|
|
9550bb9784 | ||
|
|
548a4d06d2 | ||
|
|
2aaa75261e | ||
|
|
898e0902d4 | ||
|
|
b083aa03f9 | ||
|
|
e46572f2ab | ||
|
|
6f6f50cfb1 | ||
|
|
5c6688e945 | ||
|
|
e2d3153dcc | ||
|
|
76b9b5d109 | ||
|
|
016b2baaad | ||
|
|
5930fb0551 | ||
|
|
feb5ff1747 | ||
|
|
a14ab0bd3c | ||
|
|
4eaf8b9efb | ||
|
|
e4f3ec7a80 | ||
|
|
9acd934c7f | ||
|
|
ed6cf35765 | ||
|
|
711693e55e | ||
|
|
20859a4660 | ||
|
|
6e4ec1f0e9 | ||
|
|
57824a2723 | ||
|
|
5afdaeb68f | ||
|
|
160eb908a3 | ||
|
|
28f57431d6 | ||
|
|
e946b0d923 | ||
|
|
2d986c1dd8 | ||
|
|
2978d10834 | ||
|
|
6c1b011636 | ||
|
|
c96d6b791c | ||
|
|
45f49d57c1 | ||
|
|
e448c339db | ||
|
|
805718d78d | ||
|
|
be94cd77fc | ||
|
|
6545390309 | ||
|
|
756e70a24e | ||
|
|
865d4c8417 | ||
|
|
383938155f | ||
|
|
714d795e90 | ||
|
|
c9a4bfec4c | ||
|
|
91a71881b7 | ||
|
|
2f66714e32 | ||
|
|
c70992a54c | ||
|
|
86f6eeea19 | ||
|
|
af56dc165f | ||
|
|
109a543393 | ||
|
|
d71b88d8a1 | ||
|
|
8c8ff7d0ae | ||
|
|
bec06eb80c | ||
|
|
ee44f3b85e | ||
|
|
d55af68fd7 | ||
|
|
cd1fb5e4ae | ||
|
|
a485c4e6a1 | ||
|
|
c1a2474bb0 | ||
|
|
c4f935cd41 | ||
|
|
ea70e9f2eb | ||
|
|
0aa298e32e | ||
|
|
843ed98f76 | ||
|
|
26a93e1e7d | ||
|
|
8c8988f333 | ||
|
|
137ed10eb0 | ||
|
|
badebd51c1 | ||
|
|
3e0c18440b | ||
|
|
e974cc4c4b | ||
|
|
ec7181a8af | ||
|
|
970492487a | ||
|
|
0678615de5 | ||
|
|
9e2b5a1549 | ||
|
|
07d0b20c60 | ||
|
|
756626ef5b | ||
|
|
d921fb8a3e | ||
|
|
ad375dcea0 | ||
|
|
a20a3a8354 | ||
|
|
9b632de28a | ||
|
|
0f8ba10a2e | ||
|
|
8c034eb7f5 | ||
|
|
5e7f84ce71 | ||
|
|
839ca134bc | ||
|
|
613722df0f | ||
|
|
77374aa016 | ||
|
|
d7af50c184 | ||
|
|
605bc8229c | ||
|
|
65d06eaa6a | ||
|
|
f3f31a9698 | ||
|
|
458e088170 | ||
|
|
49735a1c93 | ||
|
|
86ab09f7ec | ||
|
|
19c736766e | ||
|
|
b8718c3b4d | ||
|
|
179b7781c2 | ||
|
|
54d8a685cc | ||
|
|
eb5fb78114 | ||
|
|
eaa9023049 | ||
|
|
50b16972fe | ||
|
|
814d3a4b14 | ||
|
|
58d9d2c3a6 | ||
|
|
eb92b8315d | ||
|
|
8792c442d7 | ||
|
|
a23e36bec9 | ||
|
|
3c75056c6e | ||
|
|
da1253c21f | ||
|
|
b8d7f4ff67 | ||
|
|
438480f128 | ||
|
|
7ba80e47e7 | ||
|
|
1920f07052 | ||
|
|
09adc76e3d | ||
|
|
690f9cf5e1 | ||
|
|
fca0621098 | ||
|
|
95936bb508 | ||
|
|
3b889e09f7 | ||
|
|
600da0a25c | ||
|
|
96bfd855f8 | ||
|
|
8416508d79 | ||
|
|
f1bfebc9e2 | ||
|
|
1263024be5 | ||
|
|
23869cab0d | ||
|
|
24e76c7a13 | ||
|
|
6e7e553963 | ||
|
|
e7c62fc9d9 | ||
|
|
8703539bf0 | ||
|
|
f6ad19b1a7 | ||
|
|
4590963e88 | ||
|
|
883cbe3a8d | ||
|
|
b560e9deb8 | ||
|
|
e2f646dea5 | ||
|
|
b93dd95125 | ||
|
|
ceac7bc2e8 | ||
|
|
1d43bd8b1e | ||
|
|
9f3af8507e | ||
|
|
d165e4b5ad | ||
|
|
d5cba6e358 | ||
|
|
bd1641c9a2 | ||
|
|
71de44daba | ||
|
|
1b275bd6a7 | ||
|
|
866d9ecb29 | ||
|
|
5bb4cffd49 | ||
|
|
a319952be1 | ||
|
|
662bd641b8 | ||
|
|
dcf4f58e81 | ||
|
|
e4013acc54 | ||
|
|
df0f834227 | ||
|
|
5e592c9a0d | ||
|
|
c13b71056e | ||
|
|
900e0d3371 | ||
|
|
a280b58c10 | ||
|
|
9ebbf255f7 | ||
|
|
39cbf27904 | ||
|
|
28d0e76370 | ||
|
|
0431c5bb26 | ||
|
|
1c4f425f17 | ||
|
|
3ecbeacb46 | ||
|
|
32353eeb07 | ||
|
|
7023896d7c | ||
|
|
570cd9f10c | ||
|
|
0b4989d0b2 | ||
|
|
f67867f974 | ||
|
|
2454694de3 | ||
|
|
6151f23e15 | ||
|
|
1eb40b1f33 | ||
|
|
fa79856a4b | ||
|
|
fce7ee21a2 | ||
|
|
c75743d9d3 | ||
|
|
355f48860c | ||
|
|
51df778420 | ||
|
|
bf51a11dcf | ||
|
|
9d0b8b6a93 | ||
|
|
0e3154a553 | ||
|
|
9141c8a920 | ||
|
|
4c3f242ae2 | ||
|
|
0a80166b81 | ||
|
|
6b562358fc | ||
|
|
3d483921fe | ||
|
|
2abb798a22 | ||
|
|
fa12c5d87b | ||
|
|
7fec884864 | ||
|
|
d8fa545955 | ||
|
|
1b7b3aa668 | ||
|
|
0c7def88b5 | ||
|
|
48b97f3d8e | ||
|
|
fe497adf0c | ||
|
|
8419545a3a | ||
|
|
b892504141 | ||
|
|
bd24013a26 | ||
|
|
fa2f178b8a | ||
|
|
5b733b348d | ||
|
|
6770b9fd89 | ||
|
|
5c0fca1d96 | ||
|
|
909286cc03 | ||
|
|
276bcf788c | ||
|
|
cc11619c67 | ||
|
|
41495e30c7 | ||
|
|
50b0198423 | ||
|
|
853d787923 | ||
|
|
e5fddb97a7 | ||
|
|
4ac87146b1 | ||
|
|
2ecc86275f | ||
|
|
f42314956f | ||
|
|
98739b071c | ||
|
|
d8effcbc5c | ||
|
|
cce30d7546 | ||
|
|
33c5e5c98d | ||
|
|
2b0dfb50c8 | ||
|
|
799d0aa7a6 | ||
|
|
be464fba69 | ||
|
|
a95bd3873d | ||
|
|
71baff6015 | ||
|
|
f18ea03b07 | ||
|
|
65ee2610b6 | ||
|
|
47bcd9df8d | ||
|
|
781a2d6b0a | ||
|
|
a974d9104f | ||
|
|
b1c2f822c9 | ||
|
|
382b1b0cbb | ||
|
|
6d00f6fcbd | ||
|
|
8ed856d322 | ||
|
|
77c6fed305 | ||
|
|
c0294423f4 | ||
|
|
e05eae6a49 | ||
|
|
ed22232dee | ||
|
|
dacc1fa9ca | ||
|
|
0b2d015fc4 | ||
|
|
d8a20d9c21 | ||
|
|
188f7cd172 | ||
|
|
12af94655b | ||
|
|
4bb99763b1 | ||
|
|
9dd11ef74f | ||
|
|
fa5d14e413 | ||
|
|
df1efcfa7f | ||
|
|
fc1d7afae9 | ||
|
|
a2d1b4eeaf | ||
|
|
94f3765fcf | ||
|
|
31ed654dd0 | ||
|
|
cbaf172c63 | ||
|
|
c557cf6d18 | ||
|
|
9632980664 | ||
|
|
6457775a0f | ||
|
|
717594ef13 | ||
|
|
e4e1385eed | ||
|
|
13cc722110 | ||
|
|
7fdfee10a5 | ||
|
|
8d14232538 | ||
|
|
99f8ce3894 | ||
|
|
d05c3ee495 | ||
|
|
596718d93b | ||
|
|
a6ce817ca5 | ||
|
|
dcc4e4c36a | ||
|
|
80fd7e03cf | ||
|
|
1acb683f80 | ||
|
|
9fd7306648 | ||
|
|
9d3ca01dd0 | ||
|
|
6cd086f25f | ||
|
|
376603efb2 | ||
|
|
30d2b30217 | ||
|
|
9b186c89a8 | ||
|
|
fc3120a877 | ||
|
|
6d361e3ffb | ||
|
|
03f295b397 | ||
|
|
403d44ea78 | ||
|
|
a1caaa08d7 | ||
|
|
d192142eb9 | ||
|
|
beb7a24275 | ||
|
|
dee70033b8 | ||
|
|
189059e07e | ||
|
|
475b838c8b | ||
|
|
d6e4f9b9e3 | ||
|
|
1062e1b485 | ||
|
|
8e55ea5125 | ||
|
|
9732c5ac60 | ||
|
|
b8996f0e62 | ||
|
|
9809b5bc83 | ||
|
|
c213f5daf4 | ||
|
|
d1cf613461 | ||
|
|
d281a0fa9f | ||
|
|
9511c4677d | ||
|
|
c25a8cb89b | ||
|
|
d3261cab59 | ||
|
|
40c1af9202 | ||
|
|
b83e848699 | ||
|
|
2676cf2b06 | ||
|
|
56f43904c1 | ||
|
|
7c5894ee75 | ||
|
|
27a81b2112 | ||
|
|
7f72c60ae4 | ||
|
|
065fcdc828 | ||
|
|
9ba3039106 | ||
|
|
840fd08ab2 | ||
|
|
a8a792e3c0 | ||
|
|
8970833b14 | ||
|
|
5231c8b6bb | ||
|
|
1568bad01e | ||
|
|
357417e6d5 | ||
|
|
893d64fd31 | ||
|
|
2799303f6c | ||
|
|
a2e2942aad | ||
|
|
b7d6a0a3c3 | ||
|
|
bdcf11bca6 | ||
|
|
d939b7c45c | ||
|
|
d5219aa3c5 | ||
|
|
aa7169b93b | ||
|
|
89c356cb77 | ||
|
|
088164ef2a | ||
|
|
1e2fd10047 | ||
|
|
30a9c23a34 | ||
|
|
da29f7940d | ||
|
|
40007c80da | ||
|
|
e246ae8864 | ||
|
|
aaa54492c1 | ||
|
|
73fa81af74 | ||
|
|
a21d5c85a3 | ||
|
|
02bd7ebffd | ||
|
|
43a689d369 | ||
|
|
810236c26c | ||
|
|
33e00c3ab3 | ||
|
|
5c9a3b3b21 | ||
|
|
dc44d2e2c4 | ||
|
|
1c294ddeb6 | ||
|
|
c47a54df91 | ||
|
|
624a2142bf | ||
|
|
7b3148c0b4 | ||
|
|
cf36b2d9ba | ||
|
|
5fa9062ed9 | ||
|
|
267cd42fb6 | ||
|
|
497270dc55 | ||
|
|
aa87ff37ea | ||
|
|
0fe1e6d5a3 | ||
|
|
54b5db9842 | ||
|
|
c31bac638c | ||
|
|
203db14e9b | ||
|
|
06bb369bcb | ||
|
|
209791734c | ||
|
|
fbc6f3b536 | ||
|
|
22faa192b9 | ||
|
|
3a9244c69f | ||
|
|
8070b142b8 | ||
|
|
f0aa199611 | ||
|
|
5956940908 | ||
|
|
ea79c47c0e | ||
|
|
7e26dac198 | ||
|
|
bb32178bda | ||
|
|
5ca9375f26 | ||
|
|
e7d8b69b53 | ||
|
|
c6ad5b8109 | ||
|
|
8003c73877 | ||
|
|
b532a4c304 | ||
|
|
29d9721d1a | ||
|
|
f0f6f93e28 | ||
|
|
91d8e38208 | ||
|
|
ccdab8fb24 | ||
|
|
592c877cfc | ||
|
|
fee7ecf619 | ||
|
|
e8f61fb9d0 | ||
|
|
781174e6e9 | ||
|
|
ba76745f43 | ||
|
|
5615a80fc5 | ||
|
|
375e83d068 | ||
|
|
666a9a3557 | ||
|
|
1dde3e0044 | ||
|
|
8b87bf8b40 | ||
|
|
94715b6fa8 | ||
|
|
27a16744f2 | ||
|
|
75ce45588b | ||
|
|
b3e1a66a21 | ||
|
|
c94db7b8a0 | ||
|
|
a67326d358 | ||
|
|
e88b5d3691 | ||
|
|
fdb049bee3 | ||
|
|
ae144e0a39 | ||
|
|
85f5e7fc45 | ||
|
|
b51ebaf658 | ||
|
|
f232166db5 | ||
|
|
4143edd251 | ||
|
|
f9522b3913 | ||
|
|
e7848923ec | ||
|
|
eb12bbc640 | ||
|
|
6216409f96 | ||
|
|
6fe256ec2c | ||
|
|
92f52a3fc5 | ||
|
|
2bc915ed04 | ||
|
|
42b0e6eace | ||
|
|
1ca288028c | ||
|
|
a2cda8377f | ||
|
|
8ac82e7101 | ||
|
|
5429e9b6aa | ||
|
|
c743647a52 | ||
|
|
475578757f | ||
|
|
5e0ac5b56c | ||
|
|
d380736043 | ||
|
|
71d6fcc31b | ||
|
|
680602b7eb | ||
|
|
9c68ce12ec | ||
|
|
eef0b7770b | ||
|
|
2fab4817fe | ||
|
|
5ea9340def | ||
|
|
db3ad598c5 | ||
|
|
9830ee0d89 | ||
|
|
2407ecd2bf | ||
|
|
ee576ab279 | ||
|
|
ae1b8b5585 | ||
|
|
e48fb2874b | ||
|
|
7d3bf70a76 | ||
|
|
91d0785b1c | ||
|
|
5dfef7ede7 | ||
|
|
0aaf39d539 | ||
|
|
c0a6b2dd8b | ||
|
|
87a02b4c46 | ||
|
|
14fc1f510e | ||
|
|
ad0766fe3e | ||
|
|
1e69c00538 | ||
|
|
bf15380733 | ||
|
|
a993c2141d | ||
|
|
deed65095f | ||
|
|
61294af824 | ||
|
|
106fb1fe9b | ||
|
|
6b433a0de4 | ||
|
|
4de5a41720 | ||
|
|
dc5876c727 | ||
|
|
5f6b5bfd7f | ||
|
|
e5fdc371f6 | ||
|
|
452f18d8bc | ||
|
|
04b8b3eedf | ||
|
|
426da7c443 | ||
|
|
a5116a372c | ||
|
|
015f7acbc5 | ||
|
|
34e125357f | ||
|
|
c0efc7b370 | ||
|
|
48ada0cd5d | ||
|
|
aa5803fbbb | ||
|
|
8bee55e23b | ||
|
|
6f1ffccd4f | ||
|
|
13544c0f44 | ||
|
|
184c8eb5a9 | ||
|
|
acc27dabc9 | ||
|
|
d032c3cfea | ||
|
|
006cf488e8 | ||
|
|
63dd44dc86 | ||
|
|
f602794f07 | ||
|
|
f3063a8e16 | ||
|
|
f67a13a5fb | ||
|
|
5dff4d15e8 | ||
|
|
943892ddb2 | ||
|
|
31beaf1017 | ||
|
|
9664c8814c | ||
|
|
7bb99f2bd5 | ||
|
|
4a08ee89ee | ||
|
|
0197c6e211 | ||
|
|
6da8dcfc8e | ||
|
|
dc4dc42ec5 | ||
|
|
db29f53295 | ||
|
|
bc6dc0bafc | ||
|
|
36b0c1ba08 | ||
|
|
dc0ecbb2c2 | ||
|
|
aca18da21e | ||
|
|
f7d1bbe5b6 | ||
|
|
296a0935d1 | ||
|
|
0049436cd4 | ||
|
|
f4500c6ba4 | ||
|
|
c830c71e28 | ||
|
|
ea18162391 | ||
|
|
4dd7b890d4 | ||
|
|
4d20051793 | ||
|
|
88683f6e2c | ||
|
|
d6229b3937 | ||
|
|
e523e1e4c3 | ||
|
|
6d969c5845 | ||
|
|
b94d393924 | ||
|
|
e009ecb704 | ||
|
|
03e861d048 | ||
|
|
8e97c93de0 | ||
|
|
7176d89e48 | ||
|
|
e6f8c00bbe | ||
|
|
c873aa3930 | ||
|
|
800a5f232f | ||
|
|
8d1b6f079a | ||
|
|
7fcd4fd7c5 | ||
|
|
9203df6af1 | ||
|
|
d6d77a9ee4 | ||
|
|
155c6178cd | ||
|
|
3d7b74746c | ||
|
|
410d7af6b6 | ||
|
|
1b99c3148f | ||
|
|
6cf94a9797 | ||
|
|
a159f31945 | ||
|
|
dc3acda4ed | ||
|
|
ead1bdc532 | ||
|
|
2013c69c4d | ||
|
|
6615b111d9 | ||
|
|
50e62cdcd9 | ||
|
|
64d7f53357 | ||
|
|
32b5b39f2d | ||
|
|
02697459b8 | ||
|
|
7eadc90558 | ||
|
|
95c02962ba | ||
|
|
75540f9728 | ||
|
|
580f372059 | ||
|
|
1c276ef88f | ||
|
|
14a9788eb1 | ||
|
|
dbcaa50c69 | ||
|
|
362eb81701 | ||
|
|
a36c6d5778 | ||
|
|
82935cae9f | ||
|
|
455794da2c | ||
|
|
55259b3c8b | ||
|
|
328c8c725d | ||
|
|
e30c7ef4e9 | ||
|
|
02da85c9ec | ||
|
|
878e230782 | ||
|
|
1da849ac48 |
5
.flake8
@@ -1,5 +0,0 @@
|
|||||||
[flake8]
|
|
||||||
max-line-length = 88
|
|
||||||
select = C,E,F,W,B,B9
|
|
||||||
ignore = E203, E501, W503
|
|
||||||
exclude = __init__.py
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
name: Question or Problem
|
|
||||||
description: Ask a question or ask about a problem
|
|
||||||
labels: [question]
|
labels: [question]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
@@ -9,9 +7,9 @@ body:
|
|||||||
|
|
||||||
Please follow these instructions, fill every question, and do every step. 🙏
|
Please follow these instructions, fill every question, and do every step. 🙏
|
||||||
|
|
||||||
I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
|
I'm asking this because answering questions and solving problems in GitHub is what consumes most of the time.
|
||||||
|
|
||||||
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
|
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling questions.
|
||||||
|
|
||||||
All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others.
|
All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others.
|
||||||
|
|
||||||
@@ -21,16 +19,16 @@ body:
|
|||||||
|
|
||||||
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
|
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
|
||||||
|
|
||||||
As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
|
As there are too many questions, I'll have to discard and close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: checks
|
id: checks
|
||||||
attributes:
|
attributes:
|
||||||
label: First Check
|
label: First Check
|
||||||
description: Please confirm and check all the following options.
|
description: Please confirm and check all the following options.
|
||||||
options:
|
options:
|
||||||
- label: I added a very descriptive title to this issue.
|
- label: I added a very descriptive title here.
|
||||||
required: true
|
required: true
|
||||||
- label: I used the GitHub search to find a similar issue and didn't find it.
|
- label: I used the GitHub search to find a similar question and didn't find it.
|
||||||
required: true
|
required: true
|
||||||
- label: I searched the SQLModel documentation, with the integrated search.
|
- label: I searched the SQLModel documentation, with the integrated search.
|
||||||
required: true
|
required: true
|
||||||
@@ -51,7 +49,7 @@ body:
|
|||||||
|
|
||||||
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
|
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
|
||||||
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
|
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
|
||||||
* Implement a Pull Request for a confirmed bug.
|
* Review one Pull Request by downloading the code and following all the review process](https://sqlmodel.tiangolo.com/help/#review-pull-requests).
|
||||||
|
|
||||||
options:
|
options:
|
||||||
- label: I commit to help with one of those options 👆
|
- label: I commit to help with one of those options 👆
|
||||||
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -2,3 +2,12 @@ blank_issues_enabled: false
|
|||||||
contact_links:
|
contact_links:
|
||||||
- name: Security Contact
|
- name: Security Contact
|
||||||
about: Please report security vulnerabilities to security@tiangolo.com
|
about: Please report security vulnerabilities to security@tiangolo.com
|
||||||
|
- name: Question or Problem
|
||||||
|
about: Ask a question or ask about a problem in GitHub Discussions.
|
||||||
|
url: https://github.com/fastapi/sqlmodel/discussions/categories/questions
|
||||||
|
- name: Feature Request
|
||||||
|
about: To suggest an idea or ask about a feature, please start with a question saying what you would like to achieve. There might be a way to do it already.
|
||||||
|
url: https://github.com/fastapi/sqlmodel/discussions/categories/questions
|
||||||
|
- name: Show and tell
|
||||||
|
about: Show what you built with SQLModel or to be used with SQLModel.
|
||||||
|
url: https://github.com/fastapi/sqlmodel/discussions/categories/show-and-tell
|
||||||
|
|||||||
214
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -1,214 +0,0 @@
|
|||||||
name: Feature Request
|
|
||||||
description: Suggest an idea or ask for a feature that you would like to have in SQLModel
|
|
||||||
labels: [enhancement]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Thanks for your interest in SQLModel! 🚀
|
|
||||||
|
|
||||||
Please follow these instructions, fill every question, and do every step. 🙏
|
|
||||||
|
|
||||||
I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
|
|
||||||
|
|
||||||
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
|
|
||||||
|
|
||||||
All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others.
|
|
||||||
|
|
||||||
If more SQLModel users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
|
|
||||||
|
|
||||||
By asking questions in a structured way (following this) it will be much easier to help you.
|
|
||||||
|
|
||||||
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
|
|
||||||
|
|
||||||
As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
|
|
||||||
- type: checkboxes
|
|
||||||
id: checks
|
|
||||||
attributes:
|
|
||||||
label: First Check
|
|
||||||
description: Please confirm and check all the following options.
|
|
||||||
options:
|
|
||||||
- label: I added a very descriptive title to this issue.
|
|
||||||
required: true
|
|
||||||
- label: I used the GitHub search to find a similar issue and didn't find it.
|
|
||||||
required: true
|
|
||||||
- label: I searched the SQLModel documentation, with the integrated search.
|
|
||||||
required: true
|
|
||||||
- label: I already searched in Google "How to X in SQLModel" and didn't find any information.
|
|
||||||
required: true
|
|
||||||
- label: I already read and followed all the tutorial in the docs and didn't find an answer.
|
|
||||||
required: true
|
|
||||||
- label: I already checked if it is not related to SQLModel but to [Pydantic](https://github.com/samuelcolvin/pydantic).
|
|
||||||
required: true
|
|
||||||
- label: I already checked if it is not related to SQLModel but to [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy).
|
|
||||||
required: true
|
|
||||||
- type: checkboxes
|
|
||||||
id: help
|
|
||||||
attributes:
|
|
||||||
label: Commit to Help
|
|
||||||
description: |
|
|
||||||
After submitting this, I commit to one of:
|
|
||||||
|
|
||||||
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
|
|
||||||
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
|
|
||||||
* Implement a Pull Request for a confirmed bug.
|
|
||||||
|
|
||||||
options:
|
|
||||||
- label: I commit to help with one of those options 👆
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: example
|
|
||||||
attributes:
|
|
||||||
label: Example Code
|
|
||||||
description: |
|
|
||||||
Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case.
|
|
||||||
|
|
||||||
If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you.
|
|
||||||
|
|
||||||
placeholder: |
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from sqlmodel import Field, Session, SQLModel, create_engine
|
|
||||||
|
|
||||||
|
|
||||||
class Hero(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
name: str
|
|
||||||
secret_name: str
|
|
||||||
age: Optional[int] = None
|
|
||||||
|
|
||||||
|
|
||||||
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
|
|
||||||
|
|
||||||
engine = create_engine("sqlite:///database.db")
|
|
||||||
|
|
||||||
|
|
||||||
SQLModel.metadata.create_all(engine)
|
|
||||||
|
|
||||||
with Session(engine) as session:
|
|
||||||
session.add(hero_1)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(hero_1)
|
|
||||||
print(hero_1)
|
|
||||||
render: python
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: description
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
description: |
|
|
||||||
What is your feature request?
|
|
||||||
|
|
||||||
Write a short description telling me what you are trying to solve and what you are currently doing.
|
|
||||||
placeholder: |
|
|
||||||
* Create a Hero model.
|
|
||||||
* Create a Hero instance.
|
|
||||||
* Save it to a SQLite database.
|
|
||||||
* I would like it to also automatically send me an email with all the SQL code executed.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: wanted-solution
|
|
||||||
attributes:
|
|
||||||
label: Wanted Solution
|
|
||||||
description: |
|
|
||||||
Tell me what's the solution you would like.
|
|
||||||
placeholder: |
|
|
||||||
I would like it to have a `send_email` configuration that defaults to `False`, and can be set to `True` to send me an email.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: wanted-code
|
|
||||||
attributes:
|
|
||||||
label: Wanted Code
|
|
||||||
description: Show me an example of how you would want the code to look like.
|
|
||||||
placeholder: |
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from sqlmodel import Field, Session, SQLModel, create_engine
|
|
||||||
|
|
||||||
|
|
||||||
class Hero(SQLModel, table=True, send_email=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
name: str
|
|
||||||
secret_name: str
|
|
||||||
age: Optional[int] = None
|
|
||||||
|
|
||||||
|
|
||||||
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
|
|
||||||
|
|
||||||
engine = create_engine("sqlite:///database.db")
|
|
||||||
|
|
||||||
|
|
||||||
SQLModel.metadata.create_all(engine)
|
|
||||||
|
|
||||||
with Session(engine) as session:
|
|
||||||
session.add(hero_1)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(hero_1)
|
|
||||||
print(hero_1)
|
|
||||||
|
|
||||||
|
|
||||||
render: python
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: alternatives
|
|
||||||
attributes:
|
|
||||||
label: Alternatives
|
|
||||||
description: |
|
|
||||||
Tell me about alternatives you've considered.
|
|
||||||
placeholder: |
|
|
||||||
To hire someone to look at the logs, write the SQL in paper, and then send me a letter by post with it.
|
|
||||||
- type: dropdown
|
|
||||||
id: os
|
|
||||||
attributes:
|
|
||||||
label: Operating System
|
|
||||||
description: What operating system are you on?
|
|
||||||
multiple: true
|
|
||||||
options:
|
|
||||||
- Linux
|
|
||||||
- Windows
|
|
||||||
- macOS
|
|
||||||
- Other
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: os-details
|
|
||||||
attributes:
|
|
||||||
label: Operating System Details
|
|
||||||
description: You can add more details about your operating system here, in particular if you chose "Other".
|
|
||||||
- type: input
|
|
||||||
id: sqlmodel-version
|
|
||||||
attributes:
|
|
||||||
label: SQLModel Version
|
|
||||||
description: |
|
|
||||||
What SQLModel version are you using?
|
|
||||||
|
|
||||||
You can find the SQLModel version with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python -c "import sqlmodel; print(sqlmodel.__version__)"
|
|
||||||
```
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: python-version
|
|
||||||
attributes:
|
|
||||||
label: Python Version
|
|
||||||
description: |
|
|
||||||
What Python version are you using?
|
|
||||||
|
|
||||||
You can find the Python version with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python --version
|
|
||||||
```
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: context
|
|
||||||
attributes:
|
|
||||||
label: Additional Context
|
|
||||||
description: Add any additional context information or screenshots you think are useful.
|
|
||||||
22
.github/ISSUE_TEMPLATE/privileged.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: Privileged
|
||||||
|
description: You are @tiangolo or he asked you directly to create an issue here. If not, check the other options. 👇
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for your interest in SQLModel! 🚀
|
||||||
|
|
||||||
|
If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/fastapi/sqlmodel/discussions/categories/questions) instead.
|
||||||
|
- type: checkboxes
|
||||||
|
id: privileged
|
||||||
|
attributes:
|
||||||
|
label: Privileged issue
|
||||||
|
description: Confirm that you are allowed to create an issue here.
|
||||||
|
options:
|
||||||
|
- label: I'm @tiangolo or he asked me directly to create an issue here.
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: content
|
||||||
|
attributes:
|
||||||
|
label: Issue Content
|
||||||
|
description: Add the content of the issue here.
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
FROM python:3.7
|
|
||||||
|
|
||||||
RUN pip install httpx "pydantic==1.5.1" pygithub
|
|
||||||
|
|
||||||
COPY ./app /app
|
|
||||||
|
|
||||||
CMD ["python", "/app/main.py"]
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
name: Comment Docs Preview in PR
|
|
||||||
description: Comment with the docs URL preview in the PR
|
|
||||||
author: Sebastián Ramírez <tiangolo@gmail.com>
|
|
||||||
inputs:
|
|
||||||
token:
|
|
||||||
description: Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}
|
|
||||||
required: true
|
|
||||||
deploy_url:
|
|
||||||
description: The deployment URL to comment in the PR
|
|
||||||
required: true
|
|
||||||
runs:
|
|
||||||
using: docker
|
|
||||||
image: Dockerfile
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import logging
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
from github import Github
|
|
||||||
from github.PullRequest import PullRequest
|
|
||||||
from pydantic import BaseModel, BaseSettings, SecretStr, ValidationError
|
|
||||||
|
|
||||||
github_api = "https://api.github.com"
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
|
||||||
github_repository: str
|
|
||||||
github_event_path: Path
|
|
||||||
github_event_name: Optional[str] = None
|
|
||||||
input_token: SecretStr
|
|
||||||
input_deploy_url: str
|
|
||||||
|
|
||||||
|
|
||||||
class PartialGithubEventHeadCommit(BaseModel):
|
|
||||||
id: str
|
|
||||||
|
|
||||||
|
|
||||||
class PartialGithubEventWorkflowRun(BaseModel):
|
|
||||||
head_commit: PartialGithubEventHeadCommit
|
|
||||||
|
|
||||||
|
|
||||||
class PartialGithubEvent(BaseModel):
|
|
||||||
workflow_run: PartialGithubEventWorkflowRun
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
settings = Settings()
|
|
||||||
logging.info(f"Using config: {settings.json()}")
|
|
||||||
g = Github(settings.input_token.get_secret_value())
|
|
||||||
repo = g.get_repo(settings.github_repository)
|
|
||||||
try:
|
|
||||||
event = PartialGithubEvent.parse_file(settings.github_event_path)
|
|
||||||
except ValidationError as e:
|
|
||||||
logging.error(f"Error parsing event file: {e.errors()}")
|
|
||||||
sys.exit(0)
|
|
||||||
use_pr: Optional[PullRequest] = None
|
|
||||||
for pr in repo.get_pulls():
|
|
||||||
if pr.head.sha == event.workflow_run.head_commit.id:
|
|
||||||
use_pr = pr
|
|
||||||
break
|
|
||||||
if not use_pr:
|
|
||||||
logging.error(
|
|
||||||
f"No PR found for hash: {event.workflow_run.head_commit.id}"
|
|
||||||
)
|
|
||||||
sys.exit(0)
|
|
||||||
github_headers = {
|
|
||||||
"Authorization": f"token {settings.input_token.get_secret_value()}"
|
|
||||||
}
|
|
||||||
url = f"{github_api}/repos/{settings.github_repository}/issues/{use_pr.number}/comments"
|
|
||||||
logging.info(f"Using comments URL: {url}")
|
|
||||||
response = httpx.post(
|
|
||||||
url,
|
|
||||||
headers=github_headers,
|
|
||||||
json={
|
|
||||||
"body": f"📝 Docs preview for commit {use_pr.head.sha} at: {settings.input_deploy_url}"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if not (200 <= response.status_code <= 300):
|
|
||||||
logging.error(f"Error posting comment: {response.text}")
|
|
||||||
sys.exit(1)
|
|
||||||
logging.info("Finished")
|
|
||||||
7
.github/actions/watch-previews/Dockerfile
vendored
@@ -1,7 +0,0 @@
|
|||||||
FROM python:3.7
|
|
||||||
|
|
||||||
RUN pip install httpx PyGithub "pydantic==1.5.1"
|
|
||||||
|
|
||||||
COPY ./app /app
|
|
||||||
|
|
||||||
CMD ["python", "/app/main.py"]
|
|
||||||
10
.github/actions/watch-previews/action.yml
vendored
@@ -1,10 +0,0 @@
|
|||||||
name: Watch docs previews in PRs
|
|
||||||
description: Check PRs and trigger new docs deploys
|
|
||||||
author: "Sebastián Ramírez <tiangolo@gmail.com>"
|
|
||||||
inputs:
|
|
||||||
token:
|
|
||||||
description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
|
|
||||||
required: true
|
|
||||||
runs:
|
|
||||||
using: docker
|
|
||||||
image: Dockerfile
|
|
||||||
102
.github/actions/watch-previews/app/main.py
vendored
@@ -1,102 +0,0 @@
|
|||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
from github import Github
|
|
||||||
from github.NamedUser import NamedUser
|
|
||||||
from pydantic import BaseModel, BaseSettings, SecretStr
|
|
||||||
|
|
||||||
github_api = "https://api.github.com"
|
|
||||||
netlify_api = "https://api.netlify.com"
|
|
||||||
main_branch = "main"
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
|
||||||
input_token: SecretStr
|
|
||||||
github_repository: str
|
|
||||||
github_event_path: Path
|
|
||||||
github_event_name: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Artifact(BaseModel):
|
|
||||||
id: int
|
|
||||||
node_id: str
|
|
||||||
name: str
|
|
||||||
size_in_bytes: int
|
|
||||||
url: str
|
|
||||||
archive_download_url: str
|
|
||||||
expired: bool
|
|
||||||
created_at: datetime
|
|
||||||
updated_at: datetime
|
|
||||||
|
|
||||||
|
|
||||||
class ArtifactResponse(BaseModel):
|
|
||||||
total_count: int
|
|
||||||
artifacts: List[Artifact]
|
|
||||||
|
|
||||||
|
|
||||||
def get_message(commit: str) -> str:
|
|
||||||
return f"Docs preview for commit {commit} at"
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
settings = Settings()
|
|
||||||
logging.info(f"Using config: {settings.json()}")
|
|
||||||
g = Github(settings.input_token.get_secret_value())
|
|
||||||
repo = g.get_repo(settings.github_repository)
|
|
||||||
owner: NamedUser = repo.owner
|
|
||||||
headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"}
|
|
||||||
prs = list(repo.get_pulls(state="open"))
|
|
||||||
response = httpx.get(
|
|
||||||
f"{github_api}/repos/{settings.github_repository}/actions/artifacts",
|
|
||||||
headers=headers,
|
|
||||||
)
|
|
||||||
data = response.json()
|
|
||||||
artifacts_response = ArtifactResponse.parse_obj(data)
|
|
||||||
for pr in prs:
|
|
||||||
logging.info("-----")
|
|
||||||
logging.info(f"Processing PR #{pr.number}: {pr.title}")
|
|
||||||
pr_comments = list(pr.get_issue_comments())
|
|
||||||
pr_commits = list(pr.get_commits())
|
|
||||||
last_commit = pr_commits[0]
|
|
||||||
for pr_commit in pr_commits:
|
|
||||||
if pr_commit.commit.author.date > last_commit.commit.author.date:
|
|
||||||
last_commit = pr_commit
|
|
||||||
commit = last_commit.commit.sha
|
|
||||||
logging.info(f"Last commit: {commit}")
|
|
||||||
message = get_message(commit)
|
|
||||||
notified = False
|
|
||||||
for pr_comment in pr_comments:
|
|
||||||
if message in pr_comment.body:
|
|
||||||
notified = True
|
|
||||||
logging.info(f"Docs preview was notified: {notified}")
|
|
||||||
if not notified:
|
|
||||||
artifact_name = f"docs-zip-{commit}"
|
|
||||||
use_artifact: Optional[Artifact] = None
|
|
||||||
for artifact in artifacts_response.artifacts:
|
|
||||||
if artifact.name == artifact_name:
|
|
||||||
use_artifact = artifact
|
|
||||||
break
|
|
||||||
if not use_artifact:
|
|
||||||
logging.info("Artifact not available")
|
|
||||||
else:
|
|
||||||
logging.info(f"Existing artifact: {use_artifact.name}")
|
|
||||||
response = httpx.post(
|
|
||||||
f"{github_api}/repos/{settings.github_repository}/actions/workflows/preview-docs.yml/dispatches",
|
|
||||||
headers=headers,
|
|
||||||
json={
|
|
||||||
"ref": main_branch,
|
|
||||||
"inputs": {
|
|
||||||
"pr": f"{pr.number}",
|
|
||||||
"name": artifact_name,
|
|
||||||
"commit": commit,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
logging.info(
|
|
||||||
f"Trigger sent, response status: {response.status_code} - content: {response.content}"
|
|
||||||
)
|
|
||||||
logging.info("Finished")
|
|
||||||
10
.github/dependabot.yml
vendored
@@ -1,6 +1,16 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
|
# GitHub Actions
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
commit-message:
|
||||||
|
prefix: ⬆
|
||||||
|
# Python
|
||||||
- package-ecosystem: "pip"
|
- package-ecosystem: "pip"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
commit-message:
|
||||||
|
prefix: ⬆
|
||||||
|
|||||||
24
.github/labeler.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
docs:
|
||||||
|
- all:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- docs/**
|
||||||
|
- docs_src/**
|
||||||
|
- all-globs-to-all-files:
|
||||||
|
- '!sqlmodel/**'
|
||||||
|
- '!pyproject.toml'
|
||||||
|
|
||||||
|
internal:
|
||||||
|
- all:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- .github/**
|
||||||
|
- scripts/**
|
||||||
|
- .gitignore
|
||||||
|
- .pre-commit-config.yaml
|
||||||
|
- pdm_build.py
|
||||||
|
- requirements*.txt
|
||||||
|
- all-globs-to-all-files:
|
||||||
|
- '!docs/**'
|
||||||
|
- '!sqlmodel/**'
|
||||||
|
- '!pyproject.toml'
|
||||||
18
.github/workflows/add-to-project.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: Add to Project
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
issues:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
add-to-project:
|
||||||
|
name: Add to project
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/add-to-project@v1.0.2
|
||||||
|
with:
|
||||||
|
project-url: https://github.com/orgs/fastapi/projects/2
|
||||||
|
github-token: ${{ secrets.PROJECTS_TOKEN }}
|
||||||
140
.github/workflows/build-docs.yml
vendored
@@ -1,71 +1,101 @@
|
|||||||
name: Build Docs
|
name: Build Docs
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize]
|
types:
|
||||||
workflow_dispatch:
|
- opened
|
||||||
inputs:
|
- synchronize
|
||||||
debug_enabled:
|
|
||||||
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
env:
|
||||||
required: false
|
UV_SYSTEM_PYTHON: 1
|
||||||
default: false
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
changes:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Required permissions
|
||||||
|
permissions:
|
||||||
|
pull-requests: read
|
||||||
|
# Set job outputs to values from filter step
|
||||||
|
outputs:
|
||||||
|
docs: ${{ steps.filter.outputs.docs }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
# For pull requests it's not necessary to checkout the code but for the main branch it is
|
||||||
|
- uses: dorny/paths-filter@v3
|
||||||
|
id: filter
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
docs:
|
||||||
|
- README.md
|
||||||
|
- docs/**
|
||||||
|
- docs_src/**
|
||||||
|
- requirements-docs.txt
|
||||||
|
- requirements-docs-insiders.txt
|
||||||
|
- pyproject.toml
|
||||||
|
- mkdocs.yml
|
||||||
|
- mkdocs.insiders.yml
|
||||||
|
- mkdocs.maybe-insiders.yml
|
||||||
|
- mkdocs.no-insiders.yml
|
||||||
|
- .github/workflows/build-docs.yml
|
||||||
|
- .github/workflows/deploy-docs.yml
|
||||||
|
- data/**
|
||||||
|
|
||||||
build-docs:
|
build-docs:
|
||||||
runs-on: ubuntu-20.04
|
needs:
|
||||||
|
- changes
|
||||||
|
if: ${{ needs.changes.outputs.docs == 'true' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Dump GitHub context
|
- name: Dump GitHub context
|
||||||
env:
|
env:
|
||||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||||
run: echo "$GITHUB_CONTEXT"
|
run: echo "$GITHUB_CONTEXT"
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.7"
|
python-version: "3.11"
|
||||||
# Allow debugging with tmate
|
- name: Setup uv
|
||||||
- name: Setup tmate session
|
uses: astral-sh/setup-uv@v5
|
||||||
uses: mxschmitt/action-tmate@v3
|
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
|
||||||
with:
|
with:
|
||||||
limit-access-to-actor: true
|
version: "0.4.15"
|
||||||
- uses: actions/cache@v2
|
enable-cache: true
|
||||||
id: cache
|
cache-dependency-glob: |
|
||||||
with:
|
requirements**.txt
|
||||||
path: ${{ env.pythonLocation }}
|
pyproject.toml
|
||||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-docs
|
- name: Install docs extras
|
||||||
- name: Install poetry
|
run: uv pip install -r requirements-docs.txt
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
# TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
|
|
||||||
# once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6
|
|
||||||
# Ref: https://github.com/python-poetry/poetry-core/pull/188
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
|
|
||||||
python -m pip install "poetry==1.2.0a2"
|
|
||||||
python -m poetry plugin add poetry-version-plugin
|
|
||||||
- name: Configure poetry
|
|
||||||
run: python -m poetry config virtualenvs.create false
|
|
||||||
- name: Install Dependencies
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
run: python -m poetry install
|
|
||||||
- name: Install Material for MkDocs Insiders
|
- name: Install Material for MkDocs Insiders
|
||||||
if: github.event.pull_request.head.repo.fork == false && steps.cache.outputs.cache-hit != 'true'
|
if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' )
|
||||||
run: python -m poetry run pip install git+https://${{ secrets.ACTIONS_TOKEN }}@github.com/squidfunk/mkdocs-material-insiders.git
|
run: uv pip install -r requirements-docs-insiders.txt
|
||||||
- name: Build Docs
|
|
||||||
run: python -m poetry run mkdocs build
|
|
||||||
- name: Zip docs
|
|
||||||
run: python -m poetry run bash ./scripts/zip-docs.sh
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: docs-zip
|
|
||||||
path: ./docs.zip
|
|
||||||
- name: Deploy to Netlify
|
|
||||||
uses: nwtgck/actions-netlify@v1.1.5
|
|
||||||
with:
|
|
||||||
publish-dir: './site'
|
|
||||||
production-branch: main
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
enable-commit-comment: false
|
|
||||||
env:
|
env:
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
TOKEN: ${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
key: mkdocs-cards-${{ github.ref }}
|
||||||
|
path: .cache
|
||||||
|
- name: Verify README
|
||||||
|
run: python ./scripts/docs.py verify-readme
|
||||||
|
- name: Build Docs
|
||||||
|
run: python ./scripts/docs.py build
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: docs-site
|
||||||
|
path: ./site/**
|
||||||
|
include-hidden-files: true
|
||||||
|
|
||||||
|
# https://github.com/marketplace/actions/alls-green#why
|
||||||
|
docs-all-green: # This job does nothing and is only used for the branch protection
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- build-docs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Decide whether the needed jobs succeeded or failed
|
||||||
|
uses: re-actors/alls-green@release/v1
|
||||||
|
with:
|
||||||
|
jobs: ${{ toJSON(needs) }}
|
||||||
|
allowed-skips: build-docs
|
||||||
|
|||||||
78
.github/workflows/deploy-docs.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
name: Deploy Docs
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows:
|
||||||
|
- Build Docs
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
deployments: write
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
statuses: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
UV_SYSTEM_PYTHON: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-docs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Dump GitHub context
|
||||||
|
env:
|
||||||
|
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||||
|
run: echo "$GITHUB_CONTEXT"
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.11"
|
||||||
|
- name: Setup uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
with:
|
||||||
|
version: "0.4.15"
|
||||||
|
enable-cache: true
|
||||||
|
cache-dependency-glob: |
|
||||||
|
requirements**.txt
|
||||||
|
pyproject.toml
|
||||||
|
- name: Install GitHub Actions dependencies
|
||||||
|
run: uv pip install -r requirements-github-actions.txt
|
||||||
|
- name: Deploy Docs Status Pending
|
||||||
|
run: python ./scripts/deploy_docs_status.py
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||||
|
RUN_ID: ${{ github.run_id }}
|
||||||
|
|
||||||
|
- name: Clean site
|
||||||
|
run: |
|
||||||
|
rm -rf ./site
|
||||||
|
mkdir ./site
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ./site/
|
||||||
|
pattern: docs-site
|
||||||
|
merge-multiple: true
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
- name: Deploy to Cloudflare Pages
|
||||||
|
# hashFiles returns an empty string if there are no files
|
||||||
|
if: hashFiles('./site/*')
|
||||||
|
id: deploy
|
||||||
|
env:
|
||||||
|
PROJECT_NAME: sqlmodel
|
||||||
|
BRANCH: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'main' && 'main' ) || ( github.event.workflow_run.head_sha ) }}
|
||||||
|
uses: cloudflare/wrangler-action@v3
|
||||||
|
with:
|
||||||
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
command: pages deploy ./site --project-name=${{ env.PROJECT_NAME }} --branch=${{ env.BRANCH }}
|
||||||
|
- name: Comment Deploy
|
||||||
|
run: python ./scripts/deploy_docs_status.py
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }}
|
||||||
|
COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||||
|
RUN_ID: ${{ github.run_id }}
|
||||||
|
IS_DONE: "true"
|
||||||
21
.github/workflows/issue-manager.yml
vendored
@@ -2,7 +2,7 @@ name: Issue Manager
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *"
|
- cron: "13 18 * * *"
|
||||||
issue_comment:
|
issue_comment:
|
||||||
types:
|
types:
|
||||||
- created
|
- created
|
||||||
@@ -14,11 +14,20 @@ on:
|
|||||||
- labeled
|
- labeled
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
issue-manager:
|
issue-manager:
|
||||||
|
if: github.repository_owner == 'fastapi'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: tiangolo/issue-manager@0.4.0
|
- name: Dump GitHub context
|
||||||
|
env:
|
||||||
|
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||||
|
run: echo "$GITHUB_CONTEXT"
|
||||||
|
- uses: tiangolo/issue-manager@0.5.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
config: >
|
config: >
|
||||||
@@ -26,5 +35,13 @@ jobs:
|
|||||||
"answered": {
|
"answered": {
|
||||||
"delay": 864000,
|
"delay": 864000,
|
||||||
"message": "Assuming the original need was handled, this will be automatically closed now. But feel free to add more comments or create new issues or PRs."
|
"message": "Assuming the original need was handled, this will be automatically closed now. But feel free to add more comments or create new issues or PRs."
|
||||||
|
},
|
||||||
|
"waiting": {
|
||||||
|
"delay": 2628000,
|
||||||
|
"message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR."
|
||||||
|
},
|
||||||
|
"invalid": {
|
||||||
|
"delay": 0,
|
||||||
|
"message": "This was marked as invalid and will be closed now. If this is an error, please provide additional details."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
.github/workflows/labeler.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
name: Labels
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- synchronize
|
||||||
|
- reopened
|
||||||
|
# For label-checker
|
||||||
|
- labeled
|
||||||
|
- unlabeled
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
labeler:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/labeler@v5
|
||||||
|
if: ${{ github.event.action != 'labeled' && github.event.action != 'unlabeled' }}
|
||||||
|
- run: echo "Done adding labels"
|
||||||
|
# Run this after labeler applied labels
|
||||||
|
check-labels:
|
||||||
|
needs:
|
||||||
|
- labeler
|
||||||
|
permissions:
|
||||||
|
pull-requests: read
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: docker://agilepathway/pull-request-label-checker:latest
|
||||||
|
with:
|
||||||
|
one_of: breaking,security,feature,bug,refactor,upgrade,docs,lang-all,internal
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
14
.github/workflows/latest-changes.yml
vendored
@@ -14,25 +14,27 @@ on:
|
|||||||
debug_enabled:
|
debug_enabled:
|
||||||
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
||||||
required: false
|
required: false
|
||||||
default: false
|
default: 'false'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
latest-changes:
|
latest-changes:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# To allow latest-changes to commit to the main branch
|
# To allow latest-changes to commit to the main branch
|
||||||
token: ${{ secrets.ACTIONS_TOKEN }}
|
token: ${{ secrets.SQLMODEL_LATEST_CHANGES }}
|
||||||
# Allow debugging with tmate
|
# Allow debugging with tmate
|
||||||
- name: Setup tmate session
|
- name: Setup tmate session
|
||||||
uses: mxschmitt/action-tmate@v3
|
uses: mxschmitt/action-tmate@v3
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
|
||||||
with:
|
with:
|
||||||
limit-access-to-actor: true
|
limit-access-to-actor: true
|
||||||
- uses: docker://tiangolo/latest-changes:0.0.3
|
- uses: tiangolo/latest-changes@0.3.2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
latest_changes_file: docs/release-notes.md
|
latest_changes_file: docs/release-notes.md
|
||||||
latest_changes_header: '## Latest Changes\n\n'
|
latest_changes_header: '## Latest Changes'
|
||||||
|
end_regex: '^## '
|
||||||
debug_logs: true
|
debug_logs: true
|
||||||
|
label_header_prefix: '### '
|
||||||
|
|||||||
41
.github/workflows/preview-docs.yml
vendored
@@ -1,41 +0,0 @@
|
|||||||
name: Preview Docs
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows:
|
|
||||||
- Build Docs
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
preview-docs:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Download Artifact Docs
|
|
||||||
uses: dawidd6/action-download-artifact@v2.9.0
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
workflow: build-docs.yml
|
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
|
||||||
name: docs-zip
|
|
||||||
- name: Unzip docs
|
|
||||||
run: |
|
|
||||||
rm -rf ./site
|
|
||||||
unzip docs.zip
|
|
||||||
rm -f docs.zip
|
|
||||||
- name: Deploy to Netlify
|
|
||||||
id: netlify
|
|
||||||
uses: nwtgck/actions-netlify@v1.1.5
|
|
||||||
with:
|
|
||||||
publish-dir: './site'
|
|
||||||
production-deploy: false
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
enable-commit-comment: false
|
|
||||||
env:
|
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
|
||||||
- name: Comment Deploy
|
|
||||||
uses: ./.github/actions/comment-docs-preview-in-pr
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
deploy_url: "${{ steps.netlify.outputs.deploy-url }}"
|
|
||||||
55
.github/workflows/publish.yml
vendored
@@ -9,46 +9,29 @@ on:
|
|||||||
debug_enabled:
|
debug_enabled:
|
||||||
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
||||||
required: false
|
required: false
|
||||||
default: false
|
default: 'false'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
package:
|
||||||
|
- sqlmodel
|
||||||
|
- sqlmodel-slim
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.7"
|
python-version: "3.11"
|
||||||
# Allow debugging with tmate
|
- name: Install build dependencies
|
||||||
- name: Setup tmate session
|
run: pip install build
|
||||||
uses: mxschmitt/action-tmate@v3
|
- name: Build distribution
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
|
||||||
with:
|
|
||||||
limit-access-to-actor: true
|
|
||||||
- uses: actions/cache@v2
|
|
||||||
id: cache
|
|
||||||
with:
|
|
||||||
path: ${{ env.pythonLocation }}
|
|
||||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root
|
|
||||||
- name: Install poetry
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
# TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
|
|
||||||
# once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6
|
|
||||||
# Ref: https://github.com/python-poetry/poetry-core/pull/188
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
|
|
||||||
python -m pip install "poetry==1.2.0a2"
|
|
||||||
python -m poetry plugin add poetry-version-plugin
|
|
||||||
- name: Configure poetry
|
|
||||||
run: python -m poetry config virtualenvs.create false
|
|
||||||
- name: Install Dependencies
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
run: python -m poetry install
|
|
||||||
- name: Publish
|
|
||||||
env:
|
env:
|
||||||
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
|
||||||
run: |
|
run: python -m build
|
||||||
python -m poetry config pypi-token.pypi $PYPI_TOKEN
|
- name: Publish
|
||||||
bash scripts/publish.sh
|
uses: pypa/gh-action-pypi-publish@v1.12.3
|
||||||
|
|||||||
55
.github/workflows/smokeshow.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
name: Smokeshow
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: [Test]
|
||||||
|
types: [completed]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
statuses: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
UV_SYSTEM_PYTHON: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
smokeshow:
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
- name: Setup uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
with:
|
||||||
|
version: "0.4.15"
|
||||||
|
enable-cache: true
|
||||||
|
cache-dependency-glob: |
|
||||||
|
requirements**.txt
|
||||||
|
pyproject.toml
|
||||||
|
- run: uv pip install -r requirements-github-actions.txt
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: coverage-html
|
||||||
|
path: htmlcov
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
# Try 5 times to upload coverage to smokeshow
|
||||||
|
- name: Upload coverage to Smokeshow
|
||||||
|
run: |
|
||||||
|
for i in 1 2 3 4 5; do
|
||||||
|
if smokeshow upload htmlcov; then
|
||||||
|
echo "Smokeshow upload success!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Smokeshow upload error, sleep 1 sec and try again."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
env:
|
||||||
|
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage}
|
||||||
|
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 95
|
||||||
|
SMOKESHOW_GITHUB_CONTEXT: coverage
|
||||||
|
SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||||
|
SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }}
|
||||||
65
.github/workflows/test-redistribute.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
name: Test Redistribute
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- synchronize
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-redistribute:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
package:
|
||||||
|
- sqlmodel
|
||||||
|
- sqlmodel-slim
|
||||||
|
steps:
|
||||||
|
- name: Dump GitHub context
|
||||||
|
env:
|
||||||
|
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||||
|
run: echo "$GITHUB_CONTEXT"
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.10"
|
||||||
|
- name: Install build dependencies
|
||||||
|
run: pip install build
|
||||||
|
- name: Build source distribution
|
||||||
|
env:
|
||||||
|
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
|
||||||
|
run: python -m build --sdist
|
||||||
|
- name: Decompress source distribution
|
||||||
|
run: |
|
||||||
|
cd dist
|
||||||
|
tar xvf sqlmodel*.tar.gz
|
||||||
|
- name: Install test dependencies
|
||||||
|
run: |
|
||||||
|
cd dist/sqlmodel*/
|
||||||
|
pip install -r requirements-tests.txt
|
||||||
|
env:
|
||||||
|
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
|
||||||
|
- name: Run source distribution tests
|
||||||
|
run: |
|
||||||
|
cd dist/sqlmodel*/
|
||||||
|
bash scripts/test.sh
|
||||||
|
- name: Build wheel distribution
|
||||||
|
run: |
|
||||||
|
cd dist
|
||||||
|
pip wheel --no-deps sqlmodel*.tar.gz
|
||||||
|
|
||||||
|
# https://github.com/marketplace/actions/alls-green#why
|
||||||
|
test-redistribute-alls-green: # This job does nothing and is only used for the branch protection
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- test-redistribute
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Decide whether the needed jobs succeeded or failed
|
||||||
|
uses: re-actors/alls-green@release/v1
|
||||||
|
with:
|
||||||
|
jobs: ${{ toJSON(needs) }}
|
||||||
142
.github/workflows/test.yml
vendored
@@ -2,59 +2,137 @@ name: Test
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize]
|
types:
|
||||||
|
- opened
|
||||||
|
- synchronize
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
debug_enabled:
|
debug_enabled:
|
||||||
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
||||||
required: false
|
required: false
|
||||||
default: false
|
default: 'false'
|
||||||
|
schedule:
|
||||||
|
# cron every week on monday
|
||||||
|
- cron: "0 0 * * 1"
|
||||||
|
|
||||||
|
env:
|
||||||
|
UV_SYSTEM_PYTHON: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.6, 3.7, 3.8, 3.9]
|
os: [ ubuntu-latest ]
|
||||||
|
python-version:
|
||||||
|
- "3.8"
|
||||||
|
- "3.9"
|
||||||
|
- "3.10"
|
||||||
|
- "3.11"
|
||||||
|
- "3.12"
|
||||||
|
pydantic-version:
|
||||||
|
- pydantic-v1
|
||||||
|
- pydantic-v2
|
||||||
|
include:
|
||||||
|
- os: ubuntu-22.04
|
||||||
|
python-version: "3.7"
|
||||||
|
pydantic-version: pydantic-v1
|
||||||
|
- os: ubuntu-22.04
|
||||||
|
python-version: "3.7"
|
||||||
|
pydantic-version: pydantic-v2
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Setup uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
with:
|
||||||
|
version: "0.4.15"
|
||||||
|
enable-cache: true
|
||||||
|
cache-dependency-glob: |
|
||||||
|
requirements**.txt
|
||||||
|
pyproject.toml
|
||||||
# Allow debugging with tmate
|
# Allow debugging with tmate
|
||||||
- name: Setup tmate session
|
- name: Setup tmate session
|
||||||
uses: mxschmitt/action-tmate@v3
|
uses: mxschmitt/action-tmate@v3
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
|
||||||
with:
|
with:
|
||||||
limit-access-to-actor: true
|
limit-access-to-actor: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
id: cache
|
|
||||||
with:
|
|
||||||
path: ${{ env.pythonLocation }}
|
|
||||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root
|
|
||||||
- name: Install poetry
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
# TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
|
|
||||||
# once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6
|
|
||||||
# Ref: https://github.com/python-poetry/poetry-core/pull/188
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
|
|
||||||
python -m pip install "poetry==1.2.0a2"
|
|
||||||
python -m poetry plugin add poetry-version-plugin
|
|
||||||
- name: Configure poetry
|
|
||||||
run: python -m poetry config virtualenvs.create false
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
run: uv pip install -r requirements-tests.txt
|
||||||
run: python -m poetry install
|
- name: Install Pydantic v1
|
||||||
|
if: matrix.pydantic-version == 'pydantic-v1'
|
||||||
|
run: uv pip install --upgrade "pydantic>=1.10.0,<2.0.0"
|
||||||
|
- name: Install Pydantic v2
|
||||||
|
if: matrix.pydantic-version == 'pydantic-v2'
|
||||||
|
run: uv pip install --upgrade "pydantic>=2.0.2,<3.0.0" "typing-extensions==4.6.1"
|
||||||
- name: Lint
|
- name: Lint
|
||||||
if: ${{ matrix.python-version != '3.6' }}
|
# Do not run on Python 3.7 as mypy behaves differently
|
||||||
run: python -m poetry run bash scripts/lint.sh
|
if: matrix.python-version != '3.7' && matrix.pydantic-version == 'pydantic-v2'
|
||||||
|
run: bash scripts/lint.sh
|
||||||
|
- run: mkdir coverage
|
||||||
- name: Test
|
- name: Test
|
||||||
run: python -m poetry run bash scripts/test.sh
|
run: bash scripts/test.sh
|
||||||
- name: Upload coverage
|
env:
|
||||||
uses: codecov/codecov-action@v1
|
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-${{ matrix.pydantic-version }}
|
||||||
|
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
|
||||||
|
- name: Store coverage files
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: coverage-${{ matrix.python-version }}-${{ matrix.pydantic-version }}
|
||||||
|
path: coverage
|
||||||
|
include-hidden-files: true
|
||||||
|
|
||||||
|
coverage-combine:
|
||||||
|
needs:
|
||||||
|
- test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
- name: Setup uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
with:
|
||||||
|
version: "0.4.15"
|
||||||
|
enable-cache: true
|
||||||
|
cache-dependency-glob: |
|
||||||
|
requirements**.txt
|
||||||
|
pyproject.toml
|
||||||
|
- name: Get coverage files
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: coverage-*
|
||||||
|
path: coverage
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: uv pip install -r requirements-tests.txt
|
||||||
|
- run: ls -la coverage
|
||||||
|
- run: coverage combine coverage
|
||||||
|
- run: coverage report
|
||||||
|
- run: coverage html --title "Coverage for ${{ github.sha }}"
|
||||||
|
- name: Store coverage HTML
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: coverage-html
|
||||||
|
path: htmlcov
|
||||||
|
include-hidden-files: true
|
||||||
|
|
||||||
|
# https://github.com/marketplace/actions/alls-green#why
|
||||||
|
alls-green: # This job does nothing and is only used for the branch protection
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- coverage-combine
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Decide whether the needed jobs succeeded or failed
|
||||||
|
uses: re-actors/alls-green@release/v1
|
||||||
|
with:
|
||||||
|
jobs: ${{ toJSON(needs) }}
|
||||||
|
|||||||
5
.gitignore
vendored
@@ -1,5 +1,4 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
env*
|
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
@@ -7,7 +6,9 @@ poetry.lock
|
|||||||
dist
|
dist
|
||||||
htmlcov
|
htmlcov
|
||||||
*.egg-info
|
*.egg-info
|
||||||
.coverage
|
.coverage*
|
||||||
coverage.xml
|
coverage.xml
|
||||||
site
|
site
|
||||||
*.db
|
*.db
|
||||||
|
.cache
|
||||||
|
.venv*
|
||||||
|
|||||||
25
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
default_language_version:
|
||||||
|
python: python3.10
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.6.0
|
||||||
|
hooks:
|
||||||
|
- id: check-added-large-files
|
||||||
|
- id: check-toml
|
||||||
|
- id: check-yaml
|
||||||
|
args:
|
||||||
|
- --unsafe
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
rev: v0.6.5
|
||||||
|
hooks:
|
||||||
|
- id: ruff
|
||||||
|
args:
|
||||||
|
- --fix
|
||||||
|
- id: ruff-format
|
||||||
|
ci:
|
||||||
|
autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
|
||||||
|
autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate
|
||||||
24
CITATION.cff
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# This CITATION.cff file was generated with cffinit.
|
||||||
|
# Visit https://bit.ly/cffinit to generate yours today!
|
||||||
|
|
||||||
|
cff-version: 1.2.0
|
||||||
|
title: SQLModel
|
||||||
|
message: >-
|
||||||
|
If you use this software, please cite it using the
|
||||||
|
metadata from this file.
|
||||||
|
type: software
|
||||||
|
authors:
|
||||||
|
- given-names: Sebastián
|
||||||
|
family-names: Ramírez
|
||||||
|
email: tiangolo@gmail.com
|
||||||
|
identifiers:
|
||||||
|
repository-code: 'https://github.com/fastapi/sqlmodel'
|
||||||
|
url: 'https://sqlmodel.tiangolo.com'
|
||||||
|
abstract: >-
|
||||||
|
SQLModel, SQL databases in Python, designed for
|
||||||
|
simplicity, compatibility, and robustness.
|
||||||
|
keywords:
|
||||||
|
- fastapi
|
||||||
|
- pydantic
|
||||||
|
- sqlalchemy
|
||||||
|
license: MIT
|
||||||
34
README.md
@@ -1,19 +1,19 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://sqlmodel.tiangolo.com"><img src="https://sqlmodel.tiangolo.com/img/logo-margin/logo-margin-vector.svg" alt="SQLModel"></a>
|
<a href="https://sqlmodel.tiangolo.com"><img src="https://sqlmodel.tiangolo.com/img/logo-margin/logo-margin-vector.svg#only-light" alt="SQLModel"></a>
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<em>SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness.</em>
|
<em>SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness.</em>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/tiangolo/sqlmodel/actions?query=workflow%3ATest" target="_blank">
|
<a href="https://github.com/fastapi/sqlmodel/actions?query=workflow%3ATest" target="_blank">
|
||||||
<img src="https://github.com/tiangolo/sqlmodel/workflows/Test/badge.svg" alt="Test">
|
<img src="https://github.com/fastapi/sqlmodel/workflows/Test/badge.svg" alt="Test">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/tiangolo/sqlmodel/actions?query=workflow%3APublish" target="_blank">
|
<a href="https://github.com/fastapi/sqlmodel/actions?query=workflow%3APublish" target="_blank">
|
||||||
<img src="https://github.com/tiangolo/sqlmodel/workflows/Publish/badge.svg" alt="Publish">
|
<img src="https://github.com/fastapi/sqlmodel/workflows/Publish/badge.svg" alt="Publish">
|
||||||
</a>
|
|
||||||
<a href="https://codecov.io/gh/tiangolo/sqlmodel" target="_blank">
|
|
||||||
<img src="https://img.shields.io/codecov/c/github/tiangolo/sqlmodel?color=%2334D058" alt="Coverage">
|
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/sqlmodel" target="_blank">
|
||||||
|
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/sqlmodel.svg" alt="Coverage">
|
||||||
<a href="https://pypi.org/project/sqlmodel" target="_blank">
|
<a href="https://pypi.org/project/sqlmodel" target="_blank">
|
||||||
<img src="https://img.shields.io/pypi/v/sqlmodel?color=%2334D058&label=pypi%20package" alt="Package version">
|
<img src="https://img.shields.io/pypi/v/sqlmodel?color=%2334D058&label=pypi%20package" alt="Package version">
|
||||||
</a>
|
</a>
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
**Documentation**: <a href="https://sqlmodel.tiangolo.com" target="_blank">https://sqlmodel.tiangolo.com</a>
|
**Documentation**: <a href="https://sqlmodel.tiangolo.com" target="_blank">https://sqlmodel.tiangolo.com</a>
|
||||||
|
|
||||||
**Source Code**: <a href="https://github.com/tiangolo/sqlmodel" target="_blank">https://github.com/tiangolo/sqlmodel</a>
|
**Source Code**: <a href="https://github.com/fastapi/sqlmodel" target="_blank">https://github.com/fastapi/sqlmodel</a>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -39,6 +39,14 @@ The key features are:
|
|||||||
* **Extensible**: You have all the power of SQLAlchemy and Pydantic underneath.
|
* **Extensible**: You have all the power of SQLAlchemy and Pydantic underneath.
|
||||||
* **Short**: Minimize code duplication. A single type annotation does a lot of work. No need to duplicate models in SQLAlchemy and Pydantic.
|
* **Short**: Minimize code duplication. A single type annotation does a lot of work. No need to duplicate models in SQLAlchemy and Pydantic.
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
<!-- sponsors -->
|
||||||
|
|
||||||
|
<a href="https://www.govcert.lu" target="_blank" title="This project is being supported by GOVCERT.LU"><img src="https://sqlmodel.tiangolo.com/img/sponsors/govcert.png"></a>
|
||||||
|
|
||||||
|
<!-- /sponsors -->
|
||||||
|
|
||||||
## SQL Databases in FastAPI
|
## SQL Databases in FastAPI
|
||||||
|
|
||||||
<a href="https://fastapi.tiangolo.com" target="_blank"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" style="width: 20%;"></a>
|
<a href="https://fastapi.tiangolo.com" target="_blank"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" style="width: 20%;"></a>
|
||||||
@@ -51,12 +59,14 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
A recent and currently supported version of Python (right now, <a href="https://www.python.org/downloads/" class="external-link" target="_blank">Python supports versions 3.6 and above</a>).
|
A recent and currently supported <a href="https://www.python.org/downloads/" class="external-link" target="_blank">version of Python</a>.
|
||||||
|
|
||||||
As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel.
|
As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
Make sure you create a <a href="https://sqlmodel.tiangolo.com/virtual-environments/" class="external-link" target="_blank">virtual environment</a>, activate it, and then install SQLModel, for example with:
|
||||||
|
|
||||||
<div class="termy">
|
<div class="termy">
|
||||||
|
|
||||||
```console
|
```console
|
||||||
@@ -69,7 +79,7 @@ Successfully installed sqlmodel
|
|||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
For an introduction to databases, SQL, and everything else, see the <a href="https://sqlmodel.tiangolo.com" target="_blank">SQLModel documentation</a>.
|
For an introduction to databases, SQL, and everything else, see the <a href="https://sqlmodel.tiangolo.com/databases/" target="_blank">SQLModel documentation</a>.
|
||||||
|
|
||||||
Here's a quick example. ✨
|
Here's a quick example. ✨
|
||||||
|
|
||||||
@@ -212,4 +222,4 @@ And at the same time, ✨ it is also a **Pydantic** model ✨. You can use inher
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the terms of the MIT license.
|
This project is licensed under the terms of the [MIT license](https://github.com/fastapi/sqlmodel/blob/main/LICENSE).
|
||||||
|
|||||||
3
data/members.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
members:
|
||||||
|
- login: tiangolo
|
||||||
|
- login: alejsdev
|
||||||
6
data/sponsors.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
gold: []
|
||||||
|
silver:
|
||||||
|
- url: https://www.govcert.lu
|
||||||
|
title: This project is being supported by GOVCERT.LU
|
||||||
|
img: https://sqlmodel.tiangolo.com/img/sponsors/govcert.png
|
||||||
|
bronze: []
|
||||||
3
docs/about/index.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# About
|
||||||
|
|
||||||
|
About **SQLModel**, its design, inspiration, and more. 🤓
|
||||||
109
docs/advanced/decimal.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# Decimal Numbers
|
||||||
|
|
||||||
|
In some cases you might need to be able to store decimal numbers with guarantees about the precision.
|
||||||
|
|
||||||
|
This is particularly important if you are storing things like **currencies**, **prices**, **accounts**, and others, as you would want to know that you wouldn't have rounding errors.
|
||||||
|
|
||||||
|
As an example, if you open Python and sum `1.1` + `2.2` you would expect to see `3.3`, but you will actually get `3.3000000000000003`:
|
||||||
|
|
||||||
|
```Python
|
||||||
|
>>> 1.1 + 2.2
|
||||||
|
3.3000000000000003
|
||||||
|
```
|
||||||
|
|
||||||
|
This is because of the way numbers are stored in "ones and zeros" (binary). But Python has a module and some types to have strict decimal values. You can read more about it in the official <a href="https://docs.python.org/3/library/decimal.html" class="external-link" target="_blank">Python docs for Decimal</a>.
|
||||||
|
|
||||||
|
Because databases store data in the same ways as computers (in binary), they would have the same types of issues. And because of that, they also have a special **decimal** type.
|
||||||
|
|
||||||
|
In most cases this would probably not be a problem, for example measuring views in a video, or the life bar in a videogame. But as you can imagine, this is particularly important when dealing with **money** and **finances**.
|
||||||
|
|
||||||
|
## Decimal Types
|
||||||
|
|
||||||
|
Pydantic has special support for <a href="https://docs.pydantic.dev/latest/api/standard_library_types/#decimaldecimal" class="external-link" target="_blank">`Decimal` types</a>.
|
||||||
|
|
||||||
|
When you use `Decimal` you can specify the number of digits and decimal places to support in the `Field()` function. They will be validated by Pydantic (for example when using FastAPI) and the same information will also be used for the database columns.
|
||||||
|
|
||||||
|
/// info
|
||||||
|
|
||||||
|
For the database, **SQLModel** will use <a href="https://docs.sqlalchemy.org/en/20/core/type_basics.html#sqlalchemy.types.DECIMAL" class="external-link" target="_blank">SQLAlchemy's `DECIMAL` type</a>.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
## Decimals in SQLModel
|
||||||
|
|
||||||
|
Let's say that each hero in the database will have an amount of money. We could make that field a `Decimal` type using the `condecimal()` function:
|
||||||
|
|
||||||
|
{* ./docs_src/advanced/decimal/tutorial001_py310.py ln[1:11] hl[11] *}
|
||||||
|
|
||||||
|
Here we are saying that `money` can have at most `5` digits with `max_digits`, **this includes the integers** (to the left of the decimal dot) **and the decimals** (to the right of the decimal dot).
|
||||||
|
|
||||||
|
We are also saying that the number of decimal places (to the right of the decimal dot) is `3`, so we can have **3 decimal digits** for these numbers in the `money` field. This means that we will have **2 digits for the integer part** and **3 digits for the decimal part**.
|
||||||
|
|
||||||
|
✅ So, for example, these are all valid numbers for the `money` field:
|
||||||
|
|
||||||
|
* `12.345`
|
||||||
|
* `12.3`
|
||||||
|
* `12`
|
||||||
|
* `1.2`
|
||||||
|
* `0.123`
|
||||||
|
* `0`
|
||||||
|
|
||||||
|
🚫 But these are all invalid numbers for that `money` field:
|
||||||
|
|
||||||
|
* `1.2345`
|
||||||
|
* This number has more than 3 decimal places.
|
||||||
|
* `123.234`
|
||||||
|
* This number has more than 5 digits in total (integer and decimal part).
|
||||||
|
* `123`
|
||||||
|
* Even though this number doesn't have any decimals, we still have 3 places saved for them, which means that we can **only use 2 places** for the **integer part**, and this number has 3 integer digits. So, the allowed number of integer digits is `max_digits` - `decimal_places` = 2.
|
||||||
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
Make sure you adjust the number of digits and decimal places for your own needs, in your own application. 🤓
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
## Create models with Decimals
|
||||||
|
|
||||||
|
When creating new models you can actually pass normal (`float`) numbers, Pydantic will automatically convert them to `Decimal` types, and **SQLModel** will store them as `Decimal` types in the database (using SQLAlchemy).
|
||||||
|
|
||||||
|
{* ./docs_src/advanced/decimal/tutorial001_py310.py ln[24:34] hl[25:27] *}
|
||||||
|
|
||||||
|
## Select Decimal data
|
||||||
|
|
||||||
|
Then, when working with Decimal types, you can confirm that they indeed avoid those rounding errors from floats:
|
||||||
|
|
||||||
|
{* ./docs_src/advanced/decimal/tutorial001_py310.py ln[37:50] hl[49:50] *}
|
||||||
|
|
||||||
|
## Review the results
|
||||||
|
|
||||||
|
Now if you run this, instead of printing the unexpected number `3.3000000000000003`, it prints `3.300`:
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ python app.py
|
||||||
|
|
||||||
|
// Some boilerplate and previous output omitted 😉
|
||||||
|
|
||||||
|
// The type of money is Decimal('1.100')
|
||||||
|
Hero 1: id=1 secret_name='Dive Wilson' age=None name='Deadpond' money=Decimal('1.100')
|
||||||
|
|
||||||
|
// More output omitted here 🤓
|
||||||
|
|
||||||
|
// The type of money is Decimal('1.100')
|
||||||
|
Hero 2: id=3 secret_name='Tommy Sharp' age=48 name='Rusty-Man' money=Decimal('2.200')
|
||||||
|
|
||||||
|
// No rounding errors, just 3.3! 🎉
|
||||||
|
Total money: 3.300
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
/// warning
|
||||||
|
|
||||||
|
Although Decimal types are supported and used in the Python side, not all databases support it. In particular, SQLite doesn't support decimals, so it will convert them to the same floating `NUMERIC` type it supports.
|
||||||
|
|
||||||
|
But decimals are supported by most of the other SQL databases. 🎉
|
||||||
|
|
||||||
|
///
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
# Advanced User Guide
|
# Advanced User Guide
|
||||||
|
|
||||||
The **Advanced User Guide** will be coming soon to a <del>theater</del> **documentation** near you... 😅
|
The **Advanced User Guide** is gradually growing, you can already read about some advanced topics.
|
||||||
|
|
||||||
I just have to `add` it, `commit` it, and `refresh` it. 😉
|
At some point it will include:
|
||||||
|
|
||||||
It will include:
|
* How to use `async` and `await` with the async session.
|
||||||
|
|
||||||
* How to use the `async` and `await` with the async session.
|
|
||||||
* How to run migrations.
|
* How to run migrations.
|
||||||
* How to combine **SQLModel** models with SQLAlchemy.
|
* How to combine **SQLModel** models with SQLAlchemy.
|
||||||
* ...and more.
|
* ...and more. 🤓
|
||||||
|
|||||||
178
docs/advanced/uuid.md
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# UUID (Universally Unique Identifiers)
|
||||||
|
|
||||||
|
We have discussed some data types like `str`, `int`, etc.
|
||||||
|
|
||||||
|
There's another data type called `UUID` (Universally Unique Identifier).
|
||||||
|
|
||||||
|
You might have seen **UUIDs**, for example in URLs. They look something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
4ff2dab7-bffe-414d-88a5-1826b9fea8df
|
||||||
|
```
|
||||||
|
|
||||||
|
UUIDs can be particularly useful as an alternative to auto-incrementing integers for **primary keys**.
|
||||||
|
|
||||||
|
/// info
|
||||||
|
|
||||||
|
Official support for UUIDs was added in SQLModel version `0.0.20`.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
## About UUIDs
|
||||||
|
|
||||||
|
UUIDs are numbers with 128 bits, that is, 16 bytes.
|
||||||
|
|
||||||
|
They are normally seen as 32 <abbr title="numbers in base 16 (instead of base 10), using letters from A to F to represent the numbers from 10 to 15">hexadecimal</abbr> characters separated by dashes.
|
||||||
|
|
||||||
|
There are several versions of UUID, some versions include the current time in the bytes, but **UUIDs version 4** are mainly random, the way they are generated makes them virtually **unique**.
|
||||||
|
|
||||||
|
### Distributed UUIDs
|
||||||
|
|
||||||
|
You could generate one UUID in one computer, and someone else could generate another UUID in another computer, and it would be almost **impossible** for both UUIDs to be the **same**.
|
||||||
|
|
||||||
|
This means that you don't have to wait for the DB to generate the ID for you, you can **generate it in code before sending it to the database**, because you can be quite certain it will be unique.
|
||||||
|
|
||||||
|
/// note | Technical Details
|
||||||
|
|
||||||
|
Because the number of possible UUIDs is so large (2^128), the probability of generating the same UUID version 4 (the random ones) twice is very low.
|
||||||
|
|
||||||
|
If you had 103 trillion version 4 UUIDs stored in the database, the probability of generating a duplicated new one is one in a billion. 🤓
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
For the same reason, if you decided to migrate your database, combine it with another database and mix records, etc. you would most probably be able to **just use the same UUIDs** you had originally.
|
||||||
|
|
||||||
|
/// warning
|
||||||
|
|
||||||
|
There's still a chance you could have a collision, but it's very low. In most cases you could assume you wouldn't have it, but it would be good to be prepared for it.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
### UUIDs Prevent Information Leakage
|
||||||
|
|
||||||
|
Because UUIDs version 4 are **random**, you could give these IDs to the application users or to other systems, **without exposing information** about your application.
|
||||||
|
|
||||||
|
When using **auto-incremented integers** for primary keys, you could implicitly expose information about your system. For example, someone could create a new hero, and by getting the hero ID `20` **they would know that you have 20 heroes** in your system (or even less, if some heroes were already deleted).
|
||||||
|
|
||||||
|
### UUID Storage
|
||||||
|
|
||||||
|
Because UUIDs are 16 bytes, they would **consume more space** in the database than a smaller auto-incremented integer (commonly 4 bytes).
|
||||||
|
|
||||||
|
Depending on the database you use, UUIDs could have **better or worse performance**. If you are concerned about that, you should check the documentation for the specific database.
|
||||||
|
|
||||||
|
SQLite doesn't have a specific UUID type, so it will store the UUID as a string. Other databases like Postgres have a specific UUID type which would result in better performance and space usage than strings.
|
||||||
|
|
||||||
|
## Models with UUIDs
|
||||||
|
|
||||||
|
To use UUIDs as primary keys we need to import `uuid`, which is part of the Python standard library (we don't have to install anything) and use `uuid.UUID` as the **type** for the ID field.
|
||||||
|
|
||||||
|
We also want the Python code to **generate a new UUID** when creating a new instance, so we use `default_factory`.
|
||||||
|
|
||||||
|
The parameter `default_factory` takes a function (or in general, a "<abbr title="Something that can be called as a function.">callable</abbr>"). This function will be **called when creating a new instance** of the model and the value returned by the function will be used as the default value for the field.
|
||||||
|
|
||||||
|
For the function in `default_factory` we pass `uuid.uuid4`, which is a function that generates a **new UUID version 4**.
|
||||||
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
We don't call `uuid.uuid4()` ourselves in the code (we don't put the parenthesis). Instead, we pass the function itself, just `uuid.uuid4`, so that SQLModel can call it every time we create a new instance.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
This means that the UUID will be generated in the Python code, **before sending the data to the database**.
|
||||||
|
|
||||||
|
{* ./docs_src/advanced/uuid/tutorial001_py310.py ln[1:10] hl[1,7] *}
|
||||||
|
|
||||||
|
Pydantic has support for <a href="https://docs.pydantic.dev/latest/api/standard_library_types/#uuid" class="external-link" target="_blank">`UUID` types</a>.
|
||||||
|
|
||||||
|
For the database, **SQLModel** internally uses <a href="https://docs.sqlalchemy.org/en/20/core/type_basics.html#sqlalchemy.types.Uuid" class="external-link" target="_blank">SQLAlchemy's `Uuid` type</a>.
|
||||||
|
|
||||||
|
### Create a Record with a UUID
|
||||||
|
|
||||||
|
When creating a `Hero` record, the `id` field will be **automatically populated** with a new UUID because we set `default_factory=uuid.uuid4`.
|
||||||
|
|
||||||
|
As `uuid.uuid4` will be called when creating the model instance, even before sending it to the database, we can **access and use the ID right away**.
|
||||||
|
|
||||||
|
And that **same ID (a UUID)** will be saved in the database.
|
||||||
|
|
||||||
|
{* ./docs_src/advanced/uuid/tutorial001_py310.py ln[23:34] hl[25,27,29,34] *}
|
||||||
|
|
||||||
|
### Select a Hero
|
||||||
|
|
||||||
|
We can do the same operations we could do with other fields.
|
||||||
|
|
||||||
|
For example we can **select a hero by ID**:
|
||||||
|
|
||||||
|
{* ./docs_src/advanced/uuid/tutorial001_py310.py ln[37:54] hl[49] *}
|
||||||
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
Even if a database like SQLite stores the UUID as a string, we can select and run comparisons using a Python UUID object and it will work.
|
||||||
|
|
||||||
|
SQLModel (actually SQLAlchemy) will take care of making it work. ✨
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
#### Select with `session.get()`
|
||||||
|
|
||||||
|
We could also select by ID with `session.get()`:
|
||||||
|
|
||||||
|
{* ./docs_src/advanced/uuid/tutorial002_py310.py ln[37:53] hl[49] *}
|
||||||
|
|
||||||
|
The same way as with other fields, we could update, delete, etc. 🚀
|
||||||
|
|
||||||
|
### Run the program
|
||||||
|
|
||||||
|
If you run the program, you will see the **UUID** generated in the Python code, and then the record **saved in the database with the same UUID**.
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ python app.py
|
||||||
|
|
||||||
|
// Some boilerplate and previous output omitted 😉
|
||||||
|
|
||||||
|
// In SQLite, the UUID will be stored as a string
|
||||||
|
// other DBs like Postgres have a specific UUID type
|
||||||
|
CREATE TABLE hero (
|
||||||
|
id CHAR(32) NOT NULL,
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
secret_name VARCHAR NOT NULL,
|
||||||
|
age INTEGER,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Before saving in the DB we already have the UUID
|
||||||
|
The hero before saving in the DB
|
||||||
|
name='Deadpond' secret_name='Dive Wilson' id=UUID('0e44c1a6-88d3-4a35-8b8a-307faa2def28') age=None
|
||||||
|
The hero ID was already set
|
||||||
|
0e44c1a6-88d3-4a35-8b8a-307faa2def28
|
||||||
|
|
||||||
|
// The SQL statement to insert the record uses our UUID
|
||||||
|
INSERT INTO hero (id, name, secret_name, age) VALUES (?, ?, ?, ?)
|
||||||
|
('0e44c1a688d34a358b8a307faa2def28', 'Deadpond', 'Dive Wilson', None)
|
||||||
|
|
||||||
|
// And indeed, the record was saved with the UUID we created 😎
|
||||||
|
After saving in the DB
|
||||||
|
age=None id=UUID('0e44c1a6-88d3-4a35-8b8a-307faa2def28') name='Deadpond' secret_name='Dive Wilson'
|
||||||
|
|
||||||
|
// Now we create a new hero (to select it in a bit)
|
||||||
|
Created hero:
|
||||||
|
age=None id=UUID('9d90d186-85db-4eaa-891a-def7b4ae2dab') name='Spider-Boy' secret_name='Pedro Parqueador'
|
||||||
|
Created hero ID:
|
||||||
|
9d90d186-85db-4eaa-891a-def7b4ae2dab
|
||||||
|
|
||||||
|
// And now we select it
|
||||||
|
Selected hero:
|
||||||
|
age=None id=UUID('9d90d186-85db-4eaa-891a-def7b4ae2dab') name='Spider-Boy' secret_name='Pedro Parqueador'
|
||||||
|
Selected hero ID:
|
||||||
|
9d90d186-85db-4eaa-891a-def7b4ae2dab
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
You can learn more about **UUIDs** in:
|
||||||
|
|
||||||
|
* The official <a href="https://docs.python.org/3/library/uuid.html" class="external-link" target="_blank">Python docs for UUID</a>.
|
||||||
|
* The <a href="https://en.wikipedia.org/wiki/Universally_unique_identifier" class="external-link" target="_blank">Wikipedia for UUID</a>.
|
||||||
@@ -4,53 +4,43 @@ First, you might want to see the basic ways to [help SQLModel and get help](help
|
|||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
If you already cloned the repository and you know that you need to deep dive in the code, here are some guidelines to set up your environment.
|
If you already cloned the <a href="https://github.com/fastapi/sqlmodel" class="external-link" target="_blank">sqlmodel repository</a> and you want to deep dive in the code, here are some guidelines to set up your environment.
|
||||||
|
|
||||||
### Python
|
### Virtual Environment
|
||||||
|
|
||||||
SQLModel supports Python 3.6 and above, but for development you should have at least **Python 3.7**.
|
Follow the instructions to create and activate a [virtual environment](virtual-environments.md){.internal-link target=_blank} for the internal code of `sqlmodel`.
|
||||||
|
|
||||||
### Poetry
|
### Install Requirements Using `pip`
|
||||||
|
|
||||||
**SQLModel** uses <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a> to build, package, and publish the project.
|
After activating the environment, install the required packages:
|
||||||
|
|
||||||
You can learn how to install it in the <a href="https://python-poetry.org/docs/#installation" class="external-link" target="_blank">Poetry docs</a>.
|
|
||||||
|
|
||||||
After having Poetry available, you can install the development dependencies:
|
|
||||||
|
|
||||||
<div class="termy">
|
<div class="termy">
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ poetry install
|
$ pip install -r requirements.txt
|
||||||
|
|
||||||
---> 100%
|
---> 100%
|
||||||
```
|
```
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
It will also create a virtual environment automatically and will install all the dependencies and your local SQLModel in it.
|
It will install all the dependencies and your local SQLModel in your local environment.
|
||||||
|
|
||||||
### Poetry Shell
|
### Using your Local SQLModel
|
||||||
|
|
||||||
To use your current environment, and to have access to all the tools in it (for example `pytest` for the tests) enter into a Poetry Shell:
|
If you create a Python file that imports and uses SQLModel, and run it with the Python from your local environment, it will use your cloned local SQLModel source code.
|
||||||
|
|
||||||
<div class="termy">
|
And if you update that local SQLModel source code when you run that Python file again, it will use the fresh version of SQLModel you just edited.
|
||||||
|
|
||||||
```console
|
That way, you don't have to "install" your local version to be able to test every change.
|
||||||
$ poetry shell
|
|
||||||
```
|
|
||||||
|
|
||||||
</div>
|
/// note | "Technical Details"
|
||||||
|
|
||||||
That will set up the environment variables needed dand will start a new shell with them.
|
This only happens when you install using this included `requirements.txt` instead of running `pip install sqlmodel` directly.
|
||||||
|
|
||||||
#### Using your local SQLModel
|
That is because inside the `requirements.txt` file, the local version of SQLModel is marked to be installed in "editable" mode, with the `-e` option.
|
||||||
|
|
||||||
If you create a Python file that imports and uses SQLModel, and run it with the Python from your local Poetry environment, it will use your local SQLModel source code.
|
///
|
||||||
|
|
||||||
And if you update that local SQLModel source code, when you run that Python file again, it will use the fresh version of SQLModel you just edited.
|
|
||||||
|
|
||||||
Poetry takes care of making that work. But of course, it will only work in the current Poetry environment, if you install standard SQLModel in another environment (not from the source in the GitHub repo), that will use the standard SQLModel, not your custom version.
|
|
||||||
|
|
||||||
### Format
|
### Format
|
||||||
|
|
||||||
@@ -66,49 +56,6 @@ $ bash scripts/format.sh
|
|||||||
|
|
||||||
It will also auto-sort all your imports.
|
It will also auto-sort all your imports.
|
||||||
|
|
||||||
## Docs
|
|
||||||
|
|
||||||
The documentation uses <a href="https://www.mkdocs.org/" class="external-link" target="_blank">MkDocs</a> with <a href="https://squidfunk.github.io/mkdocs-material/" class="external-link" target="_blank">Material for MkDocs</a>.
|
|
||||||
|
|
||||||
All the documentation is in Markdown format in the directory `./docs`.
|
|
||||||
|
|
||||||
Many of the tutorials have blocks of code.
|
|
||||||
|
|
||||||
In most of the cases, these blocks of code are actual complete applications that can be run as is.
|
|
||||||
|
|
||||||
In fact, those blocks of code are not written inside the Markdown, they are Python files in the `./docs_src/` directory.
|
|
||||||
|
|
||||||
And those Python files are included/injected in the documentation when generating the site.
|
|
||||||
|
|
||||||
### Docs for tests
|
|
||||||
|
|
||||||
Most of the tests actually run against the example source files in the documentation.
|
|
||||||
|
|
||||||
This helps making sure that:
|
|
||||||
|
|
||||||
* The documentation is up to date.
|
|
||||||
* The documentation examples can be run as is.
|
|
||||||
* Most of the features are covered by the documentation, ensured by test coverage.
|
|
||||||
|
|
||||||
During local development, there is a script that builds the site and checks for any changes, live-reloading:
|
|
||||||
|
|
||||||
<div class="termy">
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ bash scripts/docs-live.sh
|
|
||||||
|
|
||||||
<span style="color: green;">[INFO]</span> - Building documentation...
|
|
||||||
<span style="color: green;">[INFO]</span> - Cleaning site directory
|
|
||||||
<span style="color: green;">[INFO]</span> - Documentation built in 2.74 seconds
|
|
||||||
<span style="color: green;">[INFO]</span> - Serving on http://127.0.0.1:8008
|
|
||||||
```
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
It will serve the documentation on `http://127.0.0.1:8008`.
|
|
||||||
|
|
||||||
That way, you can edit the documentation/source files and see the changes live.
|
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
There is a script that you can run locally to test all the code and generate coverage reports in HTML:
|
There is a script that you can run locally to test all the code and generate coverage reports in HTML:
|
||||||
@@ -123,6 +70,95 @@ $ bash scripts/test-cov-html.sh
|
|||||||
|
|
||||||
This command generates a directory `./htmlcov/`, if you open the file `./htmlcov/index.html` in your browser, you can explore interactively the regions of code that are covered by the tests, and notice if there is any region missing.
|
This command generates a directory `./htmlcov/`, if you open the file `./htmlcov/index.html` in your browser, you can explore interactively the regions of code that are covered by the tests, and notice if there is any region missing.
|
||||||
|
|
||||||
## Thanks
|
## Docs
|
||||||
|
|
||||||
Thanks for contributing! ☕
|
First, make sure you set up your environment as described above, that will install all the requirements.
|
||||||
|
|
||||||
|
### Docs Live
|
||||||
|
|
||||||
|
During local development, there is a script that builds the site and checks for any changes, live-reloading:
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ python ./scripts/docs.py live
|
||||||
|
|
||||||
|
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
|
||||||
|
<span style="color: green;">[INFO]</span> Start watching changes
|
||||||
|
<span style="color: green;">[INFO]</span> Start detecting changes
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
It will serve the documentation on `http://127.0.0.1:8008`.
|
||||||
|
|
||||||
|
That way, you can edit the documentation/source files and see the changes live.
|
||||||
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
Alternatively, you can perform the same steps that scripts does manually.
|
||||||
|
|
||||||
|
Go into the docs director at `docs/`:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ cd docs/
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run `mkdocs` in that directory:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ mkdocs serve --dev-addr 8008
|
||||||
|
```
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
#### Typer CLI (Optional)
|
||||||
|
|
||||||
|
The instructions here show you how to use the script at `./scripts/docs.py` with the `python` program directly.
|
||||||
|
|
||||||
|
But you can also use <a href="https://typer.tiangolo.com/typer-cli/" class="external-link" target="_blank">Typer CLI</a>, and you will get autocompletion in your terminal for the commands after installing completion.
|
||||||
|
|
||||||
|
If you install Typer CLI, you can install completion with:
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ typer --install-completion
|
||||||
|
|
||||||
|
zsh completion installed in /home/user/.bashrc.
|
||||||
|
Completion will take effect once you restart the terminal.
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Docs Structure
|
||||||
|
|
||||||
|
The documentation uses <a href="https://www.mkdocs.org/" class="external-link" target="_blank">MkDocs</a>.
|
||||||
|
|
||||||
|
And there are extra tools/scripts in place in `./scripts/docs.py`.
|
||||||
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
You don't need to see the code in `./scripts/docs.py`, you just use it in the command line.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
All the documentation is in Markdown format in the directory `./docs`.
|
||||||
|
|
||||||
|
Many of the tutorials have blocks of code.
|
||||||
|
|
||||||
|
In most of the cases, these blocks of code are actual complete applications that can be run as is.
|
||||||
|
|
||||||
|
In fact, those blocks of code are not written inside the Markdown, they are Python files in the `./docs_src/` directory.
|
||||||
|
|
||||||
|
And those Python files are included/injected in the documentation when generating the site.
|
||||||
|
|
||||||
|
### Docs for Tests
|
||||||
|
|
||||||
|
Most of the tests actually run against the example source files in the documentation.
|
||||||
|
|
||||||
|
This helps to make sure that:
|
||||||
|
|
||||||
|
* The documentation is up-to-date.
|
||||||
|
* The documentation examples can be run as is.
|
||||||
|
* Most of the features are covered by the documentation, ensured by test coverage.
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.termy .linenos {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
a.external-link::after {
|
a.external-link::after {
|
||||||
/* \00A0 is a non-breaking space
|
/* \00A0 is a non-breaking space
|
||||||
to make the mark be on the same line as the link
|
to make the mark be on the same line as the link
|
||||||
@@ -25,3 +29,43 @@ a.internal-link::after {
|
|||||||
.shadow {
|
.shadow {
|
||||||
box-shadow: 5px 5px 10px #999;
|
box-shadow: 5px 5px 10px #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-list-center {
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user {
|
||||||
|
margin: 1em;
|
||||||
|
min-width: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user .avatar-wrapper {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
margin: 10px auto;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user .avatar-wrapper img {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user .title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user .count {
|
||||||
|
font-size: 80%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
-webkit-box-sizing: border-box;
|
-webkit-box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
/* Custom line-height */
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-termynal]:before {
|
[data-termynal]:before {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
# Intro to Databases
|
# Intro to Databases
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
Are you a seasoned developer and already know everything about databases? 🤓
|
|
||||||
|
|
||||||
Then you can skip to the [Tutorial - User Guide: First Steps](tutorial/index.md){.internal-link target=_blank} right away.
|
Are you a seasoned developer and already know everything about databases? 🤓
|
||||||
|
|
||||||
|
Then you can skip to the next sections right away.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
If you don't know everything about databases, here's a quick overview.
|
If you don't know everything about databases, here's a quick overview.
|
||||||
|
|
||||||
@@ -17,8 +20,11 @@ So, what is a database?
|
|||||||
|
|
||||||
A **database** is a system to store and manage data in a structured and very efficient way.
|
A **database** is a system to store and manage data in a structured and very efficient way.
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
It's very common to abbreviate the word "database" as **"DB"**.
|
|
||||||
|
It's very common to abbreviate the word "database" as **"DB"**.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
As there's a lot of information about databases, and it can get very technical and academic, I'll give you a quick overview about some of the main concepts here.
|
As there's a lot of information about databases, and it can get very technical and academic, I'll give you a quick overview about some of the main concepts here.
|
||||||
|
|
||||||
@@ -28,8 +34,11 @@ I'll even tell you a bit about different types of databases, including the ones
|
|||||||
|
|
||||||
When starting to program, it might **not be obvious** why having a database apart from the code for your program is a **good idea**. Let's start with that.
|
When starting to program, it might **not be obvious** why having a database apart from the code for your program is a **good idea**. Let's start with that.
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
If that's obvious to you, just continue in the next section below. 👇
|
|
||||||
|
If that's obvious to you, just continue in the next section below. 👇
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
In your code you already have **variables**, **dictionaries**, **lists**, etc. They all store **data** in some way already. Why would you need to have a separate database?
|
In your code you already have **variables**, **dictionaries**, **lists**, etc. They all store **data** in some way already. Why would you need to have a separate database?
|
||||||
|
|
||||||
@@ -57,9 +66,9 @@ There are many databases of many types.
|
|||||||
|
|
||||||
### A single file database
|
### A single file database
|
||||||
|
|
||||||
A database could be a single file called `heroes.db`, managed with code in a very efficient way. An example would be SQLite, more about that on a bit.
|
A database could be a single file called `heroes.db`, managed with code in a very efficient way. An example would be SQLite, more about that in a bit.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### A server database
|
### A server database
|
||||||
|
|
||||||
@@ -71,11 +80,11 @@ In this case, your code would talk to this server application instead of reading
|
|||||||
|
|
||||||
The database could be located in a different server/machine:
|
The database could be located in a different server/machine:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Or the database could be located in the same server/machine:
|
Or the database could be located in the same server/machine:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The most important aspect of these types of databases is that **your code doesn't read or modify** the files containing the data directly.
|
The most important aspect of these types of databases is that **your code doesn't read or modify** the files containing the data directly.
|
||||||
|
|
||||||
@@ -85,11 +94,11 @@ Some examples of databases that work like this could be **PostgreSQL**, **MySQL*
|
|||||||
|
|
||||||
### Distributed servers
|
### Distributed servers
|
||||||
|
|
||||||
In some cases, the database could even be a group server applications running on different machines, working together and communicating between them to be more efficient and handle more data.
|
In some cases, the database could even be a group of server applications running on different machines, working together and communicating between them to be more efficient and handle more data.
|
||||||
|
|
||||||
In this case, your code would talk to one or more of these server applications running on different machines.
|
In this case, your code would talk to one or more of these server applications running on different machines.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Most of the databases that work as server applications also support multiple servers in one way or another.
|
Most of the databases that work as server applications also support multiple servers in one way or another.
|
||||||
|
|
||||||
@@ -128,7 +137,7 @@ If we worked with a single table to store our heroes, it could be like this:
|
|||||||
<th>id</th><th>name</th><th>secret_name</th><th>age</th><th>team</th><th>headquarters</th>
|
<th>id</th><th>name</th><th>secret_name</th><th>age</th><th>team</th><th>headquarters</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>Z-Factor</td><td>Sister Margaret’s Bar</td>
|
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>Z-Factor</td><td>Sister Margaret's Bar</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>2</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>Preventers</td><td>Sharp Tower</td>
|
<td>2</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>Preventers</td><td>Sharp Tower</td>
|
||||||
@@ -157,7 +166,7 @@ We could end up with inconsistent information, having one place saying "Prevente
|
|||||||
<th>id</th><th>name</th><th>secret_name</th><th>age</th><th>team</th><th>headquarters</th>
|
<th>id</th><th>name</th><th>secret_name</th><th>age</th><th>team</th><th>headquarters</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>2</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>Preventers</td><td>Preventers Tower ✅</td>
|
<td>2</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>Preventers</td><td>Preventers Tower ✅</td>
|
||||||
@@ -176,7 +185,7 @@ We could forget the name of the team and end up adding "Mahjong" with an invalid
|
|||||||
<th>id</th><th>name</th><th>secret_name</th><th>age</th><th>team</th><th>headquarters</th>
|
<th>id</th><th>name</th><th>secret_name</th><th>age</th><th>team</th><th>headquarters</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>2</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>Preventers</td><td>Preventers Tower</td>
|
<td>2</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>Preventers</td><td>Preventers Tower</td>
|
||||||
@@ -185,7 +194,7 @@ We could forget the name of the team and end up adding "Mahjong" with an invalid
|
|||||||
<td>3</td><td>Rusty-Man</td><td>Tommy Sharp</td><td>48</td><td>Preventers</td><td>Sharp Tower</td>
|
<td>3</td><td>Rusty-Man</td><td>Tommy Sharp</td><td>48</td><td>Preventers</td><td>Sharp Tower</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>4</td><td>Mahjong</td><td>Neena Thurgirl</td><td>31</td><td>Y-Force 🚨</td><td>Sister Margaret’s Bar</td>
|
<td>4</td><td>Mahjong</td><td>Neena Thurgirl</td><td>31</td><td>Y-Force 🚨</td><td>Sister Margaret's Bar</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -207,7 +216,7 @@ The table for the teams could look like this:
|
|||||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -248,9 +257,9 @@ For example, the table for the teams has the ID `1` for the team `Preventers` an
|
|||||||
|
|
||||||
As these **primary key** IDs can uniquely identify each row on the table for teams, we can now go to the table for heroes and refer to those IDs in the table for teams.
|
As these **primary key** IDs can uniquely identify each row on the table for teams, we can now go to the table for heroes and refer to those IDs in the table for teams.
|
||||||
|
|
||||||
<img alt="table relationships" src="/img/databases/relationships.svg">
|

|
||||||
|
|
||||||
So, in the table for heroes, we use the `team_id` column to define a relationship to the *foreign* table for teams. Each value in the `team_id` column on the table with heroes will be the same value as the `id` column of one row in the table wiwth teams.
|
So, in the table for heroes, we use the `team_id` column to define a relationship to the *foreign* table for teams. Each value in the `team_id` column on the table with heroes will be the same value as the `id` column of one row in the table with teams.
|
||||||
|
|
||||||
In the table for heroes we have a **primary key** that is the `id`. But we also have another column `team_id` that refers to a **key** in a **foreign** table. There's a technical term for that too, the `team_id` is a "**foreign key**".
|
In the table for heroes we have a **primary key** that is the `id`. But we also have another column `team_id` that refers to a **key** in a **foreign** table. There's a technical term for that too, the `team_id` is a "**foreign key**".
|
||||||
|
|
||||||
@@ -274,7 +283,7 @@ The language is called **SQL**, the name comes from for **Structured Query Langu
|
|||||||
|
|
||||||
Nevertheless, the language is not only used to *query* for data. It is also used to create records/rows, to update them, to delete them. And to manipulate the database, create tables, etc.
|
Nevertheless, the language is not only used to *query* for data. It is also used to create records/rows, to update them, to delete them. And to manipulate the database, create tables, etc.
|
||||||
|
|
||||||
This language is supported by all these databases that handle multiple tables, that's why they are called **SQL Databases**. Although, each database has small variations in the SQL language they support.
|
This language is supported by all these databases that handle multiple tables, that's why they are called **SQL Databases**. Although, each database has small variations in the SQL language they support (*dialect*).
|
||||||
|
|
||||||
Let's imagine that the table holding the heroes is called the `hero` table. An example of a SQL query to get all the data from it could look like:
|
Let's imagine that the table holding the heroes is called the `hero` table. An example of a SQL query to get all the data from it could look like:
|
||||||
|
|
||||||
@@ -308,8 +317,11 @@ Next, it receives the data and puts it in Python objects that you can continue t
|
|||||||
|
|
||||||
I'll tell you more about SQL, SQLModel, how to use them, and how they are related in the next sections.
|
I'll tell you more about SQL, SQLModel, how to use them, and how they are related in the next sections.
|
||||||
|
|
||||||
!!! info "Technical Details"
|
/// info | Technical Details
|
||||||
SQLModel is built on top of SQLAlchemy. It is, in fact, just <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> and <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a> mixed together with some sugar on top.
|
|
||||||
|
SQLModel is built on top of SQLAlchemy. It is, in fact, just <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> and <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a> mixed together with some sugar on top.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## NoSQL Databases
|
## NoSQL Databases
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ The user is probably, in some way, telling your application:
|
|||||||
2
|
2
|
||||||
```
|
```
|
||||||
|
|
||||||
And the would be this table (with a single row):
|
And the result would be this table (with a single row):
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -111,7 +111,7 @@ DROP TABLE hero;
|
|||||||
|
|
||||||
That is how you tell the database in SQL to delete the entire table `hero`.
|
That is how you tell the database in SQL to delete the entire table `hero`.
|
||||||
|
|
||||||
<a href="http://www.nooooooooooooooo.com/" class="external-link" target="_blank">Nooooo!</a> We lost all the data in the `hero` table! 💥😱
|
<a href="https://theuselessweb.site/nooooooooooooooo/" class="external-link" target="_blank">Nooooo!</a> We lost all the data in the `hero` table! 💥😱
|
||||||
|
|
||||||
### SQL Sanitization
|
### SQL Sanitization
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ If the user provides this ID:
|
|||||||
2
|
2
|
||||||
```
|
```
|
||||||
|
|
||||||
...the would be this table (with a single row):
|
...the result would be this table (with a single row):
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -172,8 +172,11 @@ The difference in the final SQL statement is subtle, but it changes the meaning
|
|||||||
SELECT * FROM hero WHERE id = "2; DROP TABLE hero;";
|
SELECT * FROM hero WHERE id = "2; DROP TABLE hero;";
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
Notice the double quotes (`"`) making it a string instead of more raw SQL.
|
|
||||||
|
Notice the double quotes (`"`) making it a string instead of more raw SQL.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
The database will not find any record with that ID:
|
The database will not find any record with that ID:
|
||||||
|
|
||||||
@@ -187,8 +190,11 @@ Then your code will continue to execute and calmly tell the user that it couldn'
|
|||||||
|
|
||||||
But we never deleted the `hero` table. 🎉
|
But we never deleted the `hero` table. 🎉
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
Of course, there are also other ways to do SQL data sanitization without using a tool like **SQLModel**, but it's still a nice feature you get by default.
|
|
||||||
|
Of course, there are also other ways to do SQL data sanitization without using a tool like **SQLModel**, but it's still a nice feature you get by default.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
### Editor Support
|
### Editor Support
|
||||||
|
|
||||||
@@ -230,8 +236,7 @@ database.execute(
|
|||||||
).all()
|
).all()
|
||||||
```
|
```
|
||||||
|
|
||||||
<img class="shadow" src="/img/db-to-code/autocompletion01.png">
|
{class="shadow"}
|
||||||
|
|
||||||
|
|
||||||
## ORMs and SQL
|
## ORMs and SQL
|
||||||
|
|
||||||
@@ -274,7 +279,7 @@ For example this **Relation** or table:
|
|||||||
|
|
||||||
* **Mapper**: this comes from Math, when there's something that can convert from some set of things to another, that's called a "**mapping function**". That's where the **Mapper** comes from.
|
* **Mapper**: this comes from Math, when there's something that can convert from some set of things to another, that's called a "**mapping function**". That's where the **Mapper** comes from.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
We could also write a **mapping function** in Python that converts from the *set of lowercase letters* to the *set of uppercase letters*, like this:
|
We could also write a **mapping function** in Python that converts from the *set of lowercase letters* to the *set of uppercase letters*, like this:
|
||||||
|
|
||||||
@@ -291,8 +296,11 @@ There are many ORMs available apart from **SQLModel**, you can read more about s
|
|||||||
|
|
||||||
## SQL Table Names
|
## SQL Table Names
|
||||||
|
|
||||||
!!! info "Technical Background"
|
/// info | Technical Background
|
||||||
This is a bit of boring background for SQL purists. Feel free to skip this section. 😉
|
|
||||||
|
This is a bit of boring background for SQL purists. Feel free to skip this section. 😉
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
When working with pure SQL, it's common to name the tables in plural. So, the table would be named `heroes` instead of `hero`, because it could contain multiple rows, each with one hero.
|
When working with pure SQL, it's common to name the tables in plural. So, the table would be named `heroes` instead of `hero`, because it could contain multiple rows, each with one hero.
|
||||||
|
|
||||||
@@ -304,5 +312,8 @@ You will see **your own code** a lot more than the internal table names, so it's
|
|||||||
|
|
||||||
So, to keep things consistent, I'll keep using the same table names that **SQLModel** would have generated.
|
So, to keep things consistent, I'll keep using the same table names that **SQLModel** would have generated.
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
You can also override the table name. You can read about it in the Advanced User Guide.
|
|
||||||
|
You can also override the table name. You can read about it in the Advanced User Guide.
|
||||||
|
|
||||||
|
///
|
||||||
|
|||||||
300
docs/environment-variables.md
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
# Environment Variables
|
||||||
|
|
||||||
|
Before we jump into code, let's cover a bit some of the **basics** that we'll need to understand how to work with Python (and programming) in general. Let's check a bit about **environment variables**.
|
||||||
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
If you already know what "environment variables" are and how to use them, feel free to skip this.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
An environment variable (also known as "**env var**") is a variable that lives **outside** of the Python code, in the **operating system**, and could be read by your Python code (or by other programs as well).
|
||||||
|
|
||||||
|
Environment variables could be useful for handling application **settings**, as part of the **installation** of Python, etc.
|
||||||
|
|
||||||
|
## Create and Use Env Vars
|
||||||
|
|
||||||
|
You can **create** and use environment variables in the **shell (terminal)**, without needing Python:
|
||||||
|
|
||||||
|
//// tab | Linux, macOS, Windows Bash
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
// You could create an env var MY_NAME with
|
||||||
|
$ export MY_NAME="Wade Wilson"
|
||||||
|
|
||||||
|
// Then you could use it with other programs, like
|
||||||
|
$ echo "Hello $MY_NAME"
|
||||||
|
|
||||||
|
Hello Wade Wilson
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
//// tab | Windows PowerShell
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
// Create an env var MY_NAME
|
||||||
|
$ $Env:MY_NAME = "Wade Wilson"
|
||||||
|
|
||||||
|
// Use it with other programs, like
|
||||||
|
$ echo "Hello $Env:MY_NAME"
|
||||||
|
|
||||||
|
Hello Wade Wilson
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
## Read env vars in Python
|
||||||
|
|
||||||
|
You could also create environment variables **outside** of Python, in the terminal (or with any other method), and then **read them in Python**.
|
||||||
|
|
||||||
|
For example you could have a file `main.py` with:
|
||||||
|
|
||||||
|
```Python hl_lines="3"
|
||||||
|
import os
|
||||||
|
|
||||||
|
name = os.getenv("MY_NAME", "World")
|
||||||
|
print(f"Hello {name} from Python")
|
||||||
|
```
|
||||||
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
The second argument to <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> is the default value to return.
|
||||||
|
|
||||||
|
If not provided, it's `None` by default, here we provide `"World"` as the default value to use.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
Then you could call that Python program:
|
||||||
|
|
||||||
|
//// tab | Linux, macOS, Windows Bash
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
// Here we don't set the env var yet
|
||||||
|
$ python main.py
|
||||||
|
|
||||||
|
// As we didn't set the env var, we get the default value
|
||||||
|
|
||||||
|
Hello World from Python
|
||||||
|
|
||||||
|
// But if we create an environment variable first
|
||||||
|
$ export MY_NAME="Wade Wilson"
|
||||||
|
|
||||||
|
// And then call the program again
|
||||||
|
$ python main.py
|
||||||
|
|
||||||
|
// Now it can read the environment variable
|
||||||
|
|
||||||
|
Hello Wade Wilson from Python
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
//// tab | Windows PowerShell
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
// Here we don't set the env var yet
|
||||||
|
$ python main.py
|
||||||
|
|
||||||
|
// As we didn't set the env var, we get the default value
|
||||||
|
|
||||||
|
Hello World from Python
|
||||||
|
|
||||||
|
// But if we create an environment variable first
|
||||||
|
$ $Env:MY_NAME = "Wade Wilson"
|
||||||
|
|
||||||
|
// And then call the program again
|
||||||
|
$ python main.py
|
||||||
|
|
||||||
|
// Now it can read the environment variable
|
||||||
|
|
||||||
|
Hello Wade Wilson from Python
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
As environment variables can be set outside of the code, but can be read by the code, and don't have to be stored (committed to `git`) with the rest of the files, it's common to use them for configurations or **settings**.
|
||||||
|
|
||||||
|
You can also create an environment variable only for a **specific program invocation**, that is only available to that program, and only for its duration.
|
||||||
|
|
||||||
|
To do that, create it right before the program itself, on the same line:
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
// Create an env var MY_NAME in line for this program call
|
||||||
|
$ MY_NAME="Wade Wilson" python main.py
|
||||||
|
|
||||||
|
// Now it can read the environment variable
|
||||||
|
|
||||||
|
Hello Wade Wilson from Python
|
||||||
|
|
||||||
|
// The env var no longer exists afterwards
|
||||||
|
$ python main.py
|
||||||
|
|
||||||
|
Hello World from Python
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
You can read more about it at <a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: Config</a>.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
## Types and Validation
|
||||||
|
|
||||||
|
These environment variables can only handle **text strings**, as they are external to Python and have to be compatible with other programs and the rest of the system (and even with different operating systems, as Linux, Windows, macOS).
|
||||||
|
|
||||||
|
That means that **any value** read in Python from an environment variable **will be a `str`**, and any conversion to a different type or any validation has to be done in code.
|
||||||
|
|
||||||
|
## `PATH` Environment Variable
|
||||||
|
|
||||||
|
There is a **special** environment variable called **`PATH`** that is used by the operating systems (Linux, macOS, Windows) to find programs to run.
|
||||||
|
|
||||||
|
The value of the variable `PATH` is a long string that is made of directories separated by a colon `:` on Linux and macOS, and by a semicolon `;` on Windows.
|
||||||
|
|
||||||
|
For example, the `PATH` environment variable could look like this:
|
||||||
|
|
||||||
|
//// tab | Linux, macOS
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
|
||||||
|
```
|
||||||
|
|
||||||
|
This means that the system should look for programs in the directories:
|
||||||
|
|
||||||
|
* `/usr/local/bin`
|
||||||
|
* `/usr/bin`
|
||||||
|
* `/bin`
|
||||||
|
* `/usr/sbin`
|
||||||
|
* `/sbin`
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
//// tab | Windows
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32
|
||||||
|
```
|
||||||
|
|
||||||
|
This means that the system should look for programs in the directories:
|
||||||
|
|
||||||
|
* `C:\Program Files\Python312\Scripts`
|
||||||
|
* `C:\Program Files\Python312`
|
||||||
|
* `C:\Windows\System32`
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
When you type a **command** in the terminal, the operating system **looks for** the program in **each of those directories** listed in the `PATH` environment variable.
|
||||||
|
|
||||||
|
For example, when you type `python` in the terminal, the operating system looks for a program called `python` in the **first directory** in that list.
|
||||||
|
|
||||||
|
If it finds it, then it will **use it**. Otherwise it keeps looking in the **other directories**.
|
||||||
|
|
||||||
|
### Installing Python and Updating the `PATH`
|
||||||
|
|
||||||
|
When you install Python, you might be asked if you want to update the `PATH` environment variable.
|
||||||
|
|
||||||
|
//// tab | Linux, macOS
|
||||||
|
|
||||||
|
Let's say you install Python and it ends up in a directory `/opt/custompython/bin`.
|
||||||
|
|
||||||
|
If you say yes to update the `PATH` environment variable, then the installer will add `/opt/custompython/bin` to the `PATH` environment variable.
|
||||||
|
|
||||||
|
It could look like this:
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one.
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
//// tab | Windows
|
||||||
|
|
||||||
|
Let's say you install Python and it ends up in a directory `C:\opt\custompython\bin`.
|
||||||
|
|
||||||
|
If you say yes to update the `PATH` environment variable, then the installer will add `C:\opt\custompython\bin` to the `PATH` environment variable.
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin
|
||||||
|
```
|
||||||
|
|
||||||
|
This way, when you type `python` in the terminal, the system will find the Python program in `C:\opt\custompython\bin` (the last directory) and use that one.
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one.
|
||||||
|
|
||||||
|
So, if you type:
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ python
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
//// tab | Linux, macOS
|
||||||
|
|
||||||
|
The system will **find** the `python` program in `/opt/custompython/bin` and run it.
|
||||||
|
|
||||||
|
It would be roughly equivalent to typing:
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ /opt/custompython/bin/python
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
//// tab | Windows
|
||||||
|
|
||||||
|
The system will **find** the `python` program in `C:\opt\custompython\bin\python` and run it.
|
||||||
|
|
||||||
|
It would be roughly equivalent to typing:
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ C:\opt\custompython\bin\python
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
This information will be useful when learning about [Virtual Environments](virtual-environments.md){.internal-link target=_blank}.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
With this you should have a basic understanding of what **environment variables** are and how to use them in Python.
|
||||||
|
|
||||||
|
You can also read more about them in the <a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">Wikipedia for Environment Variable</a>.
|
||||||
|
|
||||||
|
In many cases it's not very obvious how environment variables would be useful and applicable right away. But they keep showing up in many different scenarios when you are developing, so it's good to know about them.
|
||||||
|
|
||||||
|
For example, you will need this information in the next section, about [Virtual Environments](virtual-environments.md).
|
||||||
@@ -12,7 +12,7 @@ Nevertheless, SQLModel is completely **independent** of FastAPI and can be used
|
|||||||
|
|
||||||
## Just Modern Python
|
## Just Modern Python
|
||||||
|
|
||||||
It's all based on standard <abbr title="Python currently supported versions, 3.6 and above.">modern **Python**</abbr> type annotations. No new syntax to learn. Just standard modern Python.
|
It's all based on standard <abbr title="Currently supported versions of Python">modern **Python**</abbr> type annotations. No new syntax to learn. Just standard modern Python.
|
||||||
|
|
||||||
If you need a 2 minute refresher of how to use Python types (even if you don't use SQLModel or FastAPI), check the FastAPI tutorial section: <a href="https://fastapi.tiangolo.com/python-types/" class="external-link" target="_blank">Python types intro</a>.
|
If you need a 2 minute refresher of how to use Python types (even if you don't use SQLModel or FastAPI), check the FastAPI tutorial section: <a href="https://fastapi.tiangolo.com/python-types/" class="external-link" target="_blank">Python types intro</a>.
|
||||||
|
|
||||||
@@ -36,17 +36,10 @@ You will get completion for everything while writing the **minimum** amount of c
|
|||||||
|
|
||||||
You won't need to keep guessing the types of different attributes in your models, if they could be `None`, etc. Your editor will be able to help you with everything because **SQLModel** is based on **standard Python type annotations**.
|
You won't need to keep guessing the types of different attributes in your models, if they could be `None`, etc. Your editor will be able to help you with everything because **SQLModel** is based on **standard Python type annotations**.
|
||||||
|
|
||||||
**SQLModel** even adopts currently <a href="https://github.com/microsoft/pyright/blob/main/specs/dataclass_transforms.md" class="external-link" target="_blank">in development standards</a> for Python type annotations to ensure the **best developer experience**, so you will get inline errors and autocompletion even while creating new model instances.
|
**SQLModel** adopts <a href="https://peps.python.org/pep-0681/" class="external-link" target="_blank">PEP 681</a> for Python type annotations to ensure the **best developer experience**, so you will get inline errors and autocompletion even while creating new model instances.
|
||||||
|
|
||||||
<img class="shadow" src="/img/index/autocompletion01.png">
|
<img class="shadow" src="/img/index/autocompletion01.png">
|
||||||
|
|
||||||
!!! info
|
|
||||||
Don't worry, adopting this in-development standard only affects/improves editor support.
|
|
||||||
|
|
||||||
It doesn't affect performance or correctness. And if the in-progress standard was deprecated your code won't be affected.
|
|
||||||
|
|
||||||
Meanwhile, you will get inline errors (like type checks) and autocompletion on places you wouldn't get with any other library. 🎉
|
|
||||||
|
|
||||||
## Short
|
## Short
|
||||||
|
|
||||||
**SQLModel** has **sensible defaults** for everything, with **optional configurations** everywhere.
|
**SQLModel** has **sensible defaults** for everything, with **optional configurations** everywhere.
|
||||||
|
|||||||
166
docs/help.md
@@ -12,7 +12,7 @@ And there are several ways to get help too.
|
|||||||
|
|
||||||
## Subscribe to the FastAPI and Friends newsletter
|
## Subscribe to the FastAPI and Friends newsletter
|
||||||
|
|
||||||
You can subscribe to the (infrequent) [**FastAPI and friends** newsletter](/newsletter/){.internal-link target=_blank} to stay updated about:
|
You can subscribe to the (infrequent) <a href="https://fastapi.tiangolo.com/newsletter" class="external-link" target="_blank">**FastAPI and friends** newsletter</a> to stay updated about:
|
||||||
|
|
||||||
* News about FastAPI and friends, including SQLModel 🚀
|
* News about FastAPI and friends, including SQLModel 🚀
|
||||||
* Guides 📝
|
* Guides 📝
|
||||||
@@ -22,13 +22,13 @@ You can subscribe to the (infrequent) [**FastAPI and friends** newsletter](/news
|
|||||||
|
|
||||||
## Star **SQLModel** in GitHub
|
## Star **SQLModel** in GitHub
|
||||||
|
|
||||||
You can "star" SQLModel in GitHub (clicking the star button at the top right): <a href="https://github.com/tiangolo/sqlmodel" class="external-link" target="_blank">https://github.com/tiangolo/sqlmodel</a>. ⭐️
|
You can "star" SQLModel in GitHub (clicking the star button at the top right): <a href="https://github.com/fastapi/sqlmodel" class="external-link" target="_blank">https://github.com/fastapi/sqlmodel</a>. ⭐️
|
||||||
|
|
||||||
By adding a star, other users will be able to find it more easily and see that it has been already useful for others.
|
By adding a star, other users will be able to find it more easily and see that it has been already useful for others.
|
||||||
|
|
||||||
## Watch the GitHub repository for releases
|
## Watch the GitHub repository for releases
|
||||||
|
|
||||||
You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/tiangolo/sqlmodel" class="external-link" target="_blank">https://github.com/tiangolo/sqlmodel</a>. 👀
|
You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/fastapi/sqlmodel" class="external-link" target="_blank">https://github.com/fastapi/sqlmodel</a>. 👀
|
||||||
|
|
||||||
There you can select "Releases only".
|
There you can select "Releases only".
|
||||||
|
|
||||||
@@ -54,30 +54,132 @@ You can:
|
|||||||
|
|
||||||
## Tweet about **SQLModel**
|
## Tweet about **SQLModel**
|
||||||
|
|
||||||
<a href="https://twitter.com/compose/tweet?text=I'm loving SQLModel because... https://github.com/tiangolo/sqlmodel cc: @tiangolo" class="external-link" target="_blank">Tweet about **SQLModel**</a> and let me and others know why you like it. 🎉
|
<a href="https://twitter.com/compose/tweet?text=I'm loving SQLModel because... https://github.com/fastapi/sqlmodel cc: @tiangolo" class="external-link" target="_blank">Tweet about **SQLModel**</a> and let me and others know why you like it. 🎉
|
||||||
|
|
||||||
I love to hear about how **SQLModel** is being used, what you have liked in it, in which project/company are you using it, etc.
|
I love to hear about how **SQLModel** is being used, what you have liked in it, in which project/company are you using it, etc.
|
||||||
|
|
||||||
## Help others with issues in GitHub
|
## Help others with questions in GitHub
|
||||||
|
|
||||||
You can see <a href="https://github.com/tiangolo/sqlmodel/issues" class="external-link" target="_blank">existing issues</a> and try and help others, most of the times they are questions that you might already know the answer for. 🤓
|
You can try and help others with their questions in:
|
||||||
|
|
||||||
|
* <a href="https://github.com/fastapi/sqlmodel/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub Discussions</a>
|
||||||
|
* <a href="https://github.com/fastapi/sqlmodel/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub Issues</a>
|
||||||
|
|
||||||
|
In many cases you might already know the answer for those questions. 🤓
|
||||||
|
|
||||||
|
Just remember, the most important point is: try to be kind. People come with their frustrations and in many cases don't ask in the best way, but try as best as you can to be kind. 🤗
|
||||||
|
|
||||||
|
The idea is for the **SQLModel** community to be kind and welcoming. At the same time, don't accept bullying or disrespectful behavior towards others. We have to take care of each other.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Here's how to help others with questions (in discussions or issues):
|
||||||
|
|
||||||
|
### Understand the question
|
||||||
|
|
||||||
|
* Check if you can understand what is the **purpose** and use case of the person asking.
|
||||||
|
|
||||||
|
* Then check if the question (the vast majority are questions) is **clear**.
|
||||||
|
|
||||||
|
* In many cases the question asked is about an imaginary solution from the user, but there might be a **better** one. If you can understand the problem and use case better, you might be able to suggest a better **alternative solution**.
|
||||||
|
|
||||||
|
* If you can't understand the question, ask for more **details**.
|
||||||
|
|
||||||
|
### Reproduce the problem
|
||||||
|
|
||||||
|
For most of the cases and most of the questions there's something related to the person's **original code**.
|
||||||
|
|
||||||
|
In many cases they will only copy a fragment of the code, but that's not enough to **reproduce the problem**.
|
||||||
|
|
||||||
|
* You can ask them to provide a <a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">minimal, reproducible, example</a>, that you can **copy-paste** and run locally to see the same error or behavior they are seeing, or to understand their use case better.
|
||||||
|
|
||||||
|
* If you are feeling too generous, you can try to **create an example** like that yourself, just based on the description of the problem. Just have in mind that this might take a lot of time and it might be better to ask them to clarify the problem first.
|
||||||
|
|
||||||
|
### Suggest solutions
|
||||||
|
|
||||||
|
* After being able to understand the question, you can give them a possible **answer**.
|
||||||
|
|
||||||
|
* In many cases, it's better to understand their **underlying problem or use case**, because there might be a better way to solve it than what they are trying to do.
|
||||||
|
|
||||||
|
### Ask to close
|
||||||
|
|
||||||
|
If they reply, there's a high chance you would have solved their problem, congrats, **you're a hero**! 🦸
|
||||||
|
|
||||||
|
* Now, if that solved their problem, you can ask them to:
|
||||||
|
|
||||||
|
* In GitHub Discussions: mark the comment as the **answer**.
|
||||||
|
* In GitHub Issues: **close** the issue**.
|
||||||
|
|
||||||
## Watch the GitHub repository
|
## Watch the GitHub repository
|
||||||
|
|
||||||
You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/tiangolo/sqlmodel" class="external-link" target="_blank">https://github.com/tiangolo/sqlmodel</a>. 👀
|
You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/fastapi/sqlmodel" class="external-link" target="_blank">https://github.com/fastapi/sqlmodel</a>. 👀
|
||||||
|
|
||||||
If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue.
|
If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue or question. You can also specify that you only want to be notified about new issues, or discussions, or PRs, etc.
|
||||||
|
|
||||||
Then you can try and help them solve those issues.
|
Then you can try and help them solve those questions.
|
||||||
|
|
||||||
## Create issues
|
## Ask Questions
|
||||||
|
|
||||||
You can <a href="https://github.com/tiangolo/sqlmodel/issues/new/choose" class="external-link" target="_blank">create a new issue</a> in the GitHub repository, for example to:
|
You can <a href="https://github.com/fastapi/sqlmodel/discussions/new?category=questions" class="external-link" target="_blank">create a new question</a> in the GitHub repository, for example to:
|
||||||
|
|
||||||
* Ask a **question** or ask about a **problem**.
|
* Ask a **question** or ask about a **problem**.
|
||||||
* Suggest a new **feature**.
|
* Suggest a new **feature**.
|
||||||
|
|
||||||
**Note**: if you create an issue, then I'm going to ask you to also help others. 😉
|
**Note**: if you do it, then I'm going to ask you to also help others. 😉
|
||||||
|
|
||||||
|
## Review Pull Requests
|
||||||
|
|
||||||
|
You can help me review pull requests from others.
|
||||||
|
|
||||||
|
Again, please try your best to be kind. 🤗
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Here's what to have in mind and how to review a pull request:
|
||||||
|
|
||||||
|
### Understand the problem
|
||||||
|
|
||||||
|
* First, make sure you **understand the problem** that the pull request is trying to solve. It might have a longer discussion in a GitHub Discussion or issue.
|
||||||
|
|
||||||
|
* There's also a good chance that the pull request is not actually needed because the problem can be solved in a **different way**. Then you can suggest or ask about that.
|
||||||
|
|
||||||
|
### Don't worry about style
|
||||||
|
|
||||||
|
* Don't worry too much about things like commit message styles, I will squash and merge customizing the commit manually.
|
||||||
|
|
||||||
|
* Also don't worry about style rules, there are already automatized tools checking that.
|
||||||
|
|
||||||
|
And if there's any other style or consistency need, I'll ask directly for that, or I'll add commits on top with the needed changes.
|
||||||
|
|
||||||
|
### Check the code
|
||||||
|
|
||||||
|
* Check and read the code, see if it makes sense, **run it locally** and see if it actually solves the problem.
|
||||||
|
|
||||||
|
* Then **comment** saying that you did that, that's how I will know you really checked it.
|
||||||
|
|
||||||
|
/// info
|
||||||
|
|
||||||
|
Unfortunately, I can't simply trust PRs that just have several approvals.
|
||||||
|
|
||||||
|
Several times it has happened that there are PRs with 3, 5 or more approvals, probably because the description is appealing, but when I check the PRs, they are actually broken, have a bug, or don't solve the problem they claim to solve. 😅
|
||||||
|
|
||||||
|
So, it's really important that you actually read and run the code, and let me know in the comments that you did. 🤓
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
* If the PR can be simplified in a way, you can ask for that, but there's no need to be too picky, there might be a lot of subjective points of view (and I will have my own as well 🙈), so it's better if you can focus on the fundamental things.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
* Help me check that the PR has **tests**.
|
||||||
|
|
||||||
|
* Check that the tests **fail** before the PR. 🚨
|
||||||
|
|
||||||
|
* Then check that the tests **pass** after the PR. ✅
|
||||||
|
|
||||||
|
* Many PRs don't have tests, you can **remind** them to add tests, or you can even **suggest** some tests yourself. That's one of the things that consume most time and you can help a lot with that.
|
||||||
|
|
||||||
|
* Then also comment what you tried, that way I'll know that you checked it. 🤓
|
||||||
|
|
||||||
## Create a Pull Request
|
## Create a Pull Request
|
||||||
|
|
||||||
@@ -86,7 +188,47 @@ You can [contribute](contributing.md){.internal-link target=_blank} to the sourc
|
|||||||
* To fix a typo you found on the documentation.
|
* To fix a typo you found on the documentation.
|
||||||
* To propose new documentation sections.
|
* To propose new documentation sections.
|
||||||
* To fix an existing issue/bug.
|
* To fix an existing issue/bug.
|
||||||
|
* Make sure to add tests.
|
||||||
* To add a new feature.
|
* To add a new feature.
|
||||||
|
* Make sure to add tests.
|
||||||
|
* Make sure to add documentation if it's relevant.
|
||||||
|
|
||||||
|
## Help Maintain SQLModel
|
||||||
|
|
||||||
|
Help me maintain **SQLModel**! 🤓
|
||||||
|
|
||||||
|
There's a lot of work to do, and for most of it, **YOU** can do it.
|
||||||
|
|
||||||
|
The main tasks that you can do right now are:
|
||||||
|
|
||||||
|
* [Help others with questions in GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (see the section above).
|
||||||
|
* [Review Pull Requests](#review-pull-requests){.internal-link target=_blank} (see the section above).
|
||||||
|
|
||||||
|
Those two tasks are what **consume time the most**. That's the main work of maintaining SQLModel.
|
||||||
|
|
||||||
|
If you can help me with that, **you are helping me maintain SQLModel** and making sure it keeps **advancing faster and better**. 🚀
|
||||||
|
|
||||||
|
## Join the chat
|
||||||
|
|
||||||
|
Join the 👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">FastAPI and Friends Discord chat server</a> 👥 and hang out with others in the community. There's a `#sqlmodel` channel.
|
||||||
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
For questions, ask them in <a href="https://github.com/fastapi/sqlmodel/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussions</a>, there's a much better chance you will receive help there.
|
||||||
|
|
||||||
|
Use the chat only for other general conversations.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
### Don't use the chat for questions
|
||||||
|
|
||||||
|
Have in mind that as chats allow more "free conversation", it's easy to ask questions that are too general and more difficult to answer, so, you might not receive answers.
|
||||||
|
|
||||||
|
In GitHub, the template will guide you to write the right question so that you can more easily get a good answer, or even solve the problem yourself even before asking. And in GitHub I can make sure I always answer everything, even if it takes some time. I can't personally do that with the chat. 😅
|
||||||
|
|
||||||
|
Conversations in the chat are also not as easily searchable as in GitHub, so questions and answers might get lost in the conversation.
|
||||||
|
|
||||||
|
On the other side, there are thousands of users in the chat, so there's a high chance you'll find someone to talk to there, almost all the time. 😄
|
||||||
|
|
||||||
## Sponsor the author
|
## Sponsor the author
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
<mxCell id="56" value="<span style="font-family: &#34;roboto&#34; ; font-size: 18px">Z-Force</span>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;strokeColor=none;fillColor=none;" parent="54" vertex="1">
|
<mxCell id="56" value="<span style="font-family: &#34;roboto&#34; ; font-size: 18px">Z-Force</span>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;strokeColor=none;fillColor=none;" parent="54" vertex="1">
|
||||||
<mxGeometry x="50" width="110" height="50" as="geometry"/>
|
<mxGeometry x="50" width="110" height="50" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="57" value="<p style="background-color: rgb(255 , 255 , 255) ; line-height: 19px"><font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">Sister Margaret’s Bar</font></p>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;strokeColor=none;fillColor=none;" parent="54" vertex="1">
|
<mxCell id="57" value="<p style="background-color: rgb(255 , 255 , 255) ; line-height: 19px"><font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">Sister Margaret's Bar</font></p>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;strokeColor=none;fillColor=none;" parent="54" vertex="1">
|
||||||
<mxGeometry x="160" width="200" height="50" as="geometry"/>
|
<mxGeometry x="160" width="200" height="50" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="66" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=2;" parent="1" source="18" target="54" edge="1">
|
<mxCell id="66" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=2;" parent="1" source="18" target="54" edge="1">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 11 KiB |
100
docs/img/logo-margin/logo-margin-white-vector.svg
Normal file
|
After Width: | Height: | Size: 11 KiB |
105
docs/img/logo-margin/logo-margin-white.svg
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
id="svg8"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 848.54461 237.29972"
|
||||||
|
height="237.29971mm"
|
||||||
|
width="848.54462mm"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<text
|
||||||
|
id="text861"
|
||||||
|
y="158.23088"
|
||||||
|
x="204.64526"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:113.462px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu Bold';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.83655"
|
||||||
|
xml:space="preserve"
|
||||||
|
transform="scale(1.0209259,0.979503)"><tspan
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu Bold';fill:#ffffff;fill-opacity:1;stroke-width:2.83655"
|
||||||
|
y="158.23088"
|
||||||
|
x="204.64526"
|
||||||
|
id="tspan859">SQLModel</tspan></text>
|
||||||
|
<g
|
||||||
|
id="g1074"
|
||||||
|
transform="matrix(7.048594,0,0,6.7626049,42.411668,48.341116)">
|
||||||
|
<g
|
||||||
|
id="g1470">
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.27225;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
|
||||||
|
id="rect1457"
|
||||||
|
width="15.627598"
|
||||||
|
height="11.127809"
|
||||||
|
x="3.6129446"
|
||||||
|
y="3.9663849" />
|
||||||
|
<g
|
||||||
|
id="g1309-3"
|
||||||
|
transform="translate(2.3451874e-4,7.0648327)">
|
||||||
|
<path
|
||||||
|
id="path1225-7"
|
||||||
|
style="fill:#7e56c2;fill-opacity:1;stroke:none;stroke-width:1.82079;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
|
||||||
|
d="M 12.164062,0.94726562 C 6.0622484,0.95386183 1.1299602,2.2428239 1.1328125,3.8300781 v 5.7617188 c -0.00281,1.5920051 4.9579969,2.8829331 11.0781255,2.8828121 6.119365,-1.6e-4 11.078978,-1.291006 11.076171,-2.8828121 V 3.8300781 C 23.289909,2.2382718 18.330304,0.94742553 12.210938,0.94726562 Z"
|
||||||
|
transform="matrix(0.70540328,0,0,0.70540328,2.813504,1.2191569)" />
|
||||||
|
<ellipse
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.28439;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
|
||||||
|
id="path1225-2-5"
|
||||||
|
cx="11.426781"
|
||||||
|
cy="3.7396178"
|
||||||
|
rx="7.8139534"
|
||||||
|
ry="2.0326128" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g1309"
|
||||||
|
transform="translate(1.1725937e-4,1.6131871)">
|
||||||
|
<path
|
||||||
|
id="path1225"
|
||||||
|
style="fill:#7e56c2;fill-opacity:1;stroke:none;stroke-width:1.82079;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
|
||||||
|
d="M 12.164062,0.94726562 C 6.0622484,0.95386183 1.1299602,2.2428239 1.1328125,3.8300781 v 5.7617188 c -0.00281,1.5920051 4.9579969,2.8829331 11.0781255,2.8828121 6.119365,-1.6e-4 11.078978,-1.291006 11.076171,-2.8828121 V 3.8300781 C 23.289909,2.2382718 18.330304,0.94742553 12.210938,0.94726562 Z"
|
||||||
|
transform="matrix(0.70540328,0,0,0.70540328,2.813504,1.2191569)" />
|
||||||
|
<ellipse
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.28439;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
|
||||||
|
id="path1225-2"
|
||||||
|
cx="11.426781"
|
||||||
|
cy="3.7396178"
|
||||||
|
rx="7.8139534"
|
||||||
|
ry="2.0326128" />
|
||||||
|
</g>
|
||||||
|
<ellipse
|
||||||
|
style="fill:#7e56c2;fill-opacity:1;stroke:none;stroke-width:1.28439;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
|
||||||
|
id="path1225-2-9"
|
||||||
|
cx="11.426898"
|
||||||
|
cy="3.9663849"
|
||||||
|
rx="7.8139534"
|
||||||
|
ry="2.0326128" />
|
||||||
|
<g
|
||||||
|
id="g1391">
|
||||||
|
<circle
|
||||||
|
style="fill:#ffffff;stroke:none;stroke-width:0.638958;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
|
||||||
|
id="path1149-1-2"
|
||||||
|
cx="16.495272"
|
||||||
|
cy="8.9521942"
|
||||||
|
r="1.128226" />
|
||||||
|
<circle
|
||||||
|
style="fill:#ffffff;stroke:none;stroke-width:0.638958;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
|
||||||
|
id="path1149-6-8-8"
|
||||||
|
cx="16.495272"
|
||||||
|
cy="14.40166"
|
||||||
|
r="1.128226" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -1,39 +1,15 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<svg
|
<svg
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
id="svg8"
|
id="svg8"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
viewBox="0 0 848.54461 237.29972"
|
viewBox="0 0 848.54461 237.29972"
|
||||||
height="237.29971mm"
|
height="237.29971mm"
|
||||||
width="848.54462mm"
|
width="848.54462mm"
|
||||||
sodipodi:docname="logo-margin.svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
inkscape:version="1.0.2 (1.0.2+r75+1)">
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
<sodipodi:namedview
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
pagecolor="#ffffff"
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
bordercolor="#666666"
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="2434"
|
|
||||||
inkscape:window-height="1412"
|
|
||||||
id="namedview22"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:zoom="0.44294931"
|
|
||||||
inkscape:cx="1603.5488"
|
|
||||||
inkscape:cy="448.44039"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:current-layer="svg8" />
|
|
||||||
<defs
|
<defs
|
||||||
id="defs2" />
|
id="defs2" />
|
||||||
<metadata
|
<metadata
|
||||||
@@ -44,17 +20,9 @@
|
|||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:type
|
<dc:type
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
<dc:title />
|
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
</metadata>
|
</metadata>
|
||||||
<rect
|
|
||||||
y="4.2351647e-22"
|
|
||||||
x="0"
|
|
||||||
height="237.29971"
|
|
||||||
width="848.54462"
|
|
||||||
id="rect824"
|
|
||||||
style="opacity:0.98;fill:#ffffff;fill-opacity:1;stroke-width:0.514755" />
|
|
||||||
<g
|
<g
|
||||||
id="g939"
|
id="g939"
|
||||||
transform="translate(-23.453818,-6.0051303)">
|
transform="translate(-23.453818,-6.0051303)">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 4.8 KiB |
BIN
docs/img/sponsors/govcert.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 87 KiB |
97
docs/img/tutorial/indexes/dictionary001.drawio
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
|
||||||
|
<mxGraphModel dx="1463" dy="1403" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="39" value="<font style="font-size: 24px">A</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="40" value="<font style="font-size: 24px">B</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="41" value="<font style="font-size: 24px">C</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="42" value="<font style="font-size: 24px">D</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="43" value="<font style="font-size: 24px">E</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="44" value="<font style="font-size: 24px">F</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45" value="<font style="font-size: 24px">G</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="46" value="<font style="font-size: 24px">H</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="47" value="<font style="font-size: 24px">I</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="48" value="<font style="font-size: 24px">J</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="49" value="<font style="font-size: 24px">K</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="50" value="<font style="font-size: 24px">L</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" value="<font style="font-size: 24px">M</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="52" value="<font style="font-size: 24px">N</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="53" value="<font style="font-size: 24px">O</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="54" value="<font style="font-size: 24px">P</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="55" value="<font style="font-size: 24px">Q</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="56" value="<font style="font-size: 24px">R</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="57" value="<font style="font-size: 24px">S</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="58" value="<font style="font-size: 24px">T</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="59" value="<font style="font-size: 24px">U</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" value="<font style="font-size: 24px">V</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="<font style="font-size: 24px">W</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="62" value="<font style="font-size: 24px">X</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="63" value="<font style="font-size: 24px">Y</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="64" value="<font style="font-size: 24px">Z</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="66" value="<font style="font-size: 24px">Dictionary</font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="68" value="<font style="font-size: 24px">M</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="580" y="1030" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
57
docs/img/tutorial/indexes/dictionary001.svg
Normal file
|
After Width: | Height: | Size: 30 KiB |
97
docs/img/tutorial/indexes/dictionary002.drawio
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
|
||||||
|
<mxGraphModel dx="1707" dy="1637" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="39" value="<font style="font-size: 24px">A</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="40" value="<font style="font-size: 24px">B</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="41" value="<font style="font-size: 24px">C</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="42" value="<font style="font-size: 24px">D</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeWidth=3;strokeColor=#b85450;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="43" value="<font style="font-size: 24px">E</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="44" value="<font style="font-size: 24px">F</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45" value="<font style="font-size: 24px">G</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="46" value="<font style="font-size: 24px">H</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="47" value="<font style="font-size: 24px">I</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="48" value="<font style="font-size: 24px">J</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="49" value="<font style="font-size: 24px">K</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="50" value="<font style="font-size: 24px">L</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" value="<font style="font-size: 24px">M</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="52" value="<font style="font-size: 24px">N</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="53" value="<font style="font-size: 24px">O</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="54" value="<font style="font-size: 24px">P</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="55" value="<font style="font-size: 24px">Q</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="56" value="<font style="font-size: 24px">R</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="57" value="<font style="font-size: 24px">S</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="58" value="<font style="font-size: 24px">T</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="59" value="<font style="font-size: 24px">U</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" value="<font style="font-size: 24px">V</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="<font style="font-size: 24px">W</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="62" value="<font style="font-size: 24px">X</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="63" value="<font style="font-size: 24px">Y</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="64" value="<font style="font-size: 24px">Z</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="66" value="<font style="font-size: 24px">Dictionary</font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="68" value="<font style="font-size: 24px">M</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="580" y="1030" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
1
docs/img/tutorial/indexes/dictionary002.svg
Normal file
|
After Width: | Height: | Size: 28 KiB |
97
docs/img/tutorial/indexes/dictionary003.drawio
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
|
||||||
|
<mxGraphModel dx="1205" dy="1155" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="39" value="<font style="font-size: 24px">A</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="40" value="<font style="font-size: 24px">B</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="41" value="<font style="font-size: 24px">C</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="42" value="<font style="font-size: 24px">D</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeWidth=3;strokeColor=#b85450;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="43" value="<font style="font-size: 24px">E</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="44" value="<font style="font-size: 24px">F</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45" value="<font style="font-size: 24px">G</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="46" value="<font style="font-size: 24px">H</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="47" value="<font style="font-size: 24px">I</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="48" value="<font style="font-size: 24px">J</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="49" value="<font style="font-size: 24px">K</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="50" value="<font style="font-size: 24px">L</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" value="<font style="font-size: 24px">M</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="52" value="<font style="font-size: 24px">N</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="53" value="<font style="font-size: 24px">O</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="54" value="<font style="font-size: 24px">P</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="55" value="<font style="font-size: 24px">Q</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="56" value="<font style="font-size: 24px">R</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="57" value="<font style="font-size: 24px">S</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="58" value="<font style="font-size: 24px">T</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="59" value="<font style="font-size: 24px">U</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" value="<font style="font-size: 24px">V</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="<font style="font-size: 24px">W</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="62" value="<font style="font-size: 24px">X</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="63" value="<font style="font-size: 24px">Y</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="64" value="<font style="font-size: 24px">Z</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="66" value="<font style="font-size: 24px">Dictionary</font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="69" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="580" y="1030" width="560" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
1
docs/img/tutorial/indexes/dictionary003.svg
Normal file
|
After Width: | Height: | Size: 27 KiB |
100
docs/img/tutorial/indexes/dictionary004.drawio
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
|
||||||
|
<mxGraphModel dx="1463" dy="1403" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="39" value="<font style="font-size: 24px">A</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="40" value="<font style="font-size: 24px">B</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="41" value="<font style="font-size: 24px">C</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="42" value="<font style="font-size: 24px">D</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="43" value="<font style="font-size: 24px">E</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="44" value="<font style="font-size: 24px">F</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45" value="<font style="font-size: 24px">G</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="46" value="<font style="font-size: 24px">H</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="47" value="<font style="font-size: 24px">I</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="48" value="<font style="font-size: 24px">J</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="49" value="<font style="font-size: 24px">K</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="50" value="<font style="font-size: 24px">L</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" value="<font style="font-size: 24px">M</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="52" value="<font style="font-size: 24px">N</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="53" value="<font style="font-size: 24px">O</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="54" value="<font style="font-size: 24px">P</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="55" value="<font style="font-size: 24px">Q</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="56" value="<font style="font-size: 24px">R</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="57" value="<font style="font-size: 24px">S</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="58" value="<font style="font-size: 24px">T</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="59" value="<font style="font-size: 24px">U</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" value="<font style="font-size: 24px">V</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="<font style="font-size: 24px">W</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="62" value="<font style="font-size: 24px">X</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="63" value="<font style="font-size: 24px">Y</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="64" value="<font style="font-size: 24px">Z</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="66" value="<font style="font-size: 24px">Dictionary</font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="68" value="<font style="font-size: 24px">F</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="300" y="1030" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="69" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="580" y="1030" width="560" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
1
docs/img/tutorial/indexes/dictionary004.svg
Normal file
|
After Width: | Height: | Size: 28 KiB |
97
docs/img/tutorial/indexes/dictionary005.drawio
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
|
||||||
|
<mxGraphModel dx="1024" dy="982" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="39" value="<font style="font-size: 24px">A</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="40" value="<font style="font-size: 24px">B</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="41" value="<font style="font-size: 24px">C</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="42" value="<font style="font-size: 24px">D</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeWidth=3;strokeColor=#b85450;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="43" value="<font style="font-size: 24px">E</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="44" value="<font style="font-size: 24px">F</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45" value="<font style="font-size: 24px">G</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="46" value="<font style="font-size: 24px">H</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="47" value="<font style="font-size: 24px">I</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="48" value="<font style="font-size: 24px">J</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="49" value="<font style="font-size: 24px">K</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="50" value="<font style="font-size: 24px">L</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" value="<font style="font-size: 24px">M</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="52" value="<font style="font-size: 24px">N</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="53" value="<font style="font-size: 24px">O</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="54" value="<font style="font-size: 24px">P</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="55" value="<font style="font-size: 24px">Q</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="56" value="<font style="font-size: 24px">R</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="57" value="<font style="font-size: 24px">S</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="58" value="<font style="font-size: 24px">T</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="59" value="<font style="font-size: 24px">U</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" value="<font style="font-size: 24px">V</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="<font style="font-size: 24px">W</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="62" value="<font style="font-size: 24px">X</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="63" value="<font style="font-size: 24px">Y</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="64" value="<font style="font-size: 24px">Z</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="66" value="<font style="font-size: 24px">Dictionary</font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="69" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="300" y="1030" width="840" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
1
docs/img/tutorial/indexes/dictionary005.svg
Normal file
|
After Width: | Height: | Size: 27 KiB |
100
docs/img/tutorial/indexes/dictionary006.drawio
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
|
||||||
|
<mxGraphModel dx="1024" dy="982" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="39" value="<font style="font-size: 24px">A</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="40" value="<font style="font-size: 24px">B</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="41" value="<font style="font-size: 24px">C</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="42" value="<font style="font-size: 24px">D</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="43" value="<font style="font-size: 24px">E</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="44" value="<font style="font-size: 24px">F</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45" value="<font style="font-size: 24px">G</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="46" value="<font style="font-size: 24px">H</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="47" value="<font style="font-size: 24px">I</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="48" value="<font style="font-size: 24px">J</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="49" value="<font style="font-size: 24px">K</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="50" value="<font style="font-size: 24px">L</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" value="<font style="font-size: 24px">M</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="52" value="<font style="font-size: 24px">N</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="53" value="<font style="font-size: 24px">O</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="54" value="<font style="font-size: 24px">P</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="55" value="<font style="font-size: 24px">Q</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="56" value="<font style="font-size: 24px">R</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="57" value="<font style="font-size: 24px">S</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="58" value="<font style="font-size: 24px">T</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="59" value="<font style="font-size: 24px">U</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" value="<font style="font-size: 24px">V</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="<font style="font-size: 24px">W</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="62" value="<font style="font-size: 24px">X</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="63" value="<font style="font-size: 24px">Y</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="64" value="<font style="font-size: 24px">Z</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="66" value="<font style="font-size: 24px">Dictionary</font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="69" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="300" y="1030" width="840" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="70" value="<font style="font-size: 24px">C</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="180" y="1030" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
1
docs/img/tutorial/indexes/dictionary006.svg
Normal file
|
After Width: | Height: | Size: 28 KiB |
100
docs/img/tutorial/indexes/dictionary007.drawio
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
|
||||||
|
<mxGraphModel dx="1024" dy="982" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="39" value="<font style="font-size: 24px">A</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="40" value="<font style="font-size: 24px">B</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="41" value="<font style="font-size: 24px">C</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="42" value="<font style="font-size: 24px">D</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeWidth=3;strokeColor=#b85450;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="43" value="<font style="font-size: 24px">E</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="44" value="<font style="font-size: 24px">F</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45" value="<font style="font-size: 24px">G</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="46" value="<font style="font-size: 24px">H</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="47" value="<font style="font-size: 24px">I</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="48" value="<font style="font-size: 24px">J</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="49" value="<font style="font-size: 24px">K</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="50" value="<font style="font-size: 24px">L</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" value="<font style="font-size: 24px">M</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="52" value="<font style="font-size: 24px">N</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="53" value="<font style="font-size: 24px">O</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="54" value="<font style="font-size: 24px">P</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="55" value="<font style="font-size: 24px">Q</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="56" value="<font style="font-size: 24px">R</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="57" value="<font style="font-size: 24px">S</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="58" value="<font style="font-size: 24px">T</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="59" value="<font style="font-size: 24px">U</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" value="<font style="font-size: 24px">V</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="<font style="font-size: 24px">W</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="62" value="<font style="font-size: 24px">X</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="63" value="<font style="font-size: 24px">Y</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="64" value="<font style="font-size: 24px">Z</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="66" value="<font style="font-size: 24px">Dictionary</font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="69" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="300" y="1030" width="840" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="71" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="100" y="1030" width="120" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
1
docs/img/tutorial/indexes/dictionary007.svg
Normal file
|
After Width: | Height: | Size: 27 KiB |
103
docs/img/tutorial/indexes/dictionary008.drawio
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
|
||||||
|
<mxGraphModel dx="1024" dy="982" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="950" width="1040" height="160" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="39" value="<font style="font-size: 24px">A</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="40" value="<font style="font-size: 24px">B</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="140" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="41" value="<font style="font-size: 24px">C</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="180" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="42" value="<font style="font-size: 24px">D</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeWidth=3;strokeColor=#82b366;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="220" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="43" value="<font style="font-size: 24px">E</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="260" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="44" value="<font style="font-size: 24px">F</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="300" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45" value="<font style="font-size: 24px">G</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="340" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="46" value="<font style="font-size: 24px">H</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="380" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="47" value="<font style="font-size: 24px">I</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="420" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="48" value="<font style="font-size: 24px">J</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="460" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="49" value="<font style="font-size: 24px">K</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="500" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="50" value="<font style="font-size: 24px">L</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="540" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" value="<font style="font-size: 24px">M</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="580" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="52" value="<font style="font-size: 24px">N</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="620" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="53" value="<font style="font-size: 24px">O</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="660" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="54" value="<font style="font-size: 24px">P</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="700" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="55" value="<font style="font-size: 24px">Q</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="740" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="56" value="<font style="font-size: 24px">R</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="780" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="57" value="<font style="font-size: 24px">S</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="820" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="58" value="<font style="font-size: 24px">T</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="860" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="59" value="<font style="font-size: 24px">U</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="900" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" value="<font style="font-size: 24px">V</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="940" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="<font style="font-size: 24px">W</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="980" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="62" value="<font style="font-size: 24px">X</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1020" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="63" value="<font style="font-size: 24px">Y</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1060" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="64" value="<font style="font-size: 24px">Z</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="1100" y="840" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="66" value="<font style="font-size: 24px">Dictionary</font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="565" y="960" width="110" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="69" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="260" y="1030" width="880" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="71" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeWidth=3;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="100" y="1030" width="120" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="72" value="<font style="font-size: 24px">D</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeWidth=3;strokeColor=#82b366;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="220" y="1030" width="40" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
1
docs/img/tutorial/indexes/dictionary008.svg
Normal file
|
After Width: | Height: | Size: 28 KiB |
92
docs/img/tutorial/indexes/techbook001.drawio
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="objTApYHlBqCKos3M7rL" name="Page-1">
|
||||||
|
<mxGraphModel dx="1707" dy="1637" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="80" y="420" width="1020" height="490" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="66" value="<font style="font-size: 24px">Technical Book</font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="492.5" y="440" width="195" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="77" value="<font style="font-size: 24px">Chapter 1</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="100" y="490" width="140" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="83" value="<font style="font-size: 24px">Chapter 2</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="240" y="490" width="140" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="84" value="<font style="font-size: 24px">Chapter 3</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="380" y="490" width="140" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="85" value="<font style="font-size: 24px">Chapter 4</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="520" y="490" width="140" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="86" value="<font style="font-size: 24px">Chapter 5</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="660" y="490" width="140" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="87" value="<font style="font-size: 24px">Chapter 6</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="800" y="490" width="140" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="88" value="<font style="font-size: 24px">Chapter 7</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="940" y="490" width="140" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="74" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeWidth=3;strokeColor=#d6b656;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="100" y="580" width="980" height="310" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="75" value="<font style="font-size: 24px">Book Index</font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="498.125" y="840" width="183.75" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="89" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="42" target="77">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="92" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="42" target="86">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="42" value="<font style="font-size: 24px">Database</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="119.99615384615385" y="690" width="150.76923076923077" height="70" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="94" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="93" target="83">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="330" y="820"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="95" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="93" target="85">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="590" y="800"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="93" value="<font style="font-size: 24px">Python</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="909.9961538461539" y="780" width="150.76923076923077" height="70" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="97" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="96" target="87">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="98" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="96" target="86">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="770" y="725"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="96" value="<span style="font-size: 24px">Files</span>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="909.9961538461539" y="690" width="150.76923076923077" height="70" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="100" style="edgeStyle=orthogonalEdgeStyle;html=1;strokeWidth=3;" edge="1" parent="1" source="99" target="84">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="450" y="800"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="99" value="<span style="font-size: 24px">Editors</span>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=3;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="119.9961538461539" y="780" width="150.76923076923077" height="70" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
1
docs/img/tutorial/indexes/techbook001.svg
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -103,7 +103,7 @@
|
|||||||
<mxCell id="56" value="<span style="font-family: &#34;roboto&#34; ; font-size: 18px">Z-Force</span>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="54" vertex="1">
|
<mxCell id="56" value="<span style="font-family: &#34;roboto&#34; ; font-size: 18px">Z-Force</span>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="54" vertex="1">
|
||||||
<mxGeometry x="50" width="110" height="50" as="geometry"/>
|
<mxGeometry x="50" width="110" height="50" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="57" value="<p style="line-height: 19px"><font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">Sister Margaret’s Bar</font></p>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="54" vertex="1">
|
<mxCell id="57" value="<p style="line-height: 19px"><font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">Sister Margaret's Bar</font></p>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="54" vertex="1">
|
||||||
<mxGeometry x="160" width="200" height="50" as="geometry"/>
|
<mxGeometry x="160" width="200" height="50" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="69" value="<font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">heroteamlink</font>" style="shape=table;html=1;whiteSpace=wrap;startSize=30;container=1;collapsible=0;childLayout=tableLayout;fontStyle=1;align=center;fillColor=#FFFFFF;swimlaneFillColor=#ffffff;" vertex="1" parent="1">
|
<mxCell id="69" value="<font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">heroteamlink</font>" style="shape=table;html=1;whiteSpace=wrap;startSize=30;container=1;collapsible=0;childLayout=tableLayout;fontStyle=1;align=center;fillColor=#FFFFFF;swimlaneFillColor=#ffffff;" vertex="1" parent="1">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
@@ -115,7 +115,7 @@
|
|||||||
<mxCell id="56" value="<span style="font-family: &#34;roboto&#34; ; font-size: 18px">Z-Force</span>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;" parent="54" vertex="1">
|
<mxCell id="56" value="<span style="font-family: &#34;roboto&#34; ; font-size: 18px">Z-Force</span>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;" parent="54" vertex="1">
|
||||||
<mxGeometry x="50" width="110" height="50" as="geometry"/>
|
<mxGeometry x="50" width="110" height="50" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="57" value="<p style="background-color: rgb(255 , 255 , 255) ; line-height: 19px"><font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">Sister Margaret’s Bar</font></p>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;" parent="54" vertex="1">
|
<mxCell id="57" value="<p style="background-color: rgb(255 , 255 , 255) ; line-height: 19px"><font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">Sister Margaret's Bar</font></p>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;" parent="54" vertex="1">
|
||||||
<mxGeometry x="160" width="200" height="50" as="geometry"/>
|
<mxGeometry x="160" width="200" height="50" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="66" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=2;" parent="1" source="18" target="54" edge="1">
|
<mxCell id="66" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=2;" parent="1" source="18" target="54" edge="1">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
@@ -1,19 +1,25 @@
|
|||||||
|
<style>
|
||||||
|
.md-content .md-typeset h1 { display: none; }
|
||||||
|
</style>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://sqlmodel.tiangolo.com"><img src="https://sqlmodel.tiangolo.com/img/logo-margin/logo-margin-vector.svg" alt="SQLModel"></a>
|
<a href="https://sqlmodel.tiangolo.com"><img src="https://sqlmodel.tiangolo.com/img/logo-margin/logo-margin-vector.svg#only-light" alt="SQLModel"></a>
|
||||||
|
<!-- only-mkdocs -->
|
||||||
|
<a href="https://sqlmodel.tiangolo.com"><img src="img/logo-margin/logo-margin-white-vector.svg#only-dark" alt="SQLModel"></a>
|
||||||
|
<!-- /only-mkdocs -->
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<em>SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness.</em>
|
<em>SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness.</em>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/tiangolo/sqlmodel/actions?query=workflow%3ATest" target="_blank">
|
<a href="https://github.com/fastapi/sqlmodel/actions?query=workflow%3ATest" target="_blank">
|
||||||
<img src="https://github.com/tiangolo/sqlmodel/workflows/Test/badge.svg" alt="Test">
|
<img src="https://github.com/fastapi/sqlmodel/workflows/Test/badge.svg" alt="Test">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/tiangolo/sqlmodel/actions?query=workflow%3APublish" target="_blank">
|
<a href="https://github.com/fastapi/sqlmodel/actions?query=workflow%3APublish" target="_blank">
|
||||||
<img src="https://github.com/tiangolo/sqlmodel/workflows/Publish/badge.svg" alt="Publish">
|
<img src="https://github.com/fastapi/sqlmodel/workflows/Publish/badge.svg" alt="Publish">
|
||||||
</a>
|
|
||||||
<a href="https://codecov.io/gh/tiangolo/sqlmodel" target="_blank">
|
|
||||||
<img src="https://img.shields.io/codecov/c/github/tiangolo/sqlmodel?color=%2334D058" alt="Coverage">
|
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/sqlmodel" target="_blank">
|
||||||
|
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/sqlmodel.svg" alt="Coverage">
|
||||||
<a href="https://pypi.org/project/sqlmodel" target="_blank">
|
<a href="https://pypi.org/project/sqlmodel" target="_blank">
|
||||||
<img src="https://img.shields.io/pypi/v/sqlmodel?color=%2334D058&label=pypi%20package" alt="Package version">
|
<img src="https://img.shields.io/pypi/v/sqlmodel?color=%2334D058&label=pypi%20package" alt="Package version">
|
||||||
</a>
|
</a>
|
||||||
@@ -23,7 +29,7 @@
|
|||||||
|
|
||||||
**Documentation**: <a href="https://sqlmodel.tiangolo.com" target="_blank">https://sqlmodel.tiangolo.com</a>
|
**Documentation**: <a href="https://sqlmodel.tiangolo.com" target="_blank">https://sqlmodel.tiangolo.com</a>
|
||||||
|
|
||||||
**Source Code**: <a href="https://github.com/tiangolo/sqlmodel" target="_blank">https://github.com/tiangolo/sqlmodel</a>
|
**Source Code**: <a href="https://github.com/fastapi/sqlmodel" target="_blank">https://github.com/fastapi/sqlmodel</a>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -39,6 +45,21 @@ The key features are:
|
|||||||
* **Extensible**: You have all the power of SQLAlchemy and Pydantic underneath.
|
* **Extensible**: You have all the power of SQLAlchemy and Pydantic underneath.
|
||||||
* **Short**: Minimize code duplication. A single type annotation does a lot of work. No need to duplicate models in SQLAlchemy and Pydantic.
|
* **Short**: Minimize code duplication. A single type annotation does a lot of work. No need to duplicate models in SQLAlchemy and Pydantic.
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
<!-- sponsors -->
|
||||||
|
|
||||||
|
{% if sponsors %}
|
||||||
|
{% for sponsor in sponsors.gold -%}
|
||||||
|
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||||
|
{% endfor -%}
|
||||||
|
{%- for sponsor in sponsors.silver -%}
|
||||||
|
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- /sponsors -->
|
||||||
|
|
||||||
## SQL Databases in FastAPI
|
## SQL Databases in FastAPI
|
||||||
|
|
||||||
<a href="https://fastapi.tiangolo.com" target="_blank"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" style="width: 20%;"></a>
|
<a href="https://fastapi.tiangolo.com" target="_blank"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" style="width: 20%;"></a>
|
||||||
@@ -51,12 +72,14 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
A recent and currently supported version of Python (right now, <a href="https://www.python.org/downloads/" class="external-link" target="_blank">Python supports versions 3.6 and above</a>).
|
A recent and currently supported <a href="https://www.python.org/downloads/" class="external-link" target="_blank">version of Python</a>.
|
||||||
|
|
||||||
As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel.
|
As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
Make sure you create a <a href="https://sqlmodel.tiangolo.com/virtual-environments/" class="external-link" target="_blank">virtual environment</a>, activate it, and then install SQLModel, for example with:
|
||||||
|
|
||||||
<div class="termy">
|
<div class="termy">
|
||||||
|
|
||||||
```console
|
```console
|
||||||
@@ -69,7 +92,7 @@ Successfully installed sqlmodel
|
|||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
For an introduction to databases, SQL, and everything else, see the <a href="https://sqlmodel.tiangolo.com" target="_blank">SQLModel documentation</a>.
|
For an introduction to databases, SQL, and everything else, see the <a href="https://sqlmodel.tiangolo.com/databases/" target="_blank">SQLModel documentation</a>.
|
||||||
|
|
||||||
Here's a quick example. ✨
|
Here's a quick example. ✨
|
||||||
|
|
||||||
@@ -212,4 +235,4 @@ And at the same time, ✨ it is also a **Pydantic** model ✨. You can use inher
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the terms of the MIT license.
|
This project is licensed under the terms of the [MIT license](https://github.com/fastapi/sqlmodel/blob/main/LICENSE).
|
||||||
|
|||||||
39
docs/install.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Install **SQLModel**
|
||||||
|
|
||||||
|
Create a project directory, create a [virtual environment](virtual-environments.md){.internal-link target=_blank}, activate it, and then install **SQLModel**, for example with:
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ pip install sqlmodel
|
||||||
|
---> 100%
|
||||||
|
Successfully installed sqlmodel pydantic sqlalchemy
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
As **SQLModel** is built on top of <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> and <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a>, when you install `sqlmodel` they will also be automatically installed.
|
||||||
|
|
||||||
|
## Install DB Browser for SQLite
|
||||||
|
|
||||||
|
Remember that [SQLite is a simple database in a single file](databases.md#a-single-file-database){.internal-link target=_blank}?
|
||||||
|
|
||||||
|
For most of the tutorial I'll use SQLite for the examples.
|
||||||
|
|
||||||
|
Python has integrated support for SQLite, it is a single file read and processed from Python. And it doesn't need an [External Database Server](databases.md#a-server-database){.internal-link target=_blank}, so it will be perfect for learning.
|
||||||
|
|
||||||
|
In fact, SQLite is perfectly capable of handling quite big applications. At some point you might want to migrate to a server-based database like <a href="https://www.postgresql.org/" class="external-link" target="_blank">PostgreSQL</a> (which is also free). But for now we'll stick to SQLite.
|
||||||
|
|
||||||
|
Through the tutorial I will show you SQL fragments, and Python examples. And I hope (and expect 🧐) you to actually run them, and verify that the database is working as expected and showing you the same data.
|
||||||
|
|
||||||
|
To be able to explore the SQLite file yourself, independent of Python code (and probably at the same time), I recommend you use <a href="https://sqlitebrowser.org/" class="external-link" target="_blank">DB Browser for SQLite</a>.
|
||||||
|
|
||||||
|
It's a great and simple program to interact with SQLite databases (SQLite files) in a nice user interface.
|
||||||
|
|
||||||
|
<img src="https://sqlitebrowser.org/images/screenshot.png">
|
||||||
|
|
||||||
|
Go ahead and <a href="https://sqlitebrowser.org/" class="external-link" target="_blank">Install DB Browser for SQLite</a>, it's free.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Okay, let's get going! On the next section we'll start the [Tutorial - User Guide](tutorial/index.md). 🚀
|
||||||
@@ -13,7 +13,7 @@ function setupTermynal() {
|
|||||||
|
|
||||||
function createTermynals() {
|
function createTermynals() {
|
||||||
document
|
document
|
||||||
.querySelectorAll(`.${termynalActivateClass} .highlight`)
|
.querySelectorAll(`.${termynalActivateClass} .highlight code`)
|
||||||
.forEach(node => {
|
.forEach(node => {
|
||||||
const text = node.textContent;
|
const text = node.textContent;
|
||||||
const lines = text.split("\n");
|
const lines = text.split("\n");
|
||||||
@@ -110,4 +110,6 @@ async function main() {
|
|||||||
setupTermynal()
|
setupTermynal()
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
document$.subscribe(() => {
|
||||||
|
main()
|
||||||
|
})
|
||||||
|
|||||||
7
docs/learn/index.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Learn
|
||||||
|
|
||||||
|
Learn how to use **SQLModel** here.
|
||||||
|
|
||||||
|
This includes an introduction to **databases**, **SQL**, how to interact with databases from **code** and more.
|
||||||
|
|
||||||
|
You could consider this a **book**, a **course**, the **official** and recommended way to learn **SQLModel**. 😎
|
||||||
115
docs/management-tasks.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# Repository Management Tasks
|
||||||
|
|
||||||
|
These are the tasks that can be performed to manage the SQLModel repository by [team members](./management.md#team){.internal-link target=_blank}.
|
||||||
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
This section is useful only to a handful of people, team members with permissions to manage the repository. You can probably skip it. 😉
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
...so, you are a [team member of SQLModel](./management.md#team){.internal-link target=_blank}? Wow, you are so cool! 😎
|
||||||
|
|
||||||
|
You can help with everything on [Help SQLModel - Get Help](./help.md){.internal-link target=_blank} the same ways as external contributors. But additionally, there are some tasks that only you (as part of the team) can perform.
|
||||||
|
|
||||||
|
Here are the general instructions for the tasks you can perform.
|
||||||
|
|
||||||
|
Thanks a lot for your help. 🙇
|
||||||
|
|
||||||
|
## Be Nice
|
||||||
|
|
||||||
|
First of all, be nice. 😊
|
||||||
|
|
||||||
|
You probably are super nice if you were added to the team, but it's worth mentioning it. 🤓
|
||||||
|
|
||||||
|
### When Things are Difficult
|
||||||
|
|
||||||
|
When things are great, everything is easier, so that doesn't need much instructions. But when things are difficult, here are some guidelines.
|
||||||
|
|
||||||
|
Try to find the good side. In general, if people are not being unfriendly, try to thank their effort and interest, even if you disagree with the main subject (discussion, PR), just thank them for being interested in the project, or for having dedicated some time to try to do something.
|
||||||
|
|
||||||
|
It's difficult to convey emotion in text, use emojis to help. 😅
|
||||||
|
|
||||||
|
In discussions and PRs, in many cases, people bring their frustration and show it without filter, in many cases exaggerating, complaining, being entitled, etc. That's really not nice, and when it happens, it lowers our priority to solve their problems. But still, try to breath, and be gentle with your answers.
|
||||||
|
|
||||||
|
Try to avoid using bitter sarcasm or potentially passive-aggressive comments. If something is wrong, it's better to be direct (try to be gentle) than sarcastic.
|
||||||
|
|
||||||
|
Try to be as specific and objective as possible, avoid generalizations.
|
||||||
|
|
||||||
|
For conversations that are more difficult, for example to reject a PR, you can ask me (@tiangolo) to handle it directly.
|
||||||
|
|
||||||
|
## Edit PR Titles
|
||||||
|
|
||||||
|
* Edit the PR title to start with an emoji from <a href="https://gitmoji.dev/" class="external-link" target="_blank">gitmoji</a>.
|
||||||
|
* Use the emoji character, not the GitHub code. So, use `🐛` instead of `:bug:`. This is so that it shows up correctly outside of GitHub, for example in the release notes.
|
||||||
|
* Start the title with a verb. For example `Add`, `Refactor`, `Fix`, etc. This way the title will say the action that the PR does. Like `Add support for teleporting`, instead of `Teleporting wasn't working, so this PR fixes it`.
|
||||||
|
* Edit the text of the PR title to start in "imperative", like giving an order. So, instead of `Adding support for teleporting` use `Add support for teleporting`.
|
||||||
|
* Try to make the title descriptive about what it achieves. If it's a feature, try to describe it, for example `Add support for teleporting` instead of `Create TeleportAdapter class`.
|
||||||
|
* Do not finish the title with a period (`.`).
|
||||||
|
|
||||||
|
Once the PR is merged, a GitHub Action (<a href="https://github.com/tiangolo/latest-changes" class="external-link" target="_blank">latest-changes</a>) will use the PR title to update the latest changes automatically.
|
||||||
|
|
||||||
|
So, having a nice PR title will not only look nice in GitHub, but also in the release notes. 📝
|
||||||
|
|
||||||
|
## Add Labels to PRs
|
||||||
|
|
||||||
|
The same GitHub Action <a href="https://github.com/tiangolo/latest-changes" class="external-link" target="_blank">latest-changes</a> uses one label in the PR to decide the section in the release notes to put this PR in.
|
||||||
|
|
||||||
|
Make sure you use a supported label from the <a href="https://github.com/tiangolo/latest-changes#using-labels" class="external-link" target="_blank">latest-changes list of labels</a>:
|
||||||
|
|
||||||
|
* `breaking`: Breaking Changes
|
||||||
|
* Existing code will break if they update the version without changing their code. This rarely happens, so this label is not frequently used.
|
||||||
|
* `security`: Security Fixes
|
||||||
|
* This is for security fixes, like vulnerabilities. It would almost never be used.
|
||||||
|
* `feature`: Features
|
||||||
|
* New features, adding support for things that didn't exist before.
|
||||||
|
* `bug`: Fixes
|
||||||
|
* Something that was supported didn't work, and this fixes it. There are many PRs that claim to be bug fixes because the user is doing something in an unexpected way that is not supported, but they considered it what should be supported by default. Many of these are actually features or refactors. But in some cases there's an actual bug.
|
||||||
|
* `refactor`: Refactors
|
||||||
|
* This is normally for changes to the internal code that don't change the behavior. Normally it improves maintainability, or enables future features, etc.
|
||||||
|
* `upgrade`: Upgrades
|
||||||
|
* This is for upgrades to direct dependencies from the project, or extra optional dependencies, normally in `pyproject.toml`. So, things that would affect final users, they would end up receiving the upgrade in their code base once they update. But this is not for upgrades to internal dependencies used for development, testing, docs, etc. Those internal dependencies, normally in `requirements.txt` files or GitHub Action versions should be marked as `internal`, not `upgrade`.
|
||||||
|
* `docs`: Docs
|
||||||
|
* Changes in docs. This includes updating the docs, fixing typos. But it doesn't include changes to translations.
|
||||||
|
* You can normally quickly detect it by going to the "Files changed" tab in the PR and checking if the updated file(s) starts with `docs/en/docs`. The original version of the docs is always in English, so in `docs/en/docs`.
|
||||||
|
* `internal`: Internal
|
||||||
|
* Use this for changes that only affect how the repo is managed. For example upgrades to internal dependencies, changes in GitHub Actions or scripts, etc.
|
||||||
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
Some tools like Dependabot, will add some labels, like `dependencies`, but have in mind that this label is not used by the `latest-changes` GitHub Action, so it won't be used in the release notes. Please make sure one of the labels above is added.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
## Review PRs
|
||||||
|
|
||||||
|
If a PR doesn't explain what it does or why, ask for more information.
|
||||||
|
|
||||||
|
A PR should have a specific use case that it is solving.
|
||||||
|
|
||||||
|
* If the PR is for a feature, it should have docs.
|
||||||
|
* Unless it's a feature we want to discourage, like support for a corner case that we don't want users to use.
|
||||||
|
* The docs should include a source example file, not write Python directly in Markdown.
|
||||||
|
* If the source example(s) file can have different syntax for Python 3.8, 3.9, 3.10, there should be different versions of the file, and they should be shown in tabs in the docs.
|
||||||
|
* There should be tests testing the source example.
|
||||||
|
* Before the PR is applied, the new tests should fail.
|
||||||
|
* After applying the PR, the new tests should pass.
|
||||||
|
* Coverage should stay at 100%.
|
||||||
|
* If you see the PR makes sense, or we discussed it and considered it should be accepted, you can add commits on top of the PR to tweak it, to add docs, tests, format, refactor, remove extra files, etc.
|
||||||
|
* Feel free to comment in the PR to ask for more information, to suggest changes, etc.
|
||||||
|
* Once you think the PR is ready, move it in the internal GitHub project for me to review it.
|
||||||
|
|
||||||
|
## Dependabot PRs
|
||||||
|
|
||||||
|
Dependabot will create PRs to update dependencies for several things, and those PRs all look similar, but some are way more delicate than others.
|
||||||
|
|
||||||
|
* If the PR is for a direct dependency, so, Dependabot is modifying `pyproject.toml`, **don't merge it**. 😱 Let me check it first. There's a good chance that some additional tweaks or updates are needed.
|
||||||
|
* If the PR updates one of the internal dependencies, for example it's modifying `requirements.txt` files, or GitHub Action versions, if the tests are passing, the release notes (shown in a summary in the PR) don't show any obvious potential breaking change, you can merge it. 😎
|
||||||
|
|
||||||
|
## Mark GitHub Discussions Answers
|
||||||
|
|
||||||
|
When a question in GitHub Discussions has been answered, mark the answer by clicking "Mark as answer".
|
||||||
|
|
||||||
|
Many of the current Discussion Questions were migrated from old issues. Many have the label `answered`, that means they were answered when they were issues, but now in GitHub Discussions, it's not known what is the actual response from the messages.
|
||||||
|
|
||||||
|
You can filter discussions by <a href="https://github.com/fastapi/sqlmodel/discussions/categories/questions?discussions_q=category:Questions+is:open+is:unanswered" class="external-link" target="_blank">`Questions` that are `Unanswered`</a>.
|
||||||
45
docs/management.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Repository Management
|
||||||
|
|
||||||
|
Here's a short description of how the SQLModel repository is managed and maintained.
|
||||||
|
|
||||||
|
## Owner
|
||||||
|
|
||||||
|
I, <a href="https://github.com/tiangolo" target="_blank">@tiangolo</a>, am the creator and owner of the SQLModel repository. 🤓
|
||||||
|
|
||||||
|
I normally give the final review to each PR before merging them. I make the final decisions on the project, I'm the <a href="https://en.wikipedia.org/wiki/Benevolent_dictator_for_life" class="external-link" target="_blank"><abbr title="Benevolent Dictator For Life">BDFL</abbr></a>. 😅
|
||||||
|
|
||||||
|
## Team
|
||||||
|
|
||||||
|
There's a team of people that help manage and maintain the project. 😎
|
||||||
|
|
||||||
|
They have different levels of permissions and [specific instructions](./management-tasks.md){.internal-link target=_blank}.
|
||||||
|
|
||||||
|
Some of the tasks they can perform include:
|
||||||
|
|
||||||
|
* Adding labels to PRs.
|
||||||
|
* Editing PR titles.
|
||||||
|
* Adding commits on top of PRs to tweak them.
|
||||||
|
* Mark answers in GitHub Discussions questions, etc.
|
||||||
|
* Merge some specific types of PRs.
|
||||||
|
|
||||||
|
Joining the team is by invitation only, and I could update or remove permissions, instructions, or membership.
|
||||||
|
|
||||||
|
### Team Members
|
||||||
|
|
||||||
|
This is the current list of team members. 😎
|
||||||
|
|
||||||
|
<div class="user-list user-list-center">
|
||||||
|
{% for user in members["members"] %}
|
||||||
|
|
||||||
|
<div class="user"><a href="https://github.com/{{ user.login }}" target="_blank"><div class="avatar-wrapper"><img src="https://github.com/{{ user.login }}.png"/></div><div class="title">@{{ user.login }}</div></a></div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Additional to them, there's a large community of people helping each other and getting involved in the projects in different ways.
|
||||||
|
|
||||||
|
## External Contributions
|
||||||
|
|
||||||
|
External contributions are very welcome and appreciated, including answering questions, submitting PRs, etc. 🙇♂️
|
||||||
|
|
||||||
|
There are many ways to [help maintain SQLModel](./help.md){.internal-link target=_blank}.
|
||||||
1
docs/overrides/main.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
@@ -2,6 +2,541 @@
|
|||||||
|
|
||||||
## Latest Changes
|
## Latest Changes
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
|
||||||
|
* 🚨 Fix types for new Pydantic. PR [#1131](https://github.com/fastapi/sqlmodel/pull/1131) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* ✏️ Fix typo in `databases.md`. PR [#1113](https://github.com/fastapi/sqlmodel/pull/1113) by [@radi-dev](https://github.com/radi-dev).
|
||||||
|
* ✏️ Fix typo in `docs/tutorial/create-db-and-table.md`. PR [#1252](https://github.com/fastapi/sqlmodel/pull/1252) by [@ArianHamdi](https://github.com/ArianHamdi).
|
||||||
|
* ✏️ Fix typo in `insert.md`. PR [#1256](https://github.com/fastapi/sqlmodel/pull/1256) by [@Noushadaliam](https://github.com/Noushadaliam).
|
||||||
|
* 📝 Update markdown includes format. PR [#1254](https://github.com/fastapi/sqlmodel/pull/1254) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 📝 Update fenced code in Decimal docs for consistency. PR [#1251](https://github.com/fastapi/sqlmodel/pull/1251) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ✏️ Fix typo in the release notes of v0.0.22. PR [#1195](https://github.com/fastapi/sqlmodel/pull/1195) by [@PipeKnight](https://github.com/PipeKnight).
|
||||||
|
* 📝 Update includes for `docs/advanced/uuid.md`. PR [#1151](https://github.com/fastapi/sqlmodel/pull/1151) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 📝 Update includes for `docs/tutorial/create-db-and-table.md`. PR [#1149](https://github.com/fastapi/sqlmodel/pull/1149) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 📝 Fix internal links in docs. PR [#1148](https://github.com/fastapi/sqlmodel/pull/1148) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ✏️ Fix typo in documentation. PR [#1106](https://github.com/fastapi/sqlmodel/pull/1106) by [@Solipsistmonkey](https://github.com/Solipsistmonkey).
|
||||||
|
* 📝 Remove highlights in `indexes.md` . PR [#1100](https://github.com/fastapi/sqlmodel/pull/1100) by [@alejsdev](https://github.com/alejsdev).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* 💚 Fix CI test suite for Python 3.7. PR [#1309](https://github.com/fastapi/sqlmodel/pull/1309) by [@svlandeg](https://github.com/svlandeg).
|
||||||
|
* 👷 Revert "Add Codecov to CI, Smokeshow/Cloudflare has been flaky lately (#1303)". PR [#1306](https://github.com/fastapi/sqlmodel/pull/1306) by [@svlandeg](https://github.com/svlandeg).
|
||||||
|
* 👷 Add Codecov to CI, Smokeshow/Cloudflare has been flaky lately. PR [#1303](https://github.com/fastapi/sqlmodel/pull/1303) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Add retries to Smokeshow. PR [#1302](https://github.com/fastapi/sqlmodel/pull/1302) by [@svlandeg](https://github.com/svlandeg).
|
||||||
|
* ⬆ Bump astral-sh/setup-uv from 4 to 5. PR [#1249](https://github.com/fastapi/sqlmodel/pull/1249) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump pillow from 10.3.0 to 11.0.0. PR [#1139](https://github.com/fastapi/sqlmodel/pull/1139) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump pypa/gh-action-pypi-publish from 1.9.0 to 1.12.3. PR [#1240](https://github.com/fastapi/sqlmodel/pull/1240) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump astral-sh/setup-uv from 3 to 4. PR [#1225](https://github.com/fastapi/sqlmodel/pull/1225) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump tiangolo/latest-changes from 0.3.1 to 0.3.2. PR [#1207](https://github.com/fastapi/sqlmodel/pull/1207) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* 🔨 Update docs previews script. PR [#1236](https://github.com/fastapi/sqlmodel/pull/1236) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Update build-docs filter paths. PR [#1235](https://github.com/fastapi/sqlmodel/pull/1235) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Update team members. PR [#1234](https://github.com/fastapi/sqlmodel/pull/1234) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ⬆️ Upgrade markdown-include-variants to version 0.0.3. PR [#1152](https://github.com/fastapi/sqlmodel/pull/1152) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update issue manager workflow. PR [#1137](https://github.com/fastapi/sqlmodel/pull/1137) by [@alejsdev](https://github.com/alejsdev).
|
||||||
|
* 👷 Fix smokeshow, checkout files on CI. PR [#1136](https://github.com/fastapi/sqlmodel/pull/1136) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Use uv in CI. PR [#1135](https://github.com/fastapi/sqlmodel/pull/1135) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ➕ Add docs dependency markdown-include-variants. PR [#1129](https://github.com/fastapi/sqlmodel/pull/1129) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔨 Update script to standardize format. PR [#1130](https://github.com/fastapi/sqlmodel/pull/1130) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update `labeler.yml`. PR [#1128](https://github.com/fastapi/sqlmodel/pull/1128) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update worfkow deploy-docs-notify URL. PR [#1126](https://github.com/fastapi/sqlmodel/pull/1126) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Upgrade Cloudflare GitHub Action. PR [#1124](https://github.com/fastapi/sqlmodel/pull/1124) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1097](https://github.com/fastapi/sqlmodel/pull/1097) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
|
||||||
|
* ⬆ Bump tiangolo/issue-manager from 0.5.0 to 0.5.1. PR [#1107](https://github.com/fastapi/sqlmodel/pull/1107) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* 👷 Update `issue-manager.yml`. PR [#1103](https://github.com/fastapi/sqlmodel/pull/1103) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Fix coverage processing in CI, one name per matrix run. PR [#1104](https://github.com/fastapi/sqlmodel/pull/1104) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 💚 Set `include-hidden-files` to `True` when using the `upload-artifact` GH action. PR [#1098](https://github.com/fastapi/sqlmodel/pull/1098) by [@svlandeg](https://github.com/svlandeg).
|
||||||
|
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1088](https://github.com/fastapi/sqlmodel/pull/1088) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
|
||||||
|
|
||||||
|
## 0.0.22
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix support for types with `Optional[Annotated[x, f()]]`, e.g. `id: Optional[pydantic.UUID4]`. PR [#1093](https://github.com/fastapi/sqlmodel/pull/1093) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* ✏️ Fix a typo in `docs/virtual-environments.md`. PR [#1085](https://github.com/fastapi/sqlmodel/pull/1085) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 📝 Add docs for virtual environments and environment variables, update contributing. PR [#1082](https://github.com/fastapi/sqlmodel/pull/1082) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 📝 Add docs about repo management and team. PR [#1059](https://github.com/tiangolo/sqlmodel/pull/1059) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ✏️ Fix typo in `cascade_delete` docs. PR [#1030](https://github.com/tiangolo/sqlmodel/pull/1030) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* ✅ Refactor test_enums to make them independent of previous imports. PR [#1095](https://github.com/fastapi/sqlmodel/pull/1095) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update `latest-changes` GitHub Action. PR [#1087](https://github.com/fastapi/sqlmodel/pull/1087) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1028](https://github.com/fastapi/sqlmodel/pull/1028) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
|
||||||
|
* ⬆ Bump ruff from 0.4.7 to 0.6.2. PR [#1081](https://github.com/fastapi/sqlmodel/pull/1081) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* 🔧 Update lint script. PR [#1084](https://github.com/fastapi/sqlmodel/pull/1084) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update Python version for coverage. PR [#1083](https://github.com/fastapi/sqlmodel/pull/1083) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Update coverage config files. PR [#1077](https://github.com/fastapi/sqlmodel/pull/1077) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Add URLs to `pyproject.toml`, show up in PyPI. PR [#1074](https://github.com/fastapi/sqlmodel/pull/1074) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Do not sync labels as it overrides manually added labels. PR [#1073](https://github.com/fastapi/sqlmodel/pull/1073) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update configs for GitHub Action labeler, to add only one label. PR [#1072](https://github.com/fastapi/sqlmodel/pull/1072) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update labeler GitHub Actions permissions and dependencies. PR [#1071](https://github.com/fastapi/sqlmodel/pull/1071) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Add GitHub Action label-checker. PR [#1069](https://github.com/fastapi/sqlmodel/pull/1069) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Add GitHub Action labeler. PR [#1068](https://github.com/fastapi/sqlmodel/pull/1068) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update GitHub Action add-to-project. PR [#1067](https://github.com/fastapi/sqlmodel/pull/1067) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Add GitHub Action add-to-project. PR [#1066](https://github.com/fastapi/sqlmodel/pull/1066) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 📝 Update admonitions in annotations. PR [#1065](https://github.com/fastapi/sqlmodel/pull/1065) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 📝 Update links from github.com/tiangolo/sqlmodel to github.com/fastapi/sqlmodel. PR [#1064](https://github.com/fastapi/sqlmodel/pull/1064) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Update members. PR [#1063](https://github.com/tiangolo/sqlmodel/pull/1063) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 💄 Add dark-mode logo. PR [#1061](https://github.com/tiangolo/sqlmodel/pull/1061) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔨 Update docs.py script to enable dirty reload conditionally. PR [#1060](https://github.com/tiangolo/sqlmodel/pull/1060) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Update MkDocs previews. PR [#1058](https://github.com/tiangolo/sqlmodel/pull/1058) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 💄 Update Termynal line-height. PR [#1057](https://github.com/tiangolo/sqlmodel/pull/1057) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Upgrade build docs configs. PR [#1047](https://github.com/tiangolo/sqlmodel/pull/1047) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Add alls-green for test-redistribute. PR [#1055](https://github.com/tiangolo/sqlmodel/pull/1055) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update docs-previews to handle no docs changes. PR [#1056](https://github.com/tiangolo/sqlmodel/pull/1056) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷🏻 Show docs deployment status and preview URLs in comment. PR [#1054](https://github.com/tiangolo/sqlmodel/pull/1054) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Enable auto dark mode. PR [#1046](https://github.com/tiangolo/sqlmodel/pull/1046) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update issue-manager. PR [#1045](https://github.com/tiangolo/sqlmodel/pull/1045) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update issue-manager.yml GitHub Action permissions. PR [#1040](https://github.com/tiangolo/sqlmodel/pull/1040) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ♻️ Refactor Deploy Docs GitHub Action to be a script and update token preparing for org. PR [#1039](https://github.com/tiangolo/sqlmodel/pull/1039) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
## 0.0.21
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ✨ Add support for cascade delete relationships: `cascade_delete`, `ondelete`, and `passive_deletes`. Initial PR [#983](https://github.com/tiangolo/sqlmodel/pull/983) by [@estebanx64](https://github.com/estebanx64).
|
||||||
|
* New docs at: [Cascade Delete Relationships](https://sqlmodel.tiangolo.com/tutorial/relationship-attributes/cascade-delete-relationships/).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* 📝 Update docs . PR [#1003](https://github.com/tiangolo/sqlmodel/pull/1003) by [@alejsdev](https://github.com/alejsdev).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* ⬆ Bump actions/cache from 3 to 4. PR [#783](https://github.com/tiangolo/sqlmodel/pull/783) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump cairosvg from 2.7.0 to 2.7.1. PR [#919](https://github.com/tiangolo/sqlmodel/pull/919) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump jinja2 from 3.1.3 to 3.1.4. PR [#974](https://github.com/tiangolo/sqlmodel/pull/974) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump pypa/gh-action-pypi-publish from 1.8.11 to 1.9.0. PR [#987](https://github.com/tiangolo/sqlmodel/pull/987) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump mkdocstrings[python] from 0.23.0 to 0.25.1. PR [#927](https://github.com/tiangolo/sqlmodel/pull/927) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump dorny/paths-filter from 2 to 3. PR [#972](https://github.com/tiangolo/sqlmodel/pull/972) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
|
||||||
|
## 0.0.20
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ✨ Add official UUID support, docs and tests, internally using new SQLAlchemy 2.0 types. Initial PR [#992](https://github.com/tiangolo/sqlmodel/pull/992) by [@estebanx64](https://github.com/estebanx64).
|
||||||
|
* New docs in the [Advanced User Guide: UUID (Universally Unique Identifiers)](https://sqlmodel.tiangolo.com/advanced/uuid/).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* ✏️ Fix internal link in `docs/tutorial/create-db-and-table.md`. PR [#911](https://github.com/tiangolo/sqlmodel/pull/911) by [@tfpgh](https://github.com/tfpgh).
|
||||||
|
* ✏️ Add missing step in `create-db-and-table-with-db-browser.md`. PR [#976](https://github.com/tiangolo/sqlmodel/pull/976) by [@alejsdev](https://github.com/alejsdev).
|
||||||
|
* ✏️ Fix typo in `docs/tutorial`. PR [#943](https://github.com/tiangolo/sqlmodel/pull/943) by [@luco17](https://github.com/luco17).
|
||||||
|
* ✏️ Fix typo in `sqlmodel/_compat.py`. PR [#950](https://github.com/tiangolo/sqlmodel/pull/950) by [@Highfire1](https://github.com/Highfire1).
|
||||||
|
* ✏️ Update pip installation command in tutorial. PR [#975](https://github.com/tiangolo/sqlmodel/pull/975) by [@alejsdev](https://github.com/alejsdev).
|
||||||
|
* ✏️ Fix typo in `docs/tutorial/relationship-attributes/index.md`. PR [#880](https://github.com/tiangolo/sqlmodel/pull/880) by [@UncleGoogle](https://github.com/UncleGoogle).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#979](https://github.com/tiangolo/sqlmodel/pull/979) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
|
||||||
|
* 🔨 Update docs Termynal scripts to not include line nums for local dev. PR [#1018](https://github.com/tiangolo/sqlmodel/pull/1018) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
## 0.0.19
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix pydantic `EmailStr` support and `max_length` in several String subclasses. PR [#966](https://github.com/tiangolo/sqlmodel/pull/966) by [@estebanx64](https://github.com/estebanx64).
|
||||||
|
* 🐛 Fix set varchar limit when `max_length` is set on Pydantic models using Pydantic v2. PR [#963](https://github.com/tiangolo/sqlmodel/pull/963) by [@estebanx64](https://github.com/estebanx64).
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
|
||||||
|
* ♻️ Refactor generate select template to isolate templated code to the minimum. PR [#967](https://github.com/tiangolo/sqlmodel/pull/967) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Upgrades
|
||||||
|
|
||||||
|
* ⬆️ Update minimum SQLAlchemy version to 2.0.14 as that one includes `TryCast` used internally. PR [#964](https://github.com/tiangolo/sqlmodel/pull/964) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* ✏️ Fix broken link to `@dataclass_transform` (now PEP 681) in `docs/features.md`. PR [#753](https://github.com/tiangolo/sqlmodel/pull/753) by [@soof-golan](https://github.com/soof-golan).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* ⬆️ Upgrade Ruff and Black. PR [#968](https://github.com/tiangolo/sqlmodel/pull/968) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ⬆ Bump tiangolo/issue-manager from 0.4.1 to 0.5.0. PR [#922](https://github.com/tiangolo/sqlmodel/pull/922) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* 📌 Pin typing-extensions in tests for compatiblity with Python 3.8, dirty-equals, Pydantic. PR [#965](https://github.com/tiangolo/sqlmodel/pull/965) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update GitHub Actions to download and upload artifacts. PR [#936](https://github.com/tiangolo/sqlmodel/pull/936) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Tweak CI for test-redistribute, add needed env vars for slim. PR [#929](https://github.com/tiangolo/sqlmodel/pull/929) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
## 0.0.18
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* ✨ Add `sqlmodel-slim` setup. PR [#916](https://github.com/tiangolo/sqlmodel/pull/916) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
In the future SQLModel will include the standard default recommended packages, and `sqlmodel-slim` will come without those recommended standard packages and with a group of optional dependencies `sqlmodel-slim[standard]`, equivalent to `sqlmodel`, for those that want to opt out of those packages.
|
||||||
|
|
||||||
|
* 🔧 Re-enable MkDocs Material Social plugin. PR [#915](https://github.com/tiangolo/sqlmodel/pull/915) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
## 0.0.17
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
|
||||||
|
* ♻️ Refactor types to properly support Pydantic 2.7. PR [#913](https://github.com/tiangolo/sqlmodel/pull/913) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* 📝 Update ModelRead to ModelPublic documentation and examples. PR [#885](https://github.com/tiangolo/sqlmodel/pull/885) by [@estebanx64](https://github.com/estebanx64).
|
||||||
|
* ✨ Add source examples for Python 3.10 and 3.9 with updated syntax. PR [#842](https://github.com/tiangolo/sqlmodel/pull/842) by [@tiangolo](https://github.com/tiangolo) and [@estebanx64](https://github.com/estebanx64).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* ⬆ Bump actions/setup-python from 4 to 5. PR [#733](https://github.com/tiangolo/sqlmodel/pull/733) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* 🔨 Update internal scripts and remove unused ones. PR [#914](https://github.com/tiangolo/sqlmodel/pull/914) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Migrate from Poetry to PDM for the internal build config. PR [#912](https://github.com/tiangolo/sqlmodel/pull/912) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Update MkDocs, disable cards while I can upgrade to the latest MkDocs Material, that fixes an issue with social cards. PR [#888](https://github.com/tiangolo/sqlmodel/pull/888) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Add cron to run test once a week on monday. PR [#869](https://github.com/tiangolo/sqlmodel/pull/869) by [@estebanx64](https://github.com/estebanx64).
|
||||||
|
* ⬆️ Upgrade Ruff version and configs. PR [#859](https://github.com/tiangolo/sqlmodel/pull/859) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔥 Remove Jina QA Bot as it has been discontinued. PR [#840](https://github.com/tiangolo/sqlmodel/pull/840) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
## 0.0.16
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ✨ Add new method `.sqlmodel_update()` to update models in place, including an `update` parameter for extra data. And fix implementation for the (now documented) `update` parameter for `.model_validate()`. PR [#804](https://github.com/tiangolo/sqlmodel/pull/804) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* Updated docs: [Update Data with FastAPI](https://sqlmodel.tiangolo.com/tutorial/fastapi/update/).
|
||||||
|
* New docs: [Update with Extra Data (Hashed Passwords) with FastAPI](https://sqlmodel.tiangolo.com/tutorial/fastapi/update-extra-data/).
|
||||||
|
|
||||||
|
## 0.0.15
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix class initialization compatibility with Pydantic and SQLModel, fixing errors revealed by the latest Pydantic. PR [#807](https://github.com/tiangolo/sqlmodel/pull/807) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* ⬆ Bump tiangolo/issue-manager from 0.4.0 to 0.4.1. PR [#775](https://github.com/tiangolo/sqlmodel/pull/775) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* 👷 Fix GitHub Actions build docs filter paths for GitHub workflows. PR [#738](https://github.com/tiangolo/sqlmodel/pull/738) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
## 0.0.14
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ✨ Add support for Pydantic v2 (while keeping support for v1 if v2 is not available). PR [#722](https://github.com/tiangolo/sqlmodel/pull/722) by [@tiangolo](https://github.com/tiangolo) including initial work in PR [#699](https://github.com/tiangolo/sqlmodel/pull/699) by [@AntonDeMeester](https://github.com/AntonDeMeester).
|
||||||
|
|
||||||
|
## 0.0.13
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* ♻️ Refactor type generation of selects re-order to prioritize models to optimize editor support. PR [#718](https://github.com/tiangolo/sqlmodel/pull/718) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
|
||||||
|
* 🔇 Do not raise deprecation warnings for execute as it's automatically used internally. PR [#716](https://github.com/tiangolo/sqlmodel/pull/716) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ✅ Move OpenAPI tests inline to simplify updating them with Pydantic v2. PR [#709](https://github.com/tiangolo/sqlmodel/pull/709) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Upgrades
|
||||||
|
|
||||||
|
* ⬆️ Add support for Python 3.11 and Python 3.12. PR [#710](https://github.com/tiangolo/sqlmodel/pull/710) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* ✏️ Fix typo, simplify single quote/apostrophe character in "Sister Margaret's" everywhere in the docs. PR [#721](https://github.com/tiangolo/sqlmodel/pull/721) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 📝 Update docs for Decimal, use proper types. PR [#719](https://github.com/tiangolo/sqlmodel/pull/719) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 📝 Add source examples for Python 3.9 and 3.10. PR [#715](https://github.com/tiangolo/sqlmodel/pull/715) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* 🙈 Update gitignore, include all coverage files. PR [#711](https://github.com/tiangolo/sqlmodel/pull/711) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Update config with new pymdown extensions. PR [#712](https://github.com/tiangolo/sqlmodel/pull/712) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Update docs build setup, add support for sponsors, add sponsor GOVCERT.LU. PR [#720](https://github.com/tiangolo/sqlmodel/pull/720) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#697](https://github.com/tiangolo/sqlmodel/pull/697) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
|
||||||
|
* 🔧 Show line numbers in docs during local development. PR [#714](https://github.com/tiangolo/sqlmodel/pull/714) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 📝 Update details syntax with new pymdown extensions format. PR [#713](https://github.com/tiangolo/sqlmodel/pull/713) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
## 0.0.12
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ✨ Upgrade SQLAlchemy to 2.0. PR [#700](https://github.com/tiangolo/sqlmodel/pull/700) by [@tiangolo](https://github.com/tiangolo) including initial work in PR [#563](https://github.com/tiangolo/sqlmodel/pull/563) by [@farahats9](https://github.com/farahats9).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#686](https://github.com/tiangolo/sqlmodel/pull/686) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
|
||||||
|
* 👷 Upgrade latest-changes GitHub Action. PR [#693](https://github.com/tiangolo/sqlmodel/pull/693) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
## 0.0.11
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ✨ Add support for passing a custom SQLAlchemy type to `Field()` with `sa_type`. PR [#505](https://github.com/tiangolo/sqlmodel/pull/505) by [@maru0123-2004](https://github.com/maru0123-2004).
|
||||||
|
* You might consider this a breaking change if you were using an incompatible combination of arguments, those arguments were not taking effect and now you will have a type error and runtime error telling you that.
|
||||||
|
* ✨ Do not allow invalid combinations of field parameters for columns and relationships, `sa_column` excludes `sa_column_args`, `primary_key`, `nullable`, etc. PR [#681](https://github.com/tiangolo/sqlmodel/pull/681) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* 🎨 Update inline source examples, hide `#` in annotations (from MkDocs Material). PR [#677](https://github.com/tiangolo/sqlmodel/pull/677) by [@Matthieu-LAURENT39](https://github.com/Matthieu-LAURENT39).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* ⬆ Update coverage requirement from ^6.2 to >=6.2,<8.0. PR [#663](https://github.com/tiangolo/sqlmodel/pull/663) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Update mkdocs-material requirement from 9.1.21 to 9.2.7. PR [#675](https://github.com/tiangolo/sqlmodel/pull/675) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆️ Upgrade mypy manually. PR [#684](https://github.com/tiangolo/sqlmodel/pull/684) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ⬆ Update black requirement from ^22.10.0 to >=22.10,<24.0. PR [#664](https://github.com/tiangolo/sqlmodel/pull/664) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* 👷 Update CI to build MkDocs Insiders only when the secrets are available, for Dependabot. PR [#683](https://github.com/tiangolo/sqlmodel/pull/683) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
## 0.0.10
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ✨ Add support for all `Field` parameters from Pydantic `1.9.0` and above, make Pydantic `1.9.0` the minimum required version. PR [#440](https://github.com/tiangolo/sqlmodel/pull/440) by [@daniil-berg](https://github.com/daniil-berg).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* 🔧 Adopt Ruff for formatting. PR [#679](https://github.com/tiangolo/sqlmodel/pull/679) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
## 0.0.9
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
* 🗑️ Deprecate Python 3.6 and upgrade Poetry and Poetry Version Plugin. PR [#627](https://github.com/tiangolo/sqlmodel/pull/627) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ✨ Raise a more clear error when a type is not valid. PR [#425](https://github.com/tiangolo/sqlmodel/pull/425) by [@ddanier](https://github.com/ddanier).
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix `AsyncSession` type annotations for `exec()`. PR [#58](https://github.com/tiangolo/sqlmodel/pull/58) by [@Bobronium](https://github.com/Bobronium).
|
||||||
|
* 🐛 Fix allowing using a `ForeignKey` directly, remove repeated column construction from `SQLModelMetaclass.__init__` and upgrade minimum SQLAlchemy to `>=1.4.36`. PR [#443](https://github.com/tiangolo/sqlmodel/pull/443) by [@daniil-berg](https://github.com/daniil-berg).
|
||||||
|
* 🐛 Fix enum type checks ordering in `get_sqlalchemy_type`. PR [#669](https://github.com/tiangolo/sqlmodel/pull/669) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🐛 Fix SQLAlchemy version 1.4.36 breaks SQLModel relationships (#315). PR [#461](https://github.com/tiangolo/sqlmodel/pull/461) by [@byrman](https://github.com/byrman).
|
||||||
|
|
||||||
|
### Upgrades
|
||||||
|
|
||||||
|
* ⬆️ Upgrade support for SQLAlchemy 1.4.49, update tests. PR [#519](https://github.com/tiangolo/sqlmodel/pull/519) by [@sandrotosi](https://github.com/sandrotosi).
|
||||||
|
* ⬆ Raise SQLAlchemy version requirement to at least `1.4.29` (related to #434). PR [#439](https://github.com/tiangolo/sqlmodel/pull/439) by [@daniil-berg](https://github.com/daniil-berg).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* 📝 Clarify description of in-memory SQLite database in `docs/tutorial/create-db-and-table.md`. PR [#601](https://github.com/tiangolo/sqlmodel/pull/601) by [@SimonCW](https://github.com/SimonCW).
|
||||||
|
* 📝 Tweak wording in `docs/tutorial/fastapi/multiple-models.md`. PR [#674](https://github.com/tiangolo/sqlmodel/pull/674) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ✏️ Fix contributing instructions to run tests, update script name. PR [#634](https://github.com/tiangolo/sqlmodel/pull/634) by [@PookieBuns](https://github.com/PookieBuns).
|
||||||
|
* 📝 Update link to docs for intro to databases. PR [#593](https://github.com/tiangolo/sqlmodel/pull/593) by [@abenezerBelachew](https://github.com/abenezerBelachew).
|
||||||
|
* 📝 Update docs, use `offset` in example with `limit` and `where`. PR [#273](https://github.com/tiangolo/sqlmodel/pull/273) by [@jbmchuck](https://github.com/jbmchuck).
|
||||||
|
* 📝 Fix docs for Pydantic's fields using `le` (`lte` is invalid, use `le` ). PR [#207](https://github.com/tiangolo/sqlmodel/pull/207) by [@jrycw](https://github.com/jrycw).
|
||||||
|
* 📝 Update outdated link in `docs/db-to-code.md`. PR [#649](https://github.com/tiangolo/sqlmodel/pull/649) by [@MatveyF](https://github.com/MatveyF).
|
||||||
|
* ✏️ Fix typos found with codespell. PR [#520](https://github.com/tiangolo/sqlmodel/pull/520) by [@kianmeng](https://github.com/kianmeng).
|
||||||
|
* 📝 Fix typos (duplication) in main page. PR [#631](https://github.com/tiangolo/sqlmodel/pull/631) by [@Mr-DRP](https://github.com/Mr-DRP).
|
||||||
|
* 📝 Update release notes, add second author to PR. PR [#429](https://github.com/tiangolo/sqlmodel/pull/429) by [@br-follow](https://github.com/br-follow).
|
||||||
|
* 📝 Update instructions about how to make a foreign key required in `docs/tutorial/relationship-attributes/define-relationships-attributes.md`. PR [#474](https://github.com/tiangolo/sqlmodel/pull/474) by [@jalvaradosegura](https://github.com/jalvaradosegura).
|
||||||
|
* 📝 Update help SQLModel docs. PR [#548](https://github.com/tiangolo/sqlmodel/pull/548) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ✏️ Fix typo in internal function name `get_sqlachemy_type()`. PR [#496](https://github.com/tiangolo/sqlmodel/pull/496) by [@cmarqu](https://github.com/cmarqu).
|
||||||
|
* ✏️ Fix typo in docs. PR [#446](https://github.com/tiangolo/sqlmodel/pull/446) by [@davidbrochart](https://github.com/davidbrochart).
|
||||||
|
* ✏️ Fix typo in `docs/tutorial/create-db-and-table.md`. PR [#477](https://github.com/tiangolo/sqlmodel/pull/477) by [@FluffyDietEngine](https://github.com/FluffyDietEngine).
|
||||||
|
* ✏️ Fix small typos in docs. PR [#481](https://github.com/tiangolo/sqlmodel/pull/481) by [@micuffaro](https://github.com/micuffaro).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#672](https://github.com/tiangolo/sqlmodel/pull/672) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
|
||||||
|
* ⬆ Bump dawidd6/action-download-artifact from 2.24.2 to 2.28.0. PR [#660](https://github.com/tiangolo/sqlmodel/pull/660) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ✅ Refactor OpenAPI FastAPI tests to simplify updating them later, this moves things around without changes. PR [#671](https://github.com/tiangolo/sqlmodel/pull/671) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ⬆ Bump actions/checkout from 3 to 4. PR [#670](https://github.com/tiangolo/sqlmodel/pull/670) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* 🔧 Update mypy config, use `strict = true` instead of manual configs. PR [#428](https://github.com/tiangolo/sqlmodel/pull/428) by [@michaeloliverx](https://github.com/michaeloliverx).
|
||||||
|
* ⬆️ Upgrade MkDocs Material. PR [#668](https://github.com/tiangolo/sqlmodel/pull/668) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🎨 Update docs format and references with pre-commit and Ruff. PR [#667](https://github.com/tiangolo/sqlmodel/pull/667) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🎨 Run pre-commit on all files and autoformat. PR [#666](https://github.com/tiangolo/sqlmodel/pull/666) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Move to Ruff and add pre-commit. PR [#661](https://github.com/tiangolo/sqlmodel/pull/661) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🛠️ Add `CITATION.cff` file for academic citations. PR [#13](https://github.com/tiangolo/sqlmodel/pull/13) by [@sugatoray](https://github.com/sugatoray).
|
||||||
|
* 👷 Update docs deployments to Cloudflare. PR [#630](https://github.com/tiangolo/sqlmodel/pull/630) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷♂️ Upgrade CI for docs. PR [#628](https://github.com/tiangolo/sqlmodel/pull/628) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update CI debug mode with Tmate. PR [#629](https://github.com/tiangolo/sqlmodel/pull/629) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Update latest changes token. PR [#616](https://github.com/tiangolo/sqlmodel/pull/616) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ⬆️ Upgrade analytics. PR [#558](https://github.com/tiangolo/sqlmodel/pull/558) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Update new issue chooser to point to GitHub Discussions. PR [#546](https://github.com/tiangolo/sqlmodel/pull/546) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Add template for GitHub Discussion questions and update issues template. PR [#544](https://github.com/tiangolo/sqlmodel/pull/544) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Refactor CI artifact upload/download for docs previews. PR [#514](https://github.com/tiangolo/sqlmodel/pull/514) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ⬆ Bump actions/cache from 2 to 3. PR [#497](https://github.com/tiangolo/sqlmodel/pull/497) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump dawidd6/action-download-artifact from 2.24.0 to 2.24.2. PR [#493](https://github.com/tiangolo/sqlmodel/pull/493) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* 🔧 Update Smokeshow coverage threshold. PR [#487](https://github.com/tiangolo/sqlmodel/pull/487) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Move from Codecov to Smokeshow. PR [#486](https://github.com/tiangolo/sqlmodel/pull/486) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ⬆ Bump actions/setup-python from 2 to 4. PR [#411](https://github.com/tiangolo/sqlmodel/pull/411) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Update black requirement from ^21.5-beta.1 to ^22.10.0. PR [#460](https://github.com/tiangolo/sqlmodel/pull/460) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ➕ Add extra dev dependencies for MkDocs Material. PR [#485](https://github.com/tiangolo/sqlmodel/pull/485) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ⬆ Update mypy requirement from 0.930 to 0.971. PR [#380](https://github.com/tiangolo/sqlmodel/pull/380) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Update coverage requirement from ^5.5 to ^6.2. PR [#171](https://github.com/tiangolo/sqlmodel/pull/171) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump codecov/codecov-action from 2 to 3. PR [#415](https://github.com/tiangolo/sqlmodel/pull/415) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump actions/upload-artifact from 2 to 3. PR [#412](https://github.com/tiangolo/sqlmodel/pull/412) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Update flake8 requirement from ^3.9.2 to ^5.0.4. PR [#396](https://github.com/tiangolo/sqlmodel/pull/396) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Update pytest requirement from ^6.2.4 to ^7.0.1. PR [#242](https://github.com/tiangolo/sqlmodel/pull/242) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump actions/checkout from 2 to 3.1.0. PR [#458](https://github.com/tiangolo/sqlmodel/pull/458) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* ⬆ Bump dawidd6/action-download-artifact from 2.9.0 to 2.24.0. PR [#470](https://github.com/tiangolo/sqlmodel/pull/470) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
* 👷 Update Dependabot config. PR [#484](https://github.com/tiangolo/sqlmodel/pull/484) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
## 0.0.8
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix auto detecting and setting `nullable`, allowing overrides in field. PR [#423](https://github.com/tiangolo/sqlmodel/pull/423) by [@JonasKs](https://github.com/JonasKs) and [@br-follow](https://github.com/br-follow).
|
||||||
|
* ♻️ Update `expresion.py`, sync from Jinja2 template, implement `inherit_cache` to solve errors like: `SAWarning: Class SelectOfScalar will not make use of SQL compilation caching`. PR [#422](https://github.com/tiangolo/sqlmodel/pull/422) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* 📝 Adjust and clarify docs for `docs/tutorial/create-db-and-table.md`. PR [#426](https://github.com/tiangolo/sqlmodel/pull/426) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ✏ Fix typo in `docs/tutorial/connect/remove-data-connections.md`. PR [#421](https://github.com/tiangolo/sqlmodel/pull/421) by [@VerdantFox](https://github.com/VerdantFox).
|
||||||
|
|
||||||
|
## 0.0.7
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ✨ Allow setting `unique` in `Field()` for a column. PR [#83](https://github.com/tiangolo/sqlmodel/pull/83) by [@raphaelgibson](https://github.com/raphaelgibson).
|
||||||
|
* ✨ Update GUID handling to use stdlib `UUID.hex` instead of an `int`. PR [#26](https://github.com/tiangolo/sqlmodel/pull/26) by [@andrewbolster](https://github.com/andrewbolster).
|
||||||
|
* ✨ Raise an exception when using a Pydantic field type with no matching SQLAlchemy type. PR [#18](https://github.com/tiangolo/sqlmodel/pull/18) by [@elben10](https://github.com/elben10).
|
||||||
|
* ⬆ Upgrade constrain for SQLAlchemy = ">=1.4.17,<=1.4.41". PR [#371](https://github.com/tiangolo/sqlmodel/pull/371) by [@RobertRosca](https://github.com/RobertRosca).
|
||||||
|
* ✨ Add new `Session.get()` parameter `execution_options`. PR [#302](https://github.com/tiangolo/sqlmodel/pull/302) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix type annotations for `Model.parse_obj()`, and `Model.validate()`. PR [#321](https://github.com/tiangolo/sqlmodel/pull/321) by [@phi-friday](https://github.com/phi-friday).
|
||||||
|
* 🐛 Fix `Select` and `SelectOfScalar` to inherit cache to avoid warning: `SAWarning: Class SelectOfScalar will not make use of SQL compilation caching`. PR [#234](https://github.com/tiangolo/sqlmodel/pull/234) by [@rabinadk1](https://github.com/rabinadk1).
|
||||||
|
* 🐛 Fix handling validators for non-default values. PR [#253](https://github.com/tiangolo/sqlmodel/pull/253) by [@byrman](https://github.com/byrman).
|
||||||
|
* 🐛 Fix fields marked as "set" in models. PR [#117](https://github.com/tiangolo/sqlmodel/pull/117) by [@statt8900](https://github.com/statt8900).
|
||||||
|
* 🐛 Fix Enum handling in SQLAlchemy. PR [#165](https://github.com/tiangolo/sqlmodel/pull/165) by [@chriswhite199](https://github.com/chriswhite199).
|
||||||
|
* 🐛 Fix setting nullable property of Fields that don't accept `None`. PR [#79](https://github.com/tiangolo/sqlmodel/pull/79) by [@van51](https://github.com/van51).
|
||||||
|
* 🐛 Fix SQLAlchemy version 1.4.36 breaks SQLModel relationships (#315). PR [#322](https://github.com/tiangolo/sqlmodel/pull/322) by [@byrman](https://github.com/byrman).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* 📝 Update docs for models for updating, `id` should not be updatable. PR [#335](https://github.com/tiangolo/sqlmodel/pull/335) by [@kurtportelli](https://github.com/kurtportelli).
|
||||||
|
* ✏ Fix broken variable/typo in docs for Read Relationships, `hero_spider_boy.id` => `hero_spider_boy.team_id`. PR [#106](https://github.com/tiangolo/sqlmodel/pull/106) by [@yoannmos](https://github.com/yoannmos).
|
||||||
|
* 🎨 Remove unwanted highlight in the docs. PR [#233](https://github.com/tiangolo/sqlmodel/pull/233) by [@jalvaradosegura](https://github.com/jalvaradosegura).
|
||||||
|
* ✏ Fix typos in `docs/databases.md` and `docs/tutorial/index.md`. PR [#35](https://github.com/tiangolo/sqlmodel/pull/35) by [@prrao87](https://github.com/prrao87).
|
||||||
|
* ✏ Fix typo in `docs/tutorial/relationship-attributes/define-relationships-attributes.md`. PR [#239](https://github.com/tiangolo/sqlmodel/pull/239) by [@jalvaradosegura](https://github.com/jalvaradosegura).
|
||||||
|
* ✏ Fix typo in `docs/tutorial/fastapi/simple-hero-api.md`. PR [#80](https://github.com/tiangolo/sqlmodel/pull/80) by [@joemudryk](https://github.com/joemudryk).
|
||||||
|
* ✏ Fix typos in multiple files in the docs. PR [#400](https://github.com/tiangolo/sqlmodel/pull/400) by [@VictorGambarini](https://github.com/VictorGambarini).
|
||||||
|
* ✏ Fix typo in `docs/tutorial/code-structure.md`. PR [#344](https://github.com/tiangolo/sqlmodel/pull/344) by [@marciomazza](https://github.com/marciomazza).
|
||||||
|
* ✏ Fix typo in `docs/db-to-code.md`. PR [#155](https://github.com/tiangolo/sqlmodel/pull/155) by [@gr8jam](https://github.com/gr8jam).
|
||||||
|
* ✏ Fix typo in `docs/contributing.md`. PR [#323](https://github.com/tiangolo/sqlmodel/pull/323) by [@Fardad13](https://github.com/Fardad13).
|
||||||
|
* ✏ Fix typo in `docs/tutorial/fastapi/tests.md`. PR [#265](https://github.com/tiangolo/sqlmodel/pull/265) by [@johnhoman](https://github.com/johnhoman).
|
||||||
|
* ✏ Fix typo in `docs/tutorial/where.md`. PR [#286](https://github.com/tiangolo/sqlmodel/pull/286) by [@jalvaradosegura](https://github.com/jalvaradosegura).
|
||||||
|
* ✏ Fix typos in `docs/tutorial/fastapi/update.md`. PR [#268](https://github.com/tiangolo/sqlmodel/pull/268) by [@cirrusj](https://github.com/cirrusj).
|
||||||
|
* ✏ Fix typo in `docs/tutorial/fastapi/simple-hero-api.md`. PR [#247](https://github.com/tiangolo/sqlmodel/pull/247) by [@hao-wang](https://github.com/hao-wang).
|
||||||
|
* ✏ Fix typos in `docs/tutorial/automatic-id-none-refresh.md`, `docs/tutorial/fastapi/update.md`, `docs/tutorial/select.md`. PR [#185](https://github.com/tiangolo/sqlmodel/pull/185) by [@rootux](https://github.com/rootux).
|
||||||
|
* ✏ Fix typo in `docs/databases.md`. PR [#177](https://github.com/tiangolo/sqlmodel/pull/177) by [@seandlg](https://github.com/seandlg).
|
||||||
|
* ✏ Fix typos in `docs/tutorial/fastapi/update.md`. PR [#162](https://github.com/tiangolo/sqlmodel/pull/162) by [@wmcgee3](https://github.com/wmcgee3).
|
||||||
|
* ✏ Fix typos in `docs/tutorial/code-structure.md`, `docs/tutorial/fastapi/multiple-models.md`, `docs/tutorial/fastapi/simple-hero-api.md`, `docs/tutorial/many-to-many/index.md`. PR [#116](https://github.com/tiangolo/sqlmodel/pull/116) by [@moonso](https://github.com/moonso).
|
||||||
|
* ✏ Fix typo in `docs/tutorial/fastapi/teams.md`. PR [#154](https://github.com/tiangolo/sqlmodel/pull/154) by [@chrisgoddard](https://github.com/chrisgoddard).
|
||||||
|
* ✏ Fix typo variable in example about relationships and `back_populates`, always use `hero` instead of `owner`. PR [#120](https://github.com/tiangolo/sqlmodel/pull/120) by [@onionj](https://github.com/onionj).
|
||||||
|
* ✏ Fix typo in `docs/tutorial/fastapi/tests.md`. PR [#113](https://github.com/tiangolo/sqlmodel/pull/113) by [@feanil](https://github.com/feanil).
|
||||||
|
* ✏ Fix typo in `docs/tutorial/where.md`. PR [#72](https://github.com/tiangolo/sqlmodel/pull/72) by [@ZettZet](https://github.com/ZettZet).
|
||||||
|
* ✏ Fix typo in `docs/tutorial/code-structure.md`. PR [#91](https://github.com/tiangolo/sqlmodel/pull/91) by [@dhiraj](https://github.com/dhiraj).
|
||||||
|
* ✏ Fix broken link to newsletter sign-up in `docs/help.md`. PR [#84](https://github.com/tiangolo/sqlmodel/pull/84) by [@mborus](https://github.com/mborus).
|
||||||
|
* ✏ Fix typos in `docs/tutorial/many-to-many/create-models-with-link.md`. PR [#45](https://github.com/tiangolo/sqlmodel/pull/45) by [@xginn8](https://github.com/xginn8).
|
||||||
|
* ✏ Fix typo in `docs/tutorial/index.md`. PR [#398](https://github.com/tiangolo/sqlmodel/pull/398) by [@ryangrose](https://github.com/ryangrose).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* ♻ Refactor internal statements to simplify code. PR [#53](https://github.com/tiangolo/sqlmodel/pull/53) by [@yezz123](https://github.com/yezz123).
|
||||||
|
* ♻ Refactor internal imports to reduce redundancy. PR [#272](https://github.com/tiangolo/sqlmodel/pull/272) by [@aminalaee](https://github.com/aminalaee).
|
||||||
|
* ⬆ Update development requirement for FastAPI from `^0.68.0` to `^0.68.1`. PR [#48](https://github.com/tiangolo/sqlmodel/pull/48) by [@alucarddelta](https://github.com/alucarddelta).
|
||||||
|
* ⏪ Revert upgrade Poetry, to make a release that supports Python 3.6 first. PR [#417](https://github.com/tiangolo/sqlmodel/pull/417) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Add dependabot for GitHub Actions. PR [#410](https://github.com/tiangolo/sqlmodel/pull/410) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ⬆️ Upgrade Poetry to version `==1.2.0b1`. PR [#303](https://github.com/tiangolo/sqlmodel/pull/303) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Add CI for Python 3.10. PR [#305](https://github.com/tiangolo/sqlmodel/pull/305) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 📝 Add Jina's QA Bot to the docs to help people that want to ask quick questions. PR [#263](https://github.com/tiangolo/sqlmodel/pull/263) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 👷 Upgrade Codecov GitHub Action. PR [#304](https://github.com/tiangolo/sqlmodel/pull/304) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 💚 Only run CI on push when on master, to avoid duplicate runs on PRs. PR [#244](https://github.com/tiangolo/sqlmodel/pull/244) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🔧 Upgrade MkDocs Material and update configs. PR [#217](https://github.com/tiangolo/sqlmodel/pull/217) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ⬆ Upgrade mypy, fix type annotations. PR [#218](https://github.com/tiangolo/sqlmodel/pull/218) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
## 0.0.6
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
**SQLModel** no longer creates indexes by default for every column, indexes are now opt-in. You can read more about it in PR [#205](https://github.com/tiangolo/sqlmodel/pull/205).
|
||||||
|
|
||||||
|
Before this change, if you had a model like this:
|
||||||
|
|
||||||
|
```Python
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from sqlmodel import Field, SQLModel
|
||||||
|
|
||||||
|
|
||||||
|
class Hero(SQLModel, table=True):
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
name: str
|
||||||
|
secret_name: str
|
||||||
|
age: Optional[int] = None
|
||||||
|
```
|
||||||
|
|
||||||
|
...when creating the tables, SQLModel version `0.0.5` and below, would also create an index for `name`, one for `secret_name`, and one for `age` (`id` is the primary key, so it doesn't need an additional index).
|
||||||
|
|
||||||
|
If you depended on having an index for each one of those columns, now you can (and would have to) define them explicitly:
|
||||||
|
|
||||||
|
```Python
|
||||||
|
class Hero(SQLModel, table=True):
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
name: str = Field(index=True)
|
||||||
|
secret_name: str = Field(index=True)
|
||||||
|
age: Optional[int] = Field(default=None, index=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
There's a high chance you don't need indexes for all the columns. For example, you might only need indexes for `name` and `age`, but not for `secret_name`. In that case, you could define the model as:
|
||||||
|
|
||||||
|
```Python
|
||||||
|
class Hero(SQLModel, table=True):
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
name: str = Field(index=True)
|
||||||
|
secret_name: str
|
||||||
|
age: Optional[int] = Field(default=None, index=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
If you already created your database tables with SQLModel using versions `0.0.5` or below, it would have also created those indexes in the database. In that case, you might want to manually drop (remove) some of those indexes, if they are unnecessary, to avoid the extra cost in performance and space.
|
||||||
|
|
||||||
|
Depending on the database you are using, there will be a different way to find the available indexes.
|
||||||
|
|
||||||
|
For example, let's say you no longer need the index for `secret_name`. You could check the current indexes in the database and find the one for `secret_name`, it could be named `ix_hero_secret_name`. Then you can remove it with SQL:
|
||||||
|
|
||||||
|
```SQL
|
||||||
|
DROP INDEX ix_hero_secret_name
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```SQL
|
||||||
|
DROP INDEX ix_hero_secret_name ON hero;
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's the new, extensive documentation explaining indexes and how to use them: [Indexes - Optimize Queries](https://sqlmodel.tiangolo.com/tutorial/indexes/).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* ✨ Document indexes and make them opt-in. Here's the new documentation: [Indexes - Optimize Queries](https://sqlmodel.tiangolo.com/tutorial/indexes/). This is the same change described above in **Breaking Changes**. PR [#205](https://github.com/tiangolo/sqlmodel/pull/205) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ✏ Fix typo in FastAPI tutorial. PR [#192](https://github.com/tiangolo/sqlmodel/pull/192) by [@yaquelinehoyos](https://github.com/yaquelinehoyos).
|
||||||
|
* 📝 Add links to the license file. PR [#29](https://github.com/tiangolo/sqlmodel/pull/29) by [@sobolevn](https://github.com/sobolevn).
|
||||||
|
* ✏ Fix typos in docs titles. PR [#28](https://github.com/tiangolo/sqlmodel/pull/28) by [@Batalex](https://github.com/Batalex).
|
||||||
|
* ✏ Fix multiple typos and some rewording. PR [#22](https://github.com/tiangolo/sqlmodel/pull/22) by [@egrim](https://github.com/egrim).
|
||||||
|
* ✏ Fix typo in `docs/tutorial/automatic-id-none-refresh.md`. PR [#14](https://github.com/tiangolo/sqlmodel/pull/14) by [@leynier](https://github.com/leynier).
|
||||||
|
* ✏ Fix typos in `docs/tutorial/index.md` and `docs/databases.md`. PR [#5](https://github.com/tiangolo/sqlmodel/pull/5) by [@sebastianmarines](https://github.com/sebastianmarines).
|
||||||
|
|
||||||
|
## 0.0.5
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ✨ Add support for Decimal fields from Pydantic and SQLAlchemy. Original PR [#103](https://github.com/tiangolo/sqlmodel/pull/103) by [@robcxyz](https://github.com/robcxyz). New docs: [Advanced User Guide - Decimal Numbers](https://sqlmodel.tiangolo.com/advanced/decimal/).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* ✏ Update decimal tutorial source for consistency. PR [#188](https://github.com/tiangolo/sqlmodel/pull/188) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* 🔧 Split MkDocs insiders build in CI to support building from PRs. PR [#186](https://github.com/tiangolo/sqlmodel/pull/186) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🎨 Format `expression.py` and expression template, currently needed by CI. PR [#187](https://github.com/tiangolo/sqlmodel/pull/187) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* 🐛Fix docs light/dark theme switcher. PR [#1](https://github.com/tiangolo/sqlmodel/pull/1) by [@Lehoczky](https://github.com/Lehoczky).
|
||||||
|
* 🔧 Add MkDocs Material social cards. PR [#90](https://github.com/tiangolo/sqlmodel/pull/90) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ✨ Update type annotations and upgrade mypy. PR [#173](https://github.com/tiangolo/sqlmodel/pull/173) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
## 0.0.4
|
||||||
|
|
||||||
|
* 🎨 Fix type detection of select results in PyCharm. PR [#15](https://github.com/tiangolo/sqlmodel/pull/15) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
## 0.0.3
|
## 0.0.3
|
||||||
|
|
||||||
|
|||||||
3
docs/resources/index.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Resources
|
||||||
|
|
||||||
|
Additional resources, how to **help** and get help, how to **contribute**, and more. ✈️
|
||||||
@@ -1,27 +1,12 @@
|
|||||||
# Automatic IDs, None Defaults, and Refreshing Data
|
# Automatic IDs, None Defaults, and Refreshing Data
|
||||||
|
|
||||||
In the previous chapter we saw how to add rows to the database using **SQLModel**.
|
In the previous chapter, we saw how to add rows to the database using **SQLModel**.
|
||||||
|
|
||||||
Now let's talk a bit about why the `id` field **can't be `NULL`** on the database because it's a **primary key**, and we declare it using `Field(primary_key=True)`.
|
Now let's talk a bit about why the `id` field **can't be `NULL`** on the database because it's a **primary key**, and we declare it using `Field(primary_key=True)`.
|
||||||
|
|
||||||
But the same `id` field actually **can be `None`** in the Python code, so we declare the type with `Optional[int]`, and set the default value to `Field(default=None)`:
|
But the same `id` field actually **can be `None`** in the Python code, so we declare the type with `int | None (or Optional[int])`, and set the default value to `Field(default=None)`:
|
||||||
|
|
||||||
```Python hl_lines="4"
|
{* ./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py ln[4:8] hl[5] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:6-10]!}
|
|
||||||
|
|
||||||
# Code below ommitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Next, I'll show you a bit more about the synchronization of data between the database and the Python code.
|
Next, I'll show you a bit more about the synchronization of data between the database and the Python code.
|
||||||
|
|
||||||
@@ -31,22 +16,7 @@ When do we get an actual `int` from the database in that `id` field? Let's see a
|
|||||||
|
|
||||||
When we create a new `Hero` instance, we don't set the `id`:
|
When we create a new `Hero` instance, we don't set the `id`:
|
||||||
|
|
||||||
```Python hl_lines="3-6"
|
{* ./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py ln[21:24] hl[21:24] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:23-26]!}
|
|
||||||
|
|
||||||
# Code below ommitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### How `Optional` Helps
|
### How `Optional` Helps
|
||||||
|
|
||||||
@@ -68,28 +38,13 @@ If we ran this code before saving the hero to the database and the `hero_1.id` w
|
|||||||
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
|
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
|
||||||
```
|
```
|
||||||
|
|
||||||
But by declaring it with `Optional[int]` the editor will help us to avoid writing broken code by showing us a warning telling us that the code could be invalid if `hero_1.id` is `None`. 🔍
|
But by declaring it with `Optional[int]`, the editor will help us to avoid writing broken code by showing us a warning telling us that the code could be invalid if `hero_1.id` is `None`. 🔍
|
||||||
|
|
||||||
## Print the Default `id` Values
|
## Print the Default `id` Values
|
||||||
|
|
||||||
We can confirm that by printing our heroes before adding them to the database:
|
We can confirm that by printing our heroes before adding them to the database:
|
||||||
|
|
||||||
```Python hl_lines="9-11"
|
{* ./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py ln[21:29] hl[27:29] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:23-31]!}
|
|
||||||
|
|
||||||
# Code below ommitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
That will output:
|
That will output:
|
||||||
|
|
||||||
@@ -98,7 +53,7 @@ That will output:
|
|||||||
```console
|
```console
|
||||||
$ python app.py
|
$ python app.py
|
||||||
|
|
||||||
// Output above ommitted 👆
|
// Output above omitted 👆
|
||||||
|
|
||||||
Before interacting with the database
|
Before interacting with the database
|
||||||
Hero 1: id=None name='Deadpond' secret_name='Dive Wilson' age=None
|
Hero 1: id=None name='Deadpond' secret_name='Dive Wilson' age=None
|
||||||
@@ -118,24 +73,9 @@ What happens when we `add` these objects to the **session**?
|
|||||||
|
|
||||||
After we add the `Hero` instance objects to the **session**, the IDs are *still* `None`.
|
After we add the `Hero` instance objects to the **session**, the IDs are *still* `None`.
|
||||||
|
|
||||||
We can verify by creating a session using a `with` block, and adding the objects. And then printing them again:
|
We can verify by creating a session using a `with` block and adding the objects. And then printing them again:
|
||||||
|
|
||||||
```Python hl_lines="19-21"
|
{* ./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py ln[21:39] hl[37:39] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:23-41]!}
|
|
||||||
|
|
||||||
# Code below ommitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
This will, again, output the `id`s of the objects as `None`:
|
This will, again, output the `id`s of the objects as `None`:
|
||||||
|
|
||||||
@@ -144,7 +84,7 @@ This will, again, output the `id`s of the objects as `None`:
|
|||||||
```console
|
```console
|
||||||
$ python app.py
|
$ python app.py
|
||||||
|
|
||||||
// Output above ommitted 👆
|
// Output above omitted 👆
|
||||||
|
|
||||||
After adding to the session
|
After adding to the session
|
||||||
Hero 1: id=None name='Deadpond' secret_name='Dive Wilson' age=None
|
Hero 1: id=None name='Deadpond' secret_name='Dive Wilson' age=None
|
||||||
@@ -160,22 +100,7 @@ As we saw before, the **session** is smart and doesn't talk to the database ever
|
|||||||
|
|
||||||
Then we can `commit` the changes in the session, and print again:
|
Then we can `commit` the changes in the session, and print again:
|
||||||
|
|
||||||
```Python hl_lines="13 16-18"
|
{* ./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py ln[31:46] hl[41,44:46] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-48]!}
|
|
||||||
|
|
||||||
# Code below ommitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
And now, something unexpected happens, look at the output, it seems as if the `Hero` instance objects had no data at all:
|
And now, something unexpected happens, look at the output, it seems as if the `Hero` instance objects had no data at all:
|
||||||
|
|
||||||
@@ -184,7 +109,7 @@ And now, something unexpected happens, look at the output, it seems as if the `H
|
|||||||
```console
|
```console
|
||||||
$ python app.py
|
$ python app.py
|
||||||
|
|
||||||
// Output above ommitted 👆
|
// Output above omitted 👆
|
||||||
|
|
||||||
// Here the engine talks to the database, the SQL part
|
// Here the engine talks to the database, the SQL part
|
||||||
INFO Engine BEGIN (implicit)
|
INFO Engine BEGIN (implicit)
|
||||||
@@ -233,22 +158,7 @@ We didn't access the object's attributes, like `hero.name`. We only accessed the
|
|||||||
|
|
||||||
To confirm and understand how this **automatic expiration and refresh** of data when accessing attributes work, we can print some individual fields (instance attributes):
|
To confirm and understand how this **automatic expiration and refresh** of data when accessing attributes work, we can print some individual fields (instance attributes):
|
||||||
|
|
||||||
```Python hl_lines="21-23 26-28"
|
{* ./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py ln[31:56] hl[49:51,54:56] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-58]!}
|
|
||||||
|
|
||||||
# Code below ommitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Now we are actually accessing the attributes, because instead of printing the whole object `hero_1`:
|
Now we are actually accessing the attributes, because instead of printing the whole object `hero_1`:
|
||||||
|
|
||||||
@@ -271,7 +181,7 @@ Let's see how it works:
|
|||||||
```console
|
```console
|
||||||
$ python app.py
|
$ python app.py
|
||||||
|
|
||||||
// Output above ommitted 👆
|
// Output above omitted 👆
|
||||||
|
|
||||||
// After committing, the objects are expired and have no values
|
// After committing, the objects are expired and have no values
|
||||||
After committing the session
|
After committing the session
|
||||||
@@ -317,7 +227,6 @@ Hero 2 name: Spider-Boy
|
|||||||
Hero 3 name: Rusty-Man
|
Hero 3 name: Rusty-Man
|
||||||
|
|
||||||
// Because the Session already refreshed these objects with all their data and the session knows they are not expired, it doesn't have to go again to the database for the names 🤓
|
// Because the Session already refreshed these objects with all their data and the session knows they are not expired, it doesn't have to go again to the database for the names 🤓
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -330,22 +239,7 @@ But what if you want to **explicitly refresh** the data?
|
|||||||
|
|
||||||
You can do that too with `session.refresh(object)`:
|
You can do that too with `session.refresh(object)`:
|
||||||
|
|
||||||
```Python hl_lines="30-32 35-37"
|
{* ./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py ln[31:65] hl[58:60,63:65] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-67]!}
|
|
||||||
|
|
||||||
# Code below ommitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
When Python executes this code:
|
When Python executes this code:
|
||||||
|
|
||||||
@@ -362,7 +256,7 @@ Here's how the output would look like:
|
|||||||
```console
|
```console
|
||||||
$ python app.py
|
$ python app.py
|
||||||
|
|
||||||
// Output above ommitted 👆
|
// Output above omitted 👆
|
||||||
|
|
||||||
// The first refresh
|
// The first refresh
|
||||||
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
|
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
|
||||||
@@ -399,26 +293,11 @@ In this case, after committing the object to the database with the **session**,
|
|||||||
|
|
||||||
## Print Data After Closing the Session
|
## Print Data After Closing the Session
|
||||||
|
|
||||||
Now, as a fnal experiment, we can also print data after the **session** is closed.
|
Now, as a final experiment, we can also print data after the **session** is closed.
|
||||||
|
|
||||||
There are no surprises here, it still works:
|
There are no surprises here, it still works:
|
||||||
|
|
||||||
```Python hl_lines="40-42"
|
{* ./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py ln[31:70] hl[68:70] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-72]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
And the output shows again the same data:
|
And the output shows again the same data:
|
||||||
|
|
||||||
@@ -427,7 +306,7 @@ And the output shows again the same data:
|
|||||||
```console
|
```console
|
||||||
$ python app.py
|
$ python app.py
|
||||||
|
|
||||||
// Output above ommitted 👆
|
// Output above omitted 👆
|
||||||
|
|
||||||
// By finishing the with block, the Session is closed, including a rollback of any pending transaction that could have been there and was not committed
|
// By finishing the with block, the Session is closed, including a rollback of any pending transaction that could have been there and was not committed
|
||||||
INFO Engine ROLLBACK
|
INFO Engine ROLLBACK
|
||||||
@@ -445,17 +324,34 @@ Hero 3: age=48 id=3 name='Rusty-Man' secret_name='Tommy Sharp'
|
|||||||
|
|
||||||
Now let's review all this code once again.
|
Now let's review all this code once again.
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
Each one of the numbered bubbles shows what each line will print in the output.
|
|
||||||
|
|
||||||
And as we created the **engine** with `echo=True`, we can see the SQL statements being executed at each step.
|
Each one of the numbered bubbles shows what each line will print in the output.
|
||||||
|
|
||||||
```{ .python .annotate hl_lines="54" }
|
And as we created the **engine** with `echo=True`, we can see the SQL statements being executed at each step.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
//// tab | Python 3.10+
|
||||||
|
|
||||||
|
```Python
|
||||||
|
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial002_py310.py!}
|
||||||
|
```
|
||||||
|
|
||||||
|
{!./docs_src/tutorial/automatic_id_none_refresh/annotations/en/tutorial002.md!}
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
//// tab | Python 3.7+
|
||||||
|
|
||||||
|
```Python
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial002.py!}
|
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial002.py!}
|
||||||
```
|
```
|
||||||
|
|
||||||
{!./docs_src/tutorial/automatic_id_none_refresh/annotations/en/tutorial002.md!}
|
{!./docs_src/tutorial/automatic_id_none_refresh/annotations/en/tutorial002.md!}
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
And here's all the output generated by running this program, all together:
|
And here's all the output generated by running this program, all together:
|
||||||
|
|
||||||
<div class="termy">
|
<div class="termy">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ The class `Hero` has a reference to the class `Team` internally.
|
|||||||
|
|
||||||
But the class `Team` also has a reference to the class `Hero`.
|
But the class `Team` also has a reference to the class `Hero`.
|
||||||
|
|
||||||
So, if those two classes where in separate files and you tried to import the classes in each other's file directly, it would result in a **circular import**. 🔄
|
So, if those two classes were in separate files and you tried to import the classes in each other's file directly, it would result in a **circular import**. 🔄
|
||||||
|
|
||||||
And Python will not be able to handle it and will throw an error. 🚨
|
And Python will not be able to handle it and will throw an error. 🚨
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ So, the output would be:
|
|||||||
$ python -m project.app
|
$ python -m project.app
|
||||||
|
|
||||||
Created hero: id=1 secret_name='Dive Wilson' team_id=1 name='Deadpond' age=None
|
Created hero: id=1 secret_name='Dive Wilson' team_id=1 name='Deadpond' age=None
|
||||||
Hero's team: name='Z-Force' headquarters='Sister Margaret’s Bar' id=1
|
Hero's team: name='Z-Force' headquarters='Sister Margaret's Bar' id=1
|
||||||
```
|
```
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -149,10 +149,13 @@ Let's say that for some reason you hate the idea of having all the database mode
|
|||||||
|
|
||||||
You can also do it. 😎 There's a couple of things to keep in mind. 🤓
|
You can also do it. 😎 There's a couple of things to keep in mind. 🤓
|
||||||
|
|
||||||
!!! warning
|
/// warning
|
||||||
This is a bit more advanced.
|
|
||||||
|
|
||||||
If the solution above already worked for you, that might be enough for you, and you can continue in the next chapter. 🤓
|
This is a bit more advanced.
|
||||||
|
|
||||||
|
If the solution above already worked for you, that might be enough for you, and you can continue in the next chapter. 🤓
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
Let's assume that now the file structure is:
|
Let's assume that now the file structure is:
|
||||||
|
|
||||||
@@ -168,9 +171,9 @@ Let's assume that now the file structure is:
|
|||||||
|
|
||||||
### Circular Imports and Type Annotations
|
### Circular Imports and Type Annotations
|
||||||
|
|
||||||
The problem with circular imports is that Python can't resolve them at <abbr title="While it is executing the program, as oposed to the code as just text in a file stored on disk.">*runtime*</abbr>.
|
The problem with circular imports is that Python can't resolve them at <abbr title="While it is executing the program, as opposed to the code as just text in a file stored on disk.">*runtime*</abbr>.
|
||||||
|
|
||||||
but when using Python **type annotations** it's very common to need to declare the type of some variables with classes imported from other files.
|
But when using Python **type annotations** it's very common to need to declare the type of some variables with classes imported from other files.
|
||||||
|
|
||||||
And the files with those classes might **also need to import** more things from the first files.
|
And the files with those classes might **also need to import** more things from the first files.
|
||||||
|
|
||||||
@@ -198,7 +201,7 @@ It has a value of `True` for editors and tools that analyze the code with the ty
|
|||||||
|
|
||||||
But when Python is executing, its value is `False`.
|
But when Python is executing, its value is `False`.
|
||||||
|
|
||||||
So, we can us it in an `if` block and import things inside the `if` block. And they will be "imported" only for editors, but not at runtime.
|
So, we can use it in an `if` block and import things inside the `if` block. And they will be "imported" only for editors, but not at runtime.
|
||||||
|
|
||||||
### Hero Model File
|
### Hero Model File
|
||||||
|
|
||||||
@@ -240,7 +243,7 @@ And running that achieves the same result as before:
|
|||||||
$ python -m project.app
|
$ python -m project.app
|
||||||
|
|
||||||
Created hero: id=1 age=None name='Deadpond' secret_name='Dive Wilson' team_id=1
|
Created hero: id=1 age=None name='Deadpond' secret_name='Dive Wilson' team_id=1
|
||||||
Hero's team: id=1 name='Z-Force' headquarters='Sister Margaret’s Bar'
|
Hero's team: id=1 name='Z-Force' headquarters='Sister Margaret's Bar'
|
||||||
```
|
```
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ The `team` table will look like this:
|
|||||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -37,19 +37,15 @@ Each row in the table `hero` will point to a row in the table `team`:
|
|||||||
|
|
||||||
<img alt="table relationships" src="/img/tutorial/relationships/select/relationships2.svg">
|
<img alt="table relationships" src="/img/tutorial/relationships/select/relationships2.svg">
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
We will later update **Spider-Boy** to add him to the **Preventers** team too, but not yet.
|
|
||||||
|
We will later update **Spider-Boy** to add him to the **Preventers** team too, but not yet.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
We will continue with the code in the previous example and we will add more things to it.
|
We will continue with the code in the previous example and we will add more things to it.
|
||||||
|
|
||||||
<details>
|
{* ./docs_src/tutorial/connect/create_tables/tutorial001_py310.py ln[0] *}
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/create_tables/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Make sure you remove the `database.db` file before running the examples to get the same results.
|
Make sure you remove the `database.db` file before running the examples to get the same results.
|
||||||
|
|
||||||
@@ -61,22 +57,7 @@ And now we will also create the teams there. 🎉
|
|||||||
|
|
||||||
Let's start by creating two teams:
|
Let's start by creating two teams:
|
||||||
|
|
||||||
```Python hl_lines="3-9"
|
{* ./docs_src/tutorial/connect/insert/tutorial001_py310.py ln[29:35] hl[29:35] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/insert/tutorial001.py[ln:31-37]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
This would hopefully look already familiar.
|
This would hopefully look already familiar.
|
||||||
|
|
||||||
@@ -92,22 +73,7 @@ And finally we **commit** the session to save the changes to the database.
|
|||||||
|
|
||||||
Let's not forget to add this function `create_heroes()` to the `main()` function so that we run it when calling the program from the command line:
|
Let's not forget to add this function `create_heroes()` to the `main()` function so that we run it when calling the program from the command line:
|
||||||
|
|
||||||
```Python hl_lines="5"
|
{* ./docs_src/tutorial/connect/insert/tutorial001_py310.py ln[61:63] hl[63] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/insert/tutorial001.py[ln:63-65]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Run it
|
## Run it
|
||||||
|
|
||||||
@@ -126,7 +92,7 @@ INFO Engine BEGIN (implicit)
|
|||||||
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
||||||
INFO Engine [generated in 0.00050s] ('Preventers', 'Sharp Tower')
|
INFO Engine [generated in 0.00050s] ('Preventers', 'Sharp Tower')
|
||||||
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
||||||
INFO Engine [cached since 0.002324s ago] ('Z-Force', 'Sister Margaret’s Bar')
|
INFO Engine [cached since 0.002324s ago] ('Z-Force', 'Sister Margaret's Bar')
|
||||||
INFO Engine COMMIT
|
INFO Engine COMMIT
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -140,22 +106,7 @@ Now let's create one hero object to start.
|
|||||||
|
|
||||||
As the `Hero` class model now has a field (column, attribute) `team_id`, we can set it by using the ID field from the `Team` objects we just created before:
|
As the `Hero` class model now has a field (column, attribute) `team_id`, we can set it by using the ID field from the `Team` objects we just created before:
|
||||||
|
|
||||||
```Python hl_lines="12"
|
{* ./docs_src/tutorial/connect/insert/tutorial001_py310.py ln[29:39] hl[38] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/insert/tutorial001.py[ln:31-41]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
We haven't committed this hero to the database yet, but there are already a couple of things to pay **attention** to.
|
We haven't committed this hero to the database yet, but there are already a couple of things to pay **attention** to.
|
||||||
|
|
||||||
@@ -179,22 +130,7 @@ INFO Engine [generated in 0.00025s] (2,)
|
|||||||
|
|
||||||
Let's now create two more heroes:
|
Let's now create two more heroes:
|
||||||
|
|
||||||
```Python hl_lines="14-20"
|
{* ./docs_src/tutorial/connect/insert/tutorial001_py310.py ln[29:50] hl[40:46] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/insert/tutorial001.py[ln:31-52]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
When creating `hero_rusty_man`, we are accessing `team_preventers.id`, so that will also trigger a refresh of its data, generating an output of:
|
When creating `hero_rusty_man`, we are accessing `team_preventers.id`, so that will also trigger a refresh of its data, generating an output of:
|
||||||
|
|
||||||
@@ -225,23 +161,7 @@ INFO Engine COMMIT
|
|||||||
|
|
||||||
Now let's refresh and print those new heroes to see their new ID pointing to their teams:
|
Now let's refresh and print those new heroes to see their new ID pointing to their teams:
|
||||||
|
|
||||||
```Python hl_lines="26-28 30-32"
|
{* ./docs_src/tutorial/connect/insert/tutorial001_py310.py ln[29:58] hl[52:54,56:58] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/insert/tutorial001.py[ln:31-60]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
If we execute that in the command line, it will output:
|
If we execute that in the command line, it will output:
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ The team table will look like this:
|
|||||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -57,20 +57,7 @@ Let's start by creating the tables in code.
|
|||||||
|
|
||||||
Import the things we need from `sqlmodel` and create a new `Team` model:
|
Import the things we need from `sqlmodel` and create a new `Team` model:
|
||||||
|
|
||||||
```Python hl_lines="6-9"
|
{* ./docs_src/tutorial/connect/create_tables/tutorial001_py310.py ln[1:7] hl[4:7] *}
|
||||||
{!./docs_src/tutorial/connect/create_tables/tutorial001.py[ln:1-9]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/create_tables/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
This is very similar to what we have been doing with the `Hero` model.
|
This is very similar to what we have been doing with the `Hero` model.
|
||||||
|
|
||||||
@@ -78,6 +65,7 @@ The `Team` model will be in a table automatically named `"team"`, and it will ha
|
|||||||
|
|
||||||
* `id`, the primary key, automatically generated by the database
|
* `id`, the primary key, automatically generated by the database
|
||||||
* `name`, the name of the team
|
* `name`, the name of the team
|
||||||
|
* We also tell **SQLModel** to create an index for this column
|
||||||
* `headquarters`, the headquarters of the team
|
* `headquarters`, the headquarters of the team
|
||||||
|
|
||||||
And finally we mark it as a table in the config.
|
And finally we mark it as a table in the config.
|
||||||
@@ -88,26 +76,13 @@ Now let's create the `hero` table.
|
|||||||
|
|
||||||
This is the same model we have been using up to now, we are just adding the new column `team_id`:
|
This is the same model we have been using up to now, we are just adding the new column `team_id`:
|
||||||
|
|
||||||
```Python hl_lines="18"
|
{* ./docs_src/tutorial/connect/create_tables/tutorial001_py310.py ln[1:16] hl[16] *}
|
||||||
{!./docs_src/tutorial/connect/create_tables/tutorial001.py[ln:1-18]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/create_tables/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Most of that should look familiar:
|
Most of that should look familiar:
|
||||||
|
|
||||||
The column will be named `team_id`. It will be an integer, and it could be `NULL` in the database (or `None` in Python), becase there could be some heroes that don't belong to any team.
|
The column will be named `team_id`. It will be an integer, and it could be `NULL` in the database (or `None` in Python), because there could be some heroes that don't belong to any team.
|
||||||
|
|
||||||
As we don't have to explicitly pass `team_id=None` when creating a hero, we add a default of `None` to the `Field()`.
|
We add a default of `None` to the `Field()` so we don't have to explicitly pass `team_id=None` when creating a hero.
|
||||||
|
|
||||||
Now, here's the new part:
|
Now, here's the new part:
|
||||||
|
|
||||||
@@ -125,49 +100,29 @@ This is the name of the **table** in the database, so it is `"team"`, not the na
|
|||||||
|
|
||||||
If you had a custom table name, you would use that custom table name.
|
If you had a custom table name, you would use that custom table name.
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
You can learn about setting a custom table name for a model in the Advanced User Guide.
|
|
||||||
|
You can learn about setting a custom table name for a model in the Advanced User Guide.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
### Create the Tables
|
### Create the Tables
|
||||||
|
|
||||||
Now we can add the same code as before to create the engine and the function to create the tables:
|
Now we can add the same code as before to create the engine and the function to create the tables:
|
||||||
|
|
||||||
```Python hl_lines="3-4 6 9-10"
|
{* ./docs_src/tutorial/connect/create_tables/tutorial001_py310.py ln[19:26] hl[19:20,22,25:26] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/create_tables/tutorial001.py[ln:21-28]!}
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/create_tables/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
And as before, we'll call this function from another function `main()`, and we'll add that function `main()` to the main block of the file:
|
And as before, we'll call this function from another function `main()`, and we'll add that function `main()` to the main block of the file:
|
||||||
|
|
||||||
```Python hl_lines="3-4 7-8"
|
{* ./docs_src/tutorial/connect/create_tables/tutorial001_py310.py ln[29:34] hl[29:30,33:34] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/create_tables/tutorial001.py[ln:31-36]!}
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/create_tables/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Run the Code
|
## Run the Code
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
Before running the code, make sure you delete the file `database.db` to make sure you start from scratch.
|
|
||||||
|
Before running the code, make sure you delete the file `database.db` to make sure you start from scratch.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
If we run the code we have up to now, it will go and create the database file `database.db` and the tables in it we just defined, `team` and `hero`:
|
If we run the code we have up to now, it will go and create the database file `database.db` and the tables in it we just defined, `team` and `hero`:
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ But the main advantage and feature of SQL databases is being able to handle rela
|
|||||||
|
|
||||||
Let's see how to use **SQLModel** to manage connected data in the next chapters. 🤝
|
Let's see how to use **SQLModel** to manage connected data in the next chapters. 🤝
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
We will extend this further in the next group of chapters making it even more convenient to work with in Python code, using **relationship attributes**.
|
|
||||||
|
|
||||||
But you should start in this group of chapters first. 🤓
|
We will extend this further in the next group of chapters making it even more convenient to work with in Python code, using **relationship attributes**.
|
||||||
|
|
||||||
|
But you should start in this group of chapters first. 🤓
|
||||||
|
|
||||||
|
///
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ The `team` table has this data:
|
|||||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -35,14 +35,7 @@ And the `hero` table has this data:
|
|||||||
|
|
||||||
We will continue with the code in the previous example and we will add more things to it.
|
We will continue with the code in the previous example and we will add more things to it.
|
||||||
|
|
||||||
<details>
|
{* ./docs_src/tutorial/connect/insert/tutorial001_py310.py ln[0] *}
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## `SELECT` Connected Data with SQL
|
## `SELECT` Connected Data with SQL
|
||||||
|
|
||||||
@@ -62,8 +55,11 @@ FROM hero, team
|
|||||||
WHERE hero.team_id = team.id
|
WHERE hero.team_id = team.id
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
Because we have two columns called `name`, one for `hero` and one for `team`, we can specify them with the prefix of the table name and the dot to make it explicit what we refer to.
|
|
||||||
|
Because we have two columns called `name`, one for `hero` and one for `team`, we can specify them with the prefix of the table name and the dot to make it explicit what we refer to.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
Notice that now in the `WHERE` part we are not comparing one column with a literal value (like `hero.name = "Deadpond"`), but we are comparing two columns.
|
Notice that now in the `WHERE` part we are not comparing one column with a literal value (like `hero.name = "Deadpond"`), but we are comparing two columns.
|
||||||
|
|
||||||
@@ -99,14 +95,17 @@ You can go ahead and try it in **DB Browser for SQLite**:
|
|||||||
|
|
||||||
<img class="shadow" src="/img/tutorial/relationships/select/image01.png">
|
<img class="shadow" src="/img/tutorial/relationships/select/image01.png">
|
||||||
|
|
||||||
!!! note
|
/// note
|
||||||
Wait, what about Spider-Boy? 😱
|
|
||||||
|
|
||||||
He doesn't have a team, so his `team_id` is `NULL` in the database. And this SQL is comparing that `NULL` from the `team_id` with all the `id` fields in the rows in the `team` table.
|
Wait, what about Spider-Boy? 😱
|
||||||
|
|
||||||
As there's no team with an ID of `NULL`, it doesn't find a match.
|
He doesn't have a team, so his `team_id` is `NULL` in the database. And this SQL is comparing that `NULL` from the `team_id` with all the `id` fields in the rows in the `team` table.
|
||||||
|
|
||||||
But we'll see how to fix that later with a `LEFT JOIN`.
|
As there's no team with an ID of `NULL`, it doesn't find a match.
|
||||||
|
|
||||||
|
But we'll see how to fix that later with a `LEFT JOIN`.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Select Related Data with **SQLModel**
|
## Select Related Data with **SQLModel**
|
||||||
|
|
||||||
@@ -118,22 +117,7 @@ Remember SQLModel's `select()` function? It can take more than one argument.
|
|||||||
|
|
||||||
So, we can pass the `Hero` and `Team` model classes. And we can also use both their columns in the `.where()` part:
|
So, we can pass the `Hero` and `Team` model classes. And we can also use both their columns in the `.where()` part:
|
||||||
|
|
||||||
```Python hl_lines="5"
|
{* ./docs_src/tutorial/connect/select/tutorial001_py310.py ln[61:63] hl[63] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial001.py[ln:63-65]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Notice that in the comparison with `==` we are using the class attributes for both `Hero.team_id` and `Team.id`.
|
Notice that in the comparison with `==` we are using the class attributes for both `Hero.team_id` and `Team.id`.
|
||||||
|
|
||||||
@@ -143,53 +127,25 @@ Now we can execute it and get the `results` object.
|
|||||||
|
|
||||||
And as we used `select` with two models, we will receive tuples of instances of those two models, so we can iterate over them naturally in a `for` loop:
|
And as we used `select` with two models, we will receive tuples of instances of those two models, so we can iterate over them naturally in a `for` loop:
|
||||||
|
|
||||||
```Python hl_lines="7"
|
{* ./docs_src/tutorial/connect/select/tutorial001_py310.py ln[61:66] hl[65] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial001.py[ln:63-68]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
For each iteration in the `for` loop we get a a tuple with an instance of the class `Hero` and an instance of the class `Team`.
|
For each iteration in the `for` loop we get a a tuple with an instance of the class `Hero` and an instance of the class `Team`.
|
||||||
|
|
||||||
And in this `for` loop we assign them to the variable `hero` and the variable `team`.
|
And in this `for` loop we assign them to the variable `hero` and the variable `team`.
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
There was a lot of research, design, and work behind **SQLModel** to make this provide the best possible developer experience.
|
|
||||||
|
|
||||||
And you should get autocompletion and inline errors in your editor for both `hero` and `team`. 🎉
|
There was a lot of research, design, and work behind **SQLModel** to make this provide the best possible developer experience.
|
||||||
|
|
||||||
|
And you should get autocompletion and inline errors in your editor for both `hero` and `team`. 🎉
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Add It to Main
|
## Add It to Main
|
||||||
|
|
||||||
As always, we must remember to add this new `select_heroes()` function to the `main()` function to make sure it is executed when we call this program from the command line.
|
As always, we must remember to add this new `select_heroes()` function to the `main()` function to make sure it is executed when we call this program from the command line.
|
||||||
|
|
||||||
```Python hl_lines="6"
|
{* ./docs_src/tutorial/connect/select/tutorial001_py310.py ln[69:72] hl[72] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial001.py[ln:71-74]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
## Run the Program
|
## Run the Program
|
||||||
|
|
||||||
@@ -209,7 +165,7 @@ WHERE hero.team_id = team.id
|
|||||||
2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine [no key 0.00015s] ()
|
2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine [no key 0.00015s] ()
|
||||||
|
|
||||||
// Print the first hero and team
|
// Print the first hero and team
|
||||||
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margaret’s Bar' id=2 name='Z-Force'
|
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margaret's Bar' id=2 name='Z-Force'
|
||||||
|
|
||||||
// Print the second hero and team
|
// Print the second hero and team
|
||||||
Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 Team: headquarters='Sharp Tower' id=1 name='Preventers'
|
Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 Team: headquarters='Sharp Tower' id=1 name='Preventers'
|
||||||
@@ -281,10 +237,13 @@ Also in **DB Browser for SQLite**:
|
|||||||
|
|
||||||
<img class="shadow" src="/img/tutorial/relationships/select/image02.png">
|
<img class="shadow" src="/img/tutorial/relationships/select/image02.png">
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
Why bother with all this if the result is the same?
|
|
||||||
|
|
||||||
This `JOIN` will be useful in a bit to be able to also get Spider-Boy, even if he doesn't have a team.
|
Why bother with all this if the result is the same?
|
||||||
|
|
||||||
|
This `JOIN` will be useful in a bit to be able to also get Spider-Boy, even if he doesn't have a team.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Join Tables in **SQLModel**
|
## Join Tables in **SQLModel**
|
||||||
|
|
||||||
@@ -292,22 +251,7 @@ The same way there's a `.where()` available when using `select()`, there's also
|
|||||||
|
|
||||||
And in SQLModel (actually SQLAlchemy), when using the `.join()`, because we already declared what is the `foreign_key` when creating the models, we don't have to pass an `ON` part, it is inferred automatically:
|
And in SQLModel (actually SQLAlchemy), when using the `.join()`, because we already declared what is the `foreign_key` when creating the models, we don't have to pass an `ON` part, it is inferred automatically:
|
||||||
|
|
||||||
```Python hl_lines="5"
|
{* ./docs_src/tutorial/connect/select/tutorial002_py310.py ln[61:66] hl[63] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial002.py[ln:63-68]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial002.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Also notice that we are still including `Team` in the `select(Hero, Team)`, because we still want to access that data.
|
Also notice that we are still including `Team` in the `select(Hero, Team)`, because we still want to access that data.
|
||||||
|
|
||||||
@@ -328,7 +272,7 @@ FROM hero JOIN team ON team.id = hero.team_id
|
|||||||
INFO Engine [no key 0.00032s] ()
|
INFO Engine [no key 0.00032s] ()
|
||||||
|
|
||||||
// Print the first hero and team
|
// Print the first hero and team
|
||||||
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margaret’s Bar' id=2 name='Z-Force'
|
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margaret's Bar' id=2 name='Z-Force'
|
||||||
|
|
||||||
// Print the second hero and team
|
// Print the second hero and team
|
||||||
Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 Team: headquarters='Sharp Tower' id=1 name='Preventers'
|
Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 Team: headquarters='Sharp Tower' id=1 name='Preventers'
|
||||||
@@ -420,8 +364,11 @@ And that would return the following result, including **Spider-Boy** 🎉:
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
The only difference between this query and the previous is that extra `LEFT OUTER`.
|
|
||||||
|
The only difference between this query and the previous is that extra `LEFT OUTER`.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
And here's another of the SQL variations, you could write `LEFT OUTER JOIN` or just `LEFT JOIN`, it means the same.
|
And here's another of the SQL variations, you could write `LEFT OUTER JOIN` or just `LEFT JOIN`, it means the same.
|
||||||
|
|
||||||
@@ -431,22 +378,7 @@ Now let's replicate the same query in **SQLModel**.
|
|||||||
|
|
||||||
`.join()` has a parameter we can use `isouter=True` to make the `JOIN` be a `LEFT OUTER JOIN`:
|
`.join()` has a parameter we can use `isouter=True` to make the `JOIN` be a `LEFT OUTER JOIN`:
|
||||||
|
|
||||||
```Python hl_lines="5"
|
{* ./docs_src/tutorial/connect/select/tutorial003_py310.py ln[61:66] hl[63] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial003.py[ln:63-68]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial003.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
And if we run it, it will output:
|
And if we run it, it will output:
|
||||||
|
|
||||||
@@ -464,7 +396,7 @@ FROM hero LEFT OUTER JOIN team ON team.id = hero.team_id
|
|||||||
INFO Engine [no key 0.00051s] ()
|
INFO Engine [no key 0.00051s] ()
|
||||||
|
|
||||||
// Print the first hero and team
|
// Print the first hero and team
|
||||||
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margaret’s Bar' id=2 name='Z-Force'
|
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margaret's Bar' id=2 name='Z-Force'
|
||||||
// Print the second hero and team
|
// Print the second hero and team
|
||||||
Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 Team: headquarters='Sharp Tower' id=1 name='Preventers'
|
Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 Team: headquarters='Sharp Tower' id=1 name='Preventers'
|
||||||
// Print the third hero and team, we included Spider-Boy 🎉
|
// Print the third hero and team, we included Spider-Boy 🎉
|
||||||
@@ -493,22 +425,7 @@ But we would still be able to **filter** the rows with it. 🤓
|
|||||||
|
|
||||||
We could even add some additional `.where()` after `.join()` to filter the data more, for example to return only the heroes from one team:
|
We could even add some additional `.where()` after `.join()` to filter the data more, for example to return only the heroes from one team:
|
||||||
|
|
||||||
```Python hl_lines="5"
|
{* ./docs_src/tutorial/connect/select/tutorial004_py310.py ln[61:66] hl[63] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial004.py[ln:63-68]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial004.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Here we are **filtering** with `.where()` to get only the heroes that belong to the **Preventers** team.
|
Here we are **filtering** with `.where()` to get only the heroes that belong to the **Preventers** team.
|
||||||
|
|
||||||
@@ -539,22 +456,7 @@ Preventer Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48
|
|||||||
|
|
||||||
By putting the `Team` in `select()` we tell **SQLModel** and the database that we want the team data too.
|
By putting the `Team` in `select()` we tell **SQLModel** and the database that we want the team data too.
|
||||||
|
|
||||||
```Python hl_lines="5"
|
{* ./docs_src/tutorial/connect/select/tutorial005_py310.py ln[61:66] hl[63] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial005.py[ln:63-68]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/select/tutorial005.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
And if we run that, it will output:
|
And if we run that, it will output:
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ We currently have a `team` table:
|
|||||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -35,43 +35,17 @@ Let's see how to **remove** connections between rows in tables.
|
|||||||
|
|
||||||
We will continue with the code from the previous chapter.
|
We will continue with the code from the previous chapter.
|
||||||
|
|
||||||
<details>
|
{* ./docs_src/tutorial/connect/update/tutorial001_py310.py ln[0] *}
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/update/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Break a Connection
|
## Break a Connection
|
||||||
|
|
||||||
We don't really have to delete anyting to break a connection. We can just assign `None` to the foreign key, in this case, to the `team_id`.
|
We don't really have to delete anything to break a connection. We can just assign `None` to the foreign key, in this case, to the `team_id`.
|
||||||
|
|
||||||
Let's say **Spider-Boy** is tired of the lack of friendly neighbors and wants to get out of the **Preventers**.
|
Let's say **Spider-Boy** is tired of the lack of friendly neighbors and wants to get out of the **Preventers**.
|
||||||
|
|
||||||
We can simply set the `team_id` to `None`, and now it doesn't have a connection with the team:
|
We can simply set the `team_id` to `None`, and now it doesn't have a connection with the team:
|
||||||
|
|
||||||
```Python hl_lines="8"
|
{* ./docs_src/tutorial/connect/delete/tutorial001_py310.py ln[29:30,66:70] hl[66] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/delete/tutorial001.py[ln:31-32]!}
|
|
||||||
|
|
||||||
# Previous code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/delete/tutorial001.py[ln:68-72]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/delete/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Again, we just **assign** a value to that field attribute `team_id`, now the value is `None`, which means `NULL` in the database. Then we `add()` the hero to the session, and then `commit()`.
|
Again, we just **assign** a value to that field attribute `team_id`, now the value is `None`, which means `NULL` in the database. Then we `add()` the hero to the session, and then `commit()`.
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ At this point we have a `team` table:
|
|||||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -37,14 +37,7 @@ Now we'll see how to **update** those connections between rows tables.
|
|||||||
|
|
||||||
We will continue with the code we used to create some heroes, and we'll update them.
|
We will continue with the code we used to create some heroes, and we'll update them.
|
||||||
|
|
||||||
<details>
|
{* ./docs_src/tutorial/connect/insert/tutorial001_py310.py ln[0] *}
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Assign a Team to a Hero
|
## Assign a Team to a Hero
|
||||||
|
|
||||||
@@ -52,26 +45,7 @@ Let's say that **Tommy Sharp** uses his "rich uncle" charms to recruit **Spider-
|
|||||||
|
|
||||||
Doing it is just like updating any other field:
|
Doing it is just like updating any other field:
|
||||||
|
|
||||||
```Python hl_lines="8"
|
{* ./docs_src/tutorial/connect/update/tutorial001_py310.py ln[29:30,60:64] hl[60] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/update/tutorial001.py[ln:31-32]!}
|
|
||||||
|
|
||||||
# Previous code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/connect/update/tutorial001.py[ln:62-66]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/connect/update/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
We can simply **assign** a value to that field attribute `team_id`, then `add()` the hero to the session, and then `commit()`.
|
We can simply **assign** a value to that field attribute `team_id`, then `add()` the hero to the session, and then `commit()`.
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,11 @@ Click the button <kbd>New Database</kbd>.
|
|||||||
|
|
||||||
A dialog should show up. Go to the [project directory you created](./index.md#create-a-project){.internal-link target=_blank} and save the file with a name of `database.db`.
|
A dialog should show up. Go to the [project directory you created](./index.md#create-a-project){.internal-link target=_blank} and save the file with a name of `database.db`.
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
It's common to save SQLite database files with an extension of `.db`. Sometimes also `.sqlite`.
|
|
||||||
|
It's common to save SQLite database files with an extension of `.db`. Sometimes also `.sqlite`.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Create a Table
|
## Create a Table
|
||||||
|
|
||||||
@@ -122,6 +125,8 @@ And delete that `./database.db` file in your project directory.
|
|||||||
|
|
||||||
And click again on <kbd>New Database</kbd>.
|
And click again on <kbd>New Database</kbd>.
|
||||||
|
|
||||||
|
Save the file with the name `database.db` again.
|
||||||
|
|
||||||
This time, if you see the dialog to create a new table, just close it by clicking the <kbd>Cancel</kbd> button.
|
This time, if you see the dialog to create a new table, just close it by clicking the <kbd>Cancel</kbd> button.
|
||||||
|
|
||||||
And now, go to the tab <kbd>Execute SQL</kbd>.
|
And now, go to the tab <kbd>Execute SQL</kbd>.
|
||||||
@@ -164,6 +169,6 @@ Of course, you can also go and take a full SQL course or read a book about SQL,
|
|||||||
|
|
||||||
We saw how to interact with SQLite databases in files using **DB Browser for SQLite** in a visual user interface.
|
We saw how to interact with SQLite databases in files using **DB Browser for SQLite** in a visual user interface.
|
||||||
|
|
||||||
We also saw how to use it to write some SQL directly to the SQLite database. This will be useful to verify the data in the database is looking correclty, to debug, etc.
|
We also saw how to use it to write some SQL directly to the SQLite database. This will be useful to verify the data in the database is looking correctly, to debug, etc.
|
||||||
|
|
||||||
In the next chapters we will start using **SQLModel** to interact with the database, and we will continue to use **DB Browser for SQLite** at the same time to look at the database underneath. 🔍
|
In the next chapters we will start using **SQLModel** to interact with the database, and we will continue to use **DB Browser for SQLite** at the same time to look at the database underneath. 🔍
|
||||||
|
|||||||
@@ -33,34 +33,27 @@ The first thing we need to do is create a class to represent the data in the tab
|
|||||||
|
|
||||||
A class like this that represents some data is commonly called a **model**.
|
A class like this that represents some data is commonly called a **model**.
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
That's why this package is called `SQLModel`. Because it's mainly used to create **SQL Models**.
|
|
||||||
|
That's why this package is called `SQLModel`. Because it's mainly used to create **SQL Models**.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
For that, we will import `SQLModel` (plus other things we will also use) and create a class `Hero` that inherits from `SQLModel` and represents the **table model** for our heroes:
|
For that, we will import `SQLModel` (plus other things we will also use) and create a class `Hero` that inherits from `SQLModel` and represents the **table model** for our heroes:
|
||||||
|
|
||||||
```Python hl_lines="3 6"
|
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:8] hl[1,4] *}
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-10]!}
|
|
||||||
|
|
||||||
# More code here later 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
This class `Hero` **represents the table** for our heroes. And each instance we create later will **represent a row** in the table.
|
This class `Hero` **represents the table** for our heroes. And each instance we create later will **represent a row** in the table.
|
||||||
|
|
||||||
We use the config `table=True` to tell **SQLModel** that this is a **table model**, it represents a table.
|
We use the config `table=True` to tell **SQLModel** that this is a **table model**, it represents a table.
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
It's also possible to have models without `table=True`, those would be only **data models**, without a table in the database, they would not be **table models**.
|
|
||||||
|
|
||||||
Those **data models** will be **very useful later**, but for now, we'll just keep adding the `table=True` configuration.
|
It's also possible to have models without `table=True`, those would be only **data models**, without a table in the database, they would not be **table models**.
|
||||||
|
|
||||||
|
Those **data models** will be **very useful later**, but for now, we'll just keep adding the `table=True` configuration.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Define the Fields, Columns
|
## Define the Fields, Columns
|
||||||
|
|
||||||
@@ -70,26 +63,13 @@ The name of each of these variables will be the name of the column in the table.
|
|||||||
|
|
||||||
And the type of each of them will also be the type of table column:
|
And the type of each of them will also be the type of table column:
|
||||||
|
|
||||||
```Python hl_lines="1 3 7-10"
|
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:8] hl[1,5:8] *}
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-10]!}
|
|
||||||
|
|
||||||
# More code here later 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Let's now see with more detail these field/column declarations.
|
Let's now see with more detail these field/column declarations.
|
||||||
|
|
||||||
### Optional Fields, Nullable Columns
|
### Optional Fields, Nullable Columns
|
||||||
|
|
||||||
Let's start with `age`, notice that it has a type of `Optional[int]`.
|
Let's start with `age`, notice that it has a type of `int | None (or Optional[int])`.
|
||||||
|
|
||||||
And we import that `Optional` from the `typing` standard module.
|
And we import that `Optional` from the `typing` standard module.
|
||||||
|
|
||||||
@@ -97,23 +77,13 @@ That is the standard way to declare that something "could be an `int` or `None`"
|
|||||||
|
|
||||||
And we also set the default value of `age` to `None`.
|
And we also set the default value of `age` to `None`.
|
||||||
|
|
||||||
```Python hl_lines="1 10"
|
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:8] hl[8] *}
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-10]!}
|
|
||||||
|
|
||||||
# More code here later 👇
|
/// tip
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
We also define `id` with `Optional`. But we will talk about `id` below.
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
///
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
!!! tip
|
|
||||||
We also define `id` with `Optional`. But we will talk about `id` below.
|
|
||||||
|
|
||||||
This way, we tell **SQLModel** that `age` is not required when validating data and that it has a default value of `None`.
|
This way, we tell **SQLModel** that `age` is not required when validating data and that it has a default value of `None`.
|
||||||
|
|
||||||
@@ -121,10 +91,13 @@ And we also tell it that, in the SQL database, the default value of `age` is `NU
|
|||||||
|
|
||||||
So, this column is "nullable" (can be set to `NULL`).
|
So, this column is "nullable" (can be set to `NULL`).
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
In terms of **Pydantic**, `age` is an **optional field**.
|
|
||||||
|
|
||||||
In terms of **SQLAlchemy**, `age` is a **nullable column**.
|
In terms of **Pydantic**, `age` is an **optional field**.
|
||||||
|
|
||||||
|
In terms of **SQLAlchemy**, `age` is a **nullable column**.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
### Primary Key `id`
|
### Primary Key `id`
|
||||||
|
|
||||||
@@ -134,20 +107,7 @@ So, we need to mark `id` as the **primary key**.
|
|||||||
|
|
||||||
To do that, we use the special `Field` function from `sqlmodel` and set the argument `primary_key=True`:
|
To do that, we use the special `Field` function from `sqlmodel` and set the argument `primary_key=True`:
|
||||||
|
|
||||||
```Python hl_lines="3 7"
|
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:8] hl[1,5] *}
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-10]!}
|
|
||||||
|
|
||||||
# More code here later 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
That way, we tell **SQLModel** that this `id` field/column is the primary key of the table.
|
That way, we tell **SQLModel** that this `id` field/column is the primary key of the table.
|
||||||
|
|
||||||
@@ -190,54 +150,31 @@ If you have a server database (for example PostgreSQL or MySQL), the **engine**
|
|||||||
|
|
||||||
Creating the **engine** is very simple, just call `create_engine()` with a URL for the database to use:
|
Creating the **engine** is very simple, just call `create_engine()` with a URL for the database to use:
|
||||||
|
|
||||||
```Python hl_lines="3 16"
|
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:16] hl[1,14] *}
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-16]!}
|
|
||||||
|
|
||||||
# More code here later 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
You should normally have a single **engine** object for your whole application and re-use it everywhere.
|
You should normally have a single **engine** object for your whole application and re-use it everywhere.
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
There's another related thing called a **Session** that normally should *not* be a single object per application.
|
|
||||||
|
|
||||||
But we will talk about it later.
|
There's another related thing called a **Session** that normally should *not* be a single object per application.
|
||||||
|
|
||||||
|
But we will talk about it later.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
### Engine Database URL
|
### Engine Database URL
|
||||||
|
|
||||||
Each supported database has it's own URL type. For example, for **SQLite** it is `sqlite:///` followed by the file path. For example:
|
Each supported database has its own URL type. For example, for **SQLite** it is `sqlite:///` followed by the file path. For example:
|
||||||
|
|
||||||
* `sqlite:///database.db`
|
* `sqlite:///database.db`
|
||||||
* `sqlite:///databases/local/application.db`
|
* `sqlite:///databases/local/application.db`
|
||||||
* `sqlite:///db.sqlite`
|
* `sqlite:///db.sqlite`
|
||||||
|
|
||||||
For SQLAlchemy, there's also a special one, which is a database all *in memory*, this means that it is deleted after the program terminates, and it's also very fast:
|
SQLite supports a special database that lives all *in memory*. Hence, it's very fast, but be careful, the database gets deleted after the program terminates. You can specify this in-memory database by using just two slash characters (`//`) and no file name:
|
||||||
|
|
||||||
* `sqlite://`
|
* `sqlite://`
|
||||||
|
|
||||||
```Python hl_lines="13-14 16"
|
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:16] hl[11:12,14] *}
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-19]!}
|
|
||||||
|
|
||||||
# More code here later 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
You can read a lot more about all the databases supported by **SQLAlchemy** (and that way supported by **SQLModel**) in the <a href="https://docs.sqlalchemy.org/en/14/core/engines.html" class="external-link" target="_blank">SQLAlchemy documentation</a>.
|
You can read a lot more about all the databases supported by **SQLAlchemy** (and that way supported by **SQLModel**) in the <a href="https://docs.sqlalchemy.org/en/14/core/engines.html" class="external-link" target="_blank">SQLAlchemy documentation</a>.
|
||||||
|
|
||||||
@@ -249,20 +186,7 @@ It will make the engine print all the SQL statements it executes, which can help
|
|||||||
|
|
||||||
It is particularly useful for **learning** and **debugging**:
|
It is particularly useful for **learning** and **debugging**:
|
||||||
|
|
||||||
```Python hl_lines="16"
|
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:16] hl[14] *}
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-16]!}
|
|
||||||
|
|
||||||
# More code here later 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
But in production, you would probably want to remove `echo=True`:
|
But in production, you would probably want to remove `echo=True`:
|
||||||
|
|
||||||
@@ -272,12 +196,15 @@ engine = create_engine(sqlite_url)
|
|||||||
|
|
||||||
### Engine Technical Details
|
### Engine Technical Details
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
If you didn't know about SQLAlchemy before and are just learning **SQLModel**, you can probably skip this section, scroll below.
|
|
||||||
|
If you didn't know about SQLAlchemy before and are just learning **SQLModel**, you can probably skip this section, scroll below.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
You can read a lot more about the engine in the <a href="https://docs.sqlalchemy.org/en/14/tutorial/engine.html" class="external-link" target="_blank">SQLAlchemy documentation</a>.
|
You can read a lot more about the engine in the <a href="https://docs.sqlalchemy.org/en/14/tutorial/engine.html" class="external-link" target="_blank">SQLAlchemy documentation</a>.
|
||||||
|
|
||||||
**SQLModel** defines it's own `create_engine()` function. It is the same as SQLAlchemy's `create_engine()`, but with the difference that it defaults to use `future=True` (which means that it uses the style of the latest SQLAlchemy, 1.4, and the future 2.0).
|
**SQLModel** defines its own `create_engine()` function. It is the same as SQLAlchemy's `create_engine()`, but with the difference that it defaults to use `future=True` (which means that it uses the style of the latest SQLAlchemy, 1.4, and the future 2.0).
|
||||||
|
|
||||||
And SQLModel's version of `create_engine()` is type annotated internally, so your editor will be able to help you with autocompletion and inline errors.
|
And SQLModel's version of `create_engine()` is type annotated internally, so your editor will be able to help you with autocompletion and inline errors.
|
||||||
|
|
||||||
@@ -285,16 +212,17 @@ And SQLModel's version of `create_engine()` is type annotated internally, so you
|
|||||||
|
|
||||||
Now everything is in place to finally create the database and table:
|
Now everything is in place to finally create the database and table:
|
||||||
|
|
||||||
```Python hl_lines="18"
|
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py hl[16] *}
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
Creating the engine doesn't create the `database.db` file.
|
|
||||||
|
|
||||||
But once we run `SQLModel.metadata.create_all(engine)`, it creates the `database.db` file **and** creates the `hero` table in that database.
|
Creating the engine doesn't create the `database.db` file.
|
||||||
|
|
||||||
Both things are done in this single step.
|
But once we run `SQLModel.metadata.create_all(engine)`, it creates the `database.db` file **and** creates the `hero` table in that database.
|
||||||
|
|
||||||
|
Both things are done in this single step.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
Let's unwrap that:
|
Let's unwrap that:
|
||||||
|
|
||||||
@@ -395,17 +323,13 @@ Let's run the program to see it all working.
|
|||||||
|
|
||||||
Put the code it in a file `app.py` if you haven't already.
|
Put the code it in a file `app.py` if you haven't already.
|
||||||
|
|
||||||
<details>
|
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py *}
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
/// tip
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
Remember to [activate the virtual environment](./index.md#create-a-python-virtual-environment){.internal-link target=_blank} before running it.
|
||||||
|
|
||||||
!!! tip
|
///
|
||||||
Remember to [activate the virtual environment](./index.md#create-a-python-virtual-environment){.internal-link target=_blank} before running it.
|
|
||||||
|
|
||||||
Now run the program with Python:
|
Now run the program with Python:
|
||||||
|
|
||||||
@@ -415,7 +339,7 @@ Now run the program with Python:
|
|||||||
// We set echo=True, so this will show the SQL code
|
// We set echo=True, so this will show the SQL code
|
||||||
$ python app.py
|
$ python app.py
|
||||||
|
|
||||||
// First, some boilerplate SQL that we are not that intereted in
|
// First, some boilerplate SQL that we are not that interested in
|
||||||
|
|
||||||
INFO Engine BEGIN (implicit)
|
INFO Engine BEGIN (implicit)
|
||||||
INFO Engine PRAGMA main.table_info("hero")
|
INFO Engine PRAGMA main.table_info("hero")
|
||||||
@@ -442,20 +366,23 @@ INFO Engine COMMIT
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
I simplified the output above a bit to make it easier to read.
|
|
||||||
|
|
||||||
But in reality, instead of showing:
|
I simplified the output above a bit to make it easier to read.
|
||||||
|
|
||||||
```
|
But in reality, instead of showing:
|
||||||
INFO Engine BEGIN (implicit)
|
|
||||||
```
|
|
||||||
|
|
||||||
it would show something like:
|
```
|
||||||
|
INFO Engine BEGIN (implicit)
|
||||||
|
```
|
||||||
|
|
||||||
```
|
it would show something like:
|
||||||
2021-07-25 21:37:39,175 INFO sqlalchemy.engine.Engine BEGIN (implicit)
|
|
||||||
```
|
```
|
||||||
|
2021-07-25 21:37:39,175 INFO sqlalchemy.engine.Engine BEGIN (implicit)
|
||||||
|
```
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
### `TEXT` or `VARCHAR`
|
### `TEXT` or `VARCHAR`
|
||||||
|
|
||||||
@@ -463,7 +390,7 @@ In the example in the previous chapter we created the table using `TEXT` for som
|
|||||||
|
|
||||||
But in this output SQLAlchemy is using `VARCHAR` instead. Let's see what's going on.
|
But in this output SQLAlchemy is using `VARCHAR` instead. Let's see what's going on.
|
||||||
|
|
||||||
Remember that [each SQL Database has some different variations in what they support?](../databases/#sql-the-language){.internal-link target=_blank}
|
Remember that [each SQL Database has some different variations in what they support?](../databases.md#sql-the-language){.internal-link target=_blank}
|
||||||
|
|
||||||
This is one of the differences. Each database supports some particular **data types**, like `INTEGER` and `TEXT`.
|
This is one of the differences. Each database supports some particular **data types**, like `INTEGER` and `TEXT`.
|
||||||
|
|
||||||
@@ -479,8 +406,11 @@ Additional to the difference between those two data types, some databases like M
|
|||||||
|
|
||||||
To make it easier to start using **SQLModel** right away independent of the database you use (even with MySQL), and without any extra configurations, by default, `str` fields are interpreted as `VARCHAR` in most databases and `VARCHAR(255)` in MySQL, this way you know the same class will be compatible with the most popular databases without extra effort.
|
To make it easier to start using **SQLModel** right away independent of the database you use (even with MySQL), and without any extra configurations, by default, `str` fields are interpreted as `VARCHAR` in most databases and `VARCHAR(255)` in MySQL, this way you know the same class will be compatible with the most popular databases without extra effort.
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
You will learn how to change the maximum length of string columns later in the Advanced Tutorial - User Guide.
|
|
||||||
|
You will learn how to change the maximum length of string columns later in the Advanced Tutorial - User Guide.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
### Verify the Database
|
### Verify the Database
|
||||||
|
|
||||||
@@ -498,46 +428,37 @@ In this example it's just the `SQLModel.metadata.create_all(engine)`.
|
|||||||
|
|
||||||
Let's put it in a function `create_db_and_tables()`:
|
Let's put it in a function `create_db_and_tables()`:
|
||||||
|
|
||||||
```Python hl_lines="22-23"
|
{* ./docs_src/tutorial/create_db_and_table/tutorial002_py310.py ln[1:18] hl[17:18] *}
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial002.py[ln:1-20]!}
|
|
||||||
|
|
||||||
# More code here later 👇
|
If `SQLModel.metadata.create_all(engine)` was not in a function and we tried to import something from this module (from this file) in another, it would try to create the database and table **every time** we executed that other file that imported this module.
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
We don't want that to happen like that, only when we **intend** it to happen, that's why we put it in a function, because we can make sure that the tables are created only when we call that function, and not when this module is imported somewhere else.
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial002.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
If `SQLModel.metadata.create_all(engine)` was not in a function and we tried to import something from this module (from this file) in another, it would try to create the database and table **every time**.
|
|
||||||
|
|
||||||
We don't want that to happen like that, only when we **intend** it to happen, that's why we put it in a function.
|
|
||||||
|
|
||||||
Now we would be able to, for example, import the `Hero` class in some other file without having those **side effects**.
|
Now we would be able to, for example, import the `Hero` class in some other file without having those **side effects**.
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
😅 **Spoiler alert**: The function is called `create_db_and_tables()` because we will have more **tables** in the future with other classes apart from `Hero`. 🚀
|
|
||||||
|
😅 **Spoiler alert**: The function is called `create_db_and_tables()` because we will have more **tables** in the future with other classes apart from `Hero`. 🚀
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
### Create Data as a Script
|
### Create Data as a Script
|
||||||
|
|
||||||
We prevented the side effects when importing something from your `app.py` file.
|
We prevented the side effects when importing something from your `app.py` file.
|
||||||
|
|
||||||
But we still want it to **create the database and table** when we call it with Python directly as an independent script from the terminal, just as as above.
|
But we still want it to **create the database and table** when we call it with Python directly as an independent script from the terminal, just as above.
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
Think of the word **script** and **program** as interchangeable.
|
|
||||||
|
|
||||||
The word **script** often implies that the code could be run independently and easily. Or in some cases it refers to a relatively simple program.
|
Think of the word **script** and **program** as interchangeable.
|
||||||
|
|
||||||
|
The word **script** often implies that the code could be run independently and easily. Or in some cases it refers to a relatively simple program.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
For that we can use the special variable `__name__` in an `if` block:
|
For that we can use the special variable `__name__` in an `if` block:
|
||||||
|
|
||||||
```Python hl_lines="23-24"
|
{* ./docs_src/tutorial/create_db_and_table/tutorial002_py310.py hl[21:22] *}
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial002.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
### About `__name__ == "__main__"`
|
### About `__name__ == "__main__"`
|
||||||
|
|
||||||
@@ -559,10 +480,13 @@ $ python app.py
|
|||||||
from app import Hero
|
from app import Hero
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
That `if` block using `if __name__ == "__main__":` is sometimes called the "**main block**".
|
|
||||||
|
|
||||||
The official name (in the <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">Python docs</a>) is "**Top-level script environment**".
|
That `if` block using `if __name__ == "__main__":` is sometimes called the "**main block**".
|
||||||
|
|
||||||
|
The official name (in the <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">Python docs</a>) is "**Top-level script environment**".
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
#### More details
|
#### More details
|
||||||
|
|
||||||
@@ -614,8 +538,11 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
...will **not** be executed.
|
...will **not** be executed.
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
For more information, check <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">the official Python docs</a>.
|
|
||||||
|
For more information, check <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">the official Python docs</a>.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Last Review
|
## Last Review
|
||||||
|
|
||||||
@@ -625,14 +552,31 @@ But now we can import things from this module in other files.
|
|||||||
|
|
||||||
Now, let's give the code a final look:
|
Now, let's give the code a final look:
|
||||||
|
|
||||||
|
//// tab | Python 3.10+
|
||||||
|
|
||||||
|
```{.python .annotate}
|
||||||
|
{!./docs_src/tutorial/create_db_and_table/tutorial003_py310.py!}
|
||||||
|
```
|
||||||
|
|
||||||
|
{!./docs_src/tutorial/create_db_and_table/annotations/en/tutorial003.md!}
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
//// tab | Python 3.7+
|
||||||
|
|
||||||
```{.python .annotate}
|
```{.python .annotate}
|
||||||
{!./docs_src/tutorial/create_db_and_table/tutorial003.py!}
|
{!./docs_src/tutorial/create_db_and_table/tutorial003.py!}
|
||||||
```
|
```
|
||||||
|
|
||||||
{!./docs_src/tutorial/create_db_and_table/annotations/en/tutorial003.md!}
|
{!./docs_src/tutorial/create_db_and_table/annotations/en/tutorial003.md!}
|
||||||
|
|
||||||
!!! tip
|
////
|
||||||
Review what each line does by clicking each number bubble in the code. 👆
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
Review what each line does by clicking each number bubble in the code. 👆
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Recap
|
## Recap
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,7 @@ Now let's delete some data using **SQLModel**.
|
|||||||
|
|
||||||
As before, we'll continue from where we left off with the previous code.
|
As before, we'll continue from where we left off with the previous code.
|
||||||
|
|
||||||
<details>
|
{* ./docs_src/tutorial/update/tutorial003_py310.py ln[0] *}
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/update/tutorial003.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Remember to remove the `database.db` file before running the examples to get the same results.
|
Remember to remove the `database.db` file before running the examples to get the same results.
|
||||||
|
|
||||||
@@ -63,39 +56,11 @@ To get the same results, delete the `database.db` file before running the exampl
|
|||||||
|
|
||||||
We'll start by selecting the hero `"Spider-Youngster"` that we updated in the previous chapter, this is the one we will delete:
|
We'll start by selecting the hero `"Spider-Youngster"` that we updated in the previous chapter, this is the one we will delete:
|
||||||
|
|
||||||
```Python hl_lines="5"
|
{* ./docs_src/tutorial/delete/tutorial001_py310.py ln[70:75] hl[72] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py[ln:72-77]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
As this is a new function `delete_heroes()`, we'll also add it to the `main()` function so that we call it when executing the program from the command line:
|
As this is a new function `delete_heroes()`, we'll also add it to the `main()` function so that we call it when executing the program from the command line:
|
||||||
|
|
||||||
```Python hl_lines="7"
|
{* ./docs_src/tutorial/delete/tutorial001_py310.py ln[90:98] hl[94] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py[ln:92-100]!}
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
That will print the same existing hero **Spider-Youngster**:
|
That will print the same existing hero **Spider-Youngster**:
|
||||||
|
|
||||||
@@ -123,22 +88,7 @@ Hero: name='Spider-Youngster' secret_name='Pedro Parqueador' age=16 id=2
|
|||||||
|
|
||||||
Now, very similar to how we used `session.add()` to add or update new heroes, we can use `session.delete()` to delete the hero from the session:
|
Now, very similar to how we used `session.add()` to add or update new heroes, we can use `session.delete()` to delete the hero from the session:
|
||||||
|
|
||||||
```Python hl_lines="10"
|
{* ./docs_src/tutorial/delete/tutorial001_py310.py ln[70:77] hl[77] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py[ln:72-79]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Commit the Session
|
## Commit the Session
|
||||||
|
|
||||||
@@ -146,22 +96,7 @@ To save the current changes in the session, **commit** it.
|
|||||||
|
|
||||||
This will save all the changes stored in the **session**, like the deleted hero:
|
This will save all the changes stored in the **session**, like the deleted hero:
|
||||||
|
|
||||||
```Python hl_lines="11"
|
{* ./docs_src/tutorial/delete/tutorial001_py310.py ln[70:78] hl[78] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py[ln:72-80]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
The same as we have seen before, `.commit()` will also save anything else that was added to the session. Including updates, or created heroes.
|
The same as we have seen before, `.commit()` will also save anything else that was added to the session. Including updates, or created heroes.
|
||||||
|
|
||||||
@@ -196,22 +131,7 @@ As the object is not connected to the session, it is not marked as "expired", th
|
|||||||
|
|
||||||
Because of that, the object still contains its attributes with the data in it, so we can print it:
|
Because of that, the object still contains its attributes with the data in it, so we can print it:
|
||||||
|
|
||||||
```Python hl_lines="13"
|
{* ./docs_src/tutorial/delete/tutorial001_py310.py ln[70:80] hl[80] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py[ln:72-82]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
This will output:
|
This will output:
|
||||||
|
|
||||||
@@ -234,22 +154,7 @@ Deleted hero: name='Spider-Youngster' secret_name='Pedro Parqueador' age=16 id=2
|
|||||||
|
|
||||||
To confirm if it was deleted, now let's query the database again, with the same `"Spider-Youngster"` name:
|
To confirm if it was deleted, now let's query the database again, with the same `"Spider-Youngster"` name:
|
||||||
|
|
||||||
```Python hl_lines="15-17"
|
{* ./docs_src/tutorial/delete/tutorial001_py310.py ln[70:84] hl[82:84] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py[ln:72-86]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Here we are using `results.first()` to get the first object found (in case it found multiple) or `None`, if it didn't find anything.
|
Here we are using `results.first()` to get the first object found (in case it found multiple) or `None`, if it didn't find anything.
|
||||||
|
|
||||||
@@ -286,22 +191,7 @@ Now let's just confirm that, indeed, no hero was found in the database with that
|
|||||||
|
|
||||||
We'll do it by checking that the "first" item in the `results` is `None`:
|
We'll do it by checking that the "first" item in the `results` is `None`:
|
||||||
|
|
||||||
```Python hl_lines="19-20"
|
{* ./docs_src/tutorial/delete/tutorial001_py310.py ln[70:87] hl[86:87] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py[ln:72-89]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/delete/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
This will output:
|
This will output:
|
||||||
|
|
||||||
@@ -327,14 +217,31 @@ INFO Engine ROLLBACK
|
|||||||
|
|
||||||
Now let's review all that code:
|
Now let's review all that code:
|
||||||
|
|
||||||
|
//// tab | Python 3.10+
|
||||||
|
|
||||||
|
```{ .python .annotate hl_lines="70-88" }
|
||||||
|
{!./docs_src/tutorial/delete/tutorial002_py310.py!}
|
||||||
|
```
|
||||||
|
|
||||||
|
{!./docs_src/tutorial/delete/annotations/en/tutorial002.md!}
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
//// tab | Python 3.7+
|
||||||
|
|
||||||
```{ .python .annotate hl_lines="72-90" }
|
```{ .python .annotate hl_lines="72-90" }
|
||||||
{!./docs_src/tutorial/delete/tutorial002.py!}
|
{!./docs_src/tutorial/delete/tutorial002.py!}
|
||||||
```
|
```
|
||||||
|
|
||||||
{!./docs_src/tutorial/delete/annotations/en/tutorial002.md!}
|
{!./docs_src/tutorial/delete/annotations/en/tutorial002.md!}
|
||||||
|
|
||||||
!!! tip
|
////
|
||||||
Check out the number bubbles to see what is done by each line of code.
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
Check out the number bubbles to see what is done by each line of code.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Recap
|
## Recap
|
||||||
|
|
||||||
|
|||||||
@@ -12,22 +12,7 @@ We get a `hero_id` from the path parameter and verify if it exists, just as we d
|
|||||||
|
|
||||||
And if we actually find a hero, we just delete it with the **session**.
|
And if we actually find a hero, we just delete it with the **session**.
|
||||||
|
|
||||||
```Python hl_lines="3-11"
|
{* ./docs_src/tutorial/fastapi/delete/tutorial001_py310.py ln[89:97] hl[89:97] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/delete/tutorial001.py[ln:91-99]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/delete/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
After deleting it successfully, we just return a response of:
|
After deleting it successfully, we just return a response of:
|
||||||
|
|
||||||
@@ -39,6 +24,6 @@ After deleting it successfully, we just return a response of:
|
|||||||
|
|
||||||
## Recap
|
## Recap
|
||||||
|
|
||||||
That's it, feel free to try it out in the interactve docs UI to delete some heroes. 💥
|
That's it, feel free to try it out in the interactive docs UI to delete some heroes. 💥
|
||||||
|
|
||||||
Using **FastAPI** to read data and combining it with **SQLModel** makes it quite straightforward to delete data from the database.
|
Using **FastAPI** to read data and combining it with **SQLModel** makes it quite straightforward to delete data from the database.
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
# Read Heroes with Limit and Offset wtih FastAPI
|
# Read Heroes with Limit and Offset with FastAPI
|
||||||
|
|
||||||
When a client sends a request to get all the heroes, we have been returning them all.
|
When a client sends a request to get all the heroes, we have been returning them all.
|
||||||
|
|
||||||
But if we had **thousands** of heroes that could consume a lot of **computational resources**, network bandwith, etc.
|
But if we had **thousands** of heroes that could consume a lot of **computational resources**, network bandwidth, etc.
|
||||||
|
|
||||||
So we probably want to limit it.
|
So, we probably want to limit it.
|
||||||
|
|
||||||
Let's use the same **offset** and **limit** we learned about in the previous tutorial chapters for the API.
|
Let's use the same **offset** and **limit** we learned about in the previous tutorial chapters for the API.
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
In many cases this is also called **pagination**.
|
|
||||||
|
In many cases, this is also called **pagination**.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Add a Limit and Offset to the Query Parameters
|
## Add a Limit and Offset to the Query Parameters
|
||||||
|
|
||||||
@@ -19,39 +22,25 @@ By default, we will return the first results from the database, so `offset` will
|
|||||||
|
|
||||||
And by default, we will return a maximum of `100` heroes, so `limit` will have a default value of `100`.
|
And by default, we will return a maximum of `100` heroes, so `limit` will have a default value of `100`.
|
||||||
|
|
||||||
```Python hl_lines="3 9 11"
|
{* ./docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py310.py ln[1:2,52:56] hl[1,53,55] *}
|
||||||
{!./docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py[ln:1-4]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
We want to allow clients to set different `offset` and `limit` values.
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py[ln:54-58]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
We want to allow clients to set a different `offset` and `limit` values.
|
|
||||||
|
|
||||||
But we don't want them to be able to set a `limit` of something like `9999`, that's over `9000`! 😱
|
But we don't want them to be able to set a `limit` of something like `9999`, that's over `9000`! 😱
|
||||||
|
|
||||||
So, to prevent it, we add additional validation to the `limit` query parameter, declaring that it has to be **l**ess **t**han or **e**qual to `100` with `lte=100`.
|
So, to prevent it, we add additional validation to the `limit` query parameter, declaring that it has to be **l**ess than or **e**qual to `100` with `le=100`.
|
||||||
|
|
||||||
This way, a client can decide to take less heroes if they want, but not more.
|
This way, a client can decide to take fewer heroes if they want, but not more.
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
If you need to refresh how query parameters and their validation work, check out the docs in FastAPI:
|
|
||||||
|
|
||||||
* <a href="https://fastapi.tiangolo.com/tutorial/query-params/" class="external-link" target="_blank">Query Parameters</a>
|
If you need to refresh how query parameters and their validation work, check out the docs in FastAPI:
|
||||||
* <a href="https://fastapi.tiangolo.com/tutorial/query-params-str-validations/" class="external-link" target="_blank">Query Parameters and String Validations</a>
|
|
||||||
* <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/" class="external-link" target="_blank">Path Parameters and Numeric Validations</a>
|
* <a href="https://fastapi.tiangolo.com/tutorial/query-params/" class="external-link" target="_blank">Query Parameters</a>
|
||||||
|
* <a href="https://fastapi.tiangolo.com/tutorial/query-params-str-validations/" class="external-link" target="_blank">Query Parameters and String Validations</a>
|
||||||
|
* <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/" class="external-link" target="_blank">Path Parameters and Numeric Validations</a>
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Check the Docs UI
|
## Check the Docs UI
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
We have been using the same `Hero` model to declare the schema of the data we receive in the API, the table model in the database, and the schema of the data we send back in responses.
|
We have been using the same `Hero` model to declare the schema of the data we receive in the API, the table model in the database, and the schema of the data we send back in responses.
|
||||||
|
|
||||||
But in most of the cases there are slight differences, let's use multiple models to solve it.
|
But in most of the cases, there are slight differences. Let's use multiple models to solve it.
|
||||||
|
|
||||||
Here you will see the main and biggest feature of **SQLModel**. 😎
|
Here you will see the main and biggest feature of **SQLModel**. 😎
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ Here you will see the main and biggest feature of **SQLModel**. 😎
|
|||||||
|
|
||||||
Let's start by reviewing the automatically generated schemas from the docs UI.
|
Let's start by reviewing the automatically generated schemas from the docs UI.
|
||||||
|
|
||||||
For input we have:
|
For input, we have:
|
||||||
|
|
||||||
<img class="shadow" alt="Interactive API docs UI" src="/img/tutorial/fastapi/simple-hero-api/image01.png">
|
<img class="shadow" alt="Interactive API docs UI" src="/img/tutorial/fastapi/simple-hero-api/image01.png">
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ This means that the client could try to use the same ID that already exists in t
|
|||||||
|
|
||||||
That's not what we want.
|
That's not what we want.
|
||||||
|
|
||||||
We want the client to only send the data that is needed to create a new hero:
|
We want the client only to send the data that is needed to create a new hero:
|
||||||
|
|
||||||
* `name`
|
* `name`
|
||||||
* `secret_name`
|
* `secret_name`
|
||||||
@@ -53,17 +53,17 @@ Here's the weird thing, the `id` currently seems also "optional". 🤔
|
|||||||
|
|
||||||
This is because in our **SQLModel** class we declare the `id` with `Optional[int]`, because it could be `None` in memory until we save it in the database and we finally get the actual ID.
|
This is because in our **SQLModel** class we declare the `id` with `Optional[int]`, because it could be `None` in memory until we save it in the database and we finally get the actual ID.
|
||||||
|
|
||||||
But in the responses, we would always send a model from the database, and it would **always have an ID**. So the `id` in the responses could be declared as required too.
|
But in the responses, we always send a model from the database, so it **always has an ID**. So the `id` in the responses can be declared as required.
|
||||||
|
|
||||||
This would mean that our application is making the compromise with the clients that if it sends a hero, it would for sure have an `id` with a value, it would not be `None`.
|
This means that our application is making the promise to the clients that if it sends a hero, it will for sure have an `id` with a value, it will not be `None`.
|
||||||
|
|
||||||
### Why Is it Important to Compromise with the Responses
|
### Why Is it Important to Have a Contract for Responses
|
||||||
|
|
||||||
The ultimate goal of an API is for some **clients to use it**.
|
The ultimate goal of an API is for some **clients to use it**.
|
||||||
|
|
||||||
The clients could be a frontend application, a command line program, a graphical user interface, a mobile application, another backend application, etc.
|
The clients could be a frontend application, a command line program, a graphical user interface, a mobile application, another backend application, etc.
|
||||||
|
|
||||||
And the code those clients write depend on what our API tells them they **need to send**, and what they can **expect to receive**.
|
And the code those clients write depends on what our API tells them they **need to send**, and what they can **expect to receive**.
|
||||||
|
|
||||||
Making both sides very clear will make it much easier to interact with the API.
|
Making both sides very clear will make it much easier to interact with the API.
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ But we also want to have a `HeroCreate` for the data we want to receive when **c
|
|||||||
* `secret_name`, required
|
* `secret_name`, required
|
||||||
* `age`, optional
|
* `age`, optional
|
||||||
|
|
||||||
And we want to have a `HeroRead` with the `id` field, but this time annotated with `id: int`, instead of `id: Optional[int]`, to make it clear that it is required in responses **read** from the clients:
|
And we want to have a `HeroPublic` with the `id` field, but this time annotated with `id: int`, instead of `id: Optional[int]`, to make it clear that it is required in responses **read** from the clients:
|
||||||
|
|
||||||
* `id`, required
|
* `id`, required
|
||||||
* `name`, required
|
* `name`, required
|
||||||
@@ -109,35 +109,21 @@ And we want to have a `HeroRead` with the `id` field, but this time annotated wi
|
|||||||
|
|
||||||
The simplest way to solve it could be to create **multiple models**, each one with all the corresponding fields:
|
The simplest way to solve it could be to create **multiple models**, each one with all the corresponding fields:
|
||||||
|
|
||||||
```Python hl_lines="5-9 12-15 18-22"
|
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[5:22] hl[5:9,12:15,18:22] *}
|
||||||
# This would work, but there's a better option below 🚨
|
|
||||||
|
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial001.py[ln:7-24]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Here's the important detail, and probably the most important feature of **SQLModel**: only `Hero` is declared with `table = True`.
|
Here's the important detail, and probably the most important feature of **SQLModel**: only `Hero` is declared with `table = True`.
|
||||||
|
|
||||||
This means that the class `Hero` represents a **table** in the database. It is both a **Pydantic** model and a **SQLAlchemy** model.
|
This means that the class `Hero` represents a **table** in the database. It is both a **Pydantic** model and a **SQLAlchemy** model.
|
||||||
|
|
||||||
But `HeroCreate` and `HeroRead` don't have `table = True`. They are only **data models**, they are only **Pydantic** models. They won't be used with the database, but only to declare data schemas for the API (or for other uses).
|
But `HeroCreate` and `HeroPublic` don't have `table = True`. They are only **data models**, they are only **Pydantic** models. They won't be used with the database, but only to declare data schemas for the API (or for other uses).
|
||||||
|
|
||||||
This also means that `SQLModel.metadata.create_all()` won't create tables in the database for `HeroCreate` and `HeroRead`, because they don't have `table = True`, which is exactly what we want. 🚀
|
This also means that `SQLModel.metadata.create_all()` won't create tables in the database for `HeroCreate` and `HeroPublic`, because they don't have `table = True`, which is exactly what we want. 🚀
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
We will improve this code to avoid duplicating the fields, but for now we can continue learning with these models.
|
|
||||||
|
We will improve this code to avoid duplicating the fields, but for now we can continue learning with these models.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Use Multiple Models to Create a Hero
|
## Use Multiple Models to Create a Hero
|
||||||
|
|
||||||
@@ -145,91 +131,63 @@ Let's now see how to use these new models in the FastAPI application.
|
|||||||
|
|
||||||
Let's first check how is the process to create a hero now:
|
Let's first check how is the process to create a hero now:
|
||||||
|
|
||||||
```Python hl_lines="3-4 6"
|
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[44:51] hl[44:45,47] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial001.py[ln:46-53]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Let's check that in detail.
|
Let's check that in detail.
|
||||||
|
|
||||||
Now we use the type annotation `HeroCreate` for the request JSON data, in the `hero` parameter of the **path operation function**.
|
Now we use the type annotation `HeroCreate` for the request JSON data in the `hero` parameter of the **path operation function**.
|
||||||
|
|
||||||
```Python hl_lines="3"
|
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[45] hl[45] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial001.py[ln:47]!}
|
Then we create a new `Hero` (this is the actual **table** model that saves things to the database) using `Hero.model_validate()`.
|
||||||
|
|
||||||
# Code below omitted 👇
|
The method `.model_validate()` reads data from another object with attributes (or a dict) and creates a new instance of this class, in this case `Hero`.
|
||||||
```
|
|
||||||
|
|
||||||
Then we create a new `Hero` (this is the actual **table** model that saves things to the database) using `Hero.from_orm()`.
|
In this case, we have a `HeroCreate` instance in the `hero` variable. This is an object with attributes, so we use `.model_validate()` to read those attributes.
|
||||||
|
|
||||||
The method `.from_orm()` reads data from another object with attributes and creates a new instance of this class, in this case `Hero`.
|
/// tip
|
||||||
|
In versions of **SQLModel** before `0.0.14` you would use the method `.from_orm()`, but it is now deprecated and you should use `.model_validate()` instead.
|
||||||
|
///
|
||||||
|
|
||||||
The alternative is `Hero.parse_obj()` that reads data from a dictionary.
|
We can now create a new `Hero` instance (the one for the database) and put it in the variable `db_hero` from the data in the `hero` variable that is the `HeroCreate` instance we received from the request.
|
||||||
|
|
||||||
But as in this case we have a `HeroCreate` instance in the `hero` variable, this is an object with attributes, so we use `.from_orm()` to read those attributes.
|
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[47] hl[47] *}
|
||||||
|
|
||||||
With this we create a new `Hero` instance (the one for the database) and put it in the variable `db_hero` from the data in the `hero` variable that is the `HeroCreate` instance we received from the request.
|
Then we just `add` it to the **session**, `commit`, and `refresh` it, and finally, we return the same `db_hero` variable that has the just refreshed `Hero` instance.
|
||||||
|
|
||||||
```Python hl_lines="3"
|
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial001.py[ln:49]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
Then we just `add` it to the **session**, `commit`, and `refresh` it, and finally we return the same `db_hero` variable that has the just refreshed `Hero` instance.
|
|
||||||
|
|
||||||
Because it is just refreshed, it has the `id` field set with a new ID taken from the database.
|
Because it is just refreshed, it has the `id` field set with a new ID taken from the database.
|
||||||
|
|
||||||
And now that we return it, FastAPI will validate the data with the `response_model`, which is a `HeroRead`:
|
And now that we return it, FastAPI will validate the data with the `response_model`, which is a `HeroPublic`:
|
||||||
|
|
||||||
```Python hl_lines="3"
|
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[44] hl[44] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial001.py[ln:46]!}
|
This will validate that all the data that we promised is there and will remove any data we didn't declare.
|
||||||
|
|
||||||
# Code below omitted 👇
|
/// tip
|
||||||
```
|
|
||||||
|
|
||||||
This will validate that all the data that we promised is there, and will remove any data we didn't declare.
|
This filtering could be very important and could be a very good security feature, for example, to make sure you filter private data, hashed passwords, etc.
|
||||||
|
|
||||||
!!! tip
|
You can read more about it in the <a href="https://fastapi.tiangolo.com/tutorial/response-model/" class="external-link" target="_blank">FastAPI docs about Response Model</a>.
|
||||||
This filtering could be very important, and could be a very good security feature, for example to make sure you filter private data, hashed passwords, etc.
|
|
||||||
|
|
||||||
You can read more about it in the <a href="https://fastapi.tiangolo.com/tutorial/response-model/" class="external-link" target="_blank">FastAPI docs about Response Model</a>.
|
///
|
||||||
|
|
||||||
In particular, it will make sure that the `id` is there, and that it is indeed an integer (and not `None`).
|
In particular, it will make sure that the `id` is there and that it is indeed an integer (and not `None`).
|
||||||
|
|
||||||
## Shared Fields
|
## Shared Fields
|
||||||
|
|
||||||
But looking closely, we could see that these models have a lot of **duplicated information**.
|
But looking closely, we could see that these models have a lot of **duplicated information**.
|
||||||
|
|
||||||
All **the 3 models** declare that thay share some **common fields** that look exactly the same:
|
All **the 3 models** declare that they share some **common fields** that look exactly the same:
|
||||||
|
|
||||||
* `name`, required
|
* `name`, required
|
||||||
* `secret_name`, required
|
* `secret_name`, required
|
||||||
* `age`, optional
|
* `age`, optional
|
||||||
|
|
||||||
And then they declare other fields with some differences (in this case only about the `id`).
|
And then they declare other fields with some differences (in this case, only about the `id`).
|
||||||
|
|
||||||
We want to **avoid duplicated information** if possible.
|
We want to **avoid duplicated information** if possible.
|
||||||
|
|
||||||
This is important if, for example, in the future we decide to **refactor the code** and rename one field (column). For example, from `secret_name` to `secret_identity`.
|
This is important if, for example, in the future, we decide to **refactor the code** and rename one field (column). For example, from `secret_name` to `secret_identity`.
|
||||||
|
|
||||||
If we have that duplicated in multiple models, we could easily forget to update one of them. But if we **avoid duplication**, there's only one place that would need updating. ✨
|
If we have that duplicated in multiple models, we could easily forget to update one of them. But if we **avoid duplication**, there's only one place that would need updating. ✨
|
||||||
|
|
||||||
@@ -253,22 +211,7 @@ We can see from above that they all share some **base** fields:
|
|||||||
|
|
||||||
So let's create a **base** model `HeroBase` that the others can inherit from:
|
So let's create a **base** model `HeroBase` that the others can inherit from:
|
||||||
|
|
||||||
```Python hl_lines="3-6"
|
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[5:8] hl[5:8] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py[ln:7-10]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
As you can see, this is *not* a **table model**, it doesn't have the `table = True` config.
|
As you can see, this is *not* a **table model**, it doesn't have the `table = True` config.
|
||||||
|
|
||||||
@@ -278,22 +221,7 @@ But now we can create the **other models inheriting from it**, they will all sha
|
|||||||
|
|
||||||
Let's start with the only **table model**, the `Hero`:
|
Let's start with the only **table model**, the `Hero`:
|
||||||
|
|
||||||
```Python hl_lines="9-10"
|
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[5:12] hl[11:12] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py[ln:7-14]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Notice that `Hero` now doesn't inherit from `SQLModel`, but from `HeroBase`.
|
Notice that `Hero` now doesn't inherit from `SQLModel`, but from `HeroBase`.
|
||||||
|
|
||||||
@@ -305,28 +233,23 @@ And of course, all these fields will be in the columns for the resulting `hero`
|
|||||||
|
|
||||||
And those inherited fields will also be in the **autocompletion** and **inline errors** in editors, etc.
|
And those inherited fields will also be in the **autocompletion** and **inline errors** in editors, etc.
|
||||||
|
|
||||||
|
### Columns and Inheritance with Multiple Models
|
||||||
|
|
||||||
|
Notice that the parent model `HeroBase` is not a **table model**, but still, we can declare `name` and `age` using `Field(index=True)`.
|
||||||
|
|
||||||
|
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[5:12] hl[6,8,11] *}
|
||||||
|
|
||||||
|
This won't affect this parent **data model** `HeroBase`.
|
||||||
|
|
||||||
|
But once the child model `Hero` (the actual **table model**) inherits those fields, it will use those field configurations to create the indexes when creating the tables in the database.
|
||||||
|
|
||||||
### The `HeroCreate` **Data Model**
|
### The `HeroCreate` **Data Model**
|
||||||
|
|
||||||
Now let's see the `HeroCreate` model that will be used to define the data that we want to receive in the API when creating a new hero.
|
Now let's see the `HeroCreate` model that will be used to define the data that we want to receive in the API when creating a new hero.
|
||||||
|
|
||||||
This is a fun one:
|
This is a fun one:
|
||||||
|
|
||||||
```Python hl_lines="13-14"
|
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[5:16] hl[15:16] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py[ln:7-18]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
What's happening here?
|
What's happening here?
|
||||||
|
|
||||||
@@ -336,36 +259,21 @@ And because we can't leave the empty space when creating a new class, but we don
|
|||||||
|
|
||||||
This means that there's nothing else special in this class apart from the fact that it is named `HeroCreate` and that it inherits from `HeroBase`.
|
This means that there's nothing else special in this class apart from the fact that it is named `HeroCreate` and that it inherits from `HeroBase`.
|
||||||
|
|
||||||
As an alternative, we could use `HeroBase` directly in the API code instead of `HeroCreate`, but it would show up in the auomatic docs UI with that name "`HeroBase`" which could be **confusing** for clients. Instead, "`HeroCreate`" is a bit more explicit about what it is for.
|
As an alternative, we could use `HeroBase` directly in the API code instead of `HeroCreate`, but it would show up in the automatic docs UI with that name "`HeroBase`" which could be **confusing** for clients. Instead, "`HeroCreate`" is a bit more explicit about what it is for.
|
||||||
|
|
||||||
On top of that, we could easily decide in the future that we want to receive **more data** when creating a new hero apart from the data in `HeroBase` (for example a password), and now we already have the class to put those extra fields.
|
On top of that, we could easily decide in the future that we want to receive **more data** when creating a new hero apart from the data in `HeroBase` (for example, a password), and now we already have the class to put those extra fields.
|
||||||
|
|
||||||
### The `HeroRead` **Data Model**
|
### The `HeroPublic` **Data Model**
|
||||||
|
|
||||||
Now let's check the `HeroRead` model.
|
Now let's check the `HeroPublic` model.
|
||||||
|
|
||||||
This one just declares that the `id` field is required when reading a hero from the API, because a hero read from the API will come from the database, and in the database it will always have an ID.
|
This one just declares that the `id` field is required when reading a hero from the API, because a hero read from the API will come from the database, and in the database it will always have an ID.
|
||||||
|
|
||||||
```Python hl_lines="17-18"
|
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[5:20] hl[19:20] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py[ln:7-22]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Review the Updated Docs UI
|
## Review the Updated Docs UI
|
||||||
|
|
||||||
The FastAPI code is still the same as above, we still use `Hero`, `HeroCreate`, and `HeroRead`. But now we define them in a smarter way with inheritance.
|
The FastAPI code is still the same as above, we still use `Hero`, `HeroCreate`, and `HeroPublic`. But now, we define them in a smarter way with inheritance.
|
||||||
|
|
||||||
So, we can jump to the docs UI right away and see how they look with the updated data.
|
So, we can jump to the docs UI right away and see how they look with the updated data.
|
||||||
|
|
||||||
@@ -375,7 +283,7 @@ Let's see the new UI for creating a hero:
|
|||||||
|
|
||||||
<img class="shadow" alt="Interactive API docs UI" src="/img/tutorial/fastapi/multiple-models/image02.png">
|
<img class="shadow" alt="Interactive API docs UI" src="/img/tutorial/fastapi/multiple-models/image02.png">
|
||||||
|
|
||||||
Nice! It now shows that to create a hero, we just pass the `name`, `secret_name`, and optinally `age`.
|
Nice! It now shows that to create a hero, we just pass the `name`, `secret_name`, and optionally `age`.
|
||||||
|
|
||||||
We no longer pass an `id`.
|
We no longer pass an `id`.
|
||||||
|
|
||||||
@@ -391,7 +299,7 @@ And if we check the schema for the **Read Heroes** *path operation* it will also
|
|||||||
|
|
||||||
## Inheritance and Table Models
|
## Inheritance and Table Models
|
||||||
|
|
||||||
We just saw how powerful inheritance of these models can be.
|
We just saw how powerful the inheritance of these models could be.
|
||||||
|
|
||||||
This is a very simple example, and it might look a bit... meh. 😅
|
This is a very simple example, and it might look a bit... meh. 😅
|
||||||
|
|
||||||
|
|||||||
@@ -8,25 +8,13 @@ Let's add a new *path operation* to read one single hero.
|
|||||||
|
|
||||||
We want to get the hero based on the `id`, so we will use a **path parameter** `hero_id`.
|
We want to get the hero based on the `id`, so we will use a **path parameter** `hero_id`.
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
If you need to refresh how *path parameters* work, including their data validation, check the <a href="https://fastapi.tiangolo.com/tutorial/path-params/" class="external-link" target="_blank">FastAPI docs about Path Parameters</a>.
|
|
||||||
|
|
||||||
```Python hl_lines="8"
|
If you need to refresh how *path parameters* work, including their data validation, check the <a href="https://fastapi.tiangolo.com/tutorial/path-params/" class="external-link" target="_blank">FastAPI docs about Path Parameters</a>.
|
||||||
{!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:1-4]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
///
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:61-67]!}
|
{* ./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py ln[1:2,59:65] hl[59] *}
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/read_one/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
For example, to get the hero with ID `2` we would send a `GET` request to:
|
For example, to get the hero with ID `2` we would send a `GET` request to:
|
||||||
|
|
||||||
@@ -42,49 +30,19 @@ But if the integer is not the ID of any hero in the database, it will not find a
|
|||||||
|
|
||||||
So, we check it in an `if` block, if it's `None`, we raise an `HTTPException` with a `404` status code.
|
So, we check it in an `if` block, if it's `None`, we raise an `HTTPException` with a `404` status code.
|
||||||
|
|
||||||
And to use it we first import `HTTPException` from `fastapi`.
|
And to use it, we first import `HTTPException` from `fastapi`.
|
||||||
|
|
||||||
This will let the client know that they probably made a mistake on their side and requested a hero that doesn't exist in the database.
|
This will let the client know that they probably made a mistake on their side and requested a hero that doesn't exist in the database.
|
||||||
|
|
||||||
```Python hl_lines="3 11-13"
|
{* ./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py ln[1:2,59:65] hl[1,62:64] *}
|
||||||
{!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:1-4]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:61-67]!}
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/read_one/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Return the Hero
|
## Return the Hero
|
||||||
|
|
||||||
Then, if the hero exists, we return it.
|
Then, if the hero exists, we return it.
|
||||||
|
|
||||||
And because we are using the `response_model` with `HeroRead`, it will be validated, documented, etc.
|
And because we are using the `response_model` with `HeroPublic`, it will be validated, documented, etc.
|
||||||
|
|
||||||
```Python hl_lines="8 14"
|
{* ./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py ln[1:2,59:65] hl[59,65] *}
|
||||||
{!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:1-4]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:61-67]!}
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/read_one/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Check the Docs UI
|
## Check the Docs UI
|
||||||
|
|
||||||
|
|||||||
@@ -40,69 +40,23 @@ Let's update that. 🤓
|
|||||||
|
|
||||||
First, why is it that we are not getting the related data for each hero and for each team?
|
First, why is it that we are not getting the related data for each hero and for each team?
|
||||||
|
|
||||||
It's because we declared the `HeroRead` with only the same base fields of the `HeroBase` plus the `id`. But it doesn't include a field `team` for the **relationship attribute**.
|
It's because we declared the `HeroPublic` with only the same base fields of the `HeroBase` plus the `id`. But it doesn't include a field `team` for the **relationship attribute**.
|
||||||
|
|
||||||
And the same way, we declared the `TeamRead` with only the same base fields of the `TeamBase` plus the `id`. But it doesn't include a field `heroes` for the **relationship attribute**.
|
And the same way, we declared the `TeamPublic` with only the same base fields of the `TeamBase` plus the `id`. But it doesn't include a field `heroes` for the **relationship attribute**.
|
||||||
|
|
||||||
```Python hl_lines="3-5 9-10 14-19 23-24"
|
{* ./docs_src/tutorial/fastapi/teams/tutorial001_py310.py ln[5:7,20:21,29:34,43:44] hl[5:7,20:21,29:34,43:44] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:7-9]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:22-23]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:32-37]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:46-47]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/teams/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Now, remember that <a href="https://fastapi.tiangolo.com/tutorial/response-model/" class="external-link" target="_blank">FastAPI uses the `response_model` to validate and **filter** the response data</a>?
|
Now, remember that <a href="https://fastapi.tiangolo.com/tutorial/response-model/" class="external-link" target="_blank">FastAPI uses the `response_model` to validate and **filter** the response data</a>?
|
||||||
|
|
||||||
In this case, we used `response_model=TeamRead` and `response_model=HeroRead`, so FastAPI will use them to filter the response data, even if we return a **table model** that includes **relationship attributes**:
|
In this case, we used `response_model=TeamPublic` and `response_model=HeroPublic`, so FastAPI will use them to filter the response data, even if we return a **table model** that includes **relationship attributes**:
|
||||||
|
|
||||||
```Python hl_lines="3 8 12 17"
|
{* ./docs_src/tutorial/fastapi/teams/tutorial001_py310.py ln[102:107,156:161] hl[102,107,156,161] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:105-110]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:160-165]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/teams/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Don't Include All the Data
|
## Don't Include All the Data
|
||||||
|
|
||||||
Now let's stop for a second and think about it.
|
Now let's stop for a second and think about it.
|
||||||
|
|
||||||
We cannot simply include *all* the data including all the internal relationships, because each **hero** has an attribute `team` with their team, and then that **team** also has an attribute `heroes` with all the **heroes** in the team, including this one.
|
We cannot simply include *all* the data, including all the internal relationships, because each **hero** has an attribute `team` with their team, and then that **team** also has an attribute `heroes` with all the **heroes** in the team, including this one.
|
||||||
|
|
||||||
If we tried to include everything, we could make the server application **crash** trying to extract **infinite data**, going through the same hero and team over and over again internally, something like this:
|
If we tried to include everything, we could make the server application **crash** trying to extract **infinite data**, going through the same hero and team over and over again internally, something like this:
|
||||||
|
|
||||||
@@ -152,7 +106,7 @@ If we tried to include everything, we could make the server application **crash*
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
As you can see, in this example we would get the hero **Rusty-Man**, and from this hero we would get the team **Preventers**, and then from this team we would get its heroes, of course, including **Rusty-Man**... 😱
|
As you can see, in this example, we would get the hero **Rusty-Man**, and from this hero we would get the team **Preventers**, and then from this team we would get its heroes, of course, including **Rusty-Man**... 😱
|
||||||
|
|
||||||
So we start again, and in the end, the server would just crash trying to get all the data with a `"Maximum recursion error"`, we would not even get a response like the one above.
|
So we start again, and in the end, the server would just crash trying to get all the data with a `"Maximum recursion error"`, we would not even get a response like the one above.
|
||||||
|
|
||||||
@@ -164,7 +118,7 @@ This is a decision that will depend on **each application**.
|
|||||||
|
|
||||||
In our case, let's say that if we get a **list of heroes**, we don't want to also include each of their teams in each one.
|
In our case, let's say that if we get a **list of heroes**, we don't want to also include each of their teams in each one.
|
||||||
|
|
||||||
And if we get a **list of teams**, we don't want to get a a list of the heroes for each one.
|
And if we get a **list of teams**, we don't want to get a list of the heroes for each one.
|
||||||
|
|
||||||
But if we get a **single hero**, we want to include the team data (without the team's heroes).
|
But if we get a **single hero**, we want to include the team data (without the team's heroes).
|
||||||
|
|
||||||
@@ -174,36 +128,21 @@ Let's add a couple more **data models** that declare that data so we can use the
|
|||||||
|
|
||||||
## Models with Relationships
|
## Models with Relationships
|
||||||
|
|
||||||
Let's add the models `HeroReadWithTeam` and `TeamReadWithHeroes`.
|
Let's add the models `HeroPublicWithTeam` and `TeamPublicWithHeroes`.
|
||||||
|
|
||||||
We'll add them **after** the other models so that we can easily reference the previous models.
|
We'll add them **after** the other models so that we can easily reference the previous models.
|
||||||
|
|
||||||
```Python hl_lines="3-4 7-8"
|
{* ./docs_src/tutorial/fastapi/relationships/tutorial001_py310.py ln[59:64] hl[59:60,63:64] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/relationships/tutorial001.py[ln:61-66]!}
|
These two models are very **simple in code**, but there's a lot happening here. Let's check it out.
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/relationships/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
These two models are very **simple in code**, but there's a lot happening here, let's check it out.
|
|
||||||
|
|
||||||
### Inheritance and Type Annotations
|
### Inheritance and Type Annotations
|
||||||
|
|
||||||
The `HeroReadWithTeam` **inherits** from `HeroRead`, which means that it will have the **normal fields for reading**, including the required `id` that was declared in `HeroRead`.
|
The `HeroPublicWithTeam` **inherits** from `HeroPublic`, which means that it will have the **normal fields for reading**, including the required `id` that was declared in `HeroPublic`.
|
||||||
|
|
||||||
And then it adds the **new field** `team`, which could be `None`, and is declared with the type `TeamRead` with the base fields for reading a team.
|
And then it adds the **new field** `team`, which could be `None`, and is declared with the type `TeamPublic` with the base fields for reading a team.
|
||||||
|
|
||||||
Then we do the same for the `TeamReadWithHeroes`, it **inherits** from `TeamRead`, and declare the **new field** `heroes` which is a list of `HeroRead`.
|
Then we do the same for the `TeamPublicWithHeroes`, it **inherits** from `TeamPublic`, and declares the **new field** `heroes`, which is a list of `HeroPublic`.
|
||||||
|
|
||||||
### Data Models Without Relationship Attributes
|
### Data Models Without Relationship Attributes
|
||||||
|
|
||||||
@@ -213,11 +152,11 @@ Instead, here these are only **data models** that will tell FastAPI **which attr
|
|||||||
|
|
||||||
### Reference to Other Models
|
### Reference to Other Models
|
||||||
|
|
||||||
Also notice that the field `team` is not declared with this new `TeamReadWithHeroes`, because that would again create that infinite recursion of data. Instead, we declare it with the normal `TeamRead` model.
|
Also, notice that the field `team` is not declared with this new `TeamPublicWithHeroes`, because that would again create that infinite recursion of data. Instead, we declare it with the normal `TeamPublic` model.
|
||||||
|
|
||||||
And the same for `TeamReadWithHeroes`, the model used for the new field `heroes` uses `HeroRead` to get only each hero's data.
|
And the same for `TeamPublicWithHeroes`, the model used for the new field `heroes` uses `HeroPublic` to get only each hero's data.
|
||||||
|
|
||||||
This also means that, even though we have these two new models, **we still need the previous ones**, `HeroRead` and `TeamRead`, because we need to reference them here (and we are also using them in the rest of the *path operations*).
|
This also means that, even though we have these two new models, **we still need the previous ones**, `HeroPublic` and `TeamPublic`, because we need to reference them here (and we are also using them in the rest of the *path operations*).
|
||||||
|
|
||||||
## Update the Path Operations
|
## Update the Path Operations
|
||||||
|
|
||||||
@@ -227,26 +166,7 @@ This will tell **FastAPI** to take the object that we return from the *path oper
|
|||||||
|
|
||||||
In the case of the hero, this tells FastAPI to extract the `team` too. And in the case of the team, to extract the list of `heroes` too.
|
In the case of the hero, this tells FastAPI to extract the `team` too. And in the case of the team, to extract the list of `heroes` too.
|
||||||
|
|
||||||
```Python hl_lines="3 8 12 17"
|
{* ./docs_src/tutorial/fastapi/relationships/tutorial001_py310.py ln[111:116,165:170] hl[111,116,165,170] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/relationships/tutorial001.py[ln:113-118]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/relationships/tutorial001.py[ln:168-173]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/relationships/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Check It Out in the Docs UI
|
## Check It Out in the Docs UI
|
||||||
|
|
||||||
@@ -267,7 +187,7 @@ Now we get the **team** data included:
|
|||||||
"id": 1,
|
"id": 1,
|
||||||
"team": {
|
"team": {
|
||||||
"name": "Z-Force",
|
"name": "Z-Force",
|
||||||
"headquarters": "Sister Margaret’s Bar",
|
"headquarters": "Sister Margaret's Bar",
|
||||||
"id": 1
|
"id": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,7 +246,7 @@ Now we get the list of **heroes** included:
|
|||||||
|
|
||||||
## Recap
|
## Recap
|
||||||
|
|
||||||
Using the same techniques to declare additonal **data models** we can tell FastAPI what data to return in the responses, even when we return **table models**.
|
Using the same techniques to declare additional **data models**, we can tell FastAPI what data to return in the responses, even when we return **table models**.
|
||||||
|
|
||||||
Here we almost **didn't have to change the FastAPI app** code, but of course, there will be cases where you need to get the data and process it in different ways in the *path operation function* before returning it.
|
Here we almost **didn't have to change the FastAPI app** code, but of course, there will be cases where you need to get the data and process it in different ways in the *path operation function* before returning it.
|
||||||
|
|
||||||
@@ -334,4 +254,4 @@ But even in those cases, you will be able to define the **data models** to use i
|
|||||||
|
|
||||||
By this point, you already have a very robust API to handle data in a SQL database combining **SQLModel** with **FastAPI**, and implementing **best practices**, like data validation, conversion, filtering, and documentation. ✨
|
By this point, you already have a very robust API to handle data in a SQL database combining **SQLModel** with **FastAPI**, and implementing **best practices**, like data validation, conversion, filtering, and documentation. ✨
|
||||||
|
|
||||||
In the next chapter I'll tell you how to implement automated **testing** for your application using FastAPI and SQLModel. ✅
|
In the next chapter, I'll tell you how to implement automated **testing** for your application using FastAPI and SQLModel. ✅
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ You can see that there's a possible "Successful Response" with a code `200`, but
|
|||||||
|
|
||||||
<img class="shadow" alt="API docs UI without response data schemas" src="/img/tutorial/fastapi/response-model/image01.png">
|
<img class="shadow" alt="API docs UI without response data schemas" src="/img/tutorial/fastapi/response-model/image01.png">
|
||||||
|
|
||||||
Right now we only tell FastAPI the data we want to receive, but we don't tell it yet the data we want to send back.
|
Right now, we only tell FastAPI the data we want to receive, but we don't tell it yet the data we want to send back.
|
||||||
|
|
||||||
Let's do that now. 🤓
|
Let's do that now. 🤓
|
||||||
|
|
||||||
@@ -32,22 +32,7 @@ We can use `response_model` to tell FastAPI the schema of the data we want to se
|
|||||||
|
|
||||||
For example, we can pass the same `Hero` **SQLModel** class (because it is also a Pydantic model):
|
For example, we can pass the same `Hero` **SQLModel** class (because it is also a Pydantic model):
|
||||||
|
|
||||||
```Python hl_lines="3"
|
{* ./docs_src/tutorial/fastapi/response_model/tutorial001_py310.py ln[31:37] hl[31] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/response_model/tutorial001.py[ln:33-39]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/response_model/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## List of Heroes in `response_model`
|
## List of Heroes in `response_model`
|
||||||
|
|
||||||
@@ -55,24 +40,7 @@ We can also use other type annotations, the same way we can use with Pydantic fi
|
|||||||
|
|
||||||
First, we import `List` from `typing` and then we declare the `response_model` with `List[Hero]`:
|
First, we import `List` from `typing` and then we declare the `response_model` with `List[Hero]`:
|
||||||
|
|
||||||
```Python hl_lines="1 5"
|
{* ./docs_src/tutorial/fastapi/response_model/tutorial001_py310.py ln[40:44] hl[40] *}
|
||||||
{!./docs_src/tutorial/fastapi/response_model/tutorial001.py[ln:1]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/response_model/tutorial001.py[ln:42-46]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/response_model/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## FastAPI and Response Model
|
## FastAPI and Response Model
|
||||||
|
|
||||||
@@ -100,10 +68,13 @@ Additionally, because the schemas are defined in using a standard, there are man
|
|||||||
|
|
||||||
For example, client generators, that can automatically create the code necessary to talk to your API in many languages.
|
For example, client generators, that can automatically create the code necessary to talk to your API in many languages.
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
If you are curious about the standards, FastAPI generates OpenAPI, that internally uses JSON Schema.
|
|
||||||
|
|
||||||
You can read about all that in the <a href="https://fastapi.tiangolo.com/tutorial/first-steps/#openapi" class="external-link" target="_blank">FastAPI docs - First Steps</a>.
|
If you are curious about the standards, FastAPI generates OpenAPI, that internally uses JSON Schema.
|
||||||
|
|
||||||
|
You can read about all that in the <a href="https://fastapi.tiangolo.com/tutorial/first-steps/#openapi" class="external-link" target="_blank">FastAPI docs - First Steps</a>.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Recap
|
## Recap
|
||||||
|
|
||||||
|
|||||||
@@ -6,22 +6,7 @@ Before we keep adding things, let's change a bit how we get the session for each
|
|||||||
|
|
||||||
Up to now, we have been creating a session in each *path operation*, in a `with` block.
|
Up to now, we have been creating a session in each *path operation*, in a `with` block.
|
||||||
|
|
||||||
```Python hl_lines="5"
|
{* ./docs_src/tutorial/fastapi/delete/tutorial001_py310.py ln[48:55] hl[50] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/delete/tutorial001.py[ln:50-57]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/delete/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
That's perfectly fine, but in many use cases we would want to use <a href="https://fastapi.tiangolo.com/tutorial/dependencies/" class="external-link" target="_blank">FastAPI Dependencies</a>, for example to **verify** that the client is **logged in** and get the **current user** before executing any other code in the *path operation*.
|
That's perfectly fine, but in many use cases we would want to use <a href="https://fastapi.tiangolo.com/tutorial/dependencies/" class="external-link" target="_blank">FastAPI Dependencies</a>, for example to **verify** that the client is **logged in** and get the **current user** before executing any other code in the *path operation*.
|
||||||
|
|
||||||
@@ -35,22 +20,7 @@ A **FastAPI** dependency is very simple, it's just a function that returns a val
|
|||||||
|
|
||||||
It could use `yield` instead of `return`, and in that case **FastAPI** will make sure it executes all the code **after** the `yield`, once it is done with the request.
|
It could use `yield` instead of `return`, and in that case **FastAPI** will make sure it executes all the code **after** the `yield`, once it is done with the request.
|
||||||
|
|
||||||
```Python hl_lines="3-5"
|
{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[40:42] hl[40:42] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:42-44]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Use the Dependency
|
## Use the Dependency
|
||||||
|
|
||||||
@@ -58,39 +28,21 @@ Now let's make FastAPI execute a dependency and get its value in the *path opera
|
|||||||
|
|
||||||
We import `Depends()` from `fastapi`. Then we use it in the *path operation function* in a **parameter**, the same way we declared parameters to get JSON bodies, path parameters, etc.
|
We import `Depends()` from `fastapi`. Then we use it in the *path operation function* in a **parameter**, the same way we declared parameters to get JSON bodies, path parameters, etc.
|
||||||
|
|
||||||
```Python hl_lines="3 15"
|
{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[1:2,40:42,53:59] hl[1,54] *}
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:1-4]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
/// tip
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:42-44]!}
|
Here's a tip about that `*,` thing in the parameters.
|
||||||
|
|
||||||
# Code here omitted 👈
|
Here we are passing the parameter `session` that has a "default value" of `Depends(get_session)` before the parameter `hero`, that doesn't have any default value.
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:55-61]!}
|
Python would normally complain about that, but we can use the initial "parameter" `*,` to mark all the rest of the parameters as "keyword only", which solves the problem.
|
||||||
|
|
||||||
# Code below omitted 👇
|
You can read more about it in the FastAPI documentation <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#order-the-parameters-as-you-need-tricks" class="external-link" target="_blank">Path Parameters and Numeric Validations - Order the parameters as you need, tricks</a>
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
///
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
The value of a dependency will **only be used for one request**, FastAPI will call it right before calling your code and will give you the value from that dependency.
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
!!! tip
|
|
||||||
Here's a tip about that `*,` thing in the parameters.
|
|
||||||
|
|
||||||
Here we are passing the parameter `session` that has a "default value" of `Depends(get_session)` before the parameter `hero`, that doesn't have any default value.
|
|
||||||
|
|
||||||
Python would normally complain about that, but we can use the initial "parameter" `*,` to mark all the rest of the parameters as "keyword only", which solves the problem.
|
|
||||||
|
|
||||||
You can read more about it in the FastAPI documentation <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#order-the-parameters-as-you-need-tricks" class="external-link" target="_blank">Path Parameters and Numeric Validations - Order the parameters as you need, tricks</a>
|
|
||||||
|
|
||||||
The value of a dependency will **only be used for one request**, FastAPI will call it right before calling your code, and will give you the value from that dependency.
|
|
||||||
|
|
||||||
If it had `yield`, then it will continue the rest of the execution once you are done sending the response. In the case of the **session**, it will finish the cleanup code from the `with` block, closing the session, etc.
|
If it had `yield`, then it will continue the rest of the execution once you are done sending the response. In the case of the **session**, it will finish the cleanup code from the `with` block, closing the session, etc.
|
||||||
|
|
||||||
@@ -104,55 +56,13 @@ And because dependencies can use `yield`, FastAPI will make sure to run the code
|
|||||||
|
|
||||||
This means that in the main code of the *path operation function*, it will work equivalently to the previous version with the explicit `with` block.
|
This means that in the main code of the *path operation function*, it will work equivalently to the previous version with the explicit `with` block.
|
||||||
|
|
||||||
```Python hl_lines="16-20"
|
{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[1:2,40:42,53:59] hl[55:59] *}
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:1-4]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:42-44]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:55-61]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
In fact, you could think that all that block of code inside of the `create_hero()` function is still inside a `with` block for the **session**, because this is more or less what's happening behind the scenes.
|
In fact, you could think that all that block of code inside of the `create_hero()` function is still inside a `with` block for the **session**, because this is more or less what's happening behind the scenes.
|
||||||
|
|
||||||
But now, the `with` block is not explicitly in the function, but in the dependency above:
|
But now, the `with` block is not explicitly in the function, but in the dependency above:
|
||||||
|
|
||||||
```Python hl_lines="9-10"
|
{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[1:2,40:42,53:59] hl[41:42] *}
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:1-4]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:42-44]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:55-61]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
We will see how this is very useful when testing the code later. ✅
|
We will see how this is very useful when testing the code later. ✅
|
||||||
|
|
||||||
@@ -168,26 +78,7 @@ session: Session = Depends(get_session)
|
|||||||
|
|
||||||
And then we remove the previous `with` block with the old **session**.
|
And then we remove the previous `with` block with the old **session**.
|
||||||
|
|
||||||
```Python hl_lines="15 26 35 44 59"
|
{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[1:2,40:42,53:104] hl[54,65,74,83,98] *}
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:1-4]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:42-44]!}
|
|
||||||
|
|
||||||
# Code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:55-107]!}
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Recap
|
## Recap
|
||||||
|
|
||||||
|
|||||||
@@ -10,20 +10,20 @@ FastAPI is the framework to create the **web API**.
|
|||||||
|
|
||||||
But we also need another type of program to run it, it is called a "**server**". We will use **Uvicorn** for that. And we will install Uvicorn with its *standard* dependencies.
|
But we also need another type of program to run it, it is called a "**server**". We will use **Uvicorn** for that. And we will install Uvicorn with its *standard* dependencies.
|
||||||
|
|
||||||
Make sure you [have a virtual environment activated](../index.md#create-a-python-virtual-environment){.internal-link target=_blank}.
|
Then install FastAPI.
|
||||||
|
|
||||||
Then install FastAPI and Uvicorn:
|
Make sure you create a [virtual environment](../../virtual-environments.md){.internal-link target=_blank}, activate it, and then install them, for example with:
|
||||||
|
|
||||||
<div class="termy">
|
<div class="termy">
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ python -m pip install fastapi "uvicorn[standard]"
|
$ pip install fastapi "uvicorn[standard]"
|
||||||
|
|
||||||
---> 100%
|
---> 100%
|
||||||
```
|
```
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
s
|
|
||||||
## **SQLModel** Code - Models, Engine
|
## **SQLModel** Code - Models, Engine
|
||||||
|
|
||||||
Now let's start with the SQLModel code.
|
Now let's start with the SQLModel code.
|
||||||
@@ -32,25 +32,7 @@ We will start with the **simplest version**, with just heroes (no teams yet).
|
|||||||
|
|
||||||
This is almost the same code we have seen up to now in previous examples:
|
This is almost the same code we have seen up to now in previous examples:
|
||||||
|
|
||||||
```Python hl_lines="20-21"
|
{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[2,5:20] hl[19:20] *}
|
||||||
{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py[ln:1]!}
|
|
||||||
|
|
||||||
# One line of FastAPI imports here later 👈
|
|
||||||
{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py[ln:4]!}
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py[ln:7-22]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
There's only one change here from the code we have used before, the `check_same_thread` in the `connect_args`.
|
There's only one change here from the code we have used before, the `check_same_thread` in the `connect_args`.
|
||||||
|
|
||||||
@@ -62,10 +44,13 @@ But here we will make sure we don't share the same **session** in more than one
|
|||||||
|
|
||||||
And we also need to disable it because in **FastAPI** each request could be handled by multiple interacting threads.
|
And we also need to disable it because in **FastAPI** each request could be handled by multiple interacting threads.
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
That's enough information for now, you can read more about it in the <a href="https://fastapi.tiangolo.com/async/" class="external-link" target="_blank">FastAPI docs for `async` and `await`</a>.
|
|
||||||
|
|
||||||
The main point is, by ensuring you **don't share** the same **session** with more than one request, the code is already safe.
|
That's enough information for now, you can read more about it in the <a href="https://fastapi.tiangolo.com/async/" class="external-link" target="_blank">FastAPI docs for `async` and `await`</a>.
|
||||||
|
|
||||||
|
The main point is, by ensuring you **don't share** the same **session** with more than one request, the code is already safe.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## **FastAPI** App
|
## **FastAPI** App
|
||||||
|
|
||||||
@@ -75,24 +60,7 @@ We will import the `FastAPI` class from `fastapi`.
|
|||||||
|
|
||||||
And then create an `app` object that is an instance of that `FastAPI` class:
|
And then create an `app` object that is an instance of that `FastAPI` class:
|
||||||
|
|
||||||
```Python hl_lines="3 8"
|
{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[1:2,23] hl[1,23] *}
|
||||||
{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py[ln:1-4]!}
|
|
||||||
|
|
||||||
# SQLModel code here omitted 👈
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py[ln:25]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Create Database and Tables on `startup`
|
## Create Database and Tables on `startup`
|
||||||
|
|
||||||
@@ -100,89 +68,55 @@ We want to make sure that once the app starts running, the function `create_tabl
|
|||||||
|
|
||||||
This should be called only once at startup, not before every request, so we put it in the function to handle the `"startup"` event:
|
This should be called only once at startup, not before every request, so we put it in the function to handle the `"startup"` event:
|
||||||
|
|
||||||
```Python hl_lines="6-8"
|
{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[23:28] hl[26:28] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py[ln:25-30]!}
|
|
||||||
|
|
||||||
# Code below omitted 👇
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Create Heroes *Path Operation*
|
## Create Heroes *Path Operation*
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
If you need a refresher on what a **Path Operation** is (an endpoint with a specific HTTP Operation) and how to work with it in FastAPI, check out the <a href="https://fastapi.tiangolo.com/tutorial/first-steps/" class="external-link" target="_blank">FastAPI First Steps docs</a>.
|
|
||||||
|
If you need a refresher on what a **Path Operation** is (an endpoint with a specific HTTP Operation) and how to work with it in FastAPI, check out the <a href="https://fastapi.tiangolo.com/tutorial/first-steps/" class="external-link" target="_blank">FastAPI First Steps docs</a>.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
Let's create the **path operation** code to create a new hero.
|
Let's create the **path operation** code to create a new hero.
|
||||||
|
|
||||||
It will be called when a user sends a request with a `POST` **operation** to the `/heroes/` **path**:
|
It will be called when a user sends a request with a `POST` **operation** to the `/heroes/` **path**:
|
||||||
|
|
||||||
```Python hl_lines="11-12"
|
{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[23:37] hl[31:32] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py[ln:25-39]!}
|
/// info
|
||||||
|
|
||||||
# Code below omitted 👇
|
If you need a refresher on some of those concepts, checkout the FastAPI documentation:
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
* <a href="https://fastapi.tiangolo.com/tutorial/first-steps/" class="external-link" target="_blank">First Steps</a>
|
||||||
<summary>👀 Full file preview</summary>
|
* <a href="https://fastapi.tiangolo.com/tutorial/path-params/" class="external-link" target="_blank">Path Parameters - Data Validation and Data Conversion</a>
|
||||||
|
* <a href="https://fastapi.tiangolo.com/tutorial/body/" class="external-link" target="_blank">Request Body</a>
|
||||||
|
|
||||||
```Python
|
///
|
||||||
{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
!!! info
|
|
||||||
If you need a refresher on some of those concepts, checkout the FastAPI documentation:
|
|
||||||
|
|
||||||
* <a href="https://fastapi.tiangolo.com/tutorial/first-steps/" class="external-link" target="_blank">First Steps</a>
|
|
||||||
* <a href="https://fastapi.tiangolo.com/tutorial/path-params/" class="external-link" target="_blank">Path Parameters - Data Validation and Data Conversion</a>
|
|
||||||
* <a href="https://fastapi.tiangolo.com/tutorial/body/" class="external-link" target="_blank">Request Body</a>
|
|
||||||
|
|
||||||
## The **SQLModel** Advantage
|
## The **SQLModel** Advantage
|
||||||
|
|
||||||
Here's where having our **SQLModel** class models be both **SQLAlchemy** models and **Pydantic** models at the same tieme shine. ✨
|
Here's where having our **SQLModel** class models be both **SQLAlchemy** models and **Pydantic** models at the same time shine. ✨
|
||||||
|
|
||||||
Here we use the **same** class model to define the **request body** that will be received by our API.
|
Here we use the **same** class model to define the **request body** that will be received by our API.
|
||||||
|
|
||||||
Because **FastAPI** is based on Pydantic, it will use the same model (the Pydantic part) to do automatic data validation and <abbr title="also called serialization, marshalling">conversion</abbr> from the JSON request to an object that is an actual instance of the `Hero` class.
|
Because **FastAPI** is based on Pydantic, it will use the same model (the Pydantic part) to do automatic data validation and <abbr title="also called serialization, marshalling">conversion</abbr> from the JSON request to an object that is an actual instance of the `Hero` class.
|
||||||
|
|
||||||
And then because this same **SQLModel** object is not only a **Pydantic** model instance but also a **SQLAlchemy** model instance, we can use it directly in a **session** to create the row in the database.
|
And then, because this same **SQLModel** object is not only a **Pydantic** model instance but also a **SQLAlchemy** model instance, we can use it directly in a **session** to create the row in the database.
|
||||||
|
|
||||||
So we can use intuitive standard Python **type annotations**, and we don't have to duplicate a lot of the code for the database models and the API data models. 🎉
|
So we can use intuitive standard Python **type annotations**, and we don't have to duplicate a lot of the code for the database models and the API data models. 🎉
|
||||||
|
|
||||||
!!! tip
|
/// tip
|
||||||
We will improve this further later, but for now, it already shows the power of having **SQLModel** classes be both **SQLAlchemy** models and **Pydantic** models at the same time.
|
|
||||||
|
We will improve this further later, but for now, it already shows the power of having **SQLModel** classes be both **SQLAlchemy** models and **Pydantic** models at the same time.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Read Heroes *Path Operation*
|
## Read Heroes *Path Operation*
|
||||||
|
|
||||||
Now let's add another **path operation** to read all the heroes:
|
Now let's add another **path operation** to read all the heroes:
|
||||||
|
|
||||||
```Python hl_lines="20-24"
|
{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[23:44] hl[40:44] *}
|
||||||
# Code above omitted 👆
|
|
||||||
|
|
||||||
{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py[ln:25-46]!}
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>👀 Full file preview</summary>
|
|
||||||
|
|
||||||
```Python
|
|
||||||
{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py!}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
This is pretty straightforward.
|
This is pretty straightforward.
|
||||||
|
|
||||||
@@ -190,13 +124,13 @@ When a client sends a request to the **path** `/heroes/` with a `GET` HTTP **ope
|
|||||||
|
|
||||||
## One Session per Request
|
## One Session per Request
|
||||||
|
|
||||||
Remember that we shoud use a SQLModel **session** per each group of operations and if we need other unrelated operations we should use a different session?
|
Remember that we should use a SQLModel **session** per each group of operations and if we need other unrelated operations we should use a different session?
|
||||||
|
|
||||||
Here it is much more obvious.
|
Here it is much more obvious.
|
||||||
|
|
||||||
We should normally have **one session per request** in most of the cases.
|
We should normally have **one session per request** in most of the cases.
|
||||||
|
|
||||||
In some isolated cases we would want to have new sessions inside, so, **more than one session** per request.
|
In some isolated cases, we would want to have new sessions inside, so, **more than one session** per request.
|
||||||
|
|
||||||
But we would **never want to *share* the same session** among different requests.
|
But we would **never want to *share* the same session** among different requests.
|
||||||
|
|
||||||
@@ -226,11 +160,14 @@ $ uvicorn main:app
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
!!! info
|
/// info
|
||||||
The command `uvicorn main:app` refers to:
|
|
||||||
|
|
||||||
* `main`: the file `main.py` (the Python "module").
|
The command `uvicorn main:app` refers to:
|
||||||
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
|
|
||||||
|
* `main`: the file `main.py` (the Python "module").
|
||||||
|
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
### Uvicorn `--reload`
|
### Uvicorn `--reload`
|
||||||
|
|
||||||
@@ -277,7 +214,7 @@ And then you can get them back with the **Read Heroes** *path operation*:
|
|||||||
|
|
||||||
Now you can terminate that Uvicorn server by going back to the terminal and pressing <kbd>Ctrl+C</kbd>.
|
Now you can terminate that Uvicorn server by going back to the terminal and pressing <kbd>Ctrl+C</kbd>.
|
||||||
|
|
||||||
And then you can open **DB Browser for SQLite** and check the database, to explore the data and confirm that it indeed saved the heroes. 🎉
|
And then, you can open **DB Browser for SQLite** and check the database, to explore the data and confirm that it indeed saved the heroes. 🎉
|
||||||
|
|
||||||
<img class="shadow" alt="DB Browser for SQLite showing the heroes" src="/img/tutorial/fastapi/simple-hero-api/db-browser-01.png">
|
<img class="shadow" alt="DB Browser for SQLite showing the heroes" src="/img/tutorial/fastapi/simple-hero-api/db-browser-01.png">
|
||||||
|
|
||||||
@@ -287,4 +224,4 @@ Good job! This is already a FastAPI **web API** application to interact with the
|
|||||||
|
|
||||||
There are several things we can improve and extend. For example, we want the database to decide the ID of each new hero, we don't want to allow a user to send it.
|
There are several things we can improve and extend. For example, we want the database to decide the ID of each new hero, we don't want to allow a user to send it.
|
||||||
|
|
||||||
We will do all those improvements in the next chapters. 🚀
|
We will make all those improvements in the next chapters. 🚀
|
||||||
|
|||||||