diff --git a/docs/en/data/github_sponsors.yml b/docs/en/data/github_sponsors.yml new file mode 100644 index 0000000..921ca28 --- /dev/null +++ b/docs/en/data/github_sponsors.yml @@ -0,0 +1,4 @@ +sponsors: +- - login: cryptapi + avatarUrl: https://avatars.githubusercontent.com/u/44925437?u=61369138589bc7fee6c417f3fbd50fbd38286cc4&v=4 + url: https://github.com/cryptapi diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml new file mode 100644 index 0000000..dd2dbe5 --- /dev/null +++ b/docs/en/data/people.yml @@ -0,0 +1,546 @@ +maintainers: +- login: tiangolo + answers: 1844 + prs: 430 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=740f11212a731f56798f558ceddb0bd07642afa7&v=4 + url: https://github.com/tiangolo +experts: +- login: Kludex + count: 434 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 + url: https://github.com/Kludex +- login: dmontagu + count: 237 + avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 + url: https://github.com/dmontagu +- login: Mause + count: 220 + avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4 + url: https://github.com/Mause +- login: ycd + count: 217 + avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=bba5af018423a2858d49309bed2a899bb5c34ac5&v=4 + url: https://github.com/ycd +- login: JarroVGIT + count: 193 + avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4 + url: https://github.com/JarroVGIT +- login: euri10 + count: 152 + avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4 + url: https://github.com/euri10 +- login: jgould22 + count: 139 + avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 + url: https://github.com/jgould22 +- login: phy25 + count: 126 + avatarUrl: https://avatars.githubusercontent.com/u/331403?u=191cd73f0c936497c8d1931a217bb3039d050265&v=4 + url: https://github.com/phy25 +- login: iudeen + count: 118 + avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4 + url: https://github.com/iudeen +- login: raphaelauv + count: 83 + avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 + url: https://github.com/raphaelauv +- login: ghandic + count: 71 + avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4 + url: https://github.com/ghandic +- login: ArcLightSlavik + count: 71 + avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4 + url: https://github.com/ArcLightSlavik +- login: falkben + count: 57 + avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4 + url: https://github.com/falkben +- login: sm-Fifteen + count: 49 + avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 + url: https://github.com/sm-Fifteen +- login: adriangb + count: 45 + avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4 + url: https://github.com/adriangb +- login: yinziyan1206 + count: 45 + avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 + url: https://github.com/yinziyan1206 +- login: acidjunk + count: 45 + avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 + url: https://github.com/acidjunk +- login: insomnes + count: 45 + avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4 + url: https://github.com/insomnes +- login: Dustyposa + count: 45 + avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4 + url: https://github.com/Dustyposa +- login: odiseo0 + count: 43 + avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=2da05dab6cc8e1ade557801634760a56e4101796&v=4 + url: https://github.com/odiseo0 +- login: frankie567 + count: 43 + avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4 + url: https://github.com/frankie567 +- login: includeamin + count: 40 + avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4 + url: https://github.com/includeamin +- login: STeveShary + count: 37 + avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4 + url: https://github.com/STeveShary +- login: chbndrhnns + count: 35 + avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4 + url: https://github.com/chbndrhnns +- login: krishnardt + count: 35 + avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4 + url: https://github.com/krishnardt +- login: panla + count: 32 + avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 + url: https://github.com/panla +- login: prostomarkeloff + count: 28 + avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4 + url: https://github.com/prostomarkeloff +- login: dbanty + count: 26 + avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4 + url: https://github.com/dbanty +- login: wshayes + count: 25 + avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 + url: https://github.com/wshayes +- login: SirTelemak + count: 23 + avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4 + url: https://github.com/SirTelemak +- login: acnebs + count: 22 + avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=c27e50269f1ef8ea950cc6f0268c8ec5cebbe9c9&v=4 + url: https://github.com/acnebs +- login: rafsaf + count: 21 + avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=f8f0d6d6e90fac39fa786228158ba7f013c74271&v=4 + url: https://github.com/rafsaf +- login: chris-allnutt + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/565544?v=4 + url: https://github.com/chris-allnutt +- login: nsidnev + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/22559461?u=a9cc3238217e21dc8796a1a500f01b722adb082c&v=4 + url: https://github.com/nsidnev +- login: n8sty + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 + url: https://github.com/n8sty +- login: retnikt + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4 + url: https://github.com/retnikt +- login: zoliknemet + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/22326718?u=31ba446ac290e23e56eea8e4f0c558aaf0b40779&v=4 + url: https://github.com/zoliknemet +- login: Hultner + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/2669034?u=115e53df959309898ad8dc9443fbb35fee71df07&v=4 + url: https://github.com/Hultner +- login: harunyasar + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4 + url: https://github.com/harunyasar +- login: nkhitrov + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/28262306?u=66ee21316275ef356081c2efc4ed7a4572e690dc&v=4 + url: https://github.com/nkhitrov +- login: caeser1996 + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/16540232?u=05d2beb8e034d584d0a374b99d8826327bd7f614&v=4 + url: https://github.com/caeser1996 +- login: jonatasoli + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=071c062d2861d3dd127f6b4a5258cd8ef55d4c50&v=4 + url: https://github.com/jonatasoli +- login: dstlny + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4 + url: https://github.com/dstlny +- login: abhint + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=b5d219277b4d001ac26fb8be357fddd88c29d51b&v=4 + url: https://github.com/abhint +- login: nymous + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/4216559?u=360a36fb602cded27273cbfc0afc296eece90662&v=4 + url: https://github.com/nymous +- login: ghost + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/10137?u=b1951d34a583cf12ec0d3b0781ba19be97726318&v=4 + url: https://github.com/ghost +- login: simondale00 + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/33907262?v=4 + url: https://github.com/simondale00 +- login: jorgerpo + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/12537771?u=7444d20019198e34911082780cc7ad73f2b97cb3&v=4 + url: https://github.com/jorgerpo +last_month_active: +- login: jgould22 + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 + url: https://github.com/jgould22 +- login: Kludex + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 + url: https://github.com/Kludex +- login: abhint + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=b5d219277b4d001ac26fb8be357fddd88c29d51b&v=4 + url: https://github.com/abhint +- login: chrisK824 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4 + url: https://github.com/chrisK824 +- login: arjwilliams + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/22227620?v=4 + url: https://github.com/arjwilliams +- login: wu-clan + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/52145145?u=f8c9e5c8c259d248e1683fedf5027b4ee08a0967&v=4 + url: https://github.com/wu-clan +- login: Ahmed-Abdou14 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/104530599?u=d1e1c064d57c3ad5b6481716928da840f6d5a492&v=4 + url: https://github.com/Ahmed-Abdou14 +- login: esrefzeki + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/54935247?u=193cf5a169ca05fc54995a4dceabc82c7dc6e5ea&v=4 + url: https://github.com/esrefzeki +top_contributors: +- login: waynerv + count: 25 + avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 + url: https://github.com/waynerv +- login: tokusumi + count: 22 + avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 + url: https://github.com/tokusumi +- login: Kludex + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 + url: https://github.com/Kludex +- login: jaystone776 + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4 + url: https://github.com/jaystone776 +- login: dmontagu + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 + url: https://github.com/dmontagu +- login: euri10 + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4 + url: https://github.com/euri10 +- login: Xewus + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4 + url: https://github.com/Xewus +- login: mariacamilagl + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 + url: https://github.com/mariacamilagl +- login: Smlep + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4 + url: https://github.com/Smlep +- login: Serrones + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4 + url: https://github.com/Serrones +- login: rjNemo + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 + url: https://github.com/rjNemo +- login: RunningIkkyu + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=494ecc298e3f26197495bb357ad0f57cfd5f7a32&v=4 + url: https://github.com/RunningIkkyu +- login: hard-coders + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4 + url: https://github.com/hard-coders +- login: Alexandrhub + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4 + url: https://github.com/Alexandrhub +- login: batlopes + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/33462923?u=0fb3d7acb316764616f11e4947faf080e49ad8d9&v=4 + url: https://github.com/batlopes +- login: wshayes + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 + url: https://github.com/wshayes +- login: samuelcolvin + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=807390ba9cfe23906c3bf8a0d56aaca3cf2bfa0d&v=4 + url: https://github.com/samuelcolvin +- login: SwftAlpc + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 + url: https://github.com/SwftAlpc +- login: Attsun1031 + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/1175560?v=4 + url: https://github.com/Attsun1031 +- login: ComicShrimp + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=f440bc9062afb3c43b9b9c6cdfdcfe31d58699ef&v=4 + url: https://github.com/ComicShrimp +- login: NinaHwang + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=eee6bfe9224c71193025ab7477f4f96ceaa05c62&v=4 + url: https://github.com/NinaHwang +- login: jekirl + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/2546697?u=a027452387d85bd4a14834e19d716c99255fb3b7&v=4 + url: https://github.com/jekirl +- login: jfunez + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/805749?v=4 + url: https://github.com/jfunez +- login: ycd + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=bba5af018423a2858d49309bed2a899bb5c34ac5&v=4 + url: https://github.com/ycd +- login: komtaki + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4 + url: https://github.com/komtaki +- login: hitrust + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/3360631?u=5fa1f475ad784d64eb9666bdd43cc4d285dcc773&v=4 + url: https://github.com/hitrust +- login: lsglucas + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4 + url: https://github.com/lsglucas +- login: axel584 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/1334088?u=9667041f5b15dc002b6f9665fda8c0412933ac04&v=4 + url: https://github.com/axel584 +- login: ivan-abc + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/36765187?u=c6e0ba571c1ccb6db9d94e62e4b8b5eda811a870&v=4 + url: https://github.com/ivan-abc +top_reviewers: +- login: Kludex + count: 122 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 + url: https://github.com/Kludex +- login: BilalAlpaslan + count: 79 + avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4 + url: https://github.com/BilalAlpaslan +- login: yezz123 + count: 77 + avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=d7062cbc6eb7671d5dc9cc0e32a24ae335e0f225&v=4 + url: https://github.com/yezz123 +- login: tokusumi + count: 51 + avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 + url: https://github.com/tokusumi +- login: waynerv + count: 47 + avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 + url: https://github.com/waynerv +- login: Laineyzhang55 + count: 47 + avatarUrl: https://avatars.githubusercontent.com/u/59285379?v=4 + url: https://github.com/Laineyzhang55 +- login: ycd + count: 45 + avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=bba5af018423a2858d49309bed2a899bb5c34ac5&v=4 + url: https://github.com/ycd +- login: iudeen + count: 44 + avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4 + url: https://github.com/iudeen +- login: cikay + count: 41 + avatarUrl: https://avatars.githubusercontent.com/u/24587499?u=e772190a051ab0eaa9c8542fcff1892471638f2b&v=4 + url: https://github.com/cikay +- login: Xewus + count: 35 + avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4 + url: https://github.com/Xewus +- login: JarroVGIT + count: 34 + avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4 + url: https://github.com/JarroVGIT +- login: AdrianDeAnda + count: 33 + avatarUrl: https://avatars.githubusercontent.com/u/1024932?u=b2ea249c6b41ddf98679c8d110d0f67d4a3ebf93&v=4 + url: https://github.com/AdrianDeAnda +- login: ArcLightSlavik + count: 31 + avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4 + url: https://github.com/ArcLightSlavik +- login: cassiobotaro + count: 28 + avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4 + url: https://github.com/cassiobotaro +- login: komtaki + count: 27 + avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4 + url: https://github.com/komtaki +- login: lsglucas + count: 26 + avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4 + url: https://github.com/lsglucas +- login: Ryandaydev + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=809f3d1074d04bbc28012a7f17f06ea56f5bd71a&v=4 + url: https://github.com/Ryandaydev +- login: dmontagu + count: 23 + avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 + url: https://github.com/dmontagu +- login: LorhanSohaky + count: 23 + avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4 + url: https://github.com/LorhanSohaky +- login: rjNemo + count: 21 + avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 + url: https://github.com/rjNemo +- login: hard-coders + count: 21 + avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4 + url: https://github.com/hard-coders +- login: odiseo0 + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=2da05dab6cc8e1ade557801634760a56e4101796&v=4 + url: https://github.com/odiseo0 +- login: 0417taehyun + count: 19 + avatarUrl: https://avatars.githubusercontent.com/u/63915557?u=47debaa860fd52c9b98c97ef357ddcec3b3fb399&v=4 + url: https://github.com/0417taehyun +- login: Smlep + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4 + url: https://github.com/Smlep +- login: zy7y + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/67154681?u=5d634834cc514028ea3f9115f7030b99a1f4d5a4&v=4 + url: https://github.com/zy7y +- login: yanever + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/21978760?v=4 + url: https://github.com/yanever +- login: SwftAlpc + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 + url: https://github.com/SwftAlpc +- login: DevDae + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/87962045?u=08e10fa516e844934f4b3fc7c38b33c61697e4a1&v=4 + url: https://github.com/DevDae +- login: pedabraham + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/16860088?u=abf922a7b920bf8fdb7867d8b43e091f1e796178&v=4 + url: https://github.com/pedabraham +- login: delhi09 + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/63476957?u=6c86e59b48e0394d4db230f37fc9ad4d7e2c27c7&v=4 + url: https://github.com/delhi09 +- login: Alexandrhub + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4 + url: https://github.com/Alexandrhub +- login: sh0nk + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4 + url: https://github.com/sh0nk +- login: peidrao + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/32584628?u=5b94b548ef0002ef3219d7c07ac0fac17c6201a2&v=4 + url: https://github.com/peidrao +- login: r0b2g1t + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/5357541?u=6428442d875d5d71aaa1bb38bb11c4be1a526bc2&v=4 + url: https://github.com/r0b2g1t +- login: RunningIkkyu + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=494ecc298e3f26197495bb357ad0f57cfd5f7a32&v=4 + url: https://github.com/RunningIkkyu +- login: axel584 + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/1334088?u=9667041f5b15dc002b6f9665fda8c0412933ac04&v=4 + url: https://github.com/axel584 +- login: ivan-abc + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/36765187?u=c6e0ba571c1ccb6db9d94e62e4b8b5eda811a870&v=4 + url: https://github.com/ivan-abc +- login: solomein-sv + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=789927ee09cfabd752d3bd554fa6baf4850d2777&v=4 + url: https://github.com/solomein-sv +- login: mariacamilagl + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 + url: https://github.com/mariacamilagl +- login: raphaelauv + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 + url: https://github.com/raphaelauv +- login: Attsun1031 + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/1175560?v=4 + url: https://github.com/Attsun1031 +- login: maoyibo + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/7887703?v=4 + url: https://github.com/maoyibo +- login: ComicShrimp + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=f440bc9062afb3c43b9b9c6cdfdcfe31d58699ef&v=4 + url: https://github.com/ComicShrimp +- login: izaguerreiro + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/2241504?v=4 + url: https://github.com/izaguerreiro +- login: graingert + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/413772?u=64b77b6aa405c68a9c6bcf45f84257c66eea5f32&v=4 + url: https://github.com/graingert +- login: PandaHun + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/13096845?u=646eba44db720e37d0dbe8e98e77ab534ea78a20&v=4 + url: https://github.com/PandaHun +- login: kty4119 + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/49435654?v=4 + url: https://github.com/kty4119 +- login: bezaca + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/69092910?u=4ac58eab99bd37d663f3d23551df96d4fbdbf760&v=4 + url: https://github.com/bezaca +- login: oandersonmagalhaes + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/83456692?v=4 + url: https://github.com/oandersonmagalhaes diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml new file mode 100644 index 0000000..1b5240b --- /dev/null +++ b/docs/en/data/sponsors.yml @@ -0,0 +1,36 @@ +gold: + - url: https://cryptapi.io/ + title: "CryptAPI: Your easy to use, secure and privacy oriented payment gateway." + img: https://fastapi.tiangolo.com/img/sponsors/cryptapi.svg + - url: https://platform.sh/try-it-now/?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023 + title: "Build, run and scale your apps on a modern, reliable, and secure PaaS." + img: https://fastapi.tiangolo.com/img/sponsors/platform-sh.png +silver: + - url: https://www.deta.sh/?ref=fastapi + title: The launchpad for all your (team's) ideas + img: https://fastapi.tiangolo.com/img/sponsors/deta.svg + - url: https://training.talkpython.fm/fastapi-courses + title: FastAPI video courses on demand from people you trust + img: https://fastapi.tiangolo.com/img/sponsors/talkpython.png + - url: https://testdriven.io/courses/tdd-fastapi/ + title: Learn to build high-quality web apps with best practices + img: https://fastapi.tiangolo.com/img/sponsors/testdriven.svg + - url: https://github.com/deepset-ai/haystack/ + title: Build powerful search from composable, open source building blocks + img: https://fastapi.tiangolo.com/img/sponsors/haystack-fastapi.svg + - url: https://careers.powens.com/ + title: Powens is hiring! + img: https://fastapi.tiangolo.com/img/sponsors/powens.png + - url: https://www.svix.com/ + title: Svix - Webhooks as a service + img: https://fastapi.tiangolo.com/img/sponsors/svix.svg + - url: https://databento.com/ + title: Pay as you go for market data + img: https://fastapi.tiangolo.com/img/sponsors/databento.svg +bronze: + - url: https://www.exoflare.com/open-source/?utm_source=FastAPI&utm_campaign=open_source + title: Biosecurity risk assessments made easy. + img: https://fastapi.tiangolo.com/img/sponsors/exoflare.png + - url: https://www.flint.sh + title: IT expertise, consulting and development by passionate people + img: https://fastapi.tiangolo.com/img/sponsors/flint.png diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml new file mode 100644 index 0000000..b3cb063 --- /dev/null +++ b/docs/en/data/sponsors_badge.yml @@ -0,0 +1,19 @@ +logins: + - jina-ai + - deta + - investsuite + - mikeckennedy + - deepset-ai + - cryptapi + - xoflare + - DropbaseHQ + - VincentParedes + - BLUE-DEVIL1134 + - ObliviousAI + - Doist + - nihpo + - svix + - armand-sauzay + - databento-bot + - nanram22 + - Flint-company diff --git a/docs/en/docs/advanced/customizing-engine.md b/docs/en/docs/advanced/customizing-engine.md new file mode 100644 index 0000000..77de456 --- /dev/null +++ b/docs/en/docs/advanced/customizing-engine.md @@ -0,0 +1 @@ +# Customizing Engine \ No newline at end of file diff --git a/docs/en/docs/advanced/customizing-logging.md b/docs/en/docs/advanced/customizing-logging.md new file mode 100644 index 0000000..6486bec --- /dev/null +++ b/docs/en/docs/advanced/customizing-logging.md @@ -0,0 +1 @@ +# Customizing Logging \ No newline at end of file diff --git a/docs/en/docs/advanced/customizing-parser.md b/docs/en/docs/advanced/customizing-parser.md new file mode 100644 index 0000000..4f02637 --- /dev/null +++ b/docs/en/docs/advanced/customizing-parser.md @@ -0,0 +1 @@ +# Customizing Parser \ No newline at end of file diff --git a/docs/en/docs/advanced/index.md b/docs/en/docs/advanced/index.md new file mode 100644 index 0000000..f498b62 --- /dev/null +++ b/docs/en/docs/advanced/index.md @@ -0,0 +1 @@ +# Advanced guide \ No newline at end of file diff --git a/docs/en/docs/advanced/middleware.md b/docs/en/docs/advanced/middleware.md new file mode 100644 index 0000000..4dd1086 --- /dev/null +++ b/docs/en/docs/advanced/middleware.md @@ -0,0 +1 @@ +# Middleware diff --git a/docs/en/docs/css/custom.css b/docs/en/docs/css/custom.css new file mode 100644 index 0000000..066b517 --- /dev/null +++ b/docs/en/docs/css/custom.css @@ -0,0 +1,146 @@ +.termynal-comment { + color: #4a968f; + font-style: italic; + display: block; +} + +.termy { + /* For right to left languages */ + direction: ltr; +} + +.termy [data-termynal] { + white-space: pre-wrap; +} + +a.external-link { + /* For right to left languages */ + direction: ltr; + display: inline-block; +} + +a.external-link::after { + /* \00A0 is a non-breaking space + to make the mark be on the same line as the link + */ + content: "\00A0[↪]"; +} + +a.internal-link::after { + /* \00A0 is a non-breaking space + to make the mark be on the same line as the link + */ + content: "\00A0↪"; +} + +.shadow { + box-shadow: 5px 5px 10px #999; +} + +/* Give space to lower icons so Gitter chat doesn't get on top of them */ +.md-footer-meta { + padding-bottom: 2em; +} + +.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; +} + +a.announce-link:link, +a.announce-link:visited { + color: #fff; +} + +a.announce-link:hover { + color: var(--md-accent-fg-color); +} + +.announce-wrapper { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + align-items: center; +} + +.announce-wrapper div.item { + display: none; +} + +.announce-wrapper .sponsor-badge { + display: block; + position: absolute; + top: -10px; + right: 0; + font-size: 0.5rem; + color: #999; + background-color: #666; + border-radius: 10px; + padding: 0 10px; + z-index: 10; +} + +.announce-wrapper .sponsor-image { + display: block; + border-radius: 20px; +} + +.announce-wrapper>div { + min-height: 40px; + display: flex; + align-items: center; +} + +.twitter { + color: #00acee; +} + +/* Right to left languages */ +code { + direction: ltr; + display: inline-block; +} + +.md-content__inner h1 { + direction: ltr !important; +} + +.illustration { + margin-top: 2em; + margin-bottom: 2em; +} diff --git a/docs/en/docs/css/termynal.css b/docs/en/docs/css/termynal.css new file mode 100644 index 0000000..406c008 --- /dev/null +++ b/docs/en/docs/css/termynal.css @@ -0,0 +1,109 @@ +/** + * termynal.js + * + * @author Ines Montani + * @version 0.0.1 + * @license MIT + */ + +:root { + --color-bg: #252a33; + --color-text: #eee; + --color-text-subtle: #a2a2a2; +} + +[data-termynal] { + width: 750px; + max-width: 100%; + background: var(--color-bg); + color: var(--color-text); + /* font-size: 18px; */ + font-size: 15px; + /* font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; */ + font-family: 'Roboto Mono', 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; + border-radius: 4px; + padding: 75px 45px 35px; + position: relative; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +[data-termynal]:before { + content: ''; + position: absolute; + top: 15px; + left: 15px; + display: inline-block; + width: 15px; + height: 15px; + border-radius: 50%; + /* A little hack to display the window buttons in one pseudo element. */ + background: #d9515d; + -webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; + box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; +} + +[data-termynal]:after { + content: 'bash'; + position: absolute; + color: var(--color-text-subtle); + top: 5px; + left: 0; + width: 100%; + text-align: center; +} + +a[data-terminal-control] { + text-align: right; + display: block; + color: #aebbff; +} + +[data-ty] { + display: block; + line-height: 2; +} + +[data-ty]:before { + /* Set up defaults and ensure empty lines are displayed. */ + content: ''; + display: inline-block; + vertical-align: middle; +} + +[data-ty="input"]:before, +[data-ty-prompt]:before { + margin-right: 0.75em; + color: var(--color-text-subtle); +} + +[data-ty="input"]:before { + content: '$'; +} + +[data-ty][data-ty-prompt]:before { + content: attr(data-ty-prompt); +} + +[data-ty-cursor]:after { + content: attr(data-ty-cursor); + font-family: monospace; + margin-left: 0.5em; + -webkit-animation: blink 1s infinite; + animation: blink 1s infinite; +} + + +/* Cursor animation */ + +@-webkit-keyframes blink { + 50% { + opacity: 0; + } +} + +@keyframes blink { + 50% { + opacity: 0; + } +} diff --git a/docs/en/docs/fastcrawler-people.md b/docs/en/docs/fastcrawler-people.md new file mode 100644 index 0000000..0abdc6b --- /dev/null +++ b/docs/en/docs/fastcrawler-people.md @@ -0,0 +1,9 @@ +# FastCrawler People + +FastAPI has an amazing community that welcomes people from all backgrounds. + +## Creator - Maintainer + +Hey! 👋 + +This is me: diff --git a/docs/en/docs/features.md b/docs/en/docs/features.md new file mode 100644 index 0000000..a058ad1 --- /dev/null +++ b/docs/en/docs/features.md @@ -0,0 +1,5 @@ +# Features + +## FastCrawler features + +**FastCrawler** gives you the following: diff --git a/docs/en/docs/img/favicon.png b/docs/en/docs/img/favicon.png new file mode 100644 index 0000000..37b68d7 Binary files /dev/null and b/docs/en/docs/img/favicon.png differ diff --git a/docs/en/docs/img/github-social-preview.png b/docs/en/docs/img/github-social-preview.png new file mode 100644 index 0000000..a12cbcd Binary files /dev/null and b/docs/en/docs/img/github-social-preview.png differ diff --git a/docs/en/docs/img/github-social-preview.svg b/docs/en/docs/img/github-social-preview.svg new file mode 100644 index 0000000..0845092 --- /dev/null +++ b/docs/en/docs/img/github-social-preview.svg @@ -0,0 +1,98 @@ + + + + + + + + image/svg+xml + + + + + + + + + FastAPI + + High performance, easy to learn,fast to code, ready for production + diff --git a/docs/en/docs/img/icon-transparent-bg.png b/docs/en/docs/img/icon-transparent-bg.png new file mode 100644 index 0000000..37b68d7 Binary files /dev/null and b/docs/en/docs/img/icon-transparent-bg.png differ diff --git a/docs/en/docs/img/icon-white-bg.png b/docs/en/docs/img/icon-white-bg.png new file mode 100644 index 0000000..1b82fd3 Binary files /dev/null and b/docs/en/docs/img/icon-white-bg.png differ diff --git a/docs/en/docs/img/icon-white.png b/docs/en/docs/img/icon-white.png new file mode 100644 index 0000000..37b68d7 Binary files /dev/null and b/docs/en/docs/img/icon-white.png differ diff --git a/docs/en/docs/img/logo-teal-vector.svg b/docs/en/docs/img/logo-teal-vector.svg new file mode 100644 index 0000000..c1d1b72 --- /dev/null +++ b/docs/en/docs/img/logo-teal-vector.svg @@ -0,0 +1,68 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/docs/en/docs/img/logo-teal.svg b/docs/en/docs/img/logo-teal.svg new file mode 100644 index 0000000..0d1136e --- /dev/null +++ b/docs/en/docs/img/logo-teal.svg @@ -0,0 +1,45 @@ + + + + + + + image/svg+xml + + + + + + + + FastAPI + + diff --git a/docs/en/docs/img/pycharm-completion.png b/docs/en/docs/img/pycharm-completion.png new file mode 100644 index 0000000..6cd204c Binary files /dev/null and b/docs/en/docs/img/pycharm-completion.png differ diff --git a/docs/en/docs/img/vscode-completion.png b/docs/en/docs/img/vscode-completion.png new file mode 100644 index 0000000..ba6e22b Binary files /dev/null and b/docs/en/docs/img/vscode-completion.png differ diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md new file mode 100644 index 0000000..14e714d --- /dev/null +++ b/docs/en/docs/index.md @@ -0,0 +1,124 @@ +

+ FastCrawler +

+

+ FastCrawler framework, high performance, easy to learn, fast to code, ready for production +

+

+ + Test + + + Coverage + + + Package version + + + Supported Python versions + +

+ +--- + +**Documentation**: https://github.com/fast-crawler/ + +**Source Code**: https://github.com/fast-crawler/ + +--- + +FastCrawler is a modern, fast (high-performance), web crawling framework for building Scrapers with Python 3.11+ based on standard Python type hints. + +The key features are: + +* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). +* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * +* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * +* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. +* **Easy**: Designed to be easy to use and learn. Less time reading docs. +* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. +* **Robust**: Get production-ready code. With automatic interactive documentation. +* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. + +* estimation based on tests on an internal development team, building production applications. + + + + + + + + + + + + +## Requirements + +Python 3.11+ + +FastCrawler stands on the shoulders of giants: + +* Pydantic for the data parts. + +## Installation + +
+ +```console +$ pip install fastcrawler + +---> 100% +``` + +
+ + + +## Example + +### Create it + +* Create a file `main.py` with: + +```Python +from fastcrawler import Spider, Crawler, Parser + +class CategoryUrlParser(Parser): + class Config: + urls_resolver = parser.JsonField("category.url") + +class ProductParser(Parser): + name: str = parser.JsonField("product.name") + price: int = parser.JsonField("product.price") + +class DigikalaList(Spider): + url = "https://google.com" + parser = CategoryUrlParser + +Crawler( + DigikalaList >> DigikalaDetail +) +``` + + +### Run it + +Run the server with: + + +```console +$ python main.py +``` + + +## License + +This project is licensed under the terms of the MIT license. diff --git a/docs/en/docs/js/chat.js b/docs/en/docs/js/chat.js new file mode 100644 index 0000000..debdef4 --- /dev/null +++ b/docs/en/docs/js/chat.js @@ -0,0 +1,3 @@ +((window.gitter = {}).chat = {}).options = { + room: 'tiangolo/fastapi' +}; diff --git a/docs/en/docs/js/custom.js b/docs/en/docs/js/custom.js new file mode 100644 index 0000000..8e3be4c --- /dev/null +++ b/docs/en/docs/js/custom.js @@ -0,0 +1,180 @@ +const div = document.querySelector('.github-topic-projects') + +async function getDataBatch(page) { + const response = await fetch(`https://api.github.com/search/repositories?q=topic:fastapi&per_page=100&page=${page}`, { headers: { Accept: 'application/vnd.github.mercy-preview+json' } }) + const data = await response.json() + return data +} + +async function getData() { + let page = 1 + let data = [] + let dataBatch = await getDataBatch(page) + data = data.concat(dataBatch.items) + const totalCount = dataBatch.total_count + while (data.length < totalCount) { + page += 1 + dataBatch = await getDataBatch(page) + data = data.concat(dataBatch.items) + } + return data +} + +function setupTermynal() { + document.querySelectorAll(".use-termynal").forEach(node => { + node.style.display = "block"; + new Termynal(node, { + lineDelay: 500 + }); + }); + const progressLiteralStart = "---> 100%"; + const promptLiteralStart = "$ "; + const customPromptLiteralStart = "# "; + const termynalActivateClass = "termy"; + let termynals = []; + + function createTermynals() { + document + .querySelectorAll(`.${termynalActivateClass} .highlight`) + .forEach(node => { + const text = node.textContent; + const lines = text.split("\n"); + const useLines = []; + let buffer = []; + function saveBuffer() { + if (buffer.length) { + let isBlankSpace = true; + buffer.forEach(line => { + if (line) { + isBlankSpace = false; + } + }); + dataValue = {}; + if (isBlankSpace) { + dataValue["delay"] = 0; + } + if (buffer[buffer.length - 1] === "") { + // A last single
won't have effect + // so put an additional one + buffer.push(""); + } + const bufferValue = buffer.join("
"); + dataValue["value"] = bufferValue; + useLines.push(dataValue); + buffer = []; + } + } + for (let line of lines) { + if (line === progressLiteralStart) { + saveBuffer(); + useLines.push({ + type: "progress" + }); + } else if (line.startsWith(promptLiteralStart)) { + saveBuffer(); + const value = line.replace(promptLiteralStart, "").trimEnd(); + useLines.push({ + type: "input", + value: value + }); + } else if (line.startsWith("// ")) { + saveBuffer(); + const value = "💬 " + line.replace("// ", "").trimEnd(); + useLines.push({ + value: value, + class: "termynal-comment", + delay: 0 + }); + } else if (line.startsWith(customPromptLiteralStart)) { + saveBuffer(); + const promptStart = line.indexOf(promptLiteralStart); + if (promptStart === -1) { + console.error("Custom prompt found but no end delimiter", line) + } + const prompt = line.slice(0, promptStart).replace(customPromptLiteralStart, "") + let value = line.slice(promptStart + promptLiteralStart.length); + useLines.push({ + type: "input", + value: value, + prompt: prompt + }); + } else { + buffer.push(line); + } + } + saveBuffer(); + const div = document.createElement("div"); + node.replaceWith(div); + const termynal = new Termynal(div, { + lineData: useLines, + noInit: true, + lineDelay: 500 + }); + termynals.push(termynal); + }); + } + + function loadVisibleTermynals() { + termynals = termynals.filter(termynal => { + if (termynal.container.getBoundingClientRect().top - innerHeight <= 0) { + termynal.init(); + return false; + } + return true; + }); + } + window.addEventListener("scroll", loadVisibleTermynals); + createTermynals(); + loadVisibleTermynals(); +} + +function shuffle(array) { + var currentIndex = array.length, temporaryValue, randomIndex; + while (0 !== currentIndex) { + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; + temporaryValue = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = temporaryValue; + } + return array; +} + +async function showRandomAnnouncement(groupId, timeInterval) { + const announceFastAPI = document.getElementById(groupId); + if (announceFastAPI) { + let children = [].slice.call(announceFastAPI.children); + children = shuffle(children) + let index = 0 + const announceRandom = () => { + children.forEach((el, i) => {el.style.display = "none"}); + children[index].style.display = "block" + index = (index + 1) % children.length + } + announceRandom() + setInterval(announceRandom, timeInterval + ) + } +} + +async function main() { + if (div) { + data = await getData() + div.innerHTML = '' + const ul = document.querySelector('.github-topic-projects ul') + data.forEach(v => { + if (v.full_name === 'tiangolo/fastapi') { + return + } + const li = document.createElement('li') + li.innerHTML = `★ ${v.stargazers_count} - ${v.full_name} by @${v.owner.login}` + ul.append(li) + }) + } + + setupTermynal(); + showRandomAnnouncement('announce-left', 5000) + showRandomAnnouncement('announce-right', 10000) +} + +main() diff --git a/docs/en/docs/js/termynal.js b/docs/en/docs/js/termynal.js new file mode 100644 index 0000000..4ac3270 --- /dev/null +++ b/docs/en/docs/js/termynal.js @@ -0,0 +1,264 @@ +/** + * termynal.js + * A lightweight, modern and extensible animated terminal window, using + * async/await. + * + * @author Ines Montani + * @version 0.0.1 + * @license MIT + */ + +'use strict'; + +/** Generate a terminal widget. */ +class Termynal { + /** + * Construct the widget's settings. + * @param {(string|Node)=} container - Query selector or container element. + * @param {Object=} options - Custom settings. + * @param {string} options.prefix - Prefix to use for data attributes. + * @param {number} options.startDelay - Delay before animation, in ms. + * @param {number} options.typeDelay - Delay between each typed character, in ms. + * @param {number} options.lineDelay - Delay between each line, in ms. + * @param {number} options.progressLength - Number of characters displayed as progress bar. + * @param {string} options.progressChar – Character to use for progress bar, defaults to █. + * @param {number} options.progressPercent - Max percent of progress. + * @param {string} options.cursor – Character to use for cursor, defaults to ▋. + * @param {Object[]} lineData - Dynamically loaded line data objects. + * @param {boolean} options.noInit - Don't initialise the animation. + */ + constructor(container = '#termynal', options = {}) { + this.container = (typeof container === 'string') ? document.querySelector(container) : container; + this.pfx = `data-${options.prefix || 'ty'}`; + this.originalStartDelay = this.startDelay = options.startDelay + || parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600; + this.originalTypeDelay = this.typeDelay = options.typeDelay + || parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90; + this.originalLineDelay = this.lineDelay = options.lineDelay + || parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 1500; + this.progressLength = options.progressLength + || parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 40; + this.progressChar = options.progressChar + || this.container.getAttribute(`${this.pfx}-progressChar`) || '█'; + this.progressPercent = options.progressPercent + || parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 100; + this.cursor = options.cursor + || this.container.getAttribute(`${this.pfx}-cursor`) || '▋'; + this.lineData = this.lineDataToElements(options.lineData || []); + this.loadLines() + if (!options.noInit) this.init() + } + + loadLines() { + // Load all the lines and create the container so that the size is fixed + // Otherwise it would be changing and the user viewport would be constantly + // moving as she/he scrolls + const finish = this.generateFinish() + finish.style.visibility = 'hidden' + this.container.appendChild(finish) + // Appends dynamically loaded lines to existing line elements. + this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData); + for (let line of this.lines) { + line.style.visibility = 'hidden' + this.container.appendChild(line) + } + const restart = this.generateRestart() + restart.style.visibility = 'hidden' + this.container.appendChild(restart) + this.container.setAttribute('data-termynal', ''); + } + + /** + * Initialise the widget, get lines, clear container and start animation. + */ + init() { + /** + * Calculates width and height of Termynal container. + * If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS. + */ + const containerStyle = getComputedStyle(this.container); + this.container.style.width = containerStyle.width !== '0px' ? + containerStyle.width : undefined; + this.container.style.minHeight = containerStyle.height !== '0px' ? + containerStyle.height : undefined; + + this.container.setAttribute('data-termynal', ''); + this.container.innerHTML = ''; + for (let line of this.lines) { + line.style.visibility = 'visible' + } + this.start(); + } + + /** + * Start the animation and rener the lines depending on their data attributes. + */ + async start() { + this.addFinish() + await this._wait(this.startDelay); + + for (let line of this.lines) { + const type = line.getAttribute(this.pfx); + const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay; + + if (type == 'input') { + line.setAttribute(`${this.pfx}-cursor`, this.cursor); + await this.type(line); + await this._wait(delay); + } + + else if (type == 'progress') { + await this.progress(line); + await this._wait(delay); + } + + else { + this.container.appendChild(line); + await this._wait(delay); + } + + line.removeAttribute(`${this.pfx}-cursor`); + } + this.addRestart() + this.finishElement.style.visibility = 'hidden' + this.lineDelay = this.originalLineDelay + this.typeDelay = this.originalTypeDelay + this.startDelay = this.originalStartDelay + } + + generateRestart() { + const restart = document.createElement('a') + restart.onclick = (e) => { + e.preventDefault() + this.container.innerHTML = '' + this.init() + } + restart.href = '#' + restart.setAttribute('data-terminal-control', '') + restart.innerHTML = "restart ↻" + return restart + } + + generateFinish() { + const finish = document.createElement('a') + finish.onclick = (e) => { + e.preventDefault() + this.lineDelay = 0 + this.typeDelay = 0 + this.startDelay = 0 + } + finish.href = '#' + finish.setAttribute('data-terminal-control', '') + finish.innerHTML = "fast →" + this.finishElement = finish + return finish + } + + addRestart() { + const restart = this.generateRestart() + this.container.appendChild(restart) + } + + addFinish() { + const finish = this.generateFinish() + this.container.appendChild(finish) + } + + /** + * Animate a typed line. + * @param {Node} line - The line element to render. + */ + async type(line) { + const chars = [...line.textContent]; + line.textContent = ''; + this.container.appendChild(line); + + for (let char of chars) { + const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay; + await this._wait(delay); + line.textContent += char; + } + } + + /** + * Animate a progress bar. + * @param {Node} line - The line element to render. + */ + async progress(line) { + const progressLength = line.getAttribute(`${this.pfx}-progressLength`) + || this.progressLength; + const progressChar = line.getAttribute(`${this.pfx}-progressChar`) + || this.progressChar; + const chars = progressChar.repeat(progressLength); + const progressPercent = line.getAttribute(`${this.pfx}-progressPercent`) + || this.progressPercent; + line.textContent = ''; + this.container.appendChild(line); + + for (let i = 1; i < chars.length + 1; i++) { + await this._wait(this.typeDelay); + const percent = Math.round(i / chars.length * 100); + line.textContent = `${chars.slice(0, i)} ${percent}%`; + if (percent>progressPercent) { + break; + } + } + } + + /** + * Helper function for animation delays, called with `await`. + * @param {number} time - Timeout, in ms. + */ + _wait(time) { + return new Promise(resolve => setTimeout(resolve, time)); + } + + /** + * Converts line data objects into line elements. + * + * @param {Object[]} lineData - Dynamically loaded lines. + * @param {Object} line - Line data object. + * @returns {Element[]} - Array of line elements. + */ + lineDataToElements(lineData) { + return lineData.map(line => { + let div = document.createElement('div'); + div.innerHTML = `${line.value || ''}`; + + return div.firstElementChild; + }); + } + + /** + * Helper function for generating attributes string. + * + * @param {Object} line - Line data object. + * @returns {string} - String of attributes. + */ + _attributes(line) { + let attrs = ''; + for (let prop in line) { + // Custom add class + if (prop === 'class') { + attrs += ` class=${line[prop]} ` + continue + } + if (prop === 'type') { + attrs += `${this.pfx}="${line[prop]}" ` + } else if (prop !== 'value') { + attrs += `${this.pfx}-${prop}="${line[prop]}" ` + } + } + + return attrs; + } +} + +/** +* HTML API: If current script has container(s) specified, initialise Termynal. +*/ +if (document.currentScript.hasAttribute('data-termynal-container')) { + const containers = document.currentScript.getAttribute('data-termynal-container'); + containers.split('|') + .forEach(container => new Termynal(container)) +} diff --git a/docs/en/docs/tutorial/DIP-in-fastcrawler.md b/docs/en/docs/tutorial/DIP-in-fastcrawler.md new file mode 100644 index 0000000..09cb031 --- /dev/null +++ b/docs/en/docs/tutorial/DIP-in-fastcrawler.md @@ -0,0 +1 @@ +# DIP in fastcrawler \ No newline at end of file diff --git a/docs/en/docs/tutorial/engine.md b/docs/en/docs/tutorial/engine.md new file mode 100644 index 0000000..cd81127 --- /dev/null +++ b/docs/en/docs/tutorial/engine.md @@ -0,0 +1 @@ +# Engine \ No newline at end of file diff --git a/docs/en/docs/tutorial/index.md b/docs/en/docs/tutorial/index.md new file mode 100644 index 0000000..67dc0b0 --- /dev/null +++ b/docs/en/docs/tutorial/index.md @@ -0,0 +1 @@ +# Beginner Guide \ No newline at end of file diff --git a/docs/en/docs/tutorial/logging.md b/docs/en/docs/tutorial/logging.md new file mode 100644 index 0000000..85f805b --- /dev/null +++ b/docs/en/docs/tutorial/logging.md @@ -0,0 +1 @@ +# Logging \ No newline at end of file diff --git a/docs/en/docs/tutorial/selectors.md b/docs/en/docs/tutorial/selectors.md new file mode 100644 index 0000000..df6cc93 --- /dev/null +++ b/docs/en/docs/tutorial/selectors.md @@ -0,0 +1 @@ +# Selectors \ No newline at end of file diff --git a/docs/en/docs/tutorial/setting-up-project.md b/docs/en/docs/tutorial/setting-up-project.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/en/docs/tutorial/spider-and-process-with-saving-data.md b/docs/en/docs/tutorial/spider-and-process-with-saving-data.md new file mode 100644 index 0000000..51313a8 --- /dev/null +++ b/docs/en/docs/tutorial/spider-and-process-with-saving-data.md @@ -0,0 +1 @@ +# Spider and Process with Saving data \ No newline at end of file diff --git a/docs/en/docs/tutorial/testing.md b/docs/en/docs/tutorial/testing.md new file mode 100644 index 0000000..94cfd7d --- /dev/null +++ b/docs/en/docs/tutorial/testing.md @@ -0,0 +1 @@ +# Testing \ No newline at end of file diff --git a/docs/en/layouts/custom.yml b/docs/en/layouts/custom.yml new file mode 100644 index 0000000..aad81af --- /dev/null +++ b/docs/en/layouts/custom.yml @@ -0,0 +1,228 @@ +# Copyright (c) 2016-2023 Martin Donath + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# ----------------------------------------------------------------------------- +# Configuration +# ----------------------------------------------------------------------------- + +# The same default card with a a configurable logo + +# Definitions +definitions: + + # Background image + - &background_image >- + {{ layout.background_image or "" }} + + # Background color (default: indigo) + - &background_color >- + {%- if layout.background_color -%} + {{ layout.background_color }} + {%- else -%} + {%- set palette = config.theme.palette or {} -%} + {%- if not palette is mapping -%} + {%- set palette = palette | first -%} + {%- endif -%} + {%- set primary = palette.get("primary", "indigo") -%} + {%- set primary = primary.replace(" ", "-") -%} + {{ { + "red": "#ef5552", + "pink": "#e92063", + "purple": "#ab47bd", + "deep-purple": "#7e56c2", + "indigo": "#4051b5", + "blue": "#2094f3", + "light-blue": "#02a6f2", + "cyan": "#00bdd6", + "teal": "#009485", + "green": "#4cae4f", + "light-green": "#8bc34b", + "lime": "#cbdc38", + "yellow": "#ffec3d", + "amber": "#ffc105", + "orange": "#ffa724", + "deep-orange": "#ff6e42", + "brown": "#795649", + "grey": "#757575", + "blue-grey": "#546d78", + "black": "#000000", + "white": "#ffffff" + }[primary] or "#4051b5" }} + {%- endif -%} + + # Text color (default: white) + - &color >- + {%- if layout.color -%} + {{ layout.color }} + {%- else -%} + {%- set palette = config.theme.palette or {} -%} + {%- if not palette is mapping -%} + {%- set palette = palette | first -%} + {%- endif -%} + {%- set primary = palette.get("primary", "indigo") -%} + {%- set primary = primary.replace(" ", "-") -%} + {{ { + "red": "#ffffff", + "pink": "#ffffff", + "purple": "#ffffff", + "deep-purple": "#ffffff", + "indigo": "#ffffff", + "blue": "#ffffff", + "light-blue": "#ffffff", + "cyan": "#ffffff", + "teal": "#ffffff", + "green": "#ffffff", + "light-green": "#ffffff", + "lime": "#000000", + "yellow": "#000000", + "amber": "#000000", + "orange": "#000000", + "deep-orange": "#ffffff", + "brown": "#ffffff", + "grey": "#ffffff", + "blue-grey": "#ffffff", + "black": "#ffffff", + "white": "#000000" + }[primary] or "#ffffff" }} + {%- endif -%} + + # Font family (default: Roboto) + - &font_family >- + {%- if layout.font_family -%} + {{ layout.font_family }} + {%- elif config.theme.font != false -%} + {{ config.theme.font.get("text", "Roboto") }} + {%- else -%} + Roboto + {%- endif -%} + + # Site name + - &site_name >- + {{ config.site_name }} + + # Page title + - &page_title >- + {{ page.meta.get("title", page.title) }} + + # Page title with site name + - &page_title_with_site_name >- + {%- if not page.is_homepage -%} + {{ page.meta.get("title", page.title) }} - {{ config.site_name }} + {%- else -%} + {{ page.meta.get("title", page.title) }} + {%- endif -%} + + # Page description + - &page_description >- + {{ page.meta.get("description", config.site_description) or "" }} + + + # Start of custom modified logic + # Logo + - &logo >- + {%- if layout.logo -%} + {{ layout.logo }} + {%- elif config.theme.logo -%} + {{ config.docs_dir }}/{{ config.theme.logo }} + {%- endif -%} + # End of custom modified logic + + # Logo (icon) + - &logo_icon >- + {{ config.theme.icon.logo or "" }} + +# Meta tags +tags: + + # Open Graph + og:type: website + og:title: *page_title_with_site_name + og:description: *page_description + og:image: "{{ image.url }}" + og:image:type: "{{ image.type }}" + og:image:width: "{{ image.width }}" + og:image:height: "{{ image.height }}" + og:url: "{{ page.canonical_url }}" + + # Twitter + twitter:card: summary_large_image + twitter.title: *page_title_with_site_name + twitter:description: *page_description + twitter:image: "{{ image.url }}" + +# ----------------------------------------------------------------------------- +# Specification +# ----------------------------------------------------------------------------- + +# Card size and layers +size: { width: 1200, height: 630 } +layers: + + # Background + - background: + image: *background_image + color: *background_color + + # Logo + - size: { width: 144, height: 144 } + offset: { x: 992, y: 64 } + background: + image: *logo + icon: + value: *logo_icon + color: *color + + # Site name + - size: { width: 832, height: 42 } + offset: { x: 64, y: 64 } + typography: + content: *site_name + color: *color + font: + family: *font_family + style: Bold + + # Page title + - size: { width: 832, height: 310 } + offset: { x: 62, y: 160 } + typography: + content: *page_title + align: start + color: *color + line: + amount: 3 + height: 1.25 + font: + family: *font_family + style: Bold + + # Page description + - size: { width: 832, height: 64 } + offset: { x: 64, y: 512 } + typography: + content: *page_description + align: start + color: *color + line: + amount: 2 + height: 1.5 + font: + family: *font_family + style: Regular diff --git a/docs/en/mkdocs.insiders.yml b/docs/en/mkdocs.insiders.yml new file mode 100644 index 0000000..d204974 --- /dev/null +++ b/docs/en/mkdocs.insiders.yml @@ -0,0 +1,7 @@ +plugins: + social: + cards_layout_dir: ../en/layouts + cards_layout: custom + cards_layout_options: + logo: ../en/docs/img/icon-white.svg + typeset: diff --git a/docs/en/mkdocs.maybe-insiders.yml b/docs/en/mkdocs.maybe-insiders.yml new file mode 100644 index 0000000..37fd933 --- /dev/null +++ b/docs/en/mkdocs.maybe-insiders.yml @@ -0,0 +1,6 @@ +# Define this here and not in the main mkdocs.yml file because that one is auto +# updated and written, and the script would remove the env var +INHERIT: !ENV [INSIDERS_FILE, '../en/mkdocs.no-insiders.yml'] +markdown_extensions: + pymdownx.highlight: + linenums: !ENV [LINENUMS, false] diff --git a/docs/en/mkdocs.no-insiders.yml b/docs/en/mkdocs.no-insiders.yml new file mode 100644 index 0000000..e69de29 diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml new file mode 100644 index 0000000..004f86a --- /dev/null +++ b/docs/en/mkdocs.yml @@ -0,0 +1,221 @@ +INHERIT: ../en/mkdocs.maybe-insiders.yml +site_name: FastCrawler +site_description: FastCrawler framework, high performance, easy to learn, fast to code, ready for production +site_url: https://fastapi.tiangolo.com/ +theme: + name: material + custom_dir: ../en/overrides + palette: + - media: '(prefers-color-scheme: light)' + scheme: default + primary: orange + accent: amber + toggle: + icon: material/lightbulb + name: Switch to dark mode + - media: '(prefers-color-scheme: dark)' + scheme: slate + primary: orange + accent: amber + toggle: + icon: material/lightbulb-outline + name: Switch to light mode + features: + - search.suggest + - search.highlight + - content.tabs.link + - navigation.indexes + - content.tooltips + - navigation.path + - content.code.annotate + - content.code.copy + - content.code.select + icon: + repo: fontawesome/brands/github-alt + logo: img/icon-white.png + favicon: img/favicon.png + language: en +repo_name: fast-crawler +repo_url: https://github.com/fast-crawler/ +edit_uri: '' +plugins: + search: null + markdownextradata: + data: ../en/data +nav: +- FastCrawler: index.md +- Languages: + - en: / + - fa: /fa/ + +- features.md +- fastcrawler-people.md +# - python-types.md +- Tutorial - Beginner guide: + - tutorial/index.md + - tutorial/setting-up-project.md + - tutorial/spider-and-process-with-saving-data.md + - tutorial/selectors.md + - tutorial/engine.md + - tutorial/logging.md + - tutorial/DIP-in-fastcrawler.md + - tutorial/testing.md + +# - tutorial/first-steps.md +# - tutorial/path-params.md +# - tutorial/query-params.md +# - tutorial/body.md +# - tutorial/query-params-str-validations.md +# - tutorial/path-params-numeric-validations.md +# - tutorial/body-multiple-params.md +# - tutorial/body-fields.md +# - tutorial/body-nested-models.md +# - tutorial/schema-extra-example.md +# - tutorial/extra-data-types.md +# - tutorial/cookie-params.md +# - tutorial/header-params.md +# - tutorial/response-model.md +# - tutorial/extra-models.md +# - tutorial/response-status-code.md +# - tutorial/request-forms.md +# - tutorial/request-files.md +# - tutorial/request-forms-and-files.md +# - tutorial/handling-errors.md +# - tutorial/path-operation-configuration.md +# - tutorial/encoder.md +# - tutorial/body-updates.md +# - Dependencies: +# - tutorial/dependencies/index.md +# - tutorial/dependencies/classes-as-dependencies.md +# - tutorial/dependencies/sub-dependencies.md +# - tutorial/dependencies/dependencies-in-path-operation-decorators.md +# - tutorial/dependencies/global-dependencies.md +# - tutorial/dependencies/dependencies-with-yield.md +# - Security: +# - tutorial/security/index.md +# - tutorial/security/first-steps.md +# - tutorial/security/get-current-user.md +# - tutorial/security/simple-oauth2.md +# - tutorial/security/oauth2-jwt.md +# - tutorial/middleware.md +# - tutorial/cors.md +# - tutorial/sql-databases.md +# - tutorial/bigger-applications.md +# - tutorial/background-tasks.md +# - tutorial/metadata.md +# - tutorial/static-files.md +# - tutorial/testing.md +# - tutorial/debugging.md +- Advanced User Guide: + - advanced/index.md + - advanced/customizing-engine.md + - advanced/customizing-logging.md + - advanced/customizing-parser.md + - advanced/middleware.md +# - advanced/additional-status-codes.md +# - advanced/response-directly.md +# - advanced/custom-response.md +# - advanced/additional-responses.md +# - advanced/response-cookies.md +# - advanced/response-headers.md +# - advanced/response-change-status-code.md +# - advanced/advanced-dependencies.md +# - Advanced Security: +# - advanced/security/index.md +# - advanced/security/oauth2-scopes.md +# - advanced/security/http-basic-auth.md +# - advanced/using-request-directly.md +# - advanced/dataclasses.md +# - advanced/middleware.md +# - advanced/sql-databases-peewee.md +# - advanced/async-sql-databases.md +# - advanced/nosql-databases.md +# - advanced/sub-applications.md +# - advanced/behind-a-proxy.md +# - advanced/templates.md +# - advanced/graphql.md +# - advanced/websockets.md +# - advanced/events.md +# - advanced/custom-request-and-route.md +# - advanced/testing-websockets.md +# - advanced/testing-events.md +# - advanced/testing-dependencies.md +# - advanced/testing-database.md +# - advanced/async-tests.md +# - advanced/settings.md +# - advanced/conditional-openapi.md +# - advanced/extending-openapi.md +# - advanced/openapi-callbacks.md +# - advanced/openapi-webhooks.md +# - advanced/wsgi.md +# - advanced/generate-clients.md +# - async.md +# - Deployment: +# - deployment/index.md +# - deployment/versions.md +# - deployment/https.md +# - deployment/manually.md +# - deployment/concepts.md +# - deployment/deta.md +# - deployment/server-workers.md +# - deployment/docker.md +# - project-generation.md +# - alternatives.md +# - history-design-future.md +# - benchmarks.md +# - help-fastapi.md +# - newsletter.md +# - contributing.md +# - release-notes.md +markdown_extensions: + toc: + permalink: true + markdown.extensions.codehilite: + guess_lang: false + mdx_include: + base_path: docs + admonition: + codehilite: + extra: + pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format '' + pymdownx.tabbed: + alternate_style: true + attr_list: + md_in_html: +extra: + analytics: + provider: google + property: G-YNEVN69SC3 + social: + - icon: fontawesome/brands/github-alt + link: https://github.com/fast-crawler + - icon: fontawesome/brands/discord + link: https://discord.gg/VQjSZaeJmf + - icon: fontawesome/brands/twitter + link: https://twitter.com/fastapi + - icon: fontawesome/brands/linkedin + link: https://www.linkedin.com/in/tiangolo + - icon: fontawesome/brands/dev + link: https://dev.to/tiangolo + - icon: fontawesome/brands/medium + link: https://medium.com/@tiangolo + - icon: fontawesome/solid/globe + link: https://tiangolo.com + alternate: + - link: / + name: en - English + - link: /fa/ + name: fa - Persian + +extra_css: +- css/termynal.css +- css/custom.css +extra_javascript: +- js/termynal.js +- js/custom.js +hooks: +- ../../scripts/mkdocs_hooks.py diff --git a/docs/en/overrides/main.html b/docs/en/overrides/main.html new file mode 100644 index 0000000..5bdc06f --- /dev/null +++ b/docs/en/overrides/main.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} + + +{%- block scripts %} +{{ super() }} + + + + + + + +{%- endblock %} diff --git a/docs/fa/docs/advanced/sub-applications.md b/docs/fa/docs/advanced/sub-applications.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fa/docs/index.md b/docs/fa/docs/index.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fa/mkdocs.yml b/docs/fa/mkdocs.yml new file mode 100644 index 0000000..de18856 --- /dev/null +++ b/docs/fa/mkdocs.yml @@ -0,0 +1 @@ +INHERIT: ../en/mkdocs.yml diff --git a/docs/missing-translation.md b/docs/missing-translation.md new file mode 100644 index 0000000..32b6016 --- /dev/null +++ b/docs/missing-translation.md @@ -0,0 +1,4 @@ +!!! warning + The current page still doesn't have a translation for this language. + + But you can help translating it: [Contributing](https://fastapi.tiangolo.com/contributing/){.internal-link target=_blank}. diff --git a/scripts/build-docs.sh b/scripts/build-docs.sh new file mode 100644 index 0000000..ebf864a --- /dev/null +++ b/scripts/build-docs.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e +set -x + +# Check README.md is up to date +python ./scripts/docs.py verify-readme +python ./scripts/docs.py build-all diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100644 index 0000000..d5a4b79 --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1,8 @@ +#!/bin/sh -e + +if [ -d 'dist' ] ; then + rm -r dist +fi +if [ -d 'site' ] ; then + rm -r site +fi diff --git a/scripts/docs-live.sh b/scripts/docs-live.sh new file mode 100644 index 0000000..30637a5 --- /dev/null +++ b/scripts/docs-live.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -e + +mkdocs serve --dev-addr 0.0.0.0:8008 diff --git a/scripts/docs.py b/scripts/docs.py new file mode 100644 index 0000000..968dd9a --- /dev/null +++ b/scripts/docs.py @@ -0,0 +1,307 @@ +import json +import logging +import os +import re +import shutil +import subprocess +from functools import lru_cache +from http.server import HTTPServer, SimpleHTTPRequestHandler +from importlib import metadata +from multiprocessing import Pool +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +import mkdocs.commands.build +import mkdocs.commands.serve +import mkdocs.config +import mkdocs.utils +import typer +import yaml +from jinja2 import Template + +logging.basicConfig(level=logging.INFO) + +app = typer.Typer() + +mkdocs_name = "mkdocs.yml" + +missing_translation_snippet = """ +{!../../../docs/missing-translation.md!} +""" + +docs_path = Path("docs") +en_docs_path = Path("docs/en") +en_config_path: Path = en_docs_path / mkdocs_name +site_path = Path("site").absolute() +build_site_path = Path("site_build").absolute() + + +@lru_cache() +def is_mkdocs_insiders() -> bool: + version = metadata.version("mkdocs-material") + return "insiders" in version + + +def get_en_config() -> Dict[str, Any]: + return mkdocs.utils.yaml_load(en_config_path.read_text(encoding="utf-8")) + + +def get_lang_paths() -> List[Path]: + return sorted(docs_path.iterdir()) + + +def lang_callback(lang: Optional[str]) -> Union[str, None]: + if lang is None: + return None + if not lang.isalpha() or len(lang) != 2: + typer.echo("Use a 2 letter language code, like: es") + raise typer.Abort() + lang = lang.lower() + return lang + + +def complete_existing_lang(incomplete: str): + lang_path: Path + for lang_path in get_lang_paths(): + if lang_path.is_dir() and lang_path.name.startswith(incomplete): + yield lang_path.name + + +@app.callback() +def callback() -> None: + if is_mkdocs_insiders(): + os.environ["INSIDERS_FILE"] = "../en/mkdocs.insiders.yml" + # For MacOS with insiders and Cairo + os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = "/opt/homebrew/lib" + + +@app.command() +def new_lang(lang: str = typer.Argument(..., callback=lang_callback)): + """ + Generate a new docs translation directory for the language LANG. + + LANG should be a 2-letter language code, like: en, es, de, pt, etc. + """ + new_path: Path = Path("docs") / lang + if new_path.exists(): + typer.echo(f"The language was already created: {lang}") + raise typer.Abort() + new_path.mkdir() + new_config_path: Path = Path(new_path) / mkdocs_name + new_config_path.write_text("INHERIT: ../en/mkdocs.yml\n", encoding="utf-8") + new_config_docs_path: Path = new_path / "docs" + new_config_docs_path.mkdir() + en_index_path: Path = en_docs_path / "docs" / "index.md" + new_index_path: Path = new_config_docs_path / "index.md" + en_index_content = en_index_path.read_text(encoding="utf-8") + new_index_content = f"{missing_translation_snippet}\n\n{en_index_content}" + new_index_path.write_text(new_index_content, encoding="utf-8") + typer.secho(f"Successfully initialized: {new_path}", color=typer.colors.GREEN) + update_languages() + + +@app.command() +def build_lang( + lang: str = typer.Argument( + ..., callback=lang_callback, autocompletion=complete_existing_lang + ) +) -> None: + """ + Build the docs for a language. + """ + insiders_env_file = os.environ.get("INSIDERS_FILE") + print(f"Insiders file {insiders_env_file}") + if is_mkdocs_insiders(): + print("Using insiders") + lang_path: Path = Path("docs") / lang + if not lang_path.is_dir(): + typer.echo(f"The language translation doesn't seem to exist yet: {lang}") + raise typer.Abort() + typer.echo(f"Building docs for: {lang}") + build_site_dist_path = build_site_path / lang + if lang == "en": + dist_path = site_path + # Don't remove en dist_path as it might already contain other languages. + # When running build_all(), that function already removes site_path. + # All this is only relevant locally, on GitHub Actions all this is done through + # artifacts and multiple workflows, so it doesn't matter if directories are + # removed or not. + else: + dist_path = site_path / lang + shutil.rmtree(dist_path, ignore_errors=True) + current_dir = os.getcwd() + os.chdir(lang_path) + shutil.rmtree(build_site_dist_path, ignore_errors=True) + subprocess.run(["mkdocs", "build", "--site-dir", build_site_dist_path], check=True) + shutil.copytree(build_site_dist_path, dist_path, dirs_exist_ok=True) + os.chdir(current_dir) + typer.secho(f"Successfully built docs for: {lang}", color=typer.colors.GREEN) + + +index_sponsors_template = """ +{% if sponsors %} +{% for sponsor in sponsors.gold -%} + +{% endfor -%} +{%- for sponsor in sponsors.silver -%} + +{% endfor %} +{% endif %} +""" + + +def generate_readme_content() -> str: + en_index = en_docs_path / "docs" / "index.md" + content = en_index.read_text("utf-8") + match_start = re.search(r"", content) + match_end = re.search(r"", content) + sponsors_data_path = en_docs_path / "data" / "sponsors.yml" + sponsors = mkdocs.utils.yaml_load(sponsors_data_path.read_text(encoding="utf-8")) + if not (match_start and match_end): + raise RuntimeError("Couldn't auto-generate sponsors section") + pre_end = match_start.end() + post_start = match_end.start() + template = Template(index_sponsors_template) + message = template.render(sponsors=sponsors) + pre_content = content[:pre_end] + post_content = content[post_start:] + new_content = pre_content + message + post_content + return new_content + + +@app.command() +def generate_readme() -> None: + """ + Generate README.md content from main index.md + """ + typer.echo("Generating README") + readme_path = Path("README.md") + new_content = generate_readme_content() + readme_path.write_text(new_content, encoding="utf-8") + + +@app.command() +def verify_readme() -> None: + """ + Verify README.md content from main index.md + """ + typer.echo("Verifying README") + readme_path = Path("README.md") + generated_content = generate_readme_content() + readme_content = readme_path.read_text("utf-8") + if generated_content != readme_content: + typer.secho( + "README.md outdated from the latest index.md", color=typer.colors.RED + ) + raise typer.Abort() + typer.echo("Valid README ✅") + + +@app.command() +def build_all() -> None: + """ + Build mkdocs site for en, and then build each language inside, end result is located + at directory ./site/ with each language inside. + """ + update_languages() + shutil.rmtree(site_path, ignore_errors=True) + langs = [lang.name for lang in get_lang_paths() if lang.is_dir()] + cpu_count = os.cpu_count() or 1 + process_pool_size = cpu_count * 4 + typer.echo(f"Using process pool size: {process_pool_size}") + with Pool(process_pool_size) as p: + p.map(build_lang, langs) + + +@app.command() +def update_languages() -> None: + """ + Update the mkdocs.yml file Languages section including all the available languages. + """ + update_config() + + +@app.command() +def serve() -> None: + """ + A quick server to preview a built site with translations. + + For development, prefer the command live (or just mkdocs serve). + + This is here only to preview a site with translations already built. + + Make sure you run the build-all command first. + """ + typer.echo("Warning: this is a very simple server.") + typer.echo("For development, use the command live instead.") + typer.echo("This is here only to preview a site with translations already built.") + typer.echo("Make sure you run the build-all command first.") + os.chdir("site") + server_address = ("", 8008) + server = HTTPServer(server_address, SimpleHTTPRequestHandler) + typer.echo("Serving at: http://127.0.0.1:8008") + server.serve_forever() + + +@app.command() +def live( + lang: str = typer.Argument( + None, callback=lang_callback, autocompletion=complete_existing_lang + ) +) -> None: + """ + Serve with livereload a docs site for a specific language. + + This only shows the actual translated files, not the placeholders created with + build-all. + + Takes an optional LANG argument with the name of the language to serve, by default + en. + """ + # Enable line numbers during local development to make it easier to highlight + os.environ["LINENUMS"] = "true" + if lang is None: + lang = "en" + lang_path: Path = docs_path / lang + os.chdir(lang_path) + mkdocs.commands.serve.serve(dev_addr="127.0.0.1:8008") + + +def update_config() -> None: + config = get_en_config() + languages = [{"en": "/"}] + alternate: List[Dict[str, str]] = config["extra"].get("alternate", []) + alternate_dict = {alt["link"]: alt["name"] for alt in alternate} + new_alternate: List[Dict[str, str]] = [] + for lang_path in get_lang_paths(): + if lang_path.name == "en" or not lang_path.is_dir(): + continue + name = lang_path.name + languages.append({name: f"/{name}/"}) + for lang_dict in languages: + name = list(lang_dict.keys())[0] + url = lang_dict[name] + if url not in alternate_dict: + new_alternate.append({"link": url, "name": name}) + else: + use_name = alternate_dict[url] + new_alternate.append({"link": url, "name": use_name}) + config["nav"][1] = {"Languages": languages} + config["extra"]["alternate"] = new_alternate + en_config_path.write_text( + yaml.dump(config, sort_keys=False, width=200, allow_unicode=True), + encoding="utf-8", + ) + + +@app.command() +def langs_json(): + langs = [] + for lang_path in get_lang_paths(): + if lang_path.is_dir(): + langs.append(lang_path.name) + print(json.dumps(langs)) + + +if __name__ == "__main__": + app() diff --git a/scripts/format.sh b/scripts/format.sh new file mode 100644 index 0000000..3fb3eb4 --- /dev/null +++ b/scripts/format.sh @@ -0,0 +1,5 @@ +#!/bin/sh -e +set -x + +ruff fastapi tests docs_src scripts --fix +black fastapi tests docs_src scripts diff --git a/scripts/gitter_releases_bot.py b/scripts/gitter_releases_bot.py new file mode 100644 index 0000000..a033d0d --- /dev/null +++ b/scripts/gitter_releases_bot.py @@ -0,0 +1,67 @@ +import inspect +import os + +import requests + +room_id = "5c9c9540d73408ce4fbc1403" # FastAPI +# room_id = "5cc46398d73408ce4fbed233" # Gitter development + +gitter_token = os.getenv("GITTER_TOKEN") +assert gitter_token +github_token = os.getenv("GITHUB_TOKEN") +assert github_token +tag_name = os.getenv("TAG") +assert tag_name + + +def get_github_graphql(tag_name: str): + github_graphql = """ + { + repository(owner: "tiangolo", name: "fastapi") { + release (tagName: "{{tag_name}}" ) { + description + } + } + } + """ + github_graphql = github_graphql.replace("{{tag_name}}", tag_name) + return github_graphql + + +def get_github_release_text(tag_name: str): + url = "https://api.github.com/graphql" + headers = {"Authorization": f"Bearer {github_token}"} + github_graphql = get_github_graphql(tag_name=tag_name) + response = requests.post(url, json={"query": github_graphql}, headers=headers) + assert response.status_code == 200 + data = response.json() + return data["data"]["repository"]["release"]["description"] + + +def get_gitter_message(release_text: str): + text = f""" + New release! :tada: :rocket: + (by FastAPI bot) + + ## {tag_name} + """ + text = inspect.cleandoc(text) + "\n\n" + release_text + return text + + +def send_gitter_message(text: str): + headers = {"Authorization": f"Bearer {gitter_token}"} + url = f"https://api.gitter.im/v1/rooms/{room_id}/chatMessages" + data = {"text": text} + response = requests.post(url, headers=headers, json=data) + assert response.status_code == 200 + + +def main(): + release_text = get_github_release_text(tag_name=tag_name) + text = get_gitter_message(release_text=release_text) + send_gitter_message(text=text) + + +if __name__ == "__main__": + main() diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100644 index 0000000..4db5caa --- /dev/null +++ b/scripts/lint.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e +set -x + +mypy fastapi +ruff fastapi tests docs_src scripts +black fastapi tests --check diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py new file mode 100644 index 0000000..008751f --- /dev/null +++ b/scripts/mkdocs_hooks.py @@ -0,0 +1,132 @@ +from functools import lru_cache +from pathlib import Path +from typing import Any, List, Union + +import material +from mkdocs.config.defaults import MkDocsConfig +from mkdocs.structure.files import File, Files +from mkdocs.structure.nav import Link, Navigation, Section +from mkdocs.structure.pages import Page + + +@lru_cache() +def get_missing_translation_content(docs_dir: str) -> str: + docs_dir_path = Path(docs_dir) + missing_translation_path = docs_dir_path.parent.parent / "missing-translation.md" + return missing_translation_path.read_text(encoding="utf-8") + + +@lru_cache() +def get_mkdocs_material_langs() -> List[str]: + material_path = Path(material.__file__).parent + material_langs_path = material_path / "partials" / "languages" + langs = [file.stem for file in material_langs_path.glob("*.html")] + return langs + + +class EnFile(File): + pass + + +def on_config(config: MkDocsConfig, **kwargs: Any) -> MkDocsConfig: + available_langs = get_mkdocs_material_langs() + dir_path = Path(config.docs_dir) + lang = dir_path.parent.name + if lang in available_langs: + config.theme["language"] = lang + if not (config.site_url or "").endswith(f"{lang}/") and not lang == "en": + config.site_url = f"{config.site_url}{lang}/" + return config + + +def resolve_file(*, item: str, files: Files, config: MkDocsConfig) -> None: + item_path = Path(config.docs_dir) / item + if not item_path.is_file(): + en_src_dir = (Path(config.docs_dir) / "../../en/docs").resolve() + potential_path = en_src_dir / item + if potential_path.is_file(): + files.append( + EnFile( + path=item, + src_dir=str(en_src_dir), + dest_dir=config.site_dir, + use_directory_urls=config.use_directory_urls, + ) + ) + + +def resolve_files(*, items: List[Any], files: Files, config: MkDocsConfig) -> None: + for item in items: + if isinstance(item, str): + resolve_file(item=item, files=files, config=config) + elif isinstance(item, dict): + assert len(item) == 1 + values = list(item.values()) + if not values: + continue + if isinstance(values[0], str): + resolve_file(item=values[0], files=files, config=config) + elif isinstance(values[0], list): + resolve_files(items=values[0], files=files, config=config) + else: + raise ValueError(f"Unexpected value: {values}") + + +def on_files(files: Files, *, config: MkDocsConfig) -> Files: + resolve_files(items=config.nav or [], files=files, config=config) + if "logo" in config.theme: + resolve_file(item=config.theme["logo"], files=files, config=config) + if "favicon" in config.theme: + resolve_file(item=config.theme["favicon"], files=files, config=config) + resolve_files(items=config.extra_css, files=files, config=config) + resolve_files(items=config.extra_javascript, files=files, config=config) + return files + + +def generate_renamed_section_items( + items: List[Union[Page, Section, Link]], *, config: MkDocsConfig +) -> List[Union[Page, Section, Link]]: + new_items: List[Union[Page, Section, Link]] = [] + for item in items: + if isinstance(item, Section): + new_title = item.title + new_children = generate_renamed_section_items(item.children, config=config) + first_child = new_children[0] + if isinstance(first_child, Page): + if first_child.file.src_path.endswith("index.md"): + # Read the source so that the title is parsed and available + first_child.read_source(config=config) + new_title = first_child.title or new_title + # Creating a new section makes it render it collapsed by default + # no idea why, so, let's just modify the existing one + # new_section = Section(title=new_title, children=new_children) + item.title = new_title + item.children = new_children + new_items.append(item) + else: + new_items.append(item) + return new_items + + +def on_nav( + nav: Navigation, *, config: MkDocsConfig, files: Files, **kwargs: Any +) -> Navigation: + new_items = generate_renamed_section_items(nav.items, config=config) + return Navigation(items=new_items, pages=nav.pages) + + +def on_pre_page(page: Page, *, config: MkDocsConfig, files: Files) -> Page: + return page + + +def on_page_markdown( + markdown: str, *, page: Page, config: MkDocsConfig, files: Files +) -> str: + if isinstance(page.file, EnFile): + missing_translation_content = get_missing_translation_content(config.docs_dir) + header = "" + body = markdown + if markdown.startswith("#"): + header, _, body = markdown.partition("\n\n") + return f"{header}\n\n{missing_translation_content}\n\n{body}" + return markdown diff --git a/scripts/netlify-docs.sh b/scripts/netlify-docs.sh new file mode 100644 index 0000000..8f9065e --- /dev/null +++ b/scripts/netlify-docs.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -x +set -e +# Install pip +cd /tmp +curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py +python3.6 get-pip.py --user +cd - +# Install Flit to be able to install all +python3.6 -m pip install --user flit +# Install with Flit +python3.6 -m flit install --user --extras doc +# Finally, run mkdocs +python3.6 -m mkdocs build diff --git a/scripts/notify.sh b/scripts/notify.sh new file mode 100644 index 0000000..8ce5500 --- /dev/null +++ b/scripts/notify.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -e + +python scripts/gitter_releases_bot.py diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100644 index 0000000..122728a --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -e + +flit publish diff --git a/scripts/test-cov-html.sh b/scripts/test-cov-html.sh new file mode 100644 index 0000000..d1bdfce --- /dev/null +++ b/scripts/test-cov-html.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e +set -x + +bash scripts/test.sh ${@} +coverage combine +coverage report --show-missing +coverage html diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100644 index 0000000..7d17add --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e +set -x + +export PYTHONPATH=./docs_src +coverage run -m pytest tests ${@}