Compare commits
714 Commits
release-0.
...
master
Author | SHA1 | Date |
---|---|---|
David Hewitt | a5a3f3f7f2 | |
Larry Z | 6be80647cb | |
David Hewitt | 90c4799951 | |
David Hewitt | 3c65132da5 | |
David Hewitt | 32b6a1aef1 | |
David Hewitt | c7c1dff710 | |
Giovanni Barillari | 1279232701 | |
David Hewitt | 97d60e869b | |
David Hewitt | 8652ac8e1c | |
Alex Gaynor | e73112f3f6 | |
David Hewitt | 186c7d3315 | |
Sede Soukossi | 1861d6d379 | |
Icxolu | 3c155d9fef | |
David Hewitt | d5c886f4c0 | |
David Hewitt | 59c4fa3f24 | |
Alex Gaynor | 9afc38ae41 | |
Owen Leung | 5860c4f7e9 | |
David Hewitt | 0af0227834 | |
Nathan Goldbaum | ee9123a2d2 | |
Alex Gaynor | ccd04475a3 | |
Alex Gaynor | f3603a0a48 | |
Kyle Barron | 872bd7e6f7 | |
Ben Beasley | 8f7450e33d | |
jatoben | 7c2f5e80de | |
David Hewitt | 2e2d4404a6 | |
David Hewitt | 91d8683814 | |
Bruno Kolenbrander | 6a0221ba2c | |
Aneesh Agrawal | c67625d683 | |
David Hewitt | a2f9399906 | |
Code Apprentice | 908ef6ad84 | |
Weijie Guo | c4d18e5ee3 | |
David Hewitt | 0b967d427a | |
David Hewitt | 41fb9572b6 | |
Alex Gaynor | 9ff3d237c1 | |
Icxolu | 56341cbc81 | |
David Hewitt | ca82681615 | |
Bruno Kolenbrander | a983b2fe7b | |
David Hewitt | 30add032b5 | |
Bruno Kolenbrander | b25b3b3a7b | |
WÁNG Xuěruì | e6b2216b04 | |
David Hewitt | 0e142f05dd | |
Alex Gaynor | ddff8bea25 | |
Alex Gaynor | baae9291cc | |
Code Apprentice | 79591f2161 | |
David Hewitt | 9648d595a5 | |
David Hewitt | 0b2f19b3c9 | |
David Hewitt | 591cdb0bf8 | |
Icxolu | 5749a08b63 | |
David Hewitt | f66124a79b | |
JRRudy1 | d2dca2169c | |
David Hewitt | 9c67057745 | |
Michael Gilbert | b8fb367582 | |
David Hewitt | fbb6f20c2b | |
Thomas Tanon | 74619143b6 | |
Adam Reichold | c644c0b0b8 | |
Thomas Tanon | 11d67b3acc | |
Cheuk Ting Ho | 37a5f6a94e | |
A. Cody Schuffelen | 93ef056711 | |
Icxolu | 7e5884c40b | |
Icxolu | 36cdeb29c1 | |
liammcinroy | b4b780b475 | |
David Hewitt | 88b6f23e3b | |
JRRudy1 | 5d47c4ae4c | |
Icxolu | a7a5c10b8a | |
Jasper van Brakel | 25c1db4767 | |
Icxolu | d1a7cf400a | |
David Brochart | cb347370ff | |
David Hewitt | 4fe5e8c689 | |
JRRudy1 | 934c663612 | |
David Hewitt | 388d1760b5 | |
Cheuk Ting Ho | d21045cbc1 | |
David Hewitt | 2c654b2906 | |
Cheuk Ting Ho | 81ba9a8cd5 | |
David Hewitt | 3e4b3c5c52 | |
Adam Reichold | 674708cb4c | |
David Hewitt | ac273a1612 | |
Alex Gaynor | fe79f54817 | |
Bruno Kolenbrander | fff053bde7 | |
Alex Gaynor | 1c64a03ea0 | |
newcomertv | 88f2f6f4d5 | |
Alex Gaynor | 8de1787580 | |
David Hewitt | 7790dab480 | |
Icxolu | 10152a7078 | |
Alex Gaynor | 57500d9b09 | |
Adam Reichold | c5f9001985 | |
Icxolu | 033caa8fd1 | |
Icxolu | 444be3bafa | |
Icxolu | 1e8e09dce3 | |
Icxolu | aef0a05719 | |
Alex Gaynor | 104328ce14 | |
David Hewitt | f3c7b90def | |
Icxolu | 21c02484d0 | |
Icxolu | 7beb64a8ca | |
David Matos | 2d19b7e2a7 | |
Icxolu | 635cb8075c | |
Icxolu | d803f7f8df | |
Icxolu | 72be1cddba | |
Icxolu | 7263fa92ef | |
deedy5 | ef13bc66e9 | |
Icxolu | e835ff0ec3 | |
Heran Lin | c10c7429d8 | |
Alex Gaynor | d1a0c7278f | |
Icxolu | c08f6c77a6 | |
Alex Gaynor | f3ab62cb7e | |
Alex Gaynor | 93cfb51ebb | |
Icxolu | 7cbb85476c | |
Icxolu | cd3f3ed67c | |
Icxolu | 9a808c35c6 | |
Icxolu | a454f6e9cc | |
Alex Gaynor | 5534a7bee8 | |
Icxolu | dc9a41521a | |
Alex Gaynor | 261d27d197 | |
Icxolu | 2f3a33fda1 | |
Icxolu | 82c00a2fe4 | |
Icxolu | 4616838ee1 | |
Icxolu | 22c5cff039 | |
Icxolu | d5452bcd8d | |
Alex Gaynor | 9e1960ea34 | |
Icxolu | 6fb972b232 | |
Icxolu | 059c8b3862 | |
Alex Gaynor | 8ff5e5b0ab | |
David Matos | c66ed292ec | |
Icxolu | 8734b76f60 | |
Alexander Clausen | 3cb286e0d2 | |
Icxolu | 6c2e6f8bcc | |
Icxolu | c951bf86de | |
Icxolu | 013a4476ea | |
Bruno Kolenbrander | f5fee94afc | |
Icxolu | 5d2f5b5702 | |
Georg Brandl | c2ac9a98e2 | |
Icxolu | da2d1aa8b4 | |
Icxolu | b0ad1e10aa | |
David Hewitt | 947b372dcc | |
David Hewitt | cd28e1408e | |
Icxolu | d42c00d21d | |
dependabot[bot] | b11174e96d | |
David Hewitt | 2c205d4586 | |
dependabot[bot] | e64eb72903 | |
Jacob Zhong | 03c50a1839 | |
Bruno Kolenbrander | 9761abf3a5 | |
Icxolu | 03f59eaf45 | |
David Matos | 2ad2a3f208 | |
David Hewitt | a5201c04af | |
David Matos | 8ed5c17b93 | |
Icxolu | cc7e16f4d6 | |
Adam Reichold | 721100a9e9 | |
Adam Reichold | 4e5167db42 | |
messense | 30348b4d3f | |
Icxolu | ee5216f406 | |
Icxolu | c8b59d7117 | |
Liam | 9a6b1962d3 | |
dependabot[bot] | 631c25f2f9 | |
Jeong, Heon | 47f9ef4174 | |
Joseph Perez | 2f0869a6d6 | |
Icxolu | 7a00b4d357 | |
David Hewitt | a4aea230ed | |
David Hewitt | c1f11fb4bd | |
David Hewitt | 8cabd2619c | |
Icxolu | 8f87b8636d | |
Icxolu | 63ba371db0 | |
David Hewitt | 336b1c982b | |
Weijie Guo | 3af9a1f4e0 | |
David Hewitt | cff4aa3cc8 | |
David Hewitt | 9d932c1061 | |
David Hewitt | 22e8dd10e1 | |
Weijie Guo | 74d9d23ba0 | |
Rikus Honey | 4d033c4497 | |
geo7 | 0e093a5911 | |
Alex Gaynor | de03ca270a | |
David Hewitt | c6f25e42a2 | |
Alex Gaynor | ae3ac7d872 | |
Alex Gaynor | 60e3f44dcf | |
David Hewitt | cf74624de9 | |
David Hewitt | b053e83c08 | |
Icxolu | dd1710256d | |
David Hewitt | bcfc848703 | |
tison | 7c03d65cda | |
Icxolu | 35faeff6f1 | |
David Hewitt | 1be2fad9bf | |
Tim Felgentreff | 9a38e709bb | |
Icxolu | 54ffaecd65 | |
David Hewitt | 7d319a906e | |
David Hewitt | aeb74c7093 | |
Lily Foote | 009cd32a44 | |
David Hewitt | 9808f7111c | |
David Hewitt | 20e477a7cd | |
David Hewitt | 990886efda | |
David Hewitt | d0f5b6af46 | |
David Hewitt | 351c6a0a49 | |
David Hewitt | 870a4bb20d | |
David Hewitt | cedac43dbb | |
Icxolu | 2736cf670c | |
David Hewitt | caf80eca66 | |
David Hewitt | 02e188e4b4 | |
David Hewitt | cbed7c11b6 | |
David Hewitt | e29fac9c46 | |
Icxolu | b06e95727b | |
David Hewitt | ebeea943fe | |
Icxolu | da24f0cf93 | |
David Hewitt | dcba984b51 | |
Icxolu | 5c86dc35c1 | |
Bruno Kolenbrander | 989d2c53ab | |
Thomas Tanon | 7cde95bba4 | |
Icxolu | ee89b2e8e2 | |
Georg Brandl | a7fa1bdf22 | |
acceptacross | 93323bc922 | |
David Hewitt | 67b1b35013 | |
David Hewitt | db0a98c040 | |
David Hewitt | 9145fcfe19 | |
David Hewitt | 75af678f43 | |
Icxolu | 908e661237 | |
David Hewitt | 14d1d2a9ee | |
David Hewitt | 770d9b7f01 | |
Icxolu | 31c4820010 | |
Thomas Tanon | fbd531195a | |
Thomas Tanon | 0f7ddb19a5 | |
Thomas Tanon | d35f41e0bf | |
David Hewitt | 57bbc32e7c | |
Thomas Tanon | fe84fed966 | |
David Hewitt | b08ee4b7e1 | |
Lily Foote | 811a3e5d00 | |
Bruno Kolenbrander | 4114dcb1a0 | |
Icxolu | 70a7aa808d | |
Icxolu | 00eb014623 | |
David Hewitt | 2e56f659ed | |
Icxolu | 81be11e67a | |
Lily Foote | d94827720e | |
Icxolu | 1c5265e1c2 | |
Icxolu | 1d224610c3 | |
Lily Foote | 56683ed553 | |
Matthew Neeley | 68ec6de0c9 | |
David Hewitt | a582fa0163 | |
Icxolu | 55833365b5 | |
David Hewitt | 8a12970c96 | |
Matthew Neeley | a15e4b1a11 | |
Icxolu | 6f03a5464f | |
Lily Foote | a3ad28b70c | |
David Hewitt | 93704047a5 | |
Lily Foote | 5c41ea0ade | |
David Hewitt | cd1c0dbf39 | |
David Hewitt | 8e2219b0d9 | |
David Hewitt | 404161c969 | |
Icxolu | 7c10ff4327 | |
David Hewitt | 8f1b99e1e9 | |
Thomas Tanon | e0e3981e17 | |
Icxolu | c06bb8f1f1 | |
David Matos | 0f29feca8f | |
Lily Foote | e145ae851a | |
David Hewitt | fbf2e91914 | |
David Hewitt | 11d143d0c9 | |
David Hewitt | 9186da3892 | |
Alexander Hill | 0f92b670b2 | |
Icxolu | 6a815875a0 | |
David Hewitt | 5ca810236d | |
Lily Foote | 22a23ffb31 | |
Icxolu | 8bd82da939 | |
Icxolu | 4f8ee96881 | |
David Hewitt | 9e74c858c2 | |
David Hewitt | c4f66657c5 | |
Lily Foote | 5ddcd46980 | |
Juniper Tyree | 885883bf68 | |
Icxolu | 61bc02d927 | |
Lily Foote | a93900686e | |
David Hewitt | 8ac7834f98 | |
David Hewitt | 7a03a6fe6d | |
Lily Foote | 76dabd4e60 | |
David Hewitt | 9a36b50789 | |
David Hewitt | 96b8c9facf | |
David Hewitt | ececa86a7d | |
David Hewitt | 4efc4b82a3 | |
Icxolu | 0bb9cab6d3 | |
David Hewitt | a85ed34c45 | |
Lily Foote | b4dc854585 | |
Icxolu | 4ce9c35983 | |
David Hewitt | f04ad56df4 | |
Icxolu | 1d295a12a0 | |
Lily Foote | 0dd568d397 | |
Lily Foote | 5f42c02e4f | |
Icxolu | c33d330b18 | |
Icxolu | 1d8d81db2d | |
David Hewitt | eb90b81d44 | |
David Hewitt | 65cf5808d9 | |
Lily Foote | 940804fe0d | |
alm | c24478e8bc | |
David Hewitt | ec6d587218 | |
Icxolu | 05aedc9032 | |
David Hewitt | dc8b948201 | |
Icxolu | f3ddd023c9 | |
Icxolu | 9902633116 | |
Icxolu | 0c12d9137f | |
Lily Foote | a1e77c5a66 | |
David Hewitt | f5eafe23f2 | |
David Hewitt | e308c8d3ac | |
Icxolu | fbfeb2ff03 | |
David Hewitt | 94b7d7e434 | |
David Hewitt | 5b9b76fe58 | |
Lily Foote | 1279467d27 | |
Icxolu | c359f5ca1d | |
Kushal Das | c983dc9773 | |
Lily Foote | baf5c8ec0a | |
Jose | c56cd3dd65 | |
Jose | f721c8c2b7 | |
David Hewitt | 55488d3880 | |
David Hewitt | 5b1104131f | |
David Hewitt | 07ea89d460 | |
David Hewitt | 6ee9c4ec5a | |
Icxolu | e45fbe493c | |
Juha-Matti Santala | 559761b2f1 | |
David Hewitt | fa53d81e5f | |
David Hewitt | 45f2b0aba5 | |
Icxolu | 4d423b0c67 | |
David Hewitt | b7fb9e672e | |
David Hewitt | 2fedea24b3 | |
Icxolu | 33dc33ecec | |
David Hewitt | 367eeaeeab | |
David Hewitt | 9bb001108b | |
David Hewitt | bcb7b88c23 | |
David Hewitt | 3541506a16 | |
David Hewitt | 030a618e0d | |
Blaž Šnuderl | aa3c938b5e | |
David Hewitt | 059e485a95 | |
Jonatan G. Frausing | b74d733244 | |
David Hewitt | f7bfa9ab11 | |
David Hewitt | dd4df29bad | |
David Hewitt | 911723389a | |
David Hewitt | 020ed39327 | |
David Hewitt | c85d72bb0e | |
dependabot[bot] | ec0be57c68 | |
David Hewitt | 86f294f6e6 | |
David Hewitt | 662eecfb44 | |
David Hewitt | ecb4ecbe22 | |
David Hewitt | 02f1df69b5 | |
Adam Reichold | c995426c81 | |
Bartosz Telenczuk | 64a6a02bf0 | |
David Hewitt | 42843de47b | |
David Hewitt | 7281268840 | |
Bruno Kolenbrander | 7938d4cadc | |
David Hewitt | de93d15eeb | |
David Hewitt | c9c6f928a1 | |
Blaž Šnuderl | f1384f3582 | |
Blaž Šnuderl | 1fd0aa2b19 | |
Blaz Snuderl | 8388b14369 | |
Blaz Snuderl | 8354590ae6 | |
David Hewitt | 5dbb51b9ce | |
David Hewitt | c5b470202d | |
Icxolu | 304c8e655a | |
David Hewitt | 0d4df9c19d | |
David Hewitt | 2a741a21e6 | |
David Hewitt | cd9c21f89f | |
Blaž Šnuderl | d1e967e9ea | |
Blaž Šnuderl | eca943ea35 | |
Blaž Šnuderl | 7efd412a63 | |
David Hewitt | 975f182e68 | |
David Hewitt | 76d1b34cd5 | |
Blaz Snuderl | b1863c73df | |
Blaz Snuderl | 507ea28b27 | |
Blaž Šnuderl | 9641b11752 | |
Blaž Šnuderl | a2a6062adc | |
Blaž Šnuderl | 7e94da576d | |
Blaž Šnuderl | 5e9d97d1c6 | |
David Hewitt | d8c5e7943c | |
Icxolu | af21a9dc74 | |
David Hewitt | 8f8d4d33fa | |
David Hewitt | 57735540e8 | |
David Hewitt | a60c1821af | |
David Hewitt | d35a6a1fd6 | |
David Hewitt | 49a57dfd18 | |
David Hewitt | 516c085131 | |
David Hewitt | 4437e8f616 | |
David Hewitt | cbc97f8ea9 | |
David Hewitt | 4c94be51a7 | |
David Hewitt | aa1a9864f7 | |
Icxolu | b14dbcf29f | |
Icxolu | e704a760b7 | |
David Hewitt | 6040d93032 | |
David Hewitt | c93073075b | |
David Hewitt | 2f00eb1423 | |
David Hewitt | 0bb9de1aba | |
David Hewitt | aa139ad422 | |
David Hewitt | fed8bcadaf | |
David Hewitt | 718be9fac5 | |
David Hewitt | fbe6f158ec | |
dependabot[bot] | ab90403953 | |
Icxolu | e323fcbb9e | |
David Hewitt | a3eb328378 | |
David Hewitt | 0d421b1ca2 | |
David Hewitt | c47565666d | |
David Hewitt | 7549a21154 | |
David Hewitt | 7f2d1d2aa5 | |
David Hewitt | 345e122bbf | |
David Hewitt | d4d08b24b0 | |
David Hewitt | c54d8976db | |
David Hewitt | ffaa03e3f1 | |
David Hewitt | 595ca4b3c1 | |
David Hewitt | eb8d11f42f | |
David Hewitt | ed7263faa2 | |
David Hewitt | 5ccc46e459 | |
David Hewitt | 796e4192b7 | |
Icxolu | 37e2a4d9c9 | |
David Hewitt | 7927a2e211 | |
David Hewitt | 1657109ae0 | |
David Hewitt | 57a49a2b12 | |
David Hewitt | 674f7282d8 | |
David Hewitt | eed196329d | |
David Hewitt | 0973da27e9 | |
Tpt | f83544910f | |
David Hewitt | 5f320d7a04 | |
David Hewitt | f09ad1e28f | |
David Hewitt | 87e0610b58 | |
Icxolu | 7fddd983b4 | |
Icxolu | 7918815cee | |
Icxolu | f86053e2c2 | |
David Hewitt | f449fc0fc1 | |
David Hewitt | 173d0afb7b | |
David Hewitt | 11b5ae7f5f | |
David Hewitt | 3af73fabcf | |
Xuanwo | 4d40f4183f | |
dependabot[bot] | bcfbbf198d | |
David Hewitt | bf32986b1d | |
Mate Kovacs | f32becacc7 | |
David Hewitt | d1b072222a | |
Mate Kovacs | 3ed5ddb0ec | |
David Hewitt | 4e24e680e8 | |
David Hewitt | 06c95432c6 | |
David Hewitt | 43504cd15a | |
Samuel Colvin | 0e876d94d6 | |
David Hewitt | 7366b1a386 | |
David Hewitt | 48e74b7829 | |
jadedpasta | 83f0f22efe | |
David Hewitt | f8878a7440 | |
David Hewitt | 590ce70bed | |
David Hewitt | 0db6dce9ce | |
David Hewitt | 8fef7a5848 | |
David Hewitt | ab699a0727 | |
David Hewitt | 1520b058e8 | |
David Hewitt | 4504a7c96e | |
dependabot[bot] | 800943ab2d | |
Tpt | 72f0c73925 | |
David Hewitt | 4b172874dc | |
David Hewitt | 026c0daf57 | |
David Hewitt | 58746bb4f9 | |
David Hewitt | 50e33d86c7 | |
Adam Reichold | f2eb121f76 | |
Adam Reichold | 68240e16a7 | |
Adam Reichold | c1c62f1f3c | |
Adam Reichold | e2c6eb86f9 | |
David Hewitt | eceb28bc79 | |
David Hewitt | 783e98b1a8 | |
David Hewitt | febf7a061d | |
Adam Reichold | 03285835bb | |
David Hewitt | 5ea2e5c048 | |
David Hewitt | 0ca97b5e53 | |
David Hewitt | 4cf58c8303 | |
David Hewitt | 823b5feed3 | |
David Hewitt | 53d25f7ff2 | |
David Hewitt | 375e3d4eee | |
David Hewitt | 8fa5294d93 | |
Alex Gaynor | 54390bc50b | |
Alex Gaynor | 339660c117 | |
David Hewitt | e1fcb4efce | |
David Hewitt | e852a4b502 | |
David Hewitt | 9a5668572b | |
David Hewitt | 6776b90e15 | |
David Hewitt | a9f867c2cb | |
David Hewitt | 3da1aac2dd | |
David Hewitt | 54b214bb93 | |
David Hewitt | 46c3190a17 | |
David Hewitt | e42d8cf612 | |
David Hewitt | 066e33488b | |
David Hewitt | ac5db1fb4b | |
Adam Reichold | 0344921326 | |
David Hewitt | 442d13dab3 | |
David Hewitt | e4fd557d2a | |
David Hewitt | 8a28a69c3d | |
David Hewitt | 271cbf9edb | |
Adam Reichold | c44d2f53d5 | |
David Hewitt | d36ad8f61f | |
David Hewitt | 1b61cb015a | |
Adam Reichold | f37c682f8c | |
Adam Reichold | ff373eb1c6 | |
Adam Reichold | bd660537d8 | |
David Hewitt | 38abfd2eed | |
David Hewitt | 382e9b9fb5 | |
Alex Gaynor | 91fdfaab45 | |
David Hewitt | d9cc0c0f24 | |
David Hewitt | 6ca63b5772 | |
David Hewitt | d669a943f7 | |
David Hewitt | 1004ffa7d6 | |
David Hewitt | 877e34ac86 | |
David Hewitt | 214ed29bbb | |
David Hewitt | f5b18468bc | |
Adam Reichold | 7d245842d4 | |
David Hewitt | 49d7718823 | |
David Hewitt | 46fa1b2b80 | |
Adam Reichold | e99058a442 | |
David Hewitt | 6832bf88f2 | |
Adam Reichold | 8bef6e3398 | |
Adam Reichold | 4dc6c1643e | |
David Hewitt | 65f25d4133 | |
Adam Reichold | e58b251fef | |
Adam Reichold | a115877bba | |
Adam Reichold | 1fa47b0409 | |
David Hewitt | 4ac6a6bf15 | |
Alex Gaynor | d92792f8ad | |
David Hewitt | 3092289020 | |
David Hewitt | e8e6fb93d7 | |
David Hewitt | 0f242c399d | |
David Hewitt | c08c6c0a41 | |
David Hewitt | a09b9f8834 | |
David Hewitt | 704e9fc7b5 | |
David Hewitt | 2f080f4075 | |
David Hewitt | 2788f4a110 | |
Adam Reichold | 5b12cf1b8a | |
David Hewitt | ee1272ed76 | |
David Hewitt | de82e2d6e2 | |
David Hewitt | 337e48328f | |
Adam Reichold | 3c97167fd1 | |
Adam Reichold | c1f4db0a9b | |
Adam Reichold | 7f626b26d4 | |
David Hewitt | 43827e39ee | |
David Hewitt | f4f3169cad | |
David Hewitt | 8bd2972201 | |
David Hewitt | 8e7c90733d | |
David Hewitt | 15d309eb1f | |
Adam Reichold | fd2fc983b1 | |
Adam Reichold | 1b3dc6d7ac | |
David Hewitt | 5181e35a61 | |
Adam Reichold | 5528895f3e | |
Adam Reichold | a605308cee | |
David Hewitt | a3c92fa319 | |
David Hewitt | bc87b7bac6 | |
Adam Reichold | 83697f0c62 | |
Adam Reichold | ca7d90dcf3 | |
Adam Reichold | d75d4bdf81 | |
Adam Reichold | 3583b9ac67 | |
Tpt | 0752942c3f | |
Tpt | 8b614745cf | |
Adam Reichold | ee54132ff6 | |
David Hewitt | 1451418ee4 | |
David Hewitt | 54ba6e82ca | |
David Hewitt | 35f7f1a78c | |
Adam Reichold | 8bb64377b8 | |
Adam Reichold | 4177dfcc81 | |
Adam Reichold | 57002d2389 | |
Adam Reichold | 3e10d64fa2 | |
Adam Reichold | 8133aaa5d8 | |
David Hewitt | e727640ef3 | |
David Hewitt | c5dce0172b | |
Adam Reichold | 87e42c96be | |
Adam Reichold | 68f417fb1c | |
Adam Reichold | 416d3c488f | |
Adam Reichold | 86989a7329 | |
Adam Reichold | f03ccf204c | |
Adam Reichold | 27019b5523 | |
Adam Reichold | 2fdd52003e | |
Tpt | d7eac6527b | |
David Hewitt | 1dca87972a | |
David Hewitt | 12b44ea16d | |
Tpt | 0d2387e858 | |
Tpt | 7705181049 | |
Adam Reichold | ff50285d1f | |
Adam Reichold | ced97f80d1 | |
David Hewitt | 867a273afc | |
Tpt | dcaed199c7 | |
Adam Reichold | 118d578ac1 | |
David Hewitt | d7adc74ba5 | |
David Hewitt | 97cf9b834c | |
David Hewitt | ef8532b175 | |
Adam Reichold | d1b4b9e7d4 | |
David Hewitt | 8a7c5002bd | |
David Hewitt | 8bdae345f3 | |
Adam Reichold | 763ecb381b | |
David Hewitt | 015f028589 | |
David Hewitt | 79a54cfc05 | |
David Hewitt | 82ac801be4 | |
David Hewitt | ac4ee2841b | |
Adam Reichold | fc82c9f870 | |
David Hewitt | a010da2c58 | |
Adam Reichold | 97e0998ac2 | |
messense | 42601f3af9 | |
dependabot[bot] | 20e9680a81 | |
Adam Reichold | b0d4ef3525 | |
David Hewitt | 24d9113974 | |
messense | 601d9573c8 | |
messense | c21a84d999 | |
Joseph Perez | e73c795967 | |
David Hewitt | 07726aefc4 | |
Joseph Perez | f34c70c2da | |
Adam Reichold | 4baf0235c3 | |
Nathan Kent | 3249feb85c | |
David Hewitt | 856c573231 | |
David Hewitt | de6d8b7d30 | |
David Hewitt | ed87637ebb | |
David Hewitt | 5326bce57f | |
David Hewitt | 93370d9f6c | |
David Hewitt | 2a5dedcbb5 | |
David Hewitt | 16ae0e2efe | |
Joseph Perez | 2ca9f59d6f | |
David Hewitt | e8f852bce7 | |
Joseph Perez | 8a674c2bd3 | |
David Hewitt | 38beac4fb2 | |
messense | 28dda997f9 | |
David Hewitt | 1556845e78 | |
Adam Reichold | e6457c5e99 | |
David Hewitt | 81ad2e8bab | |
David Hewitt | 016b11069e | |
Alex Gaynor | dd6e0339d3 | |
David Hewitt | cf67c2ce46 | |
David Hewitt | 41842f9e4b | |
David Hewitt | 8e5ef9058b | |
David Hewitt | e62e6cad5d | |
David Hewitt | 53311a90eb | |
Alex Gaynor | 5080deedbf | |
Alex Gaynor | fae209419c | |
David Hewitt | 5c6d49084f | |
David Hewitt | e76797a1d6 | |
David Hewitt | 0f34fcd4b7 | |
mejrs | 597a184b4f | |
David Hewitt | d8002c4b2b | |
Joseph Perez | 781b9e3983 | |
Adam Reichold | 1203921d5c | |
Adam Reichold | 81bc838acd | |
David Hewitt | 9f66846238 | |
messense | cbd06309ff | |
David Hewitt | cd8526ecc6 | |
David Hewitt | bead83f4b0 | |
Ivan Smirnov | a75464ee26 | |
David Hewitt | c814078866 | |
David Hewitt | 31b871a9c0 | |
David Hewitt | 5ac56b8eb0 | |
David Hewitt | aba3a3552d | |
David Hewitt | 81df150823 | |
Joseph Perez | 226a2a3f7a | |
David Hewitt | 69870d2298 | |
David Hewitt | 3f0dfa9698 | |
Joseph Perez | 627841f1e2 | |
Joseph Perez | 744de3a142 | |
David Hewitt | abe518d164 | |
David Hewitt | 7b07d6d21b | |
David Hewitt | 4a43b2f454 | |
David Hewitt | 24f3d0da62 | |
Adam Reichold | 99858236bd | |
David Hewitt | bd0174aa5d | |
David Hewitt | a9305ab389 | |
David Hewitt | d89c38822e | |
David Hewitt | 29ad73b6d5 | |
David Hewitt | 1e28ba7fd0 | |
David Hewitt | 78ae491557 | |
David Hewitt | ef9741850f | |
Surya | 0ff84d250e | |
Bruno Kolenbrander | c8fdb80630 | |
David Hewitt | 852e4fea04 | |
David Hewitt | 7a2c63da76 | |
Adam Reichold | bb808dad5c | |
Joseph Perez | f9107191f5 | |
Joseph Perez | 1fd4090bd4 | |
Joseph Perez | bb1cc93797 | |
Adam Reichold | dc251d0c9f | |
Adam Reichold | 579af5e4cb | |
Samuel Pastva | 2fbc02d06c | |
Samuel Pastva | fc787eabd2 | |
Samuel Pastva | 48c90d9586 | |
David Hewitt | 3042ab1621 | |
Adam Reichold | aa6622bb1a | |
David Hewitt | 391687dab6 | |
David Hewitt | 7534c31a6d | |
Adam Reichold | 2b91fea94f | |
Adam Reichold | 719b87cc41 | |
David Hewitt | 572a27d2d3 | |
David Hewitt | 98346dd137 | |
David Hewitt | e284f3669f | |
David Hewitt | 33ab4e5295 | |
Adam Reichold | 95c8ee9afc | |
David Hewitt | f302b1c6bc | |
David Hewitt | de6162f533 | |
David Hewitt | d835a4c86c | |
David Hewitt | b281a6cc08 | |
David Hewitt | 8c8f00e69d | |
dependabot[bot] | 0f42c688f8 | |
dependabot[bot] | 64b639b945 | |
Adam Reichold | 9d75325d0c | |
David Hewitt | d895734499 | |
David Hewitt | d8a6f37fec | |
Adam Reichold | 63e24df968 | |
David Hewitt | bf2c5a896b | |
jessekrubin | de30e3b1a5 | |
messense | 3aaa7e5615 | |
David Hewitt | e502c133a4 | |
Adam Reichold | 638a9ad84c | |
David Hewitt | 708c644ecd | |
messense | fe60f313f9 | |
David Hewitt | 0c58648059 | |
David Hewitt | 674dac8bfc | |
David Hewitt | 2ca8e573a4 | |
David Hewitt | f12f928bd5 | |
David Hewitt | aa28cec893 | |
messense | dca18b3e41 | |
messense | f4e64aadef | |
David Hewitt | cabbca4003 | |
David Hewitt | 410fef5d79 | |
messense | d4ed66fff0 | |
messense | 5b94241bd7 | |
messense | b8cf9e8358 | |
Adam Reichold | 7ad122e7d9 | |
Orhun Parmaksız | 87cbd38229 | |
David Hewitt | bed3ebeb37 | |
Orhun Parmaksız | 31acf0dc6f | |
David Hewitt | db13a97790 | |
David Hewitt | 6801c508a8 | |
David Hewitt | cac95f31c7 | |
David Hewitt | 2b7eb3a05e | |
David Hewitt | b2df27f0a1 | |
David Hewitt | 642b335ce3 | |
Orhun Parmaksız | dfeae473e5 |
|
@ -1,20 +0,0 @@
|
|||
[target.'cfg(feature = "cargo-clippy")']
|
||||
rustflags = [
|
||||
# Lints to enforce in CI
|
||||
"-Dclippy::checked_conversions",
|
||||
"-Dclippy::dbg_macro",
|
||||
"-Dclippy::explicit_into_iter_loop",
|
||||
"-Dclippy::explicit_iter_loop",
|
||||
"-Dclippy::filter_map_next",
|
||||
"-Dclippy::flat_map_option",
|
||||
"-Dclippy::let_unit_value",
|
||||
"-Dclippy::manual_assert",
|
||||
"-Dclippy::manual_ok_or",
|
||||
"-Dclippy::todo",
|
||||
"-Dclippy::unnecessary_wraps",
|
||||
"-Dclippy::useless_transmute",
|
||||
"-Dclippy::used_underscore_binding",
|
||||
"-Delided_lifetimes_in_paths",
|
||||
"-Dunused_lifetimes",
|
||||
"-Drust_2021_prelude_collisions"
|
||||
]
|
|
@ -5,7 +5,7 @@ contact_links:
|
|||
about: Ask and answer questions about PyO3 on Discussions
|
||||
- name: 🔧 Troubleshooting
|
||||
url: https://github.com/PyO3/pyo3/discussions
|
||||
about: For troubleshooting help, see the Discussions
|
||||
about: For troubleshooting help, see the Discussions
|
||||
- name: 👋 Chat
|
||||
url: https://gitter.im/PyO3/Lobby
|
||||
about: Engage with PyO3's users and developers on Gitter
|
||||
url: https://discord.gg/33kcChzH7f
|
||||
about: Engage with PyO3's users and developers on Discord
|
||||
|
|
|
@ -8,6 +8,6 @@ Please consider adding the following to your pull request:
|
|||
- docs to all new functions and / or detail in the guide
|
||||
- tests for all new or changed functions
|
||||
|
||||
PyO3's CI pipeline will check your pull request. To run its tests
|
||||
PyO3's CI pipeline will check your pull request, thus make sure you have checked the `Contributing.md` guidelines. To run most of its tests
|
||||
locally, you can run ```nox```. See ```nox --list-sessions```
|
||||
for a list of supported actions.
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
name: benches
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
pull_request:
|
||||
# `workflow_dispatch` allows CodSpeed to trigger backtest
|
||||
# performance analysis in order to generate initial data.
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}-benches
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
benchmarks:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rust-src
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: |
|
||||
.
|
||||
pyo3-benches
|
||||
continue-on-error: true
|
||||
|
||||
- name: Install cargo-codspeed
|
||||
run: cargo install cargo-codspeed
|
||||
|
||||
- name: Install nox
|
||||
run: pip install nox
|
||||
|
||||
- name: Run the benchmarks
|
||||
uses: CodSpeedHQ/action@v2
|
||||
with:
|
||||
run: nox -s codspeed
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
|
@ -16,25 +16,24 @@ on:
|
|||
rust-target:
|
||||
required: true
|
||||
type: string
|
||||
msrv:
|
||||
required: false
|
||||
type: string
|
||||
extra-features:
|
||||
MSRV:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
continue-on-error: ${{ endsWith(inputs.python-version, '-dev') || contains(fromJSON('["3.7", "pypy3.7"]'), inputs.python-version) }}
|
||||
continue-on-error: ${{ endsWith(inputs.python-version, '-dev') || contains(fromJSON('["3.7", "pypy3.7"]'), inputs.python-version) || inputs.rust == 'beta' || inputs.rust == 'nightly' }}
|
||||
runs-on: ${{ inputs.os }}
|
||||
if: ${{ !(startsWith(inputs.python-version, 'graalpy') && startsWith(inputs.os, 'windows')) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
architecture: ${{ inputs.python-architecture }}
|
||||
check-latest: ${{ startsWith(inputs.python-version, 'pypy') }} # PyPy can have FFI changes within Python versions, which creates pain in CI
|
||||
|
||||
- name: Install nox
|
||||
run: python -m pip install --upgrade pip && pip install nox
|
||||
|
@ -49,25 +48,29 @@ jobs:
|
|||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: cargo-${{ inputs.python-architecture }}-${{ inputs.os }}-${{ inputs.msrv }}
|
||||
continue-on-error: true
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
- if: inputs.os == 'ubuntu-latest'
|
||||
name: Prepare LD_LIBRARY_PATH (Ubuntu only)
|
||||
run: echo LD_LIBRARY_PATH=${pythonLocation}/lib >> $GITHUB_ENV
|
||||
|
||||
- if: inputs.msrv == 'MSRV'
|
||||
name: Prepare minimal package versions (MSRV only)
|
||||
run: nox -s set-minimal-package-versions
|
||||
- if: inputs.rust == inputs.MSRV
|
||||
name: Prepare MSRV package versions
|
||||
run: nox -s set-msrv-package-versions
|
||||
|
||||
- if: inputs.rust == 'nightly' || inputs.msrv == 'MSRV'
|
||||
- if: inputs.rust != 'stable'
|
||||
name: Ignore changed error messages when using trybuild
|
||||
run: echo "TRYBUILD=overwrite" >> "$GITHUB_ENV"
|
||||
|
||||
- if: inputs.rust == 'nightly'
|
||||
name: Prepare to test on nightly rust
|
||||
run: echo "MAYBE_NIGHTLY=nightly" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build docs
|
||||
run: cargo doc --no-deps --no-default-features --features "full ${{ inputs.extra-features }}"
|
||||
run: nox -s docs
|
||||
|
||||
- name: Build (no features)
|
||||
if: ${{ !startsWith(inputs.python-version, 'graalpy') }}
|
||||
run: cargo build --lib --tests --no-default-features
|
||||
|
||||
# --no-default-features when used with `cargo build/test -p` doesn't seem to work!
|
||||
|
@ -77,7 +80,7 @@ jobs:
|
|||
cargo build --no-default-features
|
||||
|
||||
# Run tests (except on PyPy, because no embedding API).
|
||||
- if: ${{ !startsWith(inputs.python-version, 'pypy') }}
|
||||
- if: ${{ !startsWith(inputs.python-version, 'pypy') && !startsWith(inputs.python-version, 'graalpy') }}
|
||||
name: Test (no features)
|
||||
run: cargo test --no-default-features --lib --tests
|
||||
|
||||
|
@ -88,26 +91,32 @@ jobs:
|
|||
cargo test --no-default-features
|
||||
|
||||
- name: Build (all additive features)
|
||||
run: cargo build --lib --tests --no-default-features --features "full ${{ inputs.extra-features }}"
|
||||
if: ${{ !startsWith(inputs.python-version, 'graalpy') }}
|
||||
run: cargo build --lib --tests --no-default-features --features "multiple-pymethods full $MAYBE_NIGHTLY"
|
||||
|
||||
- if: ${{ startsWith(inputs.python-version, 'pypy') }}
|
||||
name: Build PyPy (abi3-py37)
|
||||
run: cargo build --lib --tests --no-default-features --features "abi3-py37 full ${{ inputs.extra-features }}"
|
||||
run: cargo build --lib --tests --no-default-features --features "multiple-pymethods abi3-py37 full $MAYBE_NIGHTLY"
|
||||
|
||||
# Run tests (except on PyPy, because no embedding API).
|
||||
- if: ${{ !startsWith(inputs.python-version, 'pypy') }}
|
||||
- if: ${{ !startsWith(inputs.python-version, 'pypy') && !startsWith(inputs.python-version, 'graalpy') }}
|
||||
name: Test
|
||||
run: cargo test --no-default-features --features "full ${{ inputs.extra-features }}"
|
||||
run: cargo test --no-default-features --features "full $MAYBE_NIGHTLY"
|
||||
|
||||
# Repeat, with multiple-pymethods feature enabled (it's not entirely additive)
|
||||
- if: ${{ !startsWith(inputs.python-version, 'pypy') && !startsWith(inputs.python-version, 'graalpy') }}
|
||||
name: Test
|
||||
run: cargo test --no-default-features --features "multiple-pymethods full $MAYBE_NIGHTLY"
|
||||
|
||||
# Run tests again, but in abi3 mode
|
||||
- if: ${{ !startsWith(inputs.python-version, 'pypy') }}
|
||||
- if: ${{ !startsWith(inputs.python-version, 'pypy') && !startsWith(inputs.python-version, 'graalpy') }}
|
||||
name: Test (abi3)
|
||||
run: cargo test --no-default-features --features "abi3 full ${{ inputs.extra-features }}"
|
||||
run: cargo test --no-default-features --features "multiple-pymethods abi3 full $MAYBE_NIGHTLY"
|
||||
|
||||
# Run tests again, for abi3-py37 (the minimal Python version)
|
||||
- if: ${{ (!startsWith(inputs.python-version, 'pypy')) && (inputs.python-version != '3.7') }}
|
||||
- if: ${{ (!startsWith(inputs.python-version, 'pypy') && !startsWith(inputs.python-version, 'graalpy')) && (inputs.python-version != '3.7') }}
|
||||
name: Test (abi3-py37)
|
||||
run: cargo test --no-default-features --features "abi3-py37 full ${{ inputs.extra-features }}"
|
||||
run: cargo test --no-default-features --features "multiple-pymethods abi3-py37 full $MAYBE_NIGHTLY"
|
||||
|
||||
- name: Test proc-macro code
|
||||
run: cargo test --manifest-path=pyo3-macros-backend/Cargo.toml
|
||||
|
@ -121,9 +130,9 @@ jobs:
|
|||
env:
|
||||
CARGO_TARGET_DIR: ${{ github.workspace }}/target
|
||||
|
||||
- uses: dorny/paths-filter@v2
|
||||
- uses: dorny/paths-filter@v3
|
||||
# pypy 3.7 and 3.8 are not PEP 3123 compliant so fail checks here
|
||||
if: ${{ inputs.rust == 'stable' && inputs.python-version != 'pypy3.7' && inputs.python-version != 'pypy3.8' }}
|
||||
if: ${{ inputs.rust == 'stable' && inputs.python-version != 'pypy3.7' && inputs.python-version != 'pypy3.8' && !startsWith(inputs.python-version, 'graalpy') }}
|
||||
id: ffi-changes
|
||||
with:
|
||||
base: ${{ github.event.pull_request.base.ref || github.event.merge_group.base_ref }}
|
||||
|
@ -138,63 +147,9 @@ jobs:
|
|||
- name: Run pyo3-ffi-check
|
||||
# pypy 3.7 and 3.8 are not PEP 3123 compliant so fail checks here, nor
|
||||
# is pypy 3.9 on windows
|
||||
if: ${{ endsWith(inputs.python-version, '-dev') || (steps.ffi-changes.outputs.changed == 'true' && inputs.rust == 'stable' && inputs.python-version != 'pypy3.7' && inputs.python-version != 'pypy3.8' && !(inputs.python-version == 'pypy3.9' && contains(inputs.os, 'windows'))) }}
|
||||
if: ${{ endsWith(inputs.python-version, '-dev') || (steps.ffi-changes.outputs.changed == 'true' && inputs.rust == 'stable' && inputs.python-version != 'pypy3.7' && inputs.python-version != 'pypy3.8' && !startsWith(inputs.python-version, 'graalpy') && !(inputs.python-version == 'pypy3.9' && contains(inputs.os, 'windows'))) }}
|
||||
run: nox -s ffi-check
|
||||
|
||||
|
||||
- name: Test cross compilation
|
||||
if: ${{ inputs.os == 'ubuntu-latest' && inputs.python-version == '3.9' }}
|
||||
uses: PyO3/maturin-action@v1
|
||||
env:
|
||||
PYO3_CROSS_LIB_DIR: /opt/python/cp39-cp39/lib
|
||||
with:
|
||||
target: aarch64-unknown-linux-gnu
|
||||
manylinux: auto
|
||||
args: --release -i python3.9 -m examples/maturin-starter/Cargo.toml
|
||||
|
||||
- run: sudo rm -rf examples/maturin-starter/target
|
||||
if: ${{ inputs.os == 'ubuntu-latest' && inputs.python-version == '3.9' }}
|
||||
- name: Test cross compile to same architecture
|
||||
if: ${{ inputs.os == 'ubuntu-latest' && inputs.python-version == '3.9' }}
|
||||
uses: PyO3/maturin-action@v1
|
||||
env:
|
||||
PYO3_CROSS_LIB_DIR: /opt/python/cp39-cp39/lib
|
||||
with:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
manylinux: auto
|
||||
args: --release -i python3.9 -m examples/maturin-starter/Cargo.toml
|
||||
|
||||
- name: Test cross compilation
|
||||
if: ${{ inputs.os == 'macos-latest' && inputs.python-version == '3.9' }}
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: aarch64-apple-darwin
|
||||
args: --release -i python3.9 -m examples/maturin-starter/Cargo.toml
|
||||
|
||||
- name: Test cross compile to Windows
|
||||
if: ${{ inputs.os == 'ubuntu-latest' && inputs.python-version == '3.8' }}
|
||||
env:
|
||||
XWIN_ARCH: x86_64
|
||||
run: |
|
||||
set -ex
|
||||
sudo apt-get install -y mingw-w64 llvm
|
||||
rustup target add x86_64-pc-windows-gnu x86_64-pc-windows-msvc
|
||||
pip install cargo-xwin
|
||||
# abi3
|
||||
cargo build --manifest-path examples/maturin-starter/Cargo.toml --features abi3 --target x86_64-pc-windows-gnu
|
||||
cargo xwin build --manifest-path examples/maturin-starter/Cargo.toml --features abi3 --target x86_64-pc-windows-msvc
|
||||
# non-abi3
|
||||
export PYO3_CROSS_PYTHON_VERSION=3.9
|
||||
cargo build --manifest-path examples/maturin-starter/Cargo.toml --features generate-import-lib --target x86_64-pc-windows-gnu
|
||||
cargo xwin build --manifest-path examples/maturin-starter/Cargo.toml --features generate-import-lib --target x86_64-pc-windows-msvc
|
||||
|
||||
- name: Test cross compile to Windows with maturin
|
||||
if: ${{ inputs.os == 'ubuntu-latest' && inputs.python-version == '3.8' }}
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: x86_64-pc-windows-gnu
|
||||
args: -i python3.8 -m examples/maturin-starter/Cargo.toml --features abi3
|
||||
|
||||
env:
|
||||
CARGO_TERM_VERBOSE: true
|
||||
CARGO_BUILD_TARGET: ${{ inputs.rust-target }}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
name: CI Cache Cleanup
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- name: Cleanup
|
||||
run: |
|
||||
gh extension install actions/gh-actions-cache
|
||||
|
||||
echo "Fetching list of cache key"
|
||||
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
|
||||
|
||||
## Setting this to not fail the workflow while deleting cache keys.
|
||||
set +e
|
||||
echo "Deleting caches..."
|
||||
for cacheKey in $cacheKeysForPR
|
||||
do
|
||||
gh actions-cache delete -R $REPO -B $BRANCH --confirm -- $cacheKey
|
||||
done
|
||||
echo "Done"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge
|
|
@ -10,6 +10,6 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- run: python -m pip install --upgrade pip && pip install nox
|
||||
- run: nox -s check-changelog
|
||||
|
|
|
@ -18,42 +18,62 @@ env:
|
|||
|
||||
jobs:
|
||||
fmt:
|
||||
if: github.ref != 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- run: python -m pip install --upgrade pip && pip install nox
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
- name: Check python formatting (black)
|
||||
run: nox -s fmt-py
|
||||
- name: Check python formatting and lints (ruff)
|
||||
run: nox -s ruff
|
||||
- name: Check rust formatting (rustfmt)
|
||||
run: nox -s fmt-rust
|
||||
run: nox -s rustfmt
|
||||
|
||||
check-msrv:
|
||||
resolve:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
MSRV: ${{ steps.resolve-msrv.outputs.MSRV }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- name: resolve MSRV
|
||||
id: resolve-msrv
|
||||
run:
|
||||
echo MSRV=`python -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["package"]["rust-version"])'` >> $GITHUB_OUTPUT
|
||||
|
||||
semver-checks:
|
||||
if: github.ref != 'refs/heads/main'
|
||||
needs: [fmt]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref != 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
|
||||
check-msrv:
|
||||
needs: [fmt, resolve]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.56.0
|
||||
toolchain: ${{ needs.resolve.outputs.MSRV }}
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
components: rust-src
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
architecture: "x64"
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: check-msrv-1.56.0
|
||||
continue-on-error: true
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
- run: python -m pip install --upgrade pip && pip install nox
|
||||
- name: Prepare minimal package versions
|
||||
run: nox -s set-minimal-package-versions
|
||||
- run: nox -s check-all
|
||||
# This is a smoke test to confirm that CI will run on MSRV (including dev dependencies)
|
||||
- name: Check with MSRV package versions
|
||||
run: |
|
||||
nox -s set-msrv-package-versions
|
||||
nox -s check-all
|
||||
|
||||
env:
|
||||
CARGO_BUILD_TARGET: x86_64-unknown-linux-gnu
|
||||
|
@ -61,7 +81,6 @@ jobs:
|
|||
clippy:
|
||||
needs: [fmt]
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
if: github.ref != 'refs/heads/main'
|
||||
strategy:
|
||||
# If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present
|
||||
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
|
||||
|
@ -69,9 +88,9 @@ jobs:
|
|||
rust: [stable]
|
||||
platform: [
|
||||
{
|
||||
os: "macos-latest",
|
||||
python-architecture: "x64",
|
||||
rust-target: "x86_64-apple-darwin",
|
||||
os: "macos-14", # first available arm macos runner
|
||||
python-architecture: "arm64",
|
||||
rust-target: "aarch64-apple-darwin",
|
||||
},
|
||||
{
|
||||
os: "ubuntu-latest",
|
||||
|
@ -104,7 +123,17 @@ jobs:
|
|||
rust-target: "i686-pc-windows-msvc",
|
||||
},
|
||||
]
|
||||
include:
|
||||
# Run beta clippy as a way to detect any incoming lints which may affect downstream users
|
||||
- rust: beta
|
||||
platform:
|
||||
{
|
||||
os: "ubuntu-latest",
|
||||
python-architecture: "x64",
|
||||
rust-target: "x86_64-unknown-linux-gnu",
|
||||
}
|
||||
name: clippy/${{ matrix.platform.rust-target }}/${{ matrix.rust }}
|
||||
continue-on-error: ${{ matrix.rust != 'stable' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
|
@ -112,13 +141,12 @@ jobs:
|
|||
toolchain: ${{ matrix.rust }}
|
||||
targets: ${{ matrix.platform.rust-target }}
|
||||
components: clippy,rust-src
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
architecture: ${{ matrix.platform.python-architecture }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: clippy-${{ matrix.platform.rust-target }}-${{ matrix.platform.os }}-${{ matrix.rust }}
|
||||
continue-on-error: true
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
- run: python -m pip install --upgrade pip && pip install nox
|
||||
- run: nox -s clippy-all
|
||||
env:
|
||||
|
@ -127,7 +155,7 @@ jobs:
|
|||
build-pr:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-build-full') && github.event_name == 'pull_request' }}
|
||||
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} rust-${{ matrix.rust }}
|
||||
needs: [fmt]
|
||||
needs: [fmt, resolve]
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
os: ${{ matrix.platform.os }}
|
||||
|
@ -135,20 +163,23 @@ jobs:
|
|||
python-architecture: ${{ matrix.platform.python-architecture }}
|
||||
rust: ${{ matrix.rust }}
|
||||
rust-target: ${{ matrix.platform.rust-target }}
|
||||
msrv: ${{ matrix.msrv }}
|
||||
extra-features: ${{ matrix.platform.extra-features }}
|
||||
MSRV: ${{ needs.resolve.outputs.MSRV }}
|
||||
secrets: inherit
|
||||
strategy:
|
||||
# If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present
|
||||
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
|
||||
matrix:
|
||||
extra-features: ["multiple-pymethods"]
|
||||
rust: [stable]
|
||||
python-version: ["3.11"]
|
||||
python-version: ["3.12"]
|
||||
platform:
|
||||
[
|
||||
{
|
||||
os: "macos-latest",
|
||||
os: "macos-14", # first available arm macos runner
|
||||
python-architecture: "arm64",
|
||||
rust-target: "aarch64-apple-darwin",
|
||||
},
|
||||
{
|
||||
os: "macos-13", # last available x86_64 macos runner
|
||||
python-architecture: "x64",
|
||||
rust-target: "x86_64-apple-darwin",
|
||||
},
|
||||
|
@ -166,12 +197,23 @@ jobs:
|
|||
os: "windows-latest",
|
||||
python-architecture: "x86",
|
||||
rust-target: "i686-pc-windows-msvc",
|
||||
}
|
||||
},
|
||||
]
|
||||
include:
|
||||
# Test nightly Rust on PRs so that PR authors have a chance to fix nightly
|
||||
# failures, as nightly does not block merge.
|
||||
- rust: nightly
|
||||
python-version: "3.12"
|
||||
platform:
|
||||
{
|
||||
os: "ubuntu-latest",
|
||||
python-architecture: "x64",
|
||||
rust-target: "x86_64-unknown-linux-gnu",
|
||||
}
|
||||
build-full:
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || (github.event_name != 'pull_request' && github.ref != 'refs/heads/main') }}
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
|
||||
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} rust-${{ matrix.rust }}
|
||||
needs: [fmt]
|
||||
needs: [fmt, resolve]
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
os: ${{ matrix.platform.os }}
|
||||
|
@ -179,14 +221,12 @@ jobs:
|
|||
python-architecture: ${{ matrix.platform.python-architecture }}
|
||||
rust: ${{ matrix.rust }}
|
||||
rust-target: ${{ matrix.platform.rust-target }}
|
||||
msrv: ${{ matrix.msrv }}
|
||||
extra-features: ${{ matrix.platform.extra-features }}
|
||||
MSRV: ${{ needs.resolve.outputs.MSRV }}
|
||||
secrets: inherit
|
||||
strategy:
|
||||
# If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present
|
||||
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
|
||||
matrix:
|
||||
extra-features: ["multiple-pymethods"] # Because MSRV doesn't support this
|
||||
rust: [stable]
|
||||
python-version: [
|
||||
"3.7",
|
||||
|
@ -195,15 +235,22 @@ jobs:
|
|||
"3.10",
|
||||
"3.11",
|
||||
"3.12",
|
||||
"3.13-dev",
|
||||
"pypy3.7",
|
||||
"pypy3.8",
|
||||
"pypy3.9",
|
||||
"pypy3.10",
|
||||
"graalpy24.0",
|
||||
]
|
||||
platform:
|
||||
[
|
||||
# for the full matrix, use x86_64 macos runners because not all Python versions
|
||||
# PyO3 supports are available for arm on GitHub Actions. (Availability starts
|
||||
# around Python 3.10, can switch the full matrix to arm once earlier versions
|
||||
# are dropped.)
|
||||
# NB: if this switches to arm, switch the arm job below in the `include` to x86_64
|
||||
{
|
||||
os: "macos-latest",
|
||||
os: "macos-13",
|
||||
python-architecture: "x64",
|
||||
rust-target: "x86_64-apple-darwin",
|
||||
},
|
||||
|
@ -220,50 +267,66 @@ jobs:
|
|||
]
|
||||
include:
|
||||
# Test minimal supported Rust version
|
||||
- rust: 1.56.0
|
||||
python-version: "3.11"
|
||||
- rust: ${{ needs.resolve.outputs.MSRV }}
|
||||
python-version: "3.12"
|
||||
platform:
|
||||
{
|
||||
os: "ubuntu-latest",
|
||||
python-architecture: "x64",
|
||||
rust-target: "x86_64-unknown-linux-gnu",
|
||||
}
|
||||
msrv: "MSRV"
|
||||
extra-features: ""
|
||||
|
||||
# Test the `nightly` feature
|
||||
- rust: nightly
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
platform:
|
||||
{
|
||||
os: "ubuntu-latest",
|
||||
python-architecture: "x64",
|
||||
rust-target: "x86_64-unknown-linux-gnu",
|
||||
}
|
||||
|
||||
# Run rust beta to help catch toolchain regressions
|
||||
- rust: beta
|
||||
python-version: "3.12"
|
||||
platform:
|
||||
{
|
||||
os: "ubuntu-latest",
|
||||
python-architecture: "x64",
|
||||
rust-target: "x86_64-unknown-linux-gnu",
|
||||
}
|
||||
extra-features: "nightly multiple-pymethods"
|
||||
|
||||
# Test 32-bit Windows only with the latest Python version
|
||||
- rust: stable
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
platform:
|
||||
{
|
||||
os: "windows-latest",
|
||||
python-architecture: "x86",
|
||||
rust-target: "i686-pc-windows-msvc",
|
||||
}
|
||||
extra-features: "multiple-pymethods"
|
||||
|
||||
# test arm macos runner with the latest Python version
|
||||
# NB: if the full matrix switchess to arm, switch to x86_64 here
|
||||
- rust: stable
|
||||
python-version: "3.12"
|
||||
platform:
|
||||
{
|
||||
os: "macos-14",
|
||||
python-architecture: "arm64",
|
||||
rust-target: "aarch64-apple-darwin",
|
||||
}
|
||||
|
||||
valgrind:
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || (github.event_name != 'pull_request' && github.ref != 'refs/heads/main') }}
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
|
||||
needs: [fmt]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: cargo-valgrind
|
||||
continue-on-error: true
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@valgrind
|
||||
- run: python -m pip install --upgrade pip && pip install nox
|
||||
|
@ -274,51 +337,64 @@ jobs:
|
|||
TRYBUILD: overwrite
|
||||
|
||||
careful:
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || (github.event_name != 'pull_request' && github.ref != 'refs/heads/main') }}
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
|
||||
needs: [fmt]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: cargo-careful
|
||||
continue-on-error: true
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rust-src
|
||||
- run: cargo install cargo-careful
|
||||
- uses: taiki-e/install-action@cargo-careful
|
||||
- run: python -m pip install --upgrade pip && pip install nox
|
||||
- run: nox -s test-rust -- careful skip-full
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
TRYBUILD: overwrite
|
||||
|
||||
docsrs:
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
|
||||
needs: [fmt]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rust-src
|
||||
- run: cargo rustdoc --lib --no-default-features --features full -Zunstable-options --config "build.rustdocflags=[\"--cfg\", \"docsrs\"]"
|
||||
|
||||
coverage:
|
||||
needs: [fmt]
|
||||
name: coverage-${{ matrix.os }}
|
||||
name: coverage ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: ["windows", "macos", "ubuntu"]
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
os: ["windows-latest", "macos-14", "ubuntu-latest"] # first available arm macos runner
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- if: ${{ github.event_name == 'pull_request' && matrix.os != 'ubuntu' }}
|
||||
- if: ${{ github.event_name == 'pull_request' && matrix.os != 'ubuntu-latest' }}
|
||||
id: should-skip
|
||||
shell: bash
|
||||
run: echo 'skip=true' >> $GITHUB_OUTPUT
|
||||
- uses: actions/checkout@v4
|
||||
if: steps.should-skip.outputs.skip != 'true'
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
if: steps.should-skip.outputs.skip != 'true'
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
if: steps.should-skip.outputs.skip != 'true'
|
||||
with:
|
||||
key: coverage-cargo-${{ matrix.os }}
|
||||
continue-on-error: true
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
if: steps.should-skip.outputs.skip != 'true'
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
components: llvm-tools-preview,rust-src
|
||||
- name: Install cargo-llvm-cov
|
||||
if: steps.should-skip.outputs.skip != 'true'
|
||||
uses: taiki-e/install-action@cargo-llvm-cov
|
||||
|
@ -326,29 +402,35 @@ jobs:
|
|||
if: steps.should-skip.outputs.skip != 'true'
|
||||
- run: nox -s coverage
|
||||
if: steps.should-skip.outputs.skip != 'true'
|
||||
- uses: codecov/codecov-action@v3
|
||||
- uses: codecov/codecov-action@v4
|
||||
if: steps.should-skip.outputs.skip != 'true'
|
||||
with:
|
||||
file: coverage.json
|
||||
name: ${{ matrix.os }}
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
emscripten:
|
||||
name: emscripten
|
||||
if: ${{ github.event_name != 'pull_request' && github.ref != 'refs/heads/main' }}
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
|
||||
needs: [fmt]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
# TODO bump emscripten builds to test on 3.12
|
||||
python-version: 3.11
|
||||
id: setup-python
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-emscripten
|
||||
- uses: actions/setup-node@v3
|
||||
components: rust-src
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 14
|
||||
- run: python -m pip install --upgrade pip && pip install nox
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
|
@ -356,7 +438,7 @@ jobs:
|
|||
key: ${{ hashFiles('emscripten/*') }} - ${{ hashFiles('noxfile.py') }} - ${{ steps.setup-python.outputs.python-path }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: cargo-emscripten-wasm32
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
- name: Build
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: nox -s build-emscripten
|
||||
|
@ -364,13 +446,14 @@ jobs:
|
|||
run: nox -s test-emscripten
|
||||
|
||||
test-debug:
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
|
||||
needs: [fmt]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: cargo-test-debug
|
||||
continue-on-error: true
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rust-src
|
||||
|
@ -406,6 +489,131 @@ jobs:
|
|||
echo PYO3_CONFIG_FILE=$PYO3_CONFIG_FILE >> $GITHUB_ENV
|
||||
- run: python3 -m nox -s test
|
||||
|
||||
test-version-limits:
|
||||
needs: [fmt]
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: python3 -m pip install --upgrade pip && pip install nox
|
||||
- run: python3 -m nox -s test-version-limits
|
||||
|
||||
check-feature-powerset:
|
||||
needs: [fmt, resolve]
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
|
||||
runs-on: ubuntu-latest
|
||||
name: check-feature-powerset ${{ matrix.rust }}
|
||||
strategy:
|
||||
# run on stable and MSRV to check that all combinations of features are expected to build fine on our supported
|
||||
# range of compilers
|
||||
matrix:
|
||||
rust: ["stable"]
|
||||
include:
|
||||
- rust: ${{ needs.resolve.outputs.MSRV }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
- uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-hack,cargo-minimal-versions
|
||||
- run: python3 -m pip install --upgrade pip && pip install nox
|
||||
- run: python3 -m nox -s check-feature-powerset -- ${{ matrix.rust != 'stable' && 'minimal-versions' || '' }}
|
||||
|
||||
test-cross-compilation:
|
||||
needs: [fmt]
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: test-cross-compilation ${{ matrix.os }} -> ${{ matrix.target }}
|
||||
strategy:
|
||||
# If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present
|
||||
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
|
||||
matrix:
|
||||
include:
|
||||
# ubuntu "cross compile" to itself
|
||||
- os: "ubuntu-latest"
|
||||
target: "x86_64-unknown-linux-gnu"
|
||||
flags: "-i python3.12"
|
||||
manylinux: auto
|
||||
# ubuntu x86_64 -> aarch64
|
||||
- os: "ubuntu-latest"
|
||||
target: "aarch64-unknown-linux-gnu"
|
||||
flags: "-i python3.12"
|
||||
manylinux: auto
|
||||
# ubuntu x86_64 -> windows x86_64
|
||||
- os: "ubuntu-latest"
|
||||
target: "x86_64-pc-windows-gnu"
|
||||
flags: "-i python3.12 --features abi3 --features generate-import-lib"
|
||||
manylinux: off
|
||||
# macos x86_64 -> aarch64
|
||||
- os: "macos-13" # last x86_64 macos runners
|
||||
target: "aarch64-apple-darwin"
|
||||
# macos aarch64 -> x86_64
|
||||
- os: "macos-14" # aarch64 macos runners
|
||||
target: "x86_64-apple-darwin"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces:
|
||||
examples/maturin-starter
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
key: ${{ matrix.target }}
|
||||
- name: Setup cross-compiler
|
||||
if: ${{ matrix.target == 'x86_64-pc-windows-gnu' }}
|
||||
run: sudo apt-get install -y mingw-w64 llvm
|
||||
- uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: ${{ matrix.manylinux }}
|
||||
args: --release -m examples/maturin-starter/Cargo.toml ${{ matrix.flags }}
|
||||
|
||||
test-cross-compilation-windows:
|
||||
needs: [fmt]
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces:
|
||||
examples/maturin-starter
|
||||
save-if: ${{ github.event_name != 'merge_group' }}
|
||||
- uses: actions/cache/restore@v4
|
||||
with:
|
||||
# https://github.com/PyO3/maturin/discussions/1953
|
||||
path: ~/.cache/cargo-xwin
|
||||
key: cargo-xwin-cache
|
||||
- name: Test cross compile to Windows
|
||||
env:
|
||||
XWIN_ARCH: x86_64
|
||||
run: |
|
||||
set -ex
|
||||
sudo apt-get install -y mingw-w64 llvm
|
||||
rustup target add x86_64-pc-windows-gnu x86_64-pc-windows-msvc
|
||||
pip install cargo-xwin
|
||||
# abi3
|
||||
cargo build --manifest-path examples/maturin-starter/Cargo.toml --features abi3 --target x86_64-pc-windows-gnu
|
||||
cargo xwin build --manifest-path examples/maturin-starter/Cargo.toml --features abi3 --target x86_64-pc-windows-msvc
|
||||
# non-abi3
|
||||
export PYO3_CROSS_PYTHON_VERSION=3.12
|
||||
cargo build --manifest-path examples/maturin-starter/Cargo.toml --features generate-import-lib --target x86_64-pc-windows-gnu
|
||||
cargo xwin build --manifest-path examples/maturin-starter/Cargo.toml --features generate-import-lib --target x86_64-pc-windows-msvc
|
||||
- if: ${{ github.ref == 'refs/heads/main' }}
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ~/.cache/cargo-xwin
|
||||
key: cargo-xwin-cache
|
||||
conclusion:
|
||||
needs:
|
||||
- fmt
|
||||
|
@ -415,8 +623,14 @@ jobs:
|
|||
- build-full
|
||||
- valgrind
|
||||
- careful
|
||||
- docsrs
|
||||
- coverage
|
||||
- emscripten
|
||||
- test-debug
|
||||
- test-version-limits
|
||||
- check-feature-powerset
|
||||
- test-cross-compilation
|
||||
- test-cross-compilation-windows
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
|
|
@ -22,13 +22,13 @@ jobs:
|
|||
tag_name: ${{ steps.prepare_tag.outputs.tag_name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
|
||||
- name: Setup mdBook
|
||||
uses: peaceiris/actions-mdbook@v1
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
mdbook-version: "0.4.19"
|
||||
tool: mdbook,lychee
|
||||
|
||||
- name: Prepare tag
|
||||
id: prepare_tag
|
||||
|
@ -36,104 +36,19 @@ jobs:
|
|||
TAG_NAME="${GITHUB_REF##*/}"
|
||||
echo "::set-output name=tag_name::${TAG_NAME}"
|
||||
|
||||
# This builds the book in target/guide.
|
||||
# This builds the book in target/guide/.
|
||||
- name: Build the guide
|
||||
run: |
|
||||
python -m pip install --upgrade pip && pip install nox
|
||||
nox -s build-guide
|
||||
nox -s check-guide
|
||||
env:
|
||||
PYO3_VERSION_TAG: ${{ steps.prepare_tag.outputs.tag_name }}
|
||||
|
||||
- name: Deploy docs and the guide
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./target/guide/
|
||||
destination_dir: ${{ steps.prepare_tag.outputs.tag_name }}
|
||||
full_commit_message: "Upload documentation for ${{ steps.prepare_tag.outputs.tag_name }}"
|
||||
|
||||
cargo-benchmark:
|
||||
if: ${{ github.ref_name == 'main' }}
|
||||
name: Cargo benchmark
|
||||
needs: guide-build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: cargo-${{ runner.os }}-bench-${{ hashFiles('**/Cargo.toml') }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
python -m pip install --upgrade pip && pip install nox
|
||||
for bench in pyo3-benches/benches/*.rs; do
|
||||
bench_name=$(basename "$bench" .rs)
|
||||
nox -s bench -- --bench "$bench_name" -- --output-format bencher | tee -a output.txt
|
||||
done
|
||||
|
||||
# Download previous benchmark result from cache (if exists)
|
||||
- name: Download previous benchmark data
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./cache
|
||||
key: ${{ runner.os }}-benchmark
|
||||
|
||||
# Run `github-action-benchmark` action
|
||||
- name: Store benchmark result
|
||||
uses: benchmark-action/github-action-benchmark@v1
|
||||
with:
|
||||
name: pyo3-bench
|
||||
# What benchmark tool the output.txt came from
|
||||
tool: "cargo"
|
||||
# Where the output from the benchmark tool is stored
|
||||
output-file-path: output.txt
|
||||
# GitHub API token to make a commit comment
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto-push: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
pytest-benchmark:
|
||||
if: ${{ github.ref_name == 'main' }}
|
||||
name: Pytest benchmark
|
||||
needs: cargo-benchmark
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: cargo-${{ runner.os }}-pytest-bench-${{ hashFiles('**/Cargo.toml') }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Download previous benchmark data
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./cache
|
||||
key: ${{ runner.os }}-pytest-benchmark
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
python -m pip install --upgrade pip && pip install nox
|
||||
nox -f pytests/noxfile.py -s bench -- --benchmark-json $(pwd)/output.json
|
||||
- name: Store benchmark result
|
||||
uses: benchmark-action/github-action-benchmark@v1
|
||||
with:
|
||||
name: pytest-bench
|
||||
tool: "pytest"
|
||||
output-file-path: output.json
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto-push: ${{ github.event_name != 'pull_request' }}
|
||||
|
|
|
@ -14,6 +14,8 @@ dist/
|
|||
.eggs/
|
||||
venv*
|
||||
guide/book/
|
||||
guide/src/LICENSE-APACHE
|
||||
guide/src/LICENSE-MIT
|
||||
*.so
|
||||
*.out
|
||||
*.egg-info
|
||||
|
@ -22,4 +24,6 @@ pip-wheel-metadata
|
|||
valgrind-python.supp
|
||||
*.pyd
|
||||
lcov.info
|
||||
coverage.json
|
||||
netlify_build/
|
||||
.nox/
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
set -uex
|
||||
|
||||
rustup update nightly
|
||||
rustup default nightly
|
||||
|
||||
PYO3_VERSION=$(cargo search pyo3 --limit 1 | head -1 | tr -s ' ' | cut -d ' ' -f 3 | tr -d '"')
|
||||
|
@ -48,6 +49,15 @@ done
|
|||
# Add latest redirect
|
||||
echo "/latest/* /v${PYO3_VERSION}/:splat 302" >> netlify_build/_redirects
|
||||
|
||||
# some backwards compatbiility redirects
|
||||
echo "/latest/building_and_distribution/* /latest/building-and-distribution/:splat 302" >> netlify_build/_redirects
|
||||
echo "/latest/building-and-distribution/multiple_python_versions/* /latest/building-and-distribution/multiple-python-versions:splat 302" >> netlify_build/_redirects
|
||||
echo "/latest/function/error_handling/* /latest/function/error-handling/:splat 302" >> netlify_build/_redirects
|
||||
echo "/latest/getting_started/* /latest/getting-started/:splat 302" >> netlify_build/_redirects
|
||||
echo "/latest/python_from_rust/* /latest/python-from-rust/:splat 302" >> netlify_build/_redirects
|
||||
echo "/latest/python_typing_hints/* /latest/python-typing-hints/:splat 302" >> netlify_build/_redirects
|
||||
echo "/latest/trait_bounds/* /latest/trait-bounds/:splat 302" >> netlify_build/_redirects
|
||||
|
||||
## Add landing page redirect
|
||||
if [ "${CONTEXT}" == "deploy-preview" ]; then
|
||||
echo "/ /main/" >> netlify_build/_redirects
|
||||
|
@ -71,9 +81,17 @@ if [ "${INSTALLED_MDBOOK_VERSION}" != "mdbook v${MDBOOK_VERSION}" ]; then
|
|||
cargo install mdbook@${MDBOOK_VERSION} --force
|
||||
fi
|
||||
|
||||
# Install latest mdbook-linkcheck. Netlify will cache the cargo bin dir, so this will
|
||||
# only build mdbook-linkcheck if needed.
|
||||
MDBOOK_LINKCHECK_VERSION=$(cargo search mdbook-linkcheck --limit 1 | head -1 | tr -s ' ' | cut -d ' ' -f 3 | tr -d '"')
|
||||
INSTALLED_MDBOOK_LINKCHECK_VERSION=$(mdbook-linkcheck --version || echo "none")
|
||||
if [ "${INSTALLED_MDBOOK_LINKCHECK_VERSION}" != "mdbook v${MDBOOK_LINKCHECK_VERSION}" ]; then
|
||||
cargo install mdbook-linkcheck@${MDBOOK_LINKCHECK_VERSION} --force
|
||||
fi
|
||||
|
||||
pip install nox
|
||||
nox -s build-guide
|
||||
mv target/guide netlify_build/main/
|
||||
mv target/guide/ netlify_build/main/
|
||||
|
||||
## Build public docs
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
3.11
|
||||
3.12
|
||||
|
|
|
@ -24,17 +24,18 @@ To summarize, there are six main parts to the PyO3 codebase.
|
|||
- [`src/instance.rs`] and [`src/types`]
|
||||
3. [`PyClass` and related functionalities.](#3-pyclass-and-related-functionalities)
|
||||
- [`src/pycell.rs`], [`src/pyclass.rs`], and more
|
||||
4. [Protocol methods like `__getitem__`.](#4-protocol-methods)
|
||||
- [`src/class`]
|
||||
5. [Procedural macros to simplify usage for users.](#5-procedural-macros-to-simplify-usage-for-users)
|
||||
- [`src/derive_utils.rs`], [`pyo3-macros`] and [`pyo3-macros-backend`]
|
||||
6. [`build.rs` and `pyo3-build-config`](#6-buildrs-and-pyo3-build-config)
|
||||
4. [Procedural macros to simplify usage for users.](#4-procedural-macros-to-simplify-usage-for-users)
|
||||
- [`src/impl_`], [`pyo3-macros`] and [`pyo3-macros-backend`]
|
||||
5. [`build.rs` and `pyo3-build-config`](#5-buildrs-and-pyo3-build-config)
|
||||
- [`build.rs`](https://github.com/PyO3/pyo3/tree/main/build.rs)
|
||||
- [`pyo3-build-config`]
|
||||
|
||||
## 1. Low-level bindings of Python/C API
|
||||
|
||||
[`pyo3-ffi`] contains wrappers of [Python/C API].
|
||||
[`pyo3-ffi`] contains wrappers of the [Python/C API]. This is currently done by hand rather than
|
||||
automated tooling because:
|
||||
- it gives us best control about how to adapt C conventions to Rust, and
|
||||
- there are many Python interpreter versions we support in a single set of files.
|
||||
|
||||
We aim to provide straight-forward Rust wrappers resembling the file structure of
|
||||
[`cpython/Include`](https://github.com/python/cpython/tree/v3.9.2/Include).
|
||||
|
@ -44,12 +45,12 @@ the file contents upstream in CPython.
|
|||
The tracking issue is [#1289](https://github.com/PyO3/pyo3/issues/1289), and contribution is welcome.
|
||||
|
||||
In the [`pyo3-ffi`] crate, there is lots of conditional compilation such as `#[cfg(Py_LIMITED_API)]`,
|
||||
`#[cfg(Py_37)]`, and `#[cfg(PyPy)]`.
|
||||
`#[cfg(Py_3_7)]`, and `#[cfg(PyPy)]`.
|
||||
`Py_LIMITED_API` corresponds to `#define Py_LIMITED_API` macro in Python/C API.
|
||||
With `Py_LIMITED_API`, we can build a Python-version-agnostic binary called an
|
||||
[abi3 wheel](https://pyo3.rs/latest/building_and_distribution.html#py_limited_apiabi3).
|
||||
`Py_37` means that the API is available from Python >= 3.7.
|
||||
There are also `Py_38`, `Py_39`, and so on.
|
||||
[abi3 wheel](https://pyo3.rs/latest/building-and-distribution.html#py_limited_apiabi3).
|
||||
`Py_3_7` means that the API is available from Python >= 3.7.
|
||||
There are also `Py_3_8`, `Py_3_9`, and so on.
|
||||
`PyPy` means that the API definition is for PyPy.
|
||||
Those flags are set in [`build.rs`](#6-buildrs-and-pyo3-build-config).
|
||||
|
||||
|
@ -58,6 +59,7 @@ Those flags are set in [`build.rs`](#6-buildrs-and-pyo3-build-config).
|
|||
[`src/types`] contains bindings to [built-in types](https://docs.python.org/3/library/stdtypes.html)
|
||||
of Python, such as `dict` and `list`.
|
||||
For historical reasons, Python's `object` is called `PyAny` in PyO3 and located in [`src/types/any.rs`].
|
||||
|
||||
Currently, `PyAny` is a straightforward wrapper of `ffi::PyObject`, defined as:
|
||||
|
||||
```rust
|
||||
|
@ -65,38 +67,16 @@ Currently, `PyAny` is a straightforward wrapper of `ffi::PyObject`, defined as:
|
|||
pub struct PyAny(UnsafeCell<ffi::PyObject>);
|
||||
```
|
||||
|
||||
All built-in types are defined as a C struct.
|
||||
For example, `dict` is defined as:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
/* Base object */
|
||||
PyObject ob_base;
|
||||
/* Number of items in the dictionary */
|
||||
Py_ssize_t ma_used;
|
||||
/* Dictionary version */
|
||||
uint64_t ma_version_tag;
|
||||
PyDictKeysObject *ma_keys;
|
||||
PyObject **ma_values;
|
||||
} PyDictObject;
|
||||
```
|
||||
|
||||
However, we cannot access such a specific data structure with `#[cfg(Py_LIMITED_API)]` set.
|
||||
Thus, all builtin objects are implemented as opaque types by wrapping `PyAny`, e.g.,:
|
||||
Concrete Python objects are implemented by wrapping `PyAny`, e.g.,:
|
||||
|
||||
```rust
|
||||
#[repr(transparent)]
|
||||
pub struct PyDict(PyAny);
|
||||
```
|
||||
|
||||
Note that `PyAny` is not a pointer, and it is usually used as a pointer to the object in the
|
||||
Python heap, as `&PyAny`.
|
||||
This design choice can be changed
|
||||
(see the discussion in [#1056](https://github.com/PyO3/pyo3/issues/1056)).
|
||||
These types are not intended to be accessed directly, and instead are used through the `Py<T>` and `Bound<T>` smart pointers.
|
||||
|
||||
Since we need lots of boilerplate for implementing common traits for these types
|
||||
(e.g., `AsPyPointer`, `AsRef<PyAny>`, and `Debug`), we have some macros in
|
||||
[`src/types/mod.rs`].
|
||||
We have some macros in [`src/types/mod.rs`] which make it easier to implement APIs for concrete Python types.
|
||||
|
||||
## 3. `PyClass` and related functionalities
|
||||
|
||||
|
@ -104,26 +84,16 @@ Since we need lots of boilerplate for implementing common traits for these types
|
|||
traits to make `#[pyclass]` work.
|
||||
Also, [`src/pyclass_init.rs`] and [`src/impl_/pyclass.rs`] have related functionalities.
|
||||
|
||||
To realize object-oriented programming in C, all Python objects must have the following two fields
|
||||
at the beginning.
|
||||
|
||||
```rust
|
||||
#[repr(C)]
|
||||
pub struct PyObject {
|
||||
pub ob_refcnt: usize,
|
||||
pub ob_type: *mut PyTypeObject,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Thanks to this guarantee, casting `*mut A` to `*mut PyObject` is valid if `A` is a Python object.
|
||||
To realize object-oriented programming in C, all Python objects have `ob_base: PyObject` as their
|
||||
first field in their structure definition. Thanks to this guarantee, casting `*mut A` to `*mut PyObject`
|
||||
is valid if `A` is a Python object.
|
||||
|
||||
To ensure this guarantee, we have a wrapper struct `PyCell<T>` in [`src/pycell.rs`] which is roughly:
|
||||
|
||||
```rust
|
||||
#[repr(C)]
|
||||
pub struct PyCell<T: PyClass> {
|
||||
object: crate::ffi::PyObject,
|
||||
ob_base: crate::ffi::PyObject,
|
||||
inner: T,
|
||||
}
|
||||
```
|
||||
|
@ -142,7 +112,7 @@ In Python, all objects have their types, and types are also objects of `type`.
|
|||
For example, you can see `type({})` shows `dict` and `type(type({}))` shows `type` in Python REPL.
|
||||
`T: PyTypeInfo` implies that `T` has a corresponding type object.
|
||||
|
||||
## 4. Protocol methods
|
||||
### Protocol methods
|
||||
|
||||
Python has some built-in special methods called dunder methods, such as `__iter__`.
|
||||
They are called "slots" in the [abstract objects layer](https://docs.python.org/3/c-api/abstract.html) in
|
||||
|
@ -151,15 +121,15 @@ We provide a way to implement those protocols similarly, by recognizing special
|
|||
names in `#[pymethods]`, with a few new ones for slots that can not be
|
||||
implemented in Python, such as GC support.
|
||||
|
||||
## 5. Procedural macros to simplify usage for users.
|
||||
## 4. Procedural macros to simplify usage for users.
|
||||
|
||||
[`pyo3-macros`] provides five proc-macro APIs: `pymodule`, `pyfunction`, `pyclass`,
|
||||
`pymethods`, and `#[derive(FromPyObject)]`.
|
||||
[`pyo3-macros-backend`] has the actual implementations of these APIs.
|
||||
[`src/derive_utils.rs`] contains some utilities used in code generated by these proc-macros,
|
||||
[`src/impl_`] contains `#[doc(hidden)]` functionality used in code generated by these proc-macros,
|
||||
such as parsing function arguments.
|
||||
|
||||
## 6. `build.rs` and `pyo3-build-config`
|
||||
## 5. `build.rs` and `pyo3-build-config`
|
||||
|
||||
PyO3 supports a wide range of OSes, interpreters and use cases. The correct environment must be
|
||||
detected at build time in order to set up relevant conditional compilation correctly. This logic
|
||||
|
@ -218,7 +188,7 @@ Some of the functionality of `pyo3-build-config`:
|
|||
|
||||
<!-- Files -->
|
||||
|
||||
[`src/derive_utils.rs`]: https://github.com/PyO3/pyo3/blob/main/src/derive_utils.rs
|
||||
[`src/impl_`]: https://github.com/PyO3/pyo3/blob/main/src/impl_
|
||||
[`src/instance.rs`]: https://github.com/PyO3/pyo3/tree/main/src/instance.rs
|
||||
[`src/pycell.rs`]: https://github.com/PyO3/pyo3/tree/main/src/pycell.rs
|
||||
[`src/pyclass.rs`]: https://github.com/PyO3/pyo3/tree/main/src/pyclass.rs
|
||||
|
|
243
CHANGELOG.md
243
CHANGELOG.md
|
@ -10,6 +10,231 @@ To see unreleased changes, please see the [CHANGELOG on the main branch guide](h
|
|||
|
||||
<!-- towncrier release notes start -->
|
||||
|
||||
## [0.22.1] - 2024-07-06
|
||||
|
||||
### Added
|
||||
|
||||
- Add `#[pyo3(submodule)]` option for declarative `#[pymodule]`s. [#4301](https://github.com/PyO3/pyo3/pull/4301)
|
||||
- Implement `PartialEq<bool>` for `Bound<'py, PyBool>`. [#4305](https://github.com/PyO3/pyo3/pull/4305)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Return `NotImplemented` instead of raising `TypeError` from generated equality method when comparing different types. [#4287](https://github.com/PyO3/pyo3/pull/4287)
|
||||
- Handle full-path `#[pyo3::prelude::pymodule]` and similar for `#[pyclass]` and `#[pyfunction]` in declarative modules.[#4288](https://github.com/PyO3/pyo3/pull/4288)
|
||||
- Fix 128-bit int regression on big-endian platforms with Python <3.13. [#4291](https://github.com/PyO3/pyo3/pull/4291)
|
||||
- Stop generating code that will never be covered with declarative modules. [#4297](https://github.com/PyO3/pyo3/pull/4297)
|
||||
- Fix invalid deprecation warning for trailing optional on `#[setter]` function. [#4304](https://github.com/PyO3/pyo3/pull/4304)
|
||||
|
||||
|
||||
## [0.22.0] - 2024-06-24
|
||||
|
||||
### Packaging
|
||||
|
||||
- Update `heck` dependency to 0.5. [#3966](https://github.com/PyO3/pyo3/pull/3966)
|
||||
- Extend range of supported versions of `chrono-tz` optional dependency to include version 0.10. [#4061](https://github.com/PyO3/pyo3/pull/4061)
|
||||
- Update MSRV to 1.63. [#4129](https://github.com/PyO3/pyo3/pull/4129)
|
||||
- Add optional `num-rational` feature to add conversions with Python's `fractions.Fraction`. [#4148](https://github.com/PyO3/pyo3/pull/4148)
|
||||
- Support Python 3.13. [#4184](https://github.com/PyO3/pyo3/pull/4184)
|
||||
|
||||
### Added
|
||||
|
||||
- Add `PyWeakref`, `PyWeakrefReference` and `PyWeakrefProxy`. [#3835](https://github.com/PyO3/pyo3/pull/3835)
|
||||
- Support `#[pyclass]` on enums that have tuple variants. [#4072](https://github.com/PyO3/pyo3/pull/4072)
|
||||
- Add support for scientific notation in `Decimal` conversion. [#4079](https://github.com/PyO3/pyo3/pull/4079)
|
||||
- Add `pyo3_disable_reference_pool` conditional compilation flag to avoid the overhead of the global reference pool at the cost of known limitations as explained in the performance section of the guide. [#4095](https://github.com/PyO3/pyo3/pull/4095)
|
||||
- Add `#[pyo3(constructor = (...))]` to customize the generated constructors for complex enum variants. [#4158](https://github.com/PyO3/pyo3/pull/4158)
|
||||
- Add `PyType::module`, which always matches Python `__module__`. [#4196](https://github.com/PyO3/pyo3/pull/4196)
|
||||
- Add `PyType::fully_qualified_name` which matches the "fully qualified name" defined in [PEP 737](https://peps.python.org/pep-0737). [#4196](https://github.com/PyO3/pyo3/pull/4196)
|
||||
- Add `PyTypeMethods::mro` and `PyTypeMethods::bases`. [#4197](https://github.com/PyO3/pyo3/pull/4197)
|
||||
- Add `#[pyclass(ord)]` to implement ordering based on `PartialOrd`. [#4202](https://github.com/PyO3/pyo3/pull/4202)
|
||||
- Implement `ToPyObject` and `IntoPy<PyObject>` for `PyBackedStr` and `PyBackedBytes`. [#4205](https://github.com/PyO3/pyo3/pull/4205)
|
||||
- Add `#[pyclass(hash)]` option to implement `__hash__` in terms of the `Hash` implementation [#4206](https://github.com/PyO3/pyo3/pull/4206)
|
||||
- Add `#[pyclass(eq)]` option to generate `__eq__` based on `PartialEq`, and `#[pyclass(eq_int)]` for simple enums to implement equality based on their discriminants. [#4210](https://github.com/PyO3/pyo3/pull/4210)
|
||||
- Implement `From<Bound<'py, T>>` for `PyClassInitializer<T>`. [#4214](https://github.com/PyO3/pyo3/pull/4214)
|
||||
- Add `as_super` methods to `PyRef` and `PyRefMut` for accesing the base class by reference. [#4219](https://github.com/PyO3/pyo3/pull/4219)
|
||||
- Implement `PartialEq<str>` for `Bound<'py, PyString>`. [#4245](https://github.com/PyO3/pyo3/pull/4245)
|
||||
- Implement `PyModuleMethods::filename` on PyPy. [#4249](https://github.com/PyO3/pyo3/pull/4249)
|
||||
- Implement `PartialEq<[u8]>` for `Bound<'py, PyBytes>`. [#4250](https://github.com/PyO3/pyo3/pull/4250)
|
||||
- Add `pyo3_ffi::c_str` macro to create `&'static CStr` on Rust versions which don't have 1.77's `c""` literals. [#4255](https://github.com/PyO3/pyo3/pull/4255)
|
||||
- Support `bool` conversion with `numpy` 2.0's `numpy.bool` type [#4258](https://github.com/PyO3/pyo3/pull/4258)
|
||||
- Add `PyAnyMethods::{bitnot, matmul, floor_div, rem, divmod}`. [#4264](https://github.com/PyO3/pyo3/pull/4264)
|
||||
|
||||
### Changed
|
||||
|
||||
- Change the type of `PySliceIndices::slicelength` and the `length` parameter of `PySlice::indices()`. [#3761](https://github.com/PyO3/pyo3/pull/3761)
|
||||
- Deprecate implicit default for trailing optional arguments [#4078](https://github.com/PyO3/pyo3/pull/4078)
|
||||
- `Clone`ing pointers into the Python heap has been moved behind the `py-clone` feature, as it must panic without the GIL being held as a soundness fix. [#4095](https://github.com/PyO3/pyo3/pull/4095)
|
||||
- Add `#[track_caller]` to all `Py<T>`, `Bound<'py, T>` and `Borrowed<'a, 'py, T>` methods which can panic. [#4098](https://github.com/PyO3/pyo3/pull/4098)
|
||||
- Change `PyAnyMethods::dir` to be fallible and return `PyResult<Bound<'py, PyList>>` (and similar for `PyAny::dir`). [#4100](https://github.com/PyO3/pyo3/pull/4100)
|
||||
- The global reference pool (to track pending reference count decrements) is now initialized lazily to avoid the overhead of taking a mutex upon function entry when the functionality is not actually used. [#4178](https://github.com/PyO3/pyo3/pull/4178)
|
||||
- Emit error messages when using `weakref` or `dict` when compiling for `abi3` for Python older than 3.9. [#4194](https://github.com/PyO3/pyo3/pull/4194)
|
||||
- Change `PyType::name` to always match Python `__name__`. [#4196](https://github.com/PyO3/pyo3/pull/4196)
|
||||
- Remove CPython internal ffi call for complex number including: add, sub, mul, div, neg, abs, pow. Added PyAnyMethods::{abs, pos, neg} [#4201](https://github.com/PyO3/pyo3/pull/4201)
|
||||
- Deprecate implicit integer comparision for simple enums in favor of `#[pyclass(eq_int)]`. [#4210](https://github.com/PyO3/pyo3/pull/4210)
|
||||
- Set the `module=` attribute of declarative modules' child `#[pymodule]`s and `#[pyclass]`es. [#4213](https://github.com/PyO3/pyo3/pull/4213)
|
||||
- Set the `module` option for complex enum variants from the value set on the complex enum `module`. [#4228](https://github.com/PyO3/pyo3/pull/4228)
|
||||
- Respect the Python "limited API" when building for the `abi3` feature on PyPy or GraalPy. [#4237](https://github.com/PyO3/pyo3/pull/4237)
|
||||
- Optimize code generated by `#[pyo3(get)]` on `#[pyclass]` fields. [#4254](https://github.com/PyO3/pyo3/pull/4254)
|
||||
- `PyCFunction::new`, `PyCFunction::new_with_keywords` and `PyCFunction::new_closure` now take `&'static CStr` name and doc arguments (previously was `&'static str`). [#4255](https://github.com/PyO3/pyo3/pull/4255)
|
||||
- The `experimental-declarative-modules` feature is now stabilized and available by default. [#4257](https://github.com/PyO3/pyo3/pull/4257)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix panic when `PYO3_CROSS_LIB_DIR` is set to a missing path. [#4043](https://github.com/PyO3/pyo3/pull/4043)
|
||||
- Fix a compile error when exporting an exception created with `create_exception!` living in a different Rust module using the `declarative-module` feature. [#4086](https://github.com/PyO3/pyo3/pull/4086)
|
||||
- Fix FFI definitions of `PY_VECTORCALL_ARGUMENTS_OFFSET` and `PyVectorcall_NARGS` to fix a false-positive assertion. [#4104](https://github.com/PyO3/pyo3/pull/4104)
|
||||
- Disable `PyUnicode_DATA` on PyPy: not exposed by PyPy. [#4116](https://github.com/PyO3/pyo3/pull/4116)
|
||||
- Correctly handle `#[pyo3(from_py_with = ...)]` attribute on dunder (`__magic__`) method arguments instead of silently ignoring it. [#4117](https://github.com/PyO3/pyo3/pull/4117)
|
||||
- Fix a compile error when declaring a standalone function or class method with a Python name that is a Rust keyword. [#4226](https://github.com/PyO3/pyo3/pull/4226)
|
||||
- Fix declarative modules discarding doc comments on the `mod` node. [#4236](https://github.com/PyO3/pyo3/pull/4236)
|
||||
- Fix `__dict__` attribute missing for `#[pyclass(dict)]` instances when building for `abi3` on Python 3.9. [#4251](https://github.com/PyO3/pyo3/pull/4251)
|
||||
|
||||
## [0.21.2] - 2024-04-16
|
||||
|
||||
### Changed
|
||||
|
||||
- Deprecate the `PySet::empty()` gil-ref constructor. [#4082](https://github.com/PyO3/pyo3/pull/4082)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix compile error for `async fn` in `#[pymethods]` with a `&self` receiver and more than one additional argument. [#4035](https://github.com/PyO3/pyo3/pull/4035)
|
||||
- Improve error message for wrong receiver type in `__traverse__`. [#4045](https://github.com/PyO3/pyo3/pull/4045)
|
||||
- Fix compile error when exporting a `#[pyclass]` living in a different Rust module using the `experimental-declarative-modules` feature. [#4054](https://github.com/PyO3/pyo3/pull/4054)
|
||||
- Fix `missing_docs` lint triggering on documented `#[pymodule]` functions. [#4067](https://github.com/PyO3/pyo3/pull/4067)
|
||||
- Fix undefined symbol errors for extension modules on AIX (by linking `libpython`). [#4073](https://github.com/PyO3/pyo3/pull/4073)
|
||||
|
||||
## [0.21.1] - 2024-04-01
|
||||
|
||||
### Added
|
||||
|
||||
- Implement `Send` and `Sync` for `PyBackedStr` and `PyBackedBytes`. [#4007](https://github.com/PyO3/pyo3/pull/4007)
|
||||
- Implement `Clone`, `Debug`, `PartialEq`, `Eq`, `PartialOrd`, `Ord` and `Hash` implementation for `PyBackedBytes` and `PyBackedStr`, and `Display` for `PyBackedStr`. [#4020](https://github.com/PyO3/pyo3/pull/4020)
|
||||
- Add `import_exception_bound!` macro to import exception types without generating GIL Ref functionality for them. [#4027](https://github.com/PyO3/pyo3/pull/4027)
|
||||
|
||||
### Changed
|
||||
|
||||
- Emit deprecation warning for uses of GIL Refs as `#[setter]` function arguments. [#3998](https://github.com/PyO3/pyo3/pull/3998)
|
||||
- Add `#[inline]` hints on many `Bound` and `Borrowed` methods. [#4024](https://github.com/PyO3/pyo3/pull/4024)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Handle `#[pyo3(from_py_with = "")]` in `#[setter]` methods [#3995](https://github.com/PyO3/pyo3/pull/3995)
|
||||
- Allow extraction of `&Bound` in `#[setter]` methods. [#3998](https://github.com/PyO3/pyo3/pull/3998)
|
||||
- Fix some uncovered code blocks emitted by `#[pymodule]`, `#[pyfunction]` and `#[pyclass]` macros. [#4009](https://github.com/PyO3/pyo3/pull/4009)
|
||||
- Fix typo in the panic message when a class referenced in `pyo3::import_exception!` does not exist. [#4012](https://github.com/PyO3/pyo3/pull/4012)
|
||||
- Fix compile error when using an async `#[pymethod]` with a receiver and additional arguments. [#4015](https://github.com/PyO3/pyo3/pull/4015)
|
||||
|
||||
|
||||
## [0.21.0] - 2024-03-25
|
||||
|
||||
### Added
|
||||
|
||||
- Add support for GraalPy (24.0 and up). [#3247](https://github.com/PyO3/pyo3/pull/3247)
|
||||
- Add `PyMemoryView` type. [#3514](https://github.com/PyO3/pyo3/pull/3514)
|
||||
- Allow `async fn` in for `#[pyfunction]` and `#[pymethods]`, with the `experimental-async` feature. [#3540](https://github.com/PyO3/pyo3/pull/3540) [#3588](https://github.com/PyO3/pyo3/pull/3588) [#3599](https://github.com/PyO3/pyo3/pull/3599) [#3931](https://github.com/PyO3/pyo3/pull/3931)
|
||||
- Implement `PyTypeInfo` for `PyEllipsis`, `PyNone` and `PyNotImplemented`. [#3577](https://github.com/PyO3/pyo3/pull/3577)
|
||||
- Support `#[pyclass]` on enums that have non-unit variants. [#3582](https://github.com/PyO3/pyo3/pull/3582)
|
||||
- Support `chrono` feature with `abi3` feature. [#3664](https://github.com/PyO3/pyo3/pull/3664)
|
||||
- `FromPyObject`, `IntoPy<PyObject>` and `ToPyObject` are implemented on `std::duration::Duration` [#3670](https://github.com/PyO3/pyo3/pull/3670)
|
||||
- Add `PyString::to_cow`. Add `Py<PyString>::to_str`, `Py<PyString>::to_cow`, and `Py<PyString>::to_string_lossy`, as ways to access Python string data safely beyond the GIL lifetime. [#3677](https://github.com/PyO3/pyo3/pull/3677)
|
||||
- Add `Bound<T>` and `Borrowed<T>` smart pointers as a new API for accessing Python objects. [#3686](https://github.com/PyO3/pyo3/pull/3686)
|
||||
- Add `PyNativeType::as_borrowed` to convert "GIL refs" to the new `Bound` smart pointer. [#3692](https://github.com/PyO3/pyo3/pull/3692)
|
||||
- Add `FromPyObject::extract_bound` method, to migrate `FromPyObject` implementations to the Bound API. [#3706](https://github.com/PyO3/pyo3/pull/3706)
|
||||
- Add `gil-refs` feature to allow continued use of the deprecated GIL Refs APIs. [#3707](https://github.com/PyO3/pyo3/pull/3707)
|
||||
- Add methods to `PyAnyMethods` for binary operators (`add`, `sub`, etc.) [#3712](https://github.com/PyO3/pyo3/pull/3712)
|
||||
- Add `chrono-tz` feature allowing conversion between `chrono_tz::Tz` and `zoneinfo.ZoneInfo` [#3730](https://github.com/PyO3/pyo3/pull/3730)
|
||||
- Add FFI definition `PyType_GetModuleByDef`. [#3734](https://github.com/PyO3/pyo3/pull/3734)
|
||||
- Conversion between `std::time::SystemTime` and `datetime.datetime` [#3736](https://github.com/PyO3/pyo3/pull/3736)
|
||||
- Add `Py::as_any` and `Py::into_any`. [#3785](https://github.com/PyO3/pyo3/pull/3785)
|
||||
- Add `PyStringMethods::encode_utf8`. [#3801](https://github.com/PyO3/pyo3/pull/3801)
|
||||
- Add `PyBackedStr` and `PyBackedBytes`, as alternatives to `&str` and `&bytes` where a Python object owns the data. [#3802](https://github.com/PyO3/pyo3/pull/3802) [#3991](https://github.com/PyO3/pyo3/pull/3991)
|
||||
- Allow `#[pymodule]` macro on Rust `mod` blocks, with the `experimental-declarative-modules` feature. [#3815](https://github.com/PyO3/pyo3/pull/3815)
|
||||
- Implement `ExactSizeIterator` for `set` and `frozenset` iterators on `abi3` feature. [#3849](https://github.com/PyO3/pyo3/pull/3849)
|
||||
- Add `Py::drop_ref` to explicitly drop a `Py`` and immediately decrease the Python reference count if the GIL is already held. [#3871](https://github.com/PyO3/pyo3/pull/3871)
|
||||
- Allow `#[pymodule]` macro on single argument functions that take `&Bound<'_, PyModule>`. [#3905](https://github.com/PyO3/pyo3/pull/3905)
|
||||
- Implement `FromPyObject` for `Cow<str>`. [#3928](https://github.com/PyO3/pyo3/pull/3928)
|
||||
- Implement `Default` for `GILOnceCell`. [#3971](https://github.com/PyO3/pyo3/pull/3971)
|
||||
- Add `PyDictMethods::into_mapping`, `PyListMethods::into_sequence` and `PyTupleMethods::into_sequence`. [#3982](https://github.com/PyO3/pyo3/pull/3982)
|
||||
|
||||
### Changed
|
||||
|
||||
- `PyDict::from_sequence` now takes a single argument of type `&PyAny` (previously took two arguments `Python` and `PyObject`). [#3532](https://github.com/PyO3/pyo3/pull/3532)
|
||||
- Deprecate `Py::is_ellipsis` and `PyAny::is_ellipsis` in favour of `any.is(py.Ellipsis())`. [#3577](https://github.com/PyO3/pyo3/pull/3577)
|
||||
- Split some `PyTypeInfo` functionality into new traits `HasPyGilRef` and `PyTypeCheck`. [#3600](https://github.com/PyO3/pyo3/pull/3600)
|
||||
- Deprecate `PyTryFrom` and `PyTryInto` traits in favor of `any.downcast()` via the `PyTypeCheck` and `PyTypeInfo` traits. [#3601](https://github.com/PyO3/pyo3/pull/3601)
|
||||
- Allow async methods to accept `&self`/`&mut self` [#3609](https://github.com/PyO3/pyo3/pull/3609)
|
||||
- `FromPyObject` for set types now also accept `frozenset` objects as input. [#3632](https://github.com/PyO3/pyo3/pull/3632)
|
||||
- `FromPyObject` for `bool` now also accepts NumPy's `bool_` as input. [#3638](https://github.com/PyO3/pyo3/pull/3638)
|
||||
- Add `AsRefSource` associated type to `PyNativeType`. [#3653](https://github.com/PyO3/pyo3/pull/3653)
|
||||
- Rename `.is_true` to `.is_truthy` on `PyAny` and `Py<PyAny>` to clarify that the test is not based on identity with or equality to the True singleton. [#3657](https://github.com/PyO3/pyo3/pull/3657)
|
||||
- `PyType::name` is now `PyType::qualname` whereas `PyType::name` efficiently accesses the full name which includes the module name. [#3660](https://github.com/PyO3/pyo3/pull/3660)
|
||||
- The `Iter(A)NextOutput` types are now deprecated and `__(a)next__` can directly return anything which can be converted into Python objects, i.e. awaitables do not need to be wrapped into `IterANextOutput` or `Option` any more. `Option` can still be used as well and returning `None` will trigger the fast path for `__next__`, stopping iteration without having to raise a `StopIteration` exception. [#3661](https://github.com/PyO3/pyo3/pull/3661)
|
||||
- Implement `FromPyObject` on `chrono::DateTime<Tz>` for all `Tz`, not just `FixedOffset` and `Utc`. [#3663](https://github.com/PyO3/pyo3/pull/3663)
|
||||
- Add lifetime parameter to `PyTzInfoAccess` trait. For the deprecated gil-ref API, the trait is now implemented for `&'py PyTime` and `&'py PyDateTime` instead of `PyTime` and `PyDate`. [#3679](https://github.com/PyO3/pyo3/pull/3679)
|
||||
- Calls to `__traverse__` become no-ops for unsendable pyclasses if on the wrong thread, thereby avoiding hard aborts at the cost of potential leakage. [#3689](https://github.com/PyO3/pyo3/pull/3689)
|
||||
- Include `PyNativeType` in `pyo3::prelude`. [#3692](https://github.com/PyO3/pyo3/pull/3692)
|
||||
- Improve performance of `extract::<i64>` (and other integer types) by avoiding call to `__index__()` converting the value to an integer for 3.10+. Gives performance improvement of around 30% for successful extraction. [#3742](https://github.com/PyO3/pyo3/pull/3742)
|
||||
- Relax bound of `FromPyObject` for `Py<T>` to just `T: PyTypeCheck`. [#3776](https://github.com/PyO3/pyo3/pull/3776)
|
||||
- `PySet` and `PyFrozenSet` iterators now always iterate the equivalent of `iter(set)`. (A "fast path" with no noticeable performance benefit was removed.) [#3849](https://github.com/PyO3/pyo3/pull/3849)
|
||||
- Move implementations of `FromPyObject` for `&str`, `Cow<str>`, `&[u8]` and `Cow<[u8]>` onto a temporary trait `FromPyObjectBound` when `gil-refs` feature is deactivated. [#3928](https://github.com/PyO3/pyo3/pull/3928)
|
||||
- Deprecate `GILPool`, `Python::with_pool`, and `Python::new_pool`. [#3947](https://github.com/PyO3/pyo3/pull/3947)
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove all functionality deprecated in PyO3 0.19. [#3603](https://github.com/PyO3/pyo3/pull/3603)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Match PyPy 7.3.14 in removing PyPy-only symbol `Py_MAX_NDIMS` in favour of `PyBUF_MAX_NDIM`. [#3757](https://github.com/PyO3/pyo3/pull/3757)
|
||||
- Fix segmentation fault using `datetime` types when an invalid `datetime` module is on sys.path. [#3818](https://github.com/PyO3/pyo3/pull/3818)
|
||||
- Fix `non_local_definitions` lint warning triggered by many PyO3 macros. [#3901](https://github.com/PyO3/pyo3/pull/3901)
|
||||
- Disable `PyCode` and `PyCode_Type` on PyPy: `PyCode_Type` is not exposed by PyPy. [#3934](https://github.com/PyO3/pyo3/pull/3934)
|
||||
|
||||
## [0.21.0-beta.0] - 2024-03-10
|
||||
|
||||
Prerelease of PyO3 0.21. See [the GitHub diff](https://github.com/pyo3/pyo3/compare/v0.21.0-beta.0...v0.21.0) for what changed between 0.21.0-beta.0 and the final release.
|
||||
|
||||
## [0.20.3] - 2024-02-23
|
||||
|
||||
### Packaging
|
||||
|
||||
- Add `portable-atomic` dependency. [#3619](https://github.com/PyO3/pyo3/pull/3619)
|
||||
- Check maximum version of Python at build time and for versions not yet supported require opt-in to the `abi3` stable ABI by the environment variable `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1`. [#3821](https://github.com/PyO3/pyo3/pull/3821)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Use `portable-atomic` to support platforms without 64-bit atomics. [#3619](https://github.com/PyO3/pyo3/pull/3619)
|
||||
- Fix compilation failure with `either` feature enabled without `experimental-inspect` enabled. [#3834](https://github.com/PyO3/pyo3/pull/3834)
|
||||
|
||||
## [0.20.2] - 2024-01-04
|
||||
|
||||
### Packaging
|
||||
|
||||
- Pin `pyo3` and `pyo3-ffi` dependencies on `pyo3-build-config` to require the same patch version, i.e. `pyo3` 0.20.2 requires _exactly_ `pyo3-build-config` 0.20.2. [#3721](https://github.com/PyO3/pyo3/pull/3721)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix compile failure when building `pyo3` 0.20.0 with latest `pyo3-build-config` 0.20.X. [#3724](https://github.com/PyO3/pyo3/pull/3724)
|
||||
- Fix docs.rs build. [#3722](https://github.com/PyO3/pyo3/pull/3722)
|
||||
|
||||
## [0.20.1] - 2023-12-30
|
||||
|
||||
### Added
|
||||
|
||||
- Add optional `either` feature to add conversions for `either::Either<L, R>` sum type. [#3456](https://github.com/PyO3/pyo3/pull/3456)
|
||||
- Add optional `smallvec` feature to add conversions for `smallvec::SmallVec`. [#3507](https://github.com/PyO3/pyo3/pull/3507)
|
||||
- Add `take` and `into_inner` methods to `GILOnceCell` [#3556](https://github.com/PyO3/pyo3/pull/3556)
|
||||
- `#[classmethod]` methods can now also receive `Py<PyType>` as their first argument. [#3587](https://github.com/PyO3/pyo3/pull/3587)
|
||||
- `#[pyfunction(pass_module)]` can now also receive `Py<PyModule>` as their first argument. [#3587](https://github.com/PyO3/pyo3/pull/3587)
|
||||
- Add `traverse` method to `GILProtected`. [#3616](https://github.com/PyO3/pyo3/pull/3616)
|
||||
- Added `abi3-py312` feature [#3687](https://github.com/PyO3/pyo3/pull/3687)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix minimum version specification for optional `chrono` dependency. [#3512](https://github.com/PyO3/pyo3/pull/3512)
|
||||
- Silenced new `clippy::unnecessary_fallible_conversions` warning when using a `Py<Self>` `self` receiver. [#3564](https://github.com/PyO3/pyo3/pull/3564)
|
||||
|
||||
|
||||
## [0.20.0] - 2023-10-11
|
||||
|
||||
### Packaging
|
||||
|
@ -571,7 +796,7 @@ To see unreleased changes, please see the [CHANGELOG on the main branch guide](h
|
|||
- Respect Rust privacy rules for items wrapped with `wrap_pyfunction` and `wrap_pymodule`. [#2081](https://github.com/PyO3/pyo3/pull/2081)
|
||||
- Add modulo argument to `__ipow__` magic method. [#2083](https://github.com/PyO3/pyo3/pull/2083)
|
||||
- Fix FFI definition for `_PyCFunctionFast`. [#2126](https://github.com/PyO3/pyo3/pull/2126)
|
||||
- `PyDateTimeAPI` and `PyDateTime_TimeZone_UTC` are are now unsafe functions instead of statics. [#2126](https://github.com/PyO3/pyo3/pull/2126)
|
||||
- `PyDateTimeAPI` and `PyDateTime_TimeZone_UTC` are now unsafe functions instead of statics. [#2126](https://github.com/PyO3/pyo3/pull/2126)
|
||||
- `PyDateTimeAPI` does not implicitly call `PyDateTime_IMPORT` anymore to reflect the original Python API more closely. Before the first call to `PyDateTime_IMPORT` a null pointer is returned. Therefore before calling any of the following FFI functions `PyDateTime_IMPORT` must be called to avoid undefined behavior: [#2126](https://github.com/PyO3/pyo3/pull/2126)
|
||||
- `PyDateTime_TimeZone_UTC`
|
||||
- `PyDate_Check`
|
||||
|
@ -1599,7 +1824,16 @@ Yanked
|
|||
|
||||
- Initial release
|
||||
|
||||
[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.20.0...HEAD
|
||||
[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.22.1...HEAD
|
||||
[0.22.1]: https://github.com/pyo3/pyo3/compare/v0.22.0...v0.22.1
|
||||
[0.22.0]: https://github.com/pyo3/pyo3/compare/v0.21.2...v0.22.0
|
||||
[0.21.2]: https://github.com/pyo3/pyo3/compare/v0.21.1...v0.21.2
|
||||
[0.21.1]: https://github.com/pyo3/pyo3/compare/v0.21.0...v0.21.1
|
||||
[0.21.0]: https://github.com/pyo3/pyo3/compare/v0.20.3...v0.21.0
|
||||
[0.21.0-beta.0]: https://github.com/pyo3/pyo3/compare/v0.20.3...v0.21.0-beta.0
|
||||
[0.20.3]: https://github.com/pyo3/pyo3/compare/v0.20.2...v0.20.3
|
||||
[0.20.2]: https://github.com/pyo3/pyo3/compare/v0.20.1...v0.20.2
|
||||
[0.20.1]: https://github.com/pyo3/pyo3/compare/v0.20.0...v0.20.1
|
||||
[0.20.0]: https://github.com/pyo3/pyo3/compare/v0.19.2...v0.20.0
|
||||
[0.19.2]: https://github.com/pyo3/pyo3/compare/v0.19.1...v0.19.2
|
||||
[0.19.1]: https://github.com/pyo3/pyo3/compare/v0.19.0...v0.19.1
|
||||
|
@ -1643,7 +1877,7 @@ Yanked
|
|||
[0.9.2]: https://github.com/pyo3/pyo3/compare/v0.9.1...v0.9.2
|
||||
[0.9.1]: https://github.com/pyo3/pyo3/compare/v0.9.0...v0.9.1
|
||||
[0.9.0]: https://github.com/pyo3/pyo3/compare/v0.8.5...v0.9.0
|
||||
[0.8.4]: https://github.com/pyo3/pyo3/compare/v0.8.4...v0.8.5
|
||||
[0.8.5]: https://github.com/pyo3/pyo3/compare/v0.8.4...v0.8.5
|
||||
[0.8.4]: https://github.com/pyo3/pyo3/compare/v0.8.3...v0.8.4
|
||||
[0.8.3]: https://github.com/pyo3/pyo3/compare/v0.8.2...v0.8.3
|
||||
[0.8.2]: https://github.com/pyo3/pyo3/compare/v0.8.1...v0.8.2
|
||||
|
@ -1652,7 +1886,8 @@ Yanked
|
|||
[0.7.0]: https://github.com/pyo3/pyo3/compare/v0.6.0...v0.7.0
|
||||
[0.6.0]: https://github.com/pyo3/pyo3/compare/v0.5.3...v0.6.0
|
||||
[0.5.3]: https://github.com/pyo3/pyo3/compare/v0.5.2...v0.5.3
|
||||
[0.5.2]: https://github.com/pyo3/pyo3/compare/v0.5.0...v0.5.2
|
||||
[0.5.2]: https://github.com/pyo3/pyo3/compare/v0.5.1...v0.5.2
|
||||
[0.5.1]: https://github.com/pyo3/pyo3/compare/v0.5.0...v0.5.1
|
||||
[0.5.0]: https://github.com/pyo3/pyo3/compare/v0.4.1...v0.5.0
|
||||
[0.4.1]: https://github.com/pyo3/pyo3/compare/v0.4.0...v0.4.1
|
||||
[0.4.0]: https://github.com/pyo3/pyo3/compare/v0.3.2...v0.4.0
|
||||
|
|
100
Cargo.toml
100
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3"
|
||||
version = "0.20.0"
|
||||
version = "0.23.0-dev"
|
||||
description = "Bindings to Python interpreter"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
readme = "README.md"
|
||||
|
@ -12,19 +12,19 @@ categories = ["api-bindings", "development-tools::ffi"]
|
|||
license = "MIT OR Apache-2.0"
|
||||
exclude = ["/.gitignore", ".cargo/config", "/codecov.yml", "/Makefile", "/pyproject.toml", "/noxfile.py", "/.github", "/tests/test_compile_error.rs", "/tests/ui"]
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
rust-version = "1.63"
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1.0"
|
||||
libc = "0.2.62"
|
||||
parking_lot = ">= 0.11, < 0.13"
|
||||
memoffset = "0.9"
|
||||
once_cell = "1.13"
|
||||
|
||||
# ffi bindings to the python interpreter, split into a separate crate so they can be used independently
|
||||
pyo3-ffi = { path = "pyo3-ffi", version = "=0.20.0" }
|
||||
pyo3-ffi = { path = "pyo3-ffi", version = "=0.23.0-dev" }
|
||||
|
||||
# support crates for macros feature
|
||||
pyo3-macros = { path = "pyo3-macros", version = "=0.20.0", optional = true }
|
||||
pyo3-macros = { path = "pyo3-macros", version = "=0.23.0-dev", optional = true }
|
||||
indoc = { version = "2.0.1", optional = true }
|
||||
unindent = { version = "0.2.1", optional = true }
|
||||
|
||||
|
@ -32,35 +32,45 @@ unindent = { version = "0.2.1", optional = true }
|
|||
inventory = { version = "0.3.0", optional = true }
|
||||
|
||||
# crate integrations that can be added using the eponymous features
|
||||
anyhow = { version = "1.0", optional = true }
|
||||
chrono = { version = "0.4", default-features = false, optional = true }
|
||||
anyhow = { version = "1.0.1", optional = true }
|
||||
chrono = { version = "0.4.25", default-features = false, optional = true }
|
||||
chrono-tz = { version = ">= 0.6, < 0.10", default-features = false, optional = true }
|
||||
either = { version = "1.9", optional = true }
|
||||
eyre = { version = ">= 0.4, < 0.7", optional = true }
|
||||
hashbrown = { version = ">= 0.9, < 0.15", optional = true }
|
||||
indexmap = { version = ">= 1.6, < 3", optional = true }
|
||||
num-bigint = { version = "0.4", optional = true }
|
||||
num-bigint = { version = "0.4.2", optional = true }
|
||||
num-complex = { version = ">= 0.2, < 0.5", optional = true }
|
||||
rust_decimal = { version = "1.0.0", default-features = false, optional = true }
|
||||
num-rational = {version = "0.4.1", optional = true }
|
||||
rust_decimal = { version = "1.15", default-features = false, optional = true }
|
||||
serde = { version = "1.0", optional = true }
|
||||
smallvec = { version = "1.0", optional = true }
|
||||
|
||||
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
|
||||
portable-atomic = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_approx_eq = "1.1.0"
|
||||
chrono = { version = "0.4.25" }
|
||||
chrono = "0.4.25"
|
||||
chrono-tz = ">= 0.6, < 0.10"
|
||||
# Required for "and $N others" normalization
|
||||
trybuild = ">=1.0.70"
|
||||
proptest = { version = "1.0", default-features = false, features = ["std"] }
|
||||
send_wrapper = "0.6"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.61"
|
||||
rayon = "1.0.2"
|
||||
rust_decimal = { version = "1.8.0", features = ["std"] }
|
||||
widestring = "0.5.1"
|
||||
rayon = "1.6.1"
|
||||
futures = "0.3.28"
|
||||
|
||||
[build-dependencies]
|
||||
pyo3-build-config = { path = "pyo3-build-config", version = "0.20.0", features = ["resolve-config"] }
|
||||
pyo3-build-config = { path = "pyo3-build-config", version = "=0.23.0-dev", features = ["resolve-config"] }
|
||||
|
||||
[features]
|
||||
default = ["macros"]
|
||||
|
||||
# Enables support for `async fn` for `#[pyfunction]` and `#[pymethods]`.
|
||||
experimental-async = ["macros", "pyo3-macros/experimental-async"]
|
||||
|
||||
# Enables pyo3::inspect module and additional type information on FromPyObject
|
||||
# and IntoPy traits
|
||||
experimental-inspect = []
|
||||
|
@ -77,14 +87,15 @@ multiple-pymethods = ["inventory", "pyo3-macros/multiple-pymethods"]
|
|||
extension-module = ["pyo3-ffi/extension-module"]
|
||||
|
||||
# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more.
|
||||
abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3", "pyo3-macros/abi3"]
|
||||
abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3"]
|
||||
|
||||
# With abi3, we can manually set the minimum Python version.
|
||||
abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37", "pyo3-ffi/abi3-py37"]
|
||||
abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38", "pyo3-ffi/abi3-py38"]
|
||||
abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39", "pyo3-ffi/abi3-py39"]
|
||||
abi3-py310 = ["abi3-py311", "pyo3-build-config/abi3-py310", "pyo3-ffi/abi3-py310"]
|
||||
abi3-py311 = ["abi3", "pyo3-build-config/abi3-py311", "pyo3-ffi/abi3-py311"]
|
||||
abi3-py311 = ["abi3-py312", "pyo3-build-config/abi3-py311", "pyo3-ffi/abi3-py311"]
|
||||
abi3-py312 = ["abi3", "pyo3-build-config/abi3-py312", "pyo3-ffi/abi3-py312"]
|
||||
|
||||
# Automatically generates `python3.dll` import libraries for Windows targets.
|
||||
generate-import-lib = ["pyo3-ffi/generate-import-lib"]
|
||||
|
@ -92,6 +103,12 @@ generate-import-lib = ["pyo3-ffi/generate-import-lib"]
|
|||
# Changes `Python::with_gil` to automatically initialize the Python interpreter if needed.
|
||||
auto-initialize = []
|
||||
|
||||
# Allows use of the deprecated "GIL Refs" APIs.
|
||||
gil-refs = ["pyo3-macros/gil-refs"]
|
||||
|
||||
# Enables `Clone`ing references to Python objects `Py<T>` which panics if the GIL is not held.
|
||||
py-clone = []
|
||||
|
||||
# Optimizes PyObject to Vec conversion and so on.
|
||||
nightly = []
|
||||
|
||||
|
@ -99,17 +116,23 @@ nightly = []
|
|||
# This is mostly intended for testing purposes - activating *all* of these isn't particularly useful.
|
||||
full = [
|
||||
"macros",
|
||||
# "multiple-pymethods", # TODO re-add this when MSRV is greater than 1.62
|
||||
# "multiple-pymethods", # Not supported by wasm
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"either",
|
||||
"experimental-async",
|
||||
"experimental-inspect",
|
||||
"eyre",
|
||||
"hashbrown",
|
||||
"indexmap",
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
"indexmap",
|
||||
"eyre",
|
||||
"anyhow",
|
||||
"experimental-inspect",
|
||||
"num-rational",
|
||||
"py-clone",
|
||||
"rust_decimal",
|
||||
"serde",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[workspace]
|
||||
|
@ -124,5 +147,34 @@ members = [
|
|||
|
||||
[package.metadata.docs.rs]
|
||||
no-default-features = true
|
||||
features = ["macros", "num-bigint", "num-complex", "hashbrown", "serde", "multiple-pymethods", "indexmap", "eyre", "chrono", "rust_decimal"]
|
||||
features = ["full", "gil-refs"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[workspace.lints.clippy]
|
||||
checked_conversions = "warn"
|
||||
dbg_macro = "warn"
|
||||
explicit_into_iter_loop = "warn"
|
||||
explicit_iter_loop = "warn"
|
||||
filter_map_next = "warn"
|
||||
flat_map_option = "warn"
|
||||
let_unit_value = "warn"
|
||||
manual_assert = "warn"
|
||||
manual_ok_or = "warn"
|
||||
todo = "warn"
|
||||
unnecessary_wraps = "warn"
|
||||
useless_transmute = "warn"
|
||||
used_underscore_binding = "warn"
|
||||
|
||||
[workspace.lints.rust]
|
||||
elided_lifetimes_in_paths = "warn"
|
||||
invalid_doc_attributes = "warn"
|
||||
rust_2018_idioms = { level = "warn", priority = -1 }
|
||||
rust_2021_prelude_collisions = "warn"
|
||||
unused_lifetimes = "warn"
|
||||
|
||||
[workspace.lints.rustdoc]
|
||||
broken_intra_doc_links = "warn"
|
||||
bare_urls = "warn"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -23,19 +23,15 @@ To work and develop PyO3, you need Python & Rust installed on your system.
|
|||
* [virtualenv](https://virtualenv.pypa.io/en/latest/) can also be used with or without Pyenv to use specific installed Python versions.
|
||||
* [`nox`][nox] is used to automate many of our CI tasks.
|
||||
|
||||
### Caveats
|
||||
|
||||
* When using pyenv on macOS, installing a Python version using `--enable-shared` is required to make it work. i.e `env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.7.12`
|
||||
|
||||
### Help users identify bugs
|
||||
|
||||
The [PyO3 Gitter channel](https://gitter.im/PyO3/Lobby) is very active with users who are new to PyO3, and often completely new to Rust. Helping them debug is a great way to get experience with the PyO3 codebase.
|
||||
The [PyO3 Discord server](https://discord.gg/33kcChzH7f) is very active with users who are new to PyO3, and often completely new to Rust. Helping them debug is a great way to get experience with the PyO3 codebase.
|
||||
|
||||
Helping others often reveals bugs, documentation weaknesses, and missing APIs. It's a good idea to open GitHub issues for these immediately so the resolution can be designed and implemented!
|
||||
|
||||
### Implement issues ready for development
|
||||
|
||||
Issues where the solution is clear and work is not in progress use the [needs-implementer](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Aneeds-implemeter) label.
|
||||
Issues where the solution is clear and work is not in progress use the [needs-implementer](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Aneeds-implementer) label.
|
||||
|
||||
Don't be afraid if the solution is not clear to you! The core PyO3 contributors will be happy to mentor you through any questions you have to help you write the solution.
|
||||
|
||||
|
@ -48,7 +44,7 @@ There are some specific areas of focus where help is currently needed for the do
|
|||
- Issues requesting documentation improvements are tracked with the [documentation](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Adocumentation) label.
|
||||
- Not all APIs had docs or examples when they were made. The goal is to have documentation on all PyO3 APIs ([#306](https://github.com/PyO3/pyo3/issues/306)). If you see an API lacking a doc, please write one and open a PR!
|
||||
|
||||
You can build the docs (including all features) with
|
||||
To build the docs (including all features), install [`nox`][nox] and then run
|
||||
|
||||
```shell
|
||||
nox -s docs -- open
|
||||
|
@ -70,6 +66,12 @@ First, install [`mdbook`][mdbook] and [`nox`][nox]. Then, run
|
|||
nox -s build-guide -- --open
|
||||
```
|
||||
|
||||
To check all links in the guide are valid, also install [`lychee`][lychee] and use the `check-guide` session instead:
|
||||
|
||||
```shell
|
||||
nox -s check-guide
|
||||
```
|
||||
|
||||
### Help design the next PyO3
|
||||
|
||||
Issues which don't yet have a clear solution use the [needs-design](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Aneeds-design) label.
|
||||
|
@ -86,19 +88,42 @@ Here are a few things to note when you are writing PRs.
|
|||
|
||||
### Continuous Integration
|
||||
|
||||
The PyO3 repo uses GitHub Actions. PRs are blocked from merging if CI is not successful.
|
||||
|
||||
Formatting, linting and tests are checked for all Rust and Python code. In addition, all warnings in Rust code are disallowed (using `RUSTFLAGS="-D warnings"`).
|
||||
The PyO3 repo uses GitHub Actions. PRs are blocked from merging if CI is not successful. Formatting, linting and tests are checked for all Rust and Python code. In addition, all warnings in Rust code are disallowed (using `RUSTFLAGS="-D warnings"`).
|
||||
|
||||
Tests run with all supported Python versions with the latest stable Rust compiler, as well as for Python 3.9 with the minimum supported Rust version.
|
||||
|
||||
If you are adding a new feature, you should add it to the `full` feature in our *Cargo.toml** so that it is tested in CI.
|
||||
|
||||
You can run these tests yourself with
|
||||
```nox```
|
||||
and
|
||||
```nox -l```
|
||||
lists further commands you can run.
|
||||
`nox`. Use `nox -l` to list the full set of subcommands you can run.
|
||||
|
||||
#### Linting Python code
|
||||
`nox -s ruff`
|
||||
|
||||
#### Linting Rust code
|
||||
`nox -s rustfmt`
|
||||
|
||||
#### Semver checks
|
||||
`cargo semver-checks check-release`
|
||||
|
||||
#### Clippy
|
||||
`nox -s clippy-all`
|
||||
|
||||
#### Tests
|
||||
`cargo test --features full`
|
||||
|
||||
#### Check all conditional compilation
|
||||
`nox -s check-feature-powerset`
|
||||
|
||||
#### UI Tests
|
||||
|
||||
PyO3 uses [`trybuild`](https://github.com/dtolnay/trybuild) to develop UI tests to capture error messages from the Rust compiler for some of the macro functionality.
|
||||
|
||||
Because there are several feature combinations for these UI tests, when updating them all (e.g. for a new Rust compiler version) it may be helpful to use the `update-ui-tests` nox session:
|
||||
|
||||
```bash
|
||||
nox -s update-ui-tests
|
||||
```
|
||||
|
||||
### Documenting changes
|
||||
|
||||
|
@ -171,15 +196,20 @@ First, there are Rust-based benchmarks located in the `pyo3-benches` subdirector
|
|||
|
||||
nox -s bench
|
||||
|
||||
Second, there is a Python-based benchmark contained in the `pytests` subdirectory. You can read more about it [here](pytests).
|
||||
Second, there is a Python-based benchmark contained in the `pytests` subdirectory. You can read more about it [here](https://github.com/PyO3/pyo3/tree/main/pytests).
|
||||
|
||||
## Code coverage
|
||||
|
||||
You can view what code is and isn't covered by PyO3's tests. We aim to have 100% coverage - please check coverage and add tests if you notice a lack of coverage!
|
||||
|
||||
- First, generate a `lcov.info` file with
|
||||
- First, ensure the llvm-cov cargo plugin is installed. You may need to run the plugin through cargo once before using it with `nox`.
|
||||
```shell
|
||||
nox -s coverage
|
||||
cargo install cargo-llvm-cov
|
||||
cargo llvm-cov
|
||||
```
|
||||
- Then, generate an `lcov.info` file with
|
||||
```shell
|
||||
nox -s coverage -- lcov
|
||||
```
|
||||
You can install an IDE plugin to view the coverage. For example, if you use VSCode:
|
||||
- Add the [coverage-gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) plugin.
|
||||
|
@ -198,7 +228,7 @@ You can install an IDE plugin to view the coverage. For example, if you use VSCo
|
|||
|
||||
## Sponsor this project
|
||||
|
||||
At the moment there is no official organisation that accepts sponsorship on PyO3's behalf. If you're seeking to provide significant funding to the PyO3 ecosystem, please reach out to us on [GitHub](https://github.com/PyO3/pyo3/issues/new) or [Gitter](https://gitter.im/PyO3/Lobby) and we can discuss.
|
||||
At the moment there is no official organisation that accepts sponsorship on PyO3's behalf. If you're seeking to provide significant funding to the PyO3 ecosystem, please reach out to us on [GitHub](https://github.com/PyO3/pyo3/issues/new) or [Discord](https://discord.gg/33kcChzH7f) and we can discuss.
|
||||
|
||||
In the meanwhile, some of our maintainers have personal GitHub sponsorship pages and would be grateful for your support:
|
||||
|
||||
|
@ -206,4 +236,5 @@ In the meanwhile, some of our maintainers have personal GitHub sponsorship pages
|
|||
- [messense](https://github.com/sponsors/messense)
|
||||
|
||||
[mdbook]: https://rust-lang.github.io/mdBook/cli/index.html
|
||||
[lychee]: https://github.com/lycheeverse/lychee
|
||||
[nox]: https://github.com/theacodes/nox
|
||||
|
|
37
README.md
37
README.md
|
@ -1,11 +1,11 @@
|
|||
# PyO3
|
||||
|
||||
[![actions status](https://img.shields.io/github/actions/workflow/status/PyO3/pyo3/ci.yml?branch=main&logo=github&style=)](https://github.com/PyO3/pyo3/actions)
|
||||
[![benchmark](https://img.shields.io/badge/benchmark-✓-Green?logo=github)](https://pyo3.rs/dev/bench/)
|
||||
[![benchmark](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/PyO3/pyo3)
|
||||
[![codecov](https://img.shields.io/codecov/c/gh/PyO3/pyo3?logo=codecov)](https://codecov.io/gh/PyO3/pyo3)
|
||||
[![crates.io](https://img.shields.io/crates/v/pyo3?logo=rust)](https://crates.io/crates/pyo3)
|
||||
[![minimum rustc 1.56](https://img.shields.io/badge/rustc-1.56+-blue?logo=rust)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
|
||||
[![dev chat](https://img.shields.io/gitter/room/PyO3/Lobby?logo=gitter)](https://gitter.im/PyO3/Lobby)
|
||||
[![minimum rustc 1.63](https://img.shields.io/badge/rustc-1.63+-blue?logo=rust)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
|
||||
[![discord server](https://img.shields.io/discord/1209263839632424990?logo=discord)](https://discord.gg/33kcChzH7f)
|
||||
[![contributing notes](https://img.shields.io/badge/contribute-on%20github-Green?logo=github)](https://github.com/PyO3/pyo3/blob/main/Contributing.md)
|
||||
|
||||
[Rust](https://www.rust-lang.org/) bindings for [Python](https://www.python.org/), including tools for creating native Python extension modules. Running and interacting with Python code from a Rust binary is also supported.
|
||||
|
@ -17,8 +17,8 @@
|
|||
## Usage
|
||||
|
||||
PyO3 supports the following software versions:
|
||||
- Python 3.7 and up (CPython and PyPy)
|
||||
- Rust 1.56 and up
|
||||
- Python 3.7 and up (CPython, PyPy, and GraalPy)
|
||||
- Rust 1.63 and up
|
||||
|
||||
You can use PyO3 to write a native Python module in Rust, or to embed Python in a Rust binary. The following sections explain each of these in turn.
|
||||
|
||||
|
@ -68,7 +68,7 @@ name = "string_sum"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
pyo3 = { version = "0.20.0", features = ["extension-module"] }
|
||||
pyo3 = { version = "0.22.1", features = ["extension-module"] }
|
||||
```
|
||||
|
||||
**`src/lib.rs`**
|
||||
|
@ -86,7 +86,7 @@ fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
|
|||
/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
|
||||
/// import the module.
|
||||
#[pymodule]
|
||||
fn string_sum(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
fn string_sum(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ maturin develop
|
|||
|
||||
If you want to be able to run `cargo test` or use this project in a Cargo workspace and are running into linker issues, there are some workarounds in [the FAQ](https://pyo3.rs/latest/faq.html#i-cant-run-cargo-test-or-i-cant-build-in-a-cargo-workspace-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror).
|
||||
|
||||
As well as with `maturin`, it is possible to build using [`setuptools-rust`](https://github.com/PyO3/setuptools-rust) or [manually](https://pyo3.rs/latest/building_and_distribution.html#manual-builds). Both offer more flexibility than `maturin` but require more configuration to get started.
|
||||
As well as with `maturin`, it is possible to build using [`setuptools-rust`](https://github.com/PyO3/setuptools-rust) or [manually](https://pyo3.rs/latest/building-and-distribution.html#manual-builds). Both offer more flexibility than `maturin` but require more configuration to get started.
|
||||
|
||||
### Using Python from Rust
|
||||
|
||||
|
@ -137,7 +137,7 @@ Start a new project with `cargo new` and add `pyo3` to the `Cargo.toml` like th
|
|||
|
||||
```toml
|
||||
[dependencies.pyo3]
|
||||
version = "0.20.0"
|
||||
version = "0.22.1"
|
||||
features = ["auto-initialize"]
|
||||
```
|
||||
|
||||
|
@ -149,12 +149,12 @@ use pyo3::types::IntoPyDict;
|
|||
|
||||
fn main() -> PyResult<()> {
|
||||
Python::with_gil(|py| {
|
||||
let sys = py.import("sys")?;
|
||||
let sys = py.import_bound("sys")?;
|
||||
let version: String = sys.getattr("version")?.extract()?;
|
||||
|
||||
let locals = [("os", py.import("os")?)].into_py_dict(py);
|
||||
let locals = [("os", py.import_bound("os")?)].into_py_dict_bound(py);
|
||||
let code = "os.getenv('USER') or os.getenv('USERNAME') or 'Unknown'";
|
||||
let user: String = py.eval(code, None, Some(&locals))?.extract()?;
|
||||
let user: String = py.eval_bound(code, None, Some(&locals))?.extract()?;
|
||||
|
||||
println!("Hello {}, I'm Python {}", user, version);
|
||||
Ok(())
|
||||
|
@ -162,7 +162,7 @@ fn main() -> PyResult<()> {
|
|||
}
|
||||
```
|
||||
|
||||
The guide has [a section](https://pyo3.rs/latest/python_from_rust.html) with lots of examples
|
||||
The guide has [a section](https://pyo3.rs/latest/python-from-rust.html) with lots of examples
|
||||
about this topic.
|
||||
|
||||
## Tools and libraries
|
||||
|
@ -176,6 +176,7 @@ about this topic.
|
|||
- [pythonize](https://github.com/davidhewitt/pythonize) _Serde serializer for converting Rust objects to JSON-compatible Python objects_
|
||||
- [pyo3-asyncio](https://github.com/awestlake87/pyo3-asyncio) _Utilities for working with Python's Asyncio library and async functions_
|
||||
- [rustimport](https://github.com/mityax/rustimport) _Directly import Rust files or crates from Python, without manual compilation step. Provides pyo3 integration by default and generates pyo3 binding code automatically._
|
||||
- [pyo3-arrow](https://crates.io/crates/pyo3-arrow) _Lightweight [Apache Arrow](https://arrow.apache.org/) integration for pyo3._
|
||||
|
||||
## Examples
|
||||
|
||||
|
@ -192,13 +193,16 @@ about this topic.
|
|||
- [fastuuid](https://github.com/thedrow/fastuuid/) _Python bindings to Rust's UUID library._
|
||||
- [feos](https://github.com/feos-org/feos) _Lightning fast thermodynamic modeling in Rust with fully developed Python interface._
|
||||
- [forust](https://github.com/jinlow/forust) _A lightweight gradient boosted decision tree library written in Rust._
|
||||
- [granian](https://github.com/emmett-framework/granian) _A Rust HTTP server for Python applications._
|
||||
- [greptimedb](https://github.com/GreptimeTeam/greptimedb/tree/main/src/script) _Support [Python scripting](https://docs.greptime.com/user-guide/python-scripts/overview) in the database_
|
||||
- [haem](https://github.com/BooleanCat/haem) _A Python library for working on Bioinformatics problems._
|
||||
- [html-py-ever](https://github.com/PyO3/setuptools-rust/tree/main/examples/html-py-ever) _Using [html5ever](https://github.com/servo/html5ever) through [kuchiki](https://github.com/kuchiki-rs/kuchiki) to speed up html parsing and css-selecting._
|
||||
- [hyperjson](https://github.com/mre/hyperjson) _A hyper-fast Python module for reading/writing JSON data using Rust's serde-json._
|
||||
- [inline-python](https://github.com/fusion-engineering/inline-python) _Inline Python code directly in your Rust code._
|
||||
- [johnnycanencrypt](https://github.com/kushaldas/johnnycanencrypt) OpenPGP library with Yubikey support.
|
||||
- [jsonschema-rs](https://github.com/Stranger6667/jsonschema-rs/tree/master/bindings/python) _Fast JSON Schema validation library._
|
||||
- [mocpy](https://github.com/cds-astro/mocpy) _Astronomical Python library offering data structures for describing any arbitrary coverage regions on the unit sphere._
|
||||
- [opendal](https://github.com/apache/incubator-opendal/tree/main/bindings/python) _A data access layer that allows users to easily and efficiently retrieve data from various storage services in a unified way._
|
||||
- [opendal](https://github.com/apache/opendal/tree/main/bindings/python) _A data access layer that allows users to easily and efficiently retrieve data from various storage services in a unified way._
|
||||
- [orjson](https://github.com/ijl/orjson) _Fast Python JSON library._
|
||||
- [ormsgpack](https://github.com/aviramha/ormsgpack) _Fast Python msgpack library._
|
||||
- [point-process](https://github.com/ManifoldFR/point-process-rust/tree/master/pylib) _High level API for pointprocesses as a Python library._
|
||||
|
@ -208,16 +212,19 @@ about this topic.
|
|||
- [pyheck](https://github.com/kevinheavey/pyheck) _Fast case conversion library, built by wrapping [heck](https://github.com/withoutboats/heck)._
|
||||
- Quite easy to follow as there's not much code.
|
||||
- [pyre](https://github.com/Project-Dream-Weaver/pyre-http) _Fast Python HTTP server written in Rust._
|
||||
- [pyreqwest_impersonate](https://github.com/deedy5/pyreqwest_impersonate) _The fastest python HTTP client that can impersonate web browsers by mimicking their headers and TLS/JA3/JA4/HTTP2 fingerprints._
|
||||
- [ril-py](https://github.com/Cryptex-github/ril-py) _A performant and high-level image processing library for Python written in Rust._
|
||||
- [river](https://github.com/online-ml/river) _Online machine learning in python, the computationally heavy statistics algorithms are implemented in Rust._
|
||||
- [rust-python-coverage](https://github.com/cjermain/rust-python-coverage) _Example PyO3 project with automated test coverage for Rust and Python._
|
||||
- [tiktoken](https://github.com/openai/tiktoken) _A fast BPE tokeniser for use with OpenAI's models._
|
||||
- [tokenizers](https://github.com/huggingface/tokenizers/tree/main/bindings/python) _Python bindings to the Hugging Face tokenizers (NLP) written in Rust._
|
||||
- [tzfpy](http://github.com/ringsaturn/tzfpy) _A fast package to convert longitude/latitude to timezone name._
|
||||
- [utiles](https://github.com/jessekrubin/utiles) _Fast Python web-map tile utilities_
|
||||
- [wasmer-python](https://github.com/wasmerio/wasmer-python) _Python library to run WebAssembly binaries._
|
||||
|
||||
## Articles and other media
|
||||
|
||||
- [(Video) Extending Python with Rust using PyO3](https://www.youtube.com/watch?v=T45ZEmSR1-s) - Dec 16, 2023
|
||||
- [A Week of PyO3 + rust-numpy (How to Speed Up Your Data Pipeline X Times)](https://terencezl.github.io/blog/2023/06/06/a-week-of-pyo3-rust-numpy/) - Jun 6, 2023
|
||||
- [(Podcast) PyO3 with David Hewitt](https://rustacean-station.org/episode/david-hewitt/) - May 19, 2023
|
||||
- [Making Python 100x faster with less than 100 lines of Rust](https://ohadravid.github.io/posts/2023-03-rusty-python/) - Mar 28, 2023
|
||||
|
@ -234,7 +241,7 @@ about this topic.
|
|||
|
||||
Everyone is welcomed to contribute to PyO3! There are many ways to support the project, such as:
|
||||
|
||||
- help PyO3 users with issues on GitHub and Gitter
|
||||
- help PyO3 users with issues on GitHub and [Discord](https://discord.gg/33kcChzH7f)
|
||||
- improve documentation
|
||||
- write features and bugfixes
|
||||
- publish blogs and examples of how to use PyO3
|
||||
|
|
|
@ -6,14 +6,16 @@ This is notes for the current process of releasing a new PyO3 version. Replace `
|
|||
|
||||
Follow the process below to update all required pieces to bump the version. All these changes are done in a single commit because it makes it clear to git readers what happened to bump the version. It also makes it easy to cherry-pick the version bump onto the `main` branch when tidying up branch history at the end of the release process.
|
||||
|
||||
1. Replace all instances of the PyO3 current version with the new version to be released. Places to check:
|
||||
1. Replace all instances of the PyO3 current version and the with the new version to be released. Places to check:
|
||||
- `Cargo.toml` for all PyO3 crates in the repository.
|
||||
- Examples in `README.md`
|
||||
- PyO3 version embedded into documentation like the README.
|
||||
- `pre-script.rhai` templates for the examples.
|
||||
- `[towncrier]` section in `pyproject.toml`.
|
||||
|
||||
Make sure not to modify the CHANGELOG during this step!
|
||||
Some of the above locations may already have the new version with a `-dev` suffix, which needs to be removed.
|
||||
|
||||
**Make sure not to modify the CHANGELOG during this step!**
|
||||
|
||||
2. Run `towncrier build` to generate the CHANGELOG. The version used by `towncrier` should automatically be correct because of the update to `pyproject.toml` in step 1.
|
||||
|
||||
|
|
11
build.rs
11
build.rs
|
@ -16,7 +16,7 @@ fn ensure_auto_initialize_ok(interpreter_config: &InterpreterConfig) -> Result<(
|
|||
\n\
|
||||
For more information, see \
|
||||
https://pyo3.rs/v{pyo3_version}/\
|
||||
building_and_distribution.html#embedding-python-in-rust",
|
||||
building-and-distribution.html#embedding-python-in-rust",
|
||||
pyo3_version = env::var("CARGO_PKG_VERSION").unwrap()
|
||||
);
|
||||
}
|
||||
|
@ -33,17 +33,20 @@ fn ensure_auto_initialize_ok(interpreter_config: &InterpreterConfig) -> Result<(
|
|||
fn configure_pyo3() -> Result<()> {
|
||||
let interpreter_config = pyo3_build_config::get();
|
||||
|
||||
interpreter_config.emit_pyo3_cfgs();
|
||||
|
||||
ensure_auto_initialize_ok(interpreter_config)?;
|
||||
|
||||
// Emit cfgs like `thread_local_const_init`
|
||||
for cfg in interpreter_config.build_script_outputs() {
|
||||
println!("{}", cfg)
|
||||
}
|
||||
|
||||
// Emit cfgs like `invalid_from_utf8_lint`
|
||||
print_feature_cfgs();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pyo3_build_config::print_expected_cfgs();
|
||||
if let Err(e) = configure_pyo3() {
|
||||
eprintln!("error: {}", e.report());
|
||||
std::process::exit(1)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
pybuilddir.txt
|
|
@ -5,10 +5,10 @@ publish = false
|
|||
edition = "2021"
|
||||
|
||||
[dev-dependencies]
|
||||
pyo3 = { version = "0.20.0", path = "..", features = ["auto-initialize", "extension-module"] }
|
||||
pyo3 = { path = "..", features = ["auto-initialize", "extension-module"] }
|
||||
|
||||
[[example]]
|
||||
name = "decorator"
|
||||
path = "decorator/src/lib.rs"
|
||||
crate_type = ["cdylib"]
|
||||
crate-type = ["cdylib"]
|
||||
doc-scrape-examples = true
|
||||
|
|
|
@ -11,6 +11,7 @@ Below is a brief description of each of these:
|
|||
| `setuptools-rust-starter` | A template project which is configured to use [`setuptools_rust`](https://github.com/PyO3/setuptools-rust/) for development. |
|
||||
| `word-count` | A quick performance comparison between word counter implementations written in each of Rust and Python. |
|
||||
| `plugin` | Illustrates how to use Python as a scripting language within a Rust application |
|
||||
| `sequential` | Illustrates how to use pyo3-ffi to write subinterpreter-safe modules |
|
||||
|
||||
## Creating new projects from these examples
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
variable::set("PYO3_VERSION", "0.20.0");
|
||||
variable::set("PYO3_VERSION", "0.23.0-dev");
|
||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||
file::rename(".template/pyproject.toml", "pyproject.toml");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -5,3 +5,6 @@ build-backend = "maturin"
|
|||
[project]
|
||||
name = "{{project-name}}"
|
||||
version = "0.1.0"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
|
|
@ -2,8 +2,7 @@ import nox
|
|||
|
||||
|
||||
@nox.session
|
||||
def python(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install("maturin")
|
||||
session.run_always("maturin", "develop")
|
||||
def python(session: nox.Session):
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
||||
|
|
|
@ -14,3 +14,6 @@ classifiers = [
|
|||
"Operating System :: POSIX",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
pytest>=3.5.0
|
||||
pip>=21.3
|
||||
maturin>=0.12,<0.13
|
|
@ -40,8 +40,8 @@ impl PyCounter {
|
|||
fn __call__(
|
||||
&self,
|
||||
py: Python<'_>,
|
||||
args: &PyTuple,
|
||||
kwargs: Option<&PyDict>,
|
||||
args: &Bound<'_, PyTuple>,
|
||||
kwargs: Option<&Bound<'_, PyDict>>,
|
||||
) -> PyResult<Py<PyAny>> {
|
||||
let old_count = self.count.get();
|
||||
let new_count = old_count + 1;
|
||||
|
@ -51,7 +51,7 @@ impl PyCounter {
|
|||
println!("{} has been called {} time(s).", name, new_count);
|
||||
|
||||
// After doing something, we finally forward the call to the wrapped function
|
||||
let ret = self.wraps.call(py, args, kwargs)?;
|
||||
let ret = self.wraps.call_bound(py, args, kwargs)?;
|
||||
|
||||
// We could do something with the return value of
|
||||
// the function before returning it
|
||||
|
@ -60,7 +60,7 @@ impl PyCounter {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
pub fn decorator(_py: Python<'_>, module: &PyModule) -> PyResult<()> {
|
||||
pub fn decorator(module: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
module.add_class::<PyCounter>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from decorator import Counter
|
||||
|
||||
|
||||
@Counter
|
||||
def say_hello():
|
||||
print("hello")
|
||||
|
|
|
@ -45,7 +45,7 @@ def test_discussion_2598():
|
|||
@Counter
|
||||
def say_hello():
|
||||
if say_hello.count < 2:
|
||||
print(f"hello from decorator")
|
||||
print("hello from decorator")
|
||||
|
||||
say_hello()
|
||||
say_hello()
|
||||
|
|
|
@ -5,3 +5,6 @@ build-backend = "maturin"
|
|||
[project]
|
||||
name = "{{project-name}}"
|
||||
version = "0.1.0"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
|
|
@ -2,8 +2,7 @@ import nox
|
|||
|
||||
|
||||
@nox.session
|
||||
def python(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install("maturin")
|
||||
session.run_always("maturin", "develop")
|
||||
def python(session: nox.Session):
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
||||
|
|
|
@ -14,3 +14,6 @@ classifiers = [
|
|||
"Operating System :: POSIX",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
pytest>=3.5.0
|
||||
pip>=21.3
|
||||
maturin>=1,<2
|
|
@ -2,12 +2,11 @@
|
|||
use pyo3::exceptions::PyTypeError;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PySlice;
|
||||
use std::os::raw::c_long;
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
enum IntOrSlice<'py> {
|
||||
Int(i32),
|
||||
Slice(&'py PySlice),
|
||||
Slice(Bound<'py, PySlice>),
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
|
@ -23,13 +22,13 @@ impl ExampleContainer {
|
|||
ExampleContainer { max_length: 100 }
|
||||
}
|
||||
|
||||
fn __getitem__(&self, key: &PyAny) -> PyResult<i32> {
|
||||
fn __getitem__(&self, key: &Bound<'_, PyAny>) -> PyResult<i32> {
|
||||
if let Ok(position) = key.extract::<i32>() {
|
||||
return Ok(position);
|
||||
} else if let Ok(slice) = key.downcast::<PySlice>() {
|
||||
// METHOD 1 - the use PySliceIndices to help with bounds checking and for cases when only start or end are provided
|
||||
// in this case the start/stop/step all filled in to give valid values based on the max_length given
|
||||
let index = slice.indices(self.max_length as c_long).unwrap();
|
||||
let index = slice.indices(self.max_length as isize).unwrap();
|
||||
let _delta = index.stop - index.start;
|
||||
|
||||
// METHOD 2 - Do the getattr manually really only needed if you have some special cases for stop/_step not being present
|
||||
|
@ -62,8 +61,11 @@ impl ExampleContainer {
|
|||
fn __setitem__(&self, idx: IntOrSlice, value: u32) -> PyResult<()> {
|
||||
match idx {
|
||||
IntOrSlice::Slice(slice) => {
|
||||
let index = slice.indices(self.max_length as c_long).unwrap();
|
||||
println!("Got a slice! {}-{}, step: {}, value: {}", index.start, index.stop, index.step, value);
|
||||
let index = slice.indices(self.max_length as isize).unwrap();
|
||||
println!(
|
||||
"Got a slice! {}-{}, step: {}, value: {}",
|
||||
index.start, index.stop, index.step, value
|
||||
);
|
||||
}
|
||||
IntOrSlice::Int(index) => {
|
||||
println!("Got an index! {} : value: {}", index, value);
|
||||
|
@ -73,9 +75,8 @@ impl ExampleContainer {
|
|||
}
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
#[pyo3(name = "getitem")]
|
||||
fn example(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
#[pymodule(name = "getitem")]
|
||||
fn example(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
// ? -https://github.com/PyO3/maturin/issues/475
|
||||
m.add_class::<ExampleContainer>()?;
|
||||
Ok(())
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
variable::set("PYO3_VERSION", "0.20.0");
|
||||
variable::set("PYO3_VERSION", "0.23.0-dev");
|
||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||
file::rename(".template/pyproject.toml", "pyproject.toml");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -5,3 +5,6 @@ build-backend = "maturin"
|
|||
[project]
|
||||
name = "{{project-name}}"
|
||||
version = "0.1.0"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# import the contents of the Rust library into the Python extension
|
||||
# optional: include the documentation from the Rust module
|
||||
from .maturin_starter import *
|
||||
from .maturin_starter import __all__, __doc__
|
||||
from .maturin_starter import __all__
|
||||
|
||||
# optional: include the documentation from the Rust module
|
||||
from .maturin_starter import __doc__ # noqa: F401
|
||||
|
||||
__all__ = __all__ + ["PythonClass"]
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import nox
|
|||
|
||||
@nox.session
|
||||
def python(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install("maturin")
|
||||
session.run_always("maturin", "develop")
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
||||
|
|
|
@ -14,3 +14,6 @@ classifiers = [
|
|||
"Operating System :: POSIX",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
pytest>=3.5.0
|
||||
pip>=21.3
|
||||
maturin>=0.12,<0.13
|
|
@ -20,15 +20,15 @@ impl ExampleClass {
|
|||
|
||||
/// An example module implemented in Rust using PyO3.
|
||||
#[pymodule]
|
||||
fn maturin_starter(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
fn maturin_starter(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<ExampleClass>()?;
|
||||
m.add_wrapped(wrap_pymodule!(submodule::submodule))?;
|
||||
|
||||
// Inserting to sys.modules allows importing submodules nicely from Python
|
||||
// e.g. from maturin_starter.submodule import SubmoduleClass
|
||||
|
||||
let sys = PyModule::import(py, "sys")?;
|
||||
let sys_modules: &PyDict = sys.getattr("modules")?.downcast()?;
|
||||
let sys = PyModule::import_bound(py, "sys")?;
|
||||
let sys_modules: Bound<'_, PyDict> = sys.getattr("modules")?.downcast_into()?;
|
||||
sys_modules.set_item("maturin_starter.submodule", m.getattr("submodule")?)?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -16,7 +16,7 @@ impl SubmoduleClass {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
pub fn submodule(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
pub fn submodule(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<SubmoduleClass>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
variable::set("PYO3_VERSION", "0.20.0");
|
||||
variable::set("PYO3_VERSION", "0.23.0-dev");
|
||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||
file::rename(".template/plugin_api/Cargo.toml", "plugin_api/Cargo.toml");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -3,7 +3,6 @@ import nox
|
|||
|
||||
@nox.session
|
||||
def python(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install("maturin")
|
||||
session.run_always("maturin", "develop", "--features", "extension-module")
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
||||
|
|
|
@ -10,3 +10,6 @@ classifiers = [
|
|||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
pytest>=3.5.0
|
||||
pip>=21.3
|
||||
maturin>=0.14
|
|
@ -26,7 +26,7 @@ impl Gadget {
|
|||
|
||||
/// A Python module for plugin interface types
|
||||
#[pymodule]
|
||||
pub fn plugin_api(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
pub fn plugin_api(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<Gadget>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
def test_import():
|
||||
import plugin_api
|
||||
import plugin_api # noqa: F401
|
||||
|
|
|
@ -19,7 +19,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
// Now we can load our python_plugin/gadget_init_plugin.py file.
|
||||
// It can in turn import other stuff as it deems appropriate
|
||||
let plugin = PyModule::import(py, "gadget_init_plugin")?;
|
||||
let plugin = PyModule::import_bound(py, "gadget_init_plugin")?;
|
||||
// and call start function there, which will return a python reference to Gadget.
|
||||
// Gadget here is a "pyclass" object reference
|
||||
let gadget = plugin.getattr("start")?.call0()?;
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
authors = ["{{authors}}"]
|
||||
name = "{{project-name}}"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "sequential"
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
||||
[dependencies]
|
||||
pyo3-ffi = { version = "{{PYO3_VERSION}}", features = ["extension-module"] }
|
|
@ -0,0 +1,4 @@
|
|||
variable::set("PYO3_VERSION", "0.19.2");
|
||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||
file::rename(".template/pyproject.toml", "pyproject.toml");
|
||||
file::delete(".template");
|
|
@ -0,0 +1,7 @@
|
|||
[build-system]
|
||||
requires = ["maturin>=1,<2"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "{{project-name}}"
|
||||
version = "0.1.0"
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "sequential"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "sequential"
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
||||
[dependencies]
|
||||
pyo3-ffi = { path = "../../pyo3-ffi", features = ["extension-module"] }
|
||||
|
||||
[workspace]
|
|
@ -0,0 +1,2 @@
|
|||
include pyproject.toml Cargo.toml
|
||||
recursive-include src *
|
|
@ -0,0 +1,36 @@
|
|||
# sequential
|
||||
|
||||
A project built using only `pyo3_ffi`, without any of PyO3's safe api. It can be executed by subinterpreters that have their own GIL.
|
||||
|
||||
## Building and Testing
|
||||
|
||||
To build this package, first install `maturin`:
|
||||
|
||||
```shell
|
||||
pip install maturin
|
||||
```
|
||||
|
||||
To build and test use `maturin develop`:
|
||||
|
||||
```shell
|
||||
pip install -r requirements-dev.txt
|
||||
maturin develop
|
||||
pytest
|
||||
```
|
||||
|
||||
Alternatively, install nox and run the tests inside an isolated environment:
|
||||
|
||||
```shell
|
||||
nox
|
||||
```
|
||||
|
||||
## Copying this example
|
||||
|
||||
Use [`cargo-generate`](https://crates.io/crates/cargo-generate):
|
||||
|
||||
```bash
|
||||
$ cargo install cargo-generate
|
||||
$ cargo generate --git https://github.com/PyO3/pyo3 examples/sequential
|
||||
```
|
||||
|
||||
(`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.)
|
|
@ -0,0 +1,5 @@
|
|||
[template]
|
||||
ignore = [".nox"]
|
||||
|
||||
[hooks]
|
||||
pre = [".template/pre-script.rhai"]
|
|
@ -0,0 +1,11 @@
|
|||
import sys
|
||||
import nox
|
||||
|
||||
|
||||
@nox.session
|
||||
def python(session):
|
||||
if sys.version_info < (3, 12):
|
||||
session.skip("Python 3.12+ is required")
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
|
@ -0,0 +1,20 @@
|
|||
[build-system]
|
||||
requires = ["maturin>=1,<2"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "sequential"
|
||||
version = "0.1.0"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Rust",
|
||||
"Operating System :: POSIX",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
]
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
|
@ -0,0 +1,136 @@
|
|||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
use core::{mem, ptr};
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::{c_char, c_int, c_uint, c_ulonglong, c_void};
|
||||
|
||||
use pyo3_ffi::*;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct PyId {
|
||||
_ob_base: PyObject,
|
||||
id: Id,
|
||||
}
|
||||
|
||||
static COUNT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct Id(u64);
|
||||
|
||||
impl Id {
|
||||
fn new() -> Self {
|
||||
Id(COUNT.fetch_add(1, Ordering::Relaxed))
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn id_new(
|
||||
subtype: *mut PyTypeObject,
|
||||
args: *mut PyObject,
|
||||
kwds: *mut PyObject,
|
||||
) -> *mut PyObject {
|
||||
if PyTuple_Size(args) != 0 || !kwds.is_null() {
|
||||
// We use pyo3-ffi's `c_str!` macro to create null-terminated literals because
|
||||
// Rust's string literals are not null-terminated
|
||||
// On Rust 1.77 or newer you can use `c"text"` instead.
|
||||
PyErr_SetString(PyExc_TypeError, c_str!("Id() takes no arguments").as_ptr());
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let f: allocfunc = (*subtype).tp_alloc.unwrap_or(PyType_GenericAlloc);
|
||||
let slf = f(subtype, 0);
|
||||
|
||||
if slf.is_null() {
|
||||
return ptr::null_mut();
|
||||
} else {
|
||||
let id = Id::new();
|
||||
let slf = slf.cast::<PyId>();
|
||||
ptr::addr_of_mut!((*slf).id).write(id);
|
||||
}
|
||||
|
||||
slf
|
||||
}
|
||||
|
||||
unsafe extern "C" fn id_repr(slf: *mut PyObject) -> *mut PyObject {
|
||||
let slf = slf.cast::<PyId>();
|
||||
let id = (*slf).id.0;
|
||||
let string = format!("Id({})", id);
|
||||
PyUnicode_FromStringAndSize(string.as_ptr().cast::<c_char>(), string.len() as Py_ssize_t)
|
||||
}
|
||||
|
||||
unsafe extern "C" fn id_int(slf: *mut PyObject) -> *mut PyObject {
|
||||
let slf = slf.cast::<PyId>();
|
||||
let id = (*slf).id.0;
|
||||
PyLong_FromUnsignedLongLong(id as c_ulonglong)
|
||||
}
|
||||
|
||||
unsafe extern "C" fn id_richcompare(
|
||||
slf: *mut PyObject,
|
||||
other: *mut PyObject,
|
||||
op: c_int,
|
||||
) -> *mut PyObject {
|
||||
let pytype = Py_TYPE(slf); // guaranteed to be `sequential.Id`
|
||||
if Py_TYPE(other) != pytype {
|
||||
return Py_NewRef(Py_NotImplemented());
|
||||
}
|
||||
let slf = (*slf.cast::<PyId>()).id;
|
||||
let other = (*other.cast::<PyId>()).id;
|
||||
|
||||
let cmp = match op {
|
||||
pyo3_ffi::Py_LT => slf < other,
|
||||
pyo3_ffi::Py_LE => slf <= other,
|
||||
pyo3_ffi::Py_EQ => slf == other,
|
||||
pyo3_ffi::Py_NE => slf != other,
|
||||
pyo3_ffi::Py_GT => slf > other,
|
||||
pyo3_ffi::Py_GE => slf >= other,
|
||||
unrecognized => {
|
||||
let msg = CString::new(&*format!(
|
||||
"unrecognized richcompare opcode {}",
|
||||
unrecognized
|
||||
))
|
||||
.unwrap();
|
||||
PyErr_SetString(PyExc_SystemError, msg.as_ptr());
|
||||
return ptr::null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
if cmp {
|
||||
Py_NewRef(Py_True())
|
||||
} else {
|
||||
Py_NewRef(Py_False())
|
||||
}
|
||||
}
|
||||
|
||||
static mut SLOTS: &[PyType_Slot] = &[
|
||||
PyType_Slot {
|
||||
slot: Py_tp_new,
|
||||
pfunc: id_new as *mut c_void,
|
||||
},
|
||||
PyType_Slot {
|
||||
slot: Py_tp_doc,
|
||||
pfunc: c_str!("An id that is increased every time an instance is created").as_ptr()
|
||||
as *mut c_void,
|
||||
},
|
||||
PyType_Slot {
|
||||
slot: Py_tp_repr,
|
||||
pfunc: id_repr as *mut c_void,
|
||||
},
|
||||
PyType_Slot {
|
||||
slot: Py_nb_int,
|
||||
pfunc: id_int as *mut c_void,
|
||||
},
|
||||
PyType_Slot {
|
||||
slot: Py_tp_richcompare,
|
||||
pfunc: id_richcompare as *mut c_void,
|
||||
},
|
||||
PyType_Slot {
|
||||
slot: 0,
|
||||
pfunc: ptr::null_mut(),
|
||||
},
|
||||
];
|
||||
|
||||
pub static mut ID_SPEC: PyType_Spec = PyType_Spec {
|
||||
name: c_str!("sequential.Id").as_ptr(),
|
||||
basicsize: mem::size_of::<PyId>() as c_int,
|
||||
itemsize: 0,
|
||||
flags: (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE) as c_uint,
|
||||
slots: unsafe { SLOTS as *const [PyType_Slot] as *mut PyType_Slot },
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
use std::ptr;
|
||||
|
||||
use pyo3_ffi::*;
|
||||
|
||||
mod id;
|
||||
mod module;
|
||||
use crate::module::MODULE_DEF;
|
||||
|
||||
// The module initialization function, which must be named `PyInit_<your_module>`.
|
||||
#[allow(non_snake_case)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn PyInit_sequential() -> *mut PyObject {
|
||||
PyModuleDef_Init(ptr::addr_of_mut!(MODULE_DEF))
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
use core::{mem, ptr};
|
||||
use pyo3_ffi::*;
|
||||
use std::os::raw::{c_int, c_void};
|
||||
|
||||
pub static mut MODULE_DEF: PyModuleDef = PyModuleDef {
|
||||
m_base: PyModuleDef_HEAD_INIT,
|
||||
m_name: c_str!("sequential").as_ptr(),
|
||||
m_doc: c_str!("A library for generating sequential ids, written in Rust.").as_ptr(),
|
||||
m_size: mem::size_of::<sequential_state>() as Py_ssize_t,
|
||||
m_methods: std::ptr::null_mut(),
|
||||
m_slots: unsafe { SEQUENTIAL_SLOTS as *const [PyModuleDef_Slot] as *mut PyModuleDef_Slot },
|
||||
m_traverse: Some(sequential_traverse),
|
||||
m_clear: Some(sequential_clear),
|
||||
m_free: Some(sequential_free),
|
||||
};
|
||||
|
||||
static mut SEQUENTIAL_SLOTS: &[PyModuleDef_Slot] = &[
|
||||
PyModuleDef_Slot {
|
||||
slot: Py_mod_exec,
|
||||
value: sequential_exec as *mut c_void,
|
||||
},
|
||||
PyModuleDef_Slot {
|
||||
slot: Py_mod_multiple_interpreters,
|
||||
value: Py_MOD_PER_INTERPRETER_GIL_SUPPORTED,
|
||||
},
|
||||
PyModuleDef_Slot {
|
||||
slot: 0,
|
||||
value: ptr::null_mut(),
|
||||
},
|
||||
];
|
||||
|
||||
unsafe extern "C" fn sequential_exec(module: *mut PyObject) -> c_int {
|
||||
let state: *mut sequential_state = PyModule_GetState(module).cast();
|
||||
|
||||
let id_type = PyType_FromModuleAndSpec(
|
||||
module,
|
||||
ptr::addr_of_mut!(crate::id::ID_SPEC),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
if id_type.is_null() {
|
||||
PyErr_SetString(
|
||||
PyExc_SystemError,
|
||||
c_str!("cannot locate type object").as_ptr(),
|
||||
);
|
||||
return -1;
|
||||
}
|
||||
(*state).id_type = id_type.cast::<PyTypeObject>();
|
||||
|
||||
PyModule_AddObjectRef(module, c_str!("Id").as_ptr(), id_type)
|
||||
}
|
||||
|
||||
unsafe extern "C" fn sequential_traverse(
|
||||
module: *mut PyObject,
|
||||
visit: visitproc,
|
||||
arg: *mut c_void,
|
||||
) -> c_int {
|
||||
let state: *mut sequential_state = PyModule_GetState(module.cast()).cast();
|
||||
let id_type: *mut PyObject = (*state).id_type.cast();
|
||||
|
||||
if id_type.is_null() {
|
||||
0
|
||||
} else {
|
||||
(visit)(id_type, arg)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn sequential_clear(module: *mut PyObject) -> c_int {
|
||||
let state: *mut sequential_state = PyModule_GetState(module.cast()).cast();
|
||||
Py_CLEAR(ptr::addr_of_mut!((*state).id_type).cast());
|
||||
0
|
||||
}
|
||||
|
||||
unsafe extern "C" fn sequential_free(module: *mut c_void) {
|
||||
sequential_clear(module.cast());
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct sequential_state {
|
||||
id_type: *mut PyTypeObject,
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
use core::ffi::{c_char, CStr};
|
||||
use core::ptr;
|
||||
use std::thread;
|
||||
|
||||
use pyo3_ffi::*;
|
||||
use sequential::PyInit_sequential;
|
||||
|
||||
static COMMAND: &'static str = c_str!(
|
||||
"
|
||||
from sequential import Id
|
||||
|
||||
s = sum(int(Id()) for _ in range(12))
|
||||
"
|
||||
);
|
||||
|
||||
// Newtype to be able to pass it to another thread.
|
||||
struct State(*mut PyThreadState);
|
||||
unsafe impl Sync for State {}
|
||||
unsafe impl Send for State {}
|
||||
|
||||
#[test]
|
||||
fn lets_go_fast() -> Result<(), String> {
|
||||
unsafe {
|
||||
let ret = PyImport_AppendInittab(c_str!("sequential").as_ptr(), Some(PyInit_sequential));
|
||||
if ret == -1 {
|
||||
return Err("could not add module to inittab".into());
|
||||
}
|
||||
|
||||
Py_Initialize();
|
||||
|
||||
let main_state = PyThreadState_Swap(ptr::null_mut());
|
||||
|
||||
const NULL: State = State(ptr::null_mut());
|
||||
let mut subs = [NULL; 12];
|
||||
|
||||
let config = PyInterpreterConfig {
|
||||
use_main_obmalloc: 0,
|
||||
allow_fork: 0,
|
||||
allow_exec: 0,
|
||||
allow_threads: 1,
|
||||
allow_daemon_threads: 0,
|
||||
check_multi_interp_extensions: 1,
|
||||
gil: PyInterpreterConfig_OWN_GIL,
|
||||
};
|
||||
|
||||
for State(state) in &mut subs {
|
||||
let status = Py_NewInterpreterFromConfig(state, &config);
|
||||
if PyStatus_IsError(status) == 1 {
|
||||
let msg = if status.err_msg.is_null() {
|
||||
"no error message".into()
|
||||
} else {
|
||||
CStr::from_ptr(status.err_msg).to_string_lossy()
|
||||
};
|
||||
PyThreadState_Swap(main_state);
|
||||
Py_FinalizeEx();
|
||||
return Err(format!("could not create new subinterpreter: {msg}"));
|
||||
}
|
||||
}
|
||||
|
||||
PyThreadState_Swap(main_state);
|
||||
|
||||
let main_state = PyEval_SaveThread(); // a PyInterpreterConfig with shared gil would deadlock otherwise
|
||||
|
||||
let ints: Vec<_> = thread::scope(move |s| {
|
||||
let mut handles = vec![];
|
||||
|
||||
for state in subs {
|
||||
let handle = s.spawn(move || {
|
||||
let state = state;
|
||||
PyEval_RestoreThread(state.0);
|
||||
|
||||
let ret = run_code();
|
||||
|
||||
Py_EndInterpreter(state.0);
|
||||
ret
|
||||
});
|
||||
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
handles.into_iter().map(|h| h.join().unwrap()).collect()
|
||||
});
|
||||
|
||||
PyEval_RestoreThread(main_state);
|
||||
|
||||
let ret = Py_FinalizeEx();
|
||||
if ret == -1 {
|
||||
return Err("could not finalize interpreter".into());
|
||||
}
|
||||
|
||||
let mut sum: u64 = 0;
|
||||
for i in ints {
|
||||
let i = i?;
|
||||
sum += i;
|
||||
}
|
||||
|
||||
assert_eq!(sum, (0..).take(12 * 12).sum());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn fetch() -> String {
|
||||
let err = PyErr_GetRaisedException();
|
||||
let err_repr = PyObject_Str(err);
|
||||
if !err_repr.is_null() {
|
||||
let mut size = 0;
|
||||
let p = PyUnicode_AsUTF8AndSize(err_repr, &mut size);
|
||||
if !p.is_null() {
|
||||
let s = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
|
||||
p.cast::<u8>(),
|
||||
size as usize,
|
||||
));
|
||||
let s = String::from(s);
|
||||
Py_DECREF(err_repr);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
String::from("could not get error")
|
||||
}
|
||||
|
||||
fn run_code() -> Result<u64, String> {
|
||||
unsafe {
|
||||
let code_obj =
|
||||
Py_CompileString(COMMAND.as_ptr(), c_str!("program").as_ptr(), Py_file_input);
|
||||
if code_obj.is_null() {
|
||||
return Err(fetch());
|
||||
}
|
||||
let globals = PyDict_New();
|
||||
let res_ptr = PyEval_EvalCode(code_obj, globals, ptr::null_mut());
|
||||
Py_DECREF(code_obj);
|
||||
if res_ptr.is_null() {
|
||||
return Err(fetch());
|
||||
} else {
|
||||
Py_DECREF(res_ptr);
|
||||
}
|
||||
let sum = PyDict_GetItemString(globals, c_str!("s").as_ptr()); /* borrowed reference */
|
||||
if sum.is_null() {
|
||||
Py_DECREF(globals);
|
||||
return Err("globals did not have `s`".into());
|
||||
}
|
||||
let int = PyLong_AsUnsignedLongLong(sum) as u64;
|
||||
|
||||
Py_DECREF(globals);
|
||||
Ok(int)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import pytest
|
||||
from sequential import Id
|
||||
|
||||
|
||||
def test_make_some():
|
||||
for x in range(12):
|
||||
i = Id()
|
||||
assert x == int(i)
|
||||
|
||||
|
||||
def test_args():
|
||||
with pytest.raises(TypeError, match="Id\\(\\) takes no arguments"):
|
||||
Id(3, 4)
|
||||
|
||||
|
||||
def test_cmp():
|
||||
a = Id()
|
||||
b = Id()
|
||||
assert a <= b
|
||||
assert a < b
|
||||
assert a == a
|
|
@ -1,4 +1,4 @@
|
|||
variable::set("PYO3_VERSION", "0.20.0");
|
||||
variable::set("PYO3_VERSION", "0.23.0-dev");
|
||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||
file::rename(".template/setup.cfg", "setup.cfg");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -2,7 +2,7 @@ import nox
|
|||
|
||||
|
||||
@nox.session
|
||||
def python(session):
|
||||
def python(session: nox.Session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.run_always(
|
||||
"pip", "install", "-e", ".", "--no-build-isolation", env={"BUILD_DEBUG": "1"}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# import the contents of the Rust library into the Python extension
|
||||
# optional: include the documentation from the Rust module
|
||||
from ._setuptools_rust_starter import *
|
||||
from ._setuptools_rust_starter import __all__, __doc__
|
||||
from ._setuptools_rust_starter import __all__
|
||||
|
||||
# optional: include the documentation from the Rust module
|
||||
from ._setuptools_rust_starter import __doc__ # noqa: F401
|
||||
|
||||
__all__ = __all__ + ["PythonClass"]
|
||||
|
||||
|
|
|
@ -20,15 +20,15 @@ impl ExampleClass {
|
|||
|
||||
/// An example module implemented in Rust using PyO3.
|
||||
#[pymodule]
|
||||
fn _setuptools_rust_starter(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
fn _setuptools_rust_starter(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<ExampleClass>()?;
|
||||
m.add_wrapped(wrap_pymodule!(submodule::submodule))?;
|
||||
|
||||
// Inserting to sys.modules allows importing submodules nicely from Python
|
||||
// e.g. from setuptools_rust_starter.submodule import SubmoduleClass
|
||||
|
||||
let sys = PyModule::import(py, "sys")?;
|
||||
let sys_modules: &PyDict = sys.getattr("modules")?.downcast()?;
|
||||
let sys = PyModule::import_bound(py, "sys")?;
|
||||
let sys_modules: Bound<'_, PyDict> = sys.getattr("modules")?.downcast_into()?;
|
||||
sys_modules.set_item("setuptools_rust_starter.submodule", m.getattr("submodule")?)?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -16,7 +16,7 @@ impl SubmoduleClass {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
pub fn submodule(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
pub fn submodule(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<SubmoduleClass>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -5,3 +5,6 @@ build-backend = "maturin"
|
|||
[project]
|
||||
name = "{{project-name}}"
|
||||
version = "0.1.0"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
|
|
@ -2,8 +2,7 @@ import nox
|
|||
|
||||
|
||||
@nox.session
|
||||
def python(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install("maturin")
|
||||
session.run_always("maturin", "develop")
|
||||
def python(session: nox.Session):
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
||||
|
|
|
@ -3,7 +3,7 @@ requires = ["maturin>=1,<2"]
|
|||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "string sum"
|
||||
name = "string_sum"
|
||||
version = "0.1.0"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
|
@ -14,3 +14,6 @@ classifiers = [
|
|||
"Operating System :: POSIX",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
pytest>=3.5.0
|
||||
pip>=21.3
|
||||
maturin>=0.12,<0.13
|
|
@ -5,10 +5,8 @@ use pyo3_ffi::*;
|
|||
|
||||
static mut MODULE_DEF: PyModuleDef = PyModuleDef {
|
||||
m_base: PyModuleDef_HEAD_INIT,
|
||||
m_name: "string_sum\0".as_ptr().cast::<c_char>(),
|
||||
m_doc: "A Python module written in Rust.\0"
|
||||
.as_ptr()
|
||||
.cast::<c_char>(),
|
||||
m_name: c_str!("string_sum").as_ptr(),
|
||||
m_doc: c_str!("A Python module written in Rust.").as_ptr(),
|
||||
m_size: 0,
|
||||
m_methods: unsafe { METHODS as *const [PyMethodDef] as *mut PyMethodDef },
|
||||
m_slots: std::ptr::null_mut(),
|
||||
|
@ -19,14 +17,12 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {
|
|||
|
||||
static mut METHODS: &[PyMethodDef] = &[
|
||||
PyMethodDef {
|
||||
ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(),
|
||||
ml_name: c_str!("sum_as_string").as_ptr(),
|
||||
ml_meth: PyMethodDefPointer {
|
||||
_PyCFunctionFast: sum_as_string,
|
||||
},
|
||||
ml_flags: METH_FASTCALL,
|
||||
ml_doc: "returns the sum of two integers as a string\0"
|
||||
.as_ptr()
|
||||
.cast::<c_char>(),
|
||||
ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
|
||||
},
|
||||
// A zeroed PyMethodDef to mark the end of the array.
|
||||
PyMethodDef::zeroed(),
|
||||
|
@ -93,9 +89,7 @@ pub unsafe extern "C" fn sum_as_string(
|
|||
if nargs != 2 {
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError,
|
||||
"sum_as_string expected 2 positional arguments\0"
|
||||
.as_ptr()
|
||||
.cast::<c_char>(),
|
||||
c_str!("sum_as_string expected 2 positional arguments").as_ptr(),
|
||||
);
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
@ -119,7 +113,7 @@ pub unsafe extern "C" fn sum_as_string(
|
|||
None => {
|
||||
PyErr_SetString(
|
||||
PyExc_OverflowError,
|
||||
"arguments too large to add\0".as_ptr().cast::<c_char>(),
|
||||
c_str!("arguments too large to add").as_ptr(),
|
||||
);
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ def test_err1():
|
|||
|
||||
with pytest.raises(
|
||||
TypeError, match="sum_as_string expected an int for positional argument 1"
|
||||
) as e:
|
||||
):
|
||||
sum_as_string(a, b)
|
||||
|
||||
|
||||
|
@ -23,19 +23,19 @@ def test_err2():
|
|||
|
||||
with pytest.raises(
|
||||
TypeError, match="sum_as_string expected an int for positional argument 2"
|
||||
) as e:
|
||||
):
|
||||
sum_as_string(a, b)
|
||||
|
||||
|
||||
def test_overflow1():
|
||||
a, b = 0, 1 << 43
|
||||
|
||||
with pytest.raises(OverflowError, match="cannot fit 8796093022208 in 32 bits") as e:
|
||||
with pytest.raises(OverflowError, match="cannot fit 8796093022208 in 32 bits"):
|
||||
sum_as_string(a, b)
|
||||
|
||||
|
||||
def test_overflow2():
|
||||
a, b = 1 << 30, 1 << 30
|
||||
|
||||
with pytest.raises(OverflowError, match="arguments too large to add") as e:
|
||||
with pytest.raises(OverflowError, match="arguments too large to add"):
|
||||
sum_as_string(a, b)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
variable::set("PYO3_VERSION", "0.20.0");
|
||||
variable::set("PYO3_VERSION", "0.23.0-dev");
|
||||
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||
file::rename(".template/pyproject.toml", "pyproject.toml");
|
||||
file::delete(".template");
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=41.0.0", "wheel", "setuptools_rust>=1.0.0"]
|
||||
requires = ["maturin>=1,<2"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "{{project-name}}"
|
||||
version = "0.1.0"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "--benchmark-disable"
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
[metadata]
|
||||
name = {{project-name}}
|
||||
version = 0.1.0
|
||||
packages =
|
||||
word_count
|
||||
|
||||
[options]
|
||||
include_package_data = True
|
||||
zip_safe = False
|
|
@ -4,15 +4,14 @@ nox.options.sessions = ["test"]
|
|||
|
||||
|
||||
@nox.session
|
||||
def test(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install("maturin")
|
||||
session.run_always("maturin", "develop")
|
||||
def test(session: nox.Session):
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
||||
|
||||
|
||||
@nox.session
|
||||
def bench(session):
|
||||
session.install("-rrequirements-dev.txt")
|
||||
session.install(".")
|
||||
def bench(session: nox.Session):
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest", "--benchmark-enable")
|
||||
|
|
|
@ -15,6 +15,8 @@ classifiers = [
|
|||
"Operating System :: MacOS :: MacOS X",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "--benchmark-disable"
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
pytest>=3.5.0
|
||||
pytest-benchmark>=3.1.1
|
|
@ -33,7 +33,7 @@ fn count_line(line: &str, needle: &str) -> usize {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn word_count(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
fn word_count(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(search, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(search_sequential, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(search_sequential_allow_threads, m)?)?;
|
||||
|
|
|
@ -9,4 +9,4 @@ command = "python3 guide/pyo3_version.py"
|
|||
[output.html]
|
||||
git-repository-url = "https://github.com/PyO3/pyo3/tree/main/guide"
|
||||
edit-url-template = "https://github.com/PyO3/pyo3/edit/main/guide/{path}"
|
||||
playground.runnable = false
|
||||
playground.runnable = false
|
||||
|
|
|
@ -2,21 +2,26 @@
|
|||
|
||||
| Parameter | Description |
|
||||
| :- | :- |
|
||||
| `constructor` | This is currently only allowed on [variants of complex enums][params-constructor]. It allows customization of the generated class constructor for each variant. It uses the same syntax and supports the same options as the `signature` attribute of functions and methods. |
|
||||
| <span style="white-space: pre">`crate = "some::path"`</span> | Path to import the `pyo3` crate, if it's not accessible at `::pyo3`. |
|
||||
| `dict` | Gives instances of this class an empty `__dict__` to store custom attributes. |
|
||||
| `eq` | Implements `__eq__` using the `PartialEq` implementation of the underlying Rust datatype. |
|
||||
| `eq_int` | Implements `__eq__` using `__int__` for simple enums. |
|
||||
| <span style="white-space: pre">`extends = BaseType`</span> | Use a custom baseclass. Defaults to [`PyAny`][params-1] |
|
||||
| <span style="white-space: pre">`freelist = N`</span> | Implements a [free list][params-2] of size N. This can improve performance for types that are often created and deleted in quick succession. Profile your code to see whether `freelist` is right for you. |
|
||||
| <span style="white-space: pre">`frozen`</span> | Declares that your pyclass is immutable. It removes the borrow checker overhead when retrieving a shared reference to the Rust struct, but disables the ability to get a mutable reference. |
|
||||
| `get_all` | Generates getters for all fields of the pyclass. |
|
||||
| `hash` | Implements `__hash__` using the `Hash` implementation of the underlying Rust datatype. |
|
||||
| `mapping` | Inform PyO3 that this class is a [`Mapping`][params-mapping], and so leave its implementation of sequence C-API slots empty. |
|
||||
| <span style="white-space: pre">`module = "module_name"`</span> | Python code will see the class as being defined in this module. Defaults to `builtins`. |
|
||||
| <span style="white-space: pre">`name = "python_name"`</span> | Sets the name that Python sees this class as. Defaults to the name of the Rust struct. |
|
||||
| `ord` | Implements `__lt__`, `__gt__`, `__le__`, & `__ge__` using the `PartialOrd` implementation of the underlying Rust datatype. *Requires `eq`* |
|
||||
| `rename_all = "renaming_rule"` | Applies renaming rules to every getters and setters of a struct, or every variants of an enum. Possible values are: "camelCase", "kebab-case", "lowercase", "PascalCase", "SCREAMING-KEBAB-CASE", "SCREAMING_SNAKE_CASE", "snake_case", "UPPERCASE". |
|
||||
| `sequence` | Inform PyO3 that this class is a [`Sequence`][params-sequence], and so leave its C-API mapping length slot empty. |
|
||||
| `set_all` | Generates setters for all fields of the pyclass. |
|
||||
| `subclass` | Allows other Python classes and `#[pyclass]` to inherit from this class. Enums cannot be subclassed. |
|
||||
| <span style="white-space: pre">`text_signature = "(arg1, arg2, ...)"`</span> | Sets the text signature for the Python class' `__new__` method. |
|
||||
| `unsendable` | Required if your struct is not [`Send`][params-3]. Rather than using `unsendable`, consider implementing your struct in a threadsafe way by e.g. substituting [`Rc`][params-4] with [`Arc`][params-5]. By using `unsendable`, your class will panic when accessed by another thread.|
|
||||
| `unsendable` | Required if your struct is not [`Send`][params-3]. Rather than using `unsendable`, consider implementing your struct in a threadsafe way by e.g. substituting [`Rc`][params-4] with [`Arc`][params-5]. By using `unsendable`, your class will panic when accessed by another thread. Also note the Python's GC is multi-threaded and while unsendable classes will not be traversed on foreign threads to avoid UB, this can lead to memory leaks. |
|
||||
| `weakref` | Allows this class to be [weakly referenceable][params-6]. |
|
||||
|
||||
All of these parameters can either be passed directly on the `#[pyclass(...)]` annotation, or as one or
|
||||
|
@ -33,11 +38,12 @@ struct MyClass {}
|
|||
struct MyClass {}
|
||||
```
|
||||
|
||||
[params-1]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html
|
||||
[params-1]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyAny.html
|
||||
[params-2]: https://en.wikipedia.org/wiki/Free_list
|
||||
[params-3]: https://doc.rust-lang.org/std/marker/trait.Send.html
|
||||
[params-4]: https://doc.rust-lang.org/std/rc/struct.Rc.html
|
||||
[params-5]: https://doc.rust-lang.org/std/sync/struct.Arc.html
|
||||
[params-6]: https://docs.python.org/3/library/weakref.html
|
||||
[params-constructor]: https://pyo3.rs/latest/class.html#complex-enums
|
||||
[params-mapping]: https://pyo3.rs/latest/class/protocols.html#mapping--sequence-types
|
||||
[params-sequence]: https://pyo3.rs/latest/class/protocols.html#mapping--sequence-types
|
|
@ -4,30 +4,34 @@
|
|||
|
||||
---
|
||||
|
||||
- [Getting started](getting_started.md)
|
||||
- [Python modules](module.md)
|
||||
- [Python functions](function.md)
|
||||
- [Function signatures](function/signature.md)
|
||||
- [Error handling](function/error_handling.md)
|
||||
- [Python classes](class.md)
|
||||
- [Class customizations](class/protocols.md)
|
||||
- [Basic object customization](class/object.md)
|
||||
- [Emulating numeric types](class/numeric.md)
|
||||
- [Emulating callable objects](class/call.md)
|
||||
- [Getting started](getting-started.md)
|
||||
- [Using Rust from Python](rust-from-python.md)
|
||||
- [Python modules](module.md)
|
||||
- [Python functions](function.md)
|
||||
- [Function signatures](function/signature.md)
|
||||
- [Error handling](function/error-handling.md)
|
||||
- [Python classes](class.md)
|
||||
- [Class customizations](class/protocols.md)
|
||||
- [Basic object customization](class/object.md)
|
||||
- [Emulating numeric types](class/numeric.md)
|
||||
- [Emulating callable objects](class/call.md)
|
||||
- [Calling Python from Rust](python-from-rust.md)
|
||||
- [Python object types](types.md)
|
||||
- [Python exceptions](exception.md)
|
||||
- [Calling Python functions](python-from-rust/function-calls.md)
|
||||
- [Executing existing Python code](python-from-rust/calling-existing-code.md)
|
||||
- [Type conversions](conversions.md)
|
||||
- [Mapping of Rust types to Python types](conversions/tables.md)]
|
||||
- [Conversion traits](conversions/traits.md)]
|
||||
- [Python exceptions](exception.md)
|
||||
- [Calling Python from Rust](python_from_rust.md)
|
||||
- [GIL, mutability and object types](types.md)
|
||||
- [Mapping of Rust types to Python types](conversions/tables.md)
|
||||
- [Conversion traits](conversions/traits.md)
|
||||
- [Using `async` and `await`](async-await.md)
|
||||
- [Parallelism](parallelism.md)
|
||||
- [Debugging](debugging.md)
|
||||
- [Features reference](features.md)
|
||||
- [Memory management](memory.md)
|
||||
- [Performance](performance.md)
|
||||
- [Advanced topics](advanced.md)
|
||||
- [Building and distribution](building_and_distribution.md)
|
||||
- [Supporting multiple Python versions](building_and_distribution/multiple_python_versions.md)
|
||||
- [Building and distribution](building-and-distribution.md)
|
||||
- [Supporting multiple Python versions](building-and-distribution/multiple-python-versions.md)
|
||||
- [Useful crates](ecosystem.md)
|
||||
- [Logging](ecosystem/logging.md)
|
||||
- [Using `async` and `await`](ecosystem/async-await.md)
|
||||
|
@ -36,9 +40,8 @@
|
|||
---
|
||||
|
||||
[Appendix A: Migration guide](migration.md)
|
||||
[Appendix B: PyO3 and rust-cpython](rust_cpython.md)
|
||||
[Appendix C: Trait bounds](trait_bounds.md)
|
||||
[Appendix D: Python typing hints](python_typing_hints.md)
|
||||
[Appendix B: Trait bounds](trait-bounds.md)
|
||||
[Appendix C: Python typing hints](python-typing-hints.md)
|
||||
[CHANGELOG](changelog.md)
|
||||
|
||||
---
|
||||
|
|
|
@ -5,10 +5,3 @@
|
|||
PyO3 exposes much of Python's C API through the `ffi` module.
|
||||
|
||||
The C API is naturally unsafe and requires you to manage reference counts, errors and specific invariants yourself. Please refer to the [C API Reference Manual](https://docs.python.org/3/c-api/) and [The Rustonomicon](https://doc.rust-lang.org/nightly/nomicon/ffi.html) before using any function from that API.
|
||||
|
||||
## Memory management
|
||||
|
||||
PyO3's `&PyAny` "owned references" and `Py<PyAny>` smart pointers are used to
|
||||
access memory stored in Python's heap. This memory sometimes lives for longer
|
||||
than expected because of differences in Rust and Python's memory models. See
|
||||
the chapter on [memory management](./memory.md) for more information.
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
# Using `async` and `await`
|
||||
|
||||
*This feature is still in active development. See [the related issue](https://github.com/PyO3/pyo3/issues/1632).*
|
||||
|
||||
`#[pyfunction]` and `#[pymethods]` attributes also support `async fn`.
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# #[cfg(feature = "experimental-async")] {
|
||||
use std::{thread, time::Duration};
|
||||
use futures::channel::oneshot;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyfunction]
|
||||
#[pyo3(signature=(seconds, result=None))]
|
||||
async fn sleep(seconds: f64, result: Option<PyObject>) -> Option<PyObject> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
thread::spawn(move || {
|
||||
thread::sleep(Duration::from_secs_f64(seconds));
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
rx.await.unwrap();
|
||||
result
|
||||
}
|
||||
# }
|
||||
```
|
||||
|
||||
*Python awaitables instantiated with this method can only be awaited in *asyncio* context. Other Python async runtime may be supported in the future.*
|
||||
|
||||
## `Send + 'static` constraint
|
||||
|
||||
Resulting future of an `async fn` decorated by `#[pyfunction]` must be `Send + 'static` to be embedded in a Python object.
|
||||
|
||||
As a consequence, `async fn` parameters and return types must also be `Send + 'static`, so it is not possible to have a signature like `async fn does_not_compile<'py>(arg: Bound<'py, PyAny>) -> Bound<'py, PyAny>`.
|
||||
|
||||
However, there is an exception for method receivers, so async methods can accept `&self`/`&mut self`. Note that this means that the class instance is borrowed for as long as the returned future is not completed, even across yield points and while waiting for I/O operations to complete. Hence, other methods cannot obtain exclusive borrows while the future is still being polled. This is the same as how async methods in Rust generally work but it is more problematic for Rust code interfacing with Python code due to pervasive shared mutability. This strongly suggests to prefer shared borrows `&self` over exclusive ones `&mut self` to avoid racy borrow check failures at runtime.
|
||||
|
||||
## Implicit GIL holding
|
||||
|
||||
Even if it is not possible to pass a `py: Python<'py>` parameter to `async fn`, the GIL is still held during the execution of the future – it's also the case for regular `fn` without `Python<'py>`/`Bound<'py, PyAny>` parameter, yet the GIL is held.
|
||||
|
||||
It is still possible to get a `Python` marker using [`Python::with_gil`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.with_gil); because `with_gil` is reentrant and optimized, the cost will be negligible.
|
||||
|
||||
## Release the GIL across `.await`
|
||||
|
||||
There is currently no simple way to release the GIL when awaiting a future, *but solutions are currently in development*.
|
||||
|
||||
Here is the advised workaround for now:
|
||||
|
||||
```rust,ignore
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::{Pin, pin},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use pyo3::prelude::*;
|
||||
|
||||
struct AllowThreads<F>(F);
|
||||
|
||||
impl<F> Future for AllowThreads<F>
|
||||
where
|
||||
F: Future + Unpin + Send,
|
||||
F::Output: Send,
|
||||
{
|
||||
type Output = F::Output;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let waker = cx.waker();
|
||||
Python::with_gil(|gil| {
|
||||
gil.allow_threads(|| pin!(&mut self.0).poll(&mut Context::from_waker(waker)))
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cancellation
|
||||
|
||||
Cancellation on the Python side can be caught using [`CancelHandle`]({{#PYO3_DOCS_URL}}/pyo3/coroutine/struct.CancelHandle.html) type, by annotating a function parameter with `#[pyo3(cancel_handle)]`.
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# #[cfg(feature = "experimental-async")] {
|
||||
use futures::FutureExt;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::coroutine::CancelHandle;
|
||||
|
||||
#[pyfunction]
|
||||
async fn cancellable(#[pyo3(cancel_handle)] mut cancel: CancelHandle) {
|
||||
futures::select! {
|
||||
/* _ = ... => println!("done"), */
|
||||
_ = cancel.cancelled().fuse() => println!("cancelled"),
|
||||
}
|
||||
}
|
||||
# }
|
||||
```
|
||||
|
||||
## The `Coroutine` type
|
||||
|
||||
To make a Rust future awaitable in Python, PyO3 defines a [`Coroutine`]({{#PYO3_DOCS_URL}}/pyo3/coroutine/struct.Coroutine.html) type, which implements the Python [coroutine protocol](https://docs.python.org/3/library/collections.abc.html#collections.abc.Coroutine).
|
||||
|
||||
Each `coroutine.send` call is translated to a `Future::poll` call. If a [`CancelHandle`]({{#PYO3_DOCS_URL}}/pyo3/coroutine/struct.CancelHandle.html) parameter is declared, the exception passed to `coroutine.throw` call is stored in it and can be retrieved with [`CancelHandle::cancelled`]({{#PYO3_DOCS_URL}}/pyo3/coroutine/struct.CancelHandle.html#method.cancelled); otherwise, it cancels the Rust future, and the exception is reraised;
|
||||
|
||||
*The type does not yet have a public constructor until the design is finalized.*
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
This chapter of the guide goes into detail on how to build and distribute projects using PyO3. The way to achieve this is very different depending on whether the project is a Python module implemented in Rust, or a Rust binary embedding Python. For both types of project there are also common problems such as the Python version to build for and the [linker](https://en.wikipedia.org/wiki/Linker_(computing)) arguments to use.
|
||||
|
||||
The material in this chapter is intended for users who have already read the PyO3 [README](#index.md). It covers in turn the choices that can be made for Python modules and for Rust binaries. There is also a section at the end about cross-compiling projects using PyO3.
|
||||
The material in this chapter is intended for users who have already read the PyO3 [README](./index.md). It covers in turn the choices that can be made for Python modules and for Rust binaries. There is also a section at the end about cross-compiling projects using PyO3.
|
||||
|
||||
There is an additional sub-chapter dedicated to [supporting multiple Python versions](./building_and_distribution/multiple_python_versions.html).
|
||||
There is an additional sub-chapter dedicated to [supporting multiple Python versions](./building-and-distribution/multiple-python-versions.md).
|
||||
|
||||
## Configuring the Python version
|
||||
|
||||
|
@ -151,7 +151,7 @@ rustflags = [
|
|||
]
|
||||
```
|
||||
|
||||
Alternatively, on rust >= 1.56, one can include in `build.rs`:
|
||||
Alternatively, one can include in `build.rs`:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
|
@ -163,7 +163,7 @@ fn main() {
|
|||
|
||||
For more discussion on and workarounds for MacOS linking problems [see this issue](https://github.com/PyO3/pyo3/issues/1800#issuecomment-906786649).
|
||||
|
||||
Finally, don't forget that on MacOS the `extension-module` feature will cause `cargo test` to fail without the `--no-default-features` flag (see [the FAQ](https://pyo3.rs/main/faq.html#i-cant-run-cargo-test-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror)).
|
||||
Finally, don't forget that on MacOS the `extension-module` feature will cause `cargo test` to fail without the `--no-default-features` flag (see [the FAQ](https://pyo3.rs/main/faq.html#i-cant-run-cargo-test-or-i-cant-build-in-a-cargo-workspace-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror)).
|
||||
|
||||
### The `extension-module` feature
|
||||
|
||||
|
@ -177,7 +177,7 @@ The downside of not linking to `libpython` is that binaries, tests, and examples
|
|||
|
||||
By default, Python extension modules can only be used with the same Python version they were compiled against. For example, an extension module built for Python 3.5 can't be imported in Python 3.8. [PEP 384](https://www.python.org/dev/peps/pep-0384/) introduced the idea of the limited Python API, which would have a stable ABI enabling extension modules built with it to be used against multiple Python versions. This is also known as `abi3`.
|
||||
|
||||
The advantage of building extension modules using the limited Python API is that package vendors only need to build and distribute a single copy (for each OS / architecture), and users can install it on all Python versions from the [minimum version](#minimum-python-version-for-abi3) and up. The downside of this is that PyO3 can't use optimizations which rely on being compiled against a known exact Python version. It's up to you to decide whether this matters for your extension module. It's also possible to design your extension module such that you can distribute `abi3` wheels but allow users compiling from source to benefit from additional optimizations - see the [support for multiple python versions](./building_and_distribution/multiple_python_versions.html) section of this guide, in particular the `#[cfg(Py_LIMITED_API)]` flag.
|
||||
The advantage of building extension modules using the limited Python API is that package vendors only need to build and distribute a single copy (for each OS / architecture), and users can install it on all Python versions from the [minimum version](#minimum-python-version-for-abi3) and up. The downside of this is that PyO3 can't use optimizations which rely on being compiled against a known exact Python version. It's up to you to decide whether this matters for your extension module. It's also possible to design your extension module such that you can distribute `abi3` wheels but allow users compiling from source to benefit from additional optimizations - see the [support for multiple python versions](./building-and-distribution/multiple-python-versions.md) section of this guide, in particular the `#[cfg(Py_LIMITED_API)]` flag.
|
||||
|
||||
There are three steps involved in making use of `abi3` when building Python packages as wheels:
|
||||
|
||||
|
@ -198,7 +198,7 @@ See the [corresponding](https://github.com/PyO3/maturin/pull/353) [PRs](https://
|
|||
Because a single `abi3` wheel can be used with many different Python versions, PyO3 has feature flags `abi3-py37`, `abi3-py38`, `abi3-py39` etc. to set the minimum required Python version for your `abi3` wheel.
|
||||
For example, if you set the `abi3-py37` feature, your extension wheel can be used on all Python 3 versions from Python 3.7 and up. `maturin` and `setuptools-rust` will give the wheel a name like `my-extension-1.0-cp37-abi3-manylinux2020_x86_64.whl`.
|
||||
|
||||
As your extension module may be run with multiple different Python versions you may occasionally find you need to check the Python version at runtime to customize behavior. See [the relevant section of this guide](./building_and_distribution/multiple_python_versions.html#checking-the-python-version-at-runtime) on supporting multiple Python versions at runtime.
|
||||
As your extension module may be run with multiple different Python versions you may occasionally find you need to check the Python version at runtime to customize behavior. See [the relevant section of this guide](./building-and-distribution/multiple-python-versions.md#checking-the-python-version-at-runtime) on supporting multiple Python versions at runtime.
|
||||
|
||||
PyO3 is only able to link your extension module to abi3 version up to and including your host Python version. E.g., if you set `abi3-py38` and try to compile the crate with a host of Python 3.7, the build will fail.
|
||||
|
||||
|
@ -232,7 +232,7 @@ not work when compiling for `abi3`. These are:
|
|||
|
||||
If you want to embed the Python interpreter inside a Rust program, there are two modes in which this can be done: dynamically and statically. We'll cover each of these modes in the following sections. Each of them affect how you must distribute your program. Instead of learning how to do this yourself, you might want to consider using a project like [PyOxidizer] to ship your application and all of its dependencies in a single file.
|
||||
|
||||
PyO3 automatically switches between the two linking modes depending on whether the Python distribution you have configured PyO3 to use ([see above](#python-version)) contains a shared library or a static library. The static library is most often seen in Python distributions compiled from source without the `--enable-shared` configuration option. For example, this is the default for `pyenv` on macOS.
|
||||
PyO3 automatically switches between the two linking modes depending on whether the Python distribution you have configured PyO3 to use ([see above](#configuring-the-python-version)) contains a shared library or a static library. The static library is most often seen in Python distributions compiled from source without the `--enable-shared` configuration option.
|
||||
|
||||
### Dynamically embedding the Python interpreter
|
||||
|
||||
|
@ -242,7 +242,7 @@ This mode of embedding works well for Rust tests which need access to the Python
|
|||
|
||||
For distributing your program to non-technical users, you will have to consider including the Python shared library in your distribution as well as setting up wrapper scripts to set the right environment variables (such as `LD_LIBRARY_PATH` on UNIX, or `PATH` on Windows).
|
||||
|
||||
Note that PyPy cannot be embedded in Rust (or any other software). Support for this is tracked on the [PyPy issue tracker](https://foss.heptapod.net/pypy/pypy/-/issues/3286).
|
||||
Note that PyPy cannot be embedded in Rust (or any other software). Support for this is tracked on the [PyPy issue tracker](https://github.com/pypy/pypy/issues/3836).
|
||||
|
||||
### Statically embedding the Python interpreter
|
||||
|
||||
|
@ -285,7 +285,7 @@ Thanks to Rust's great cross-compilation support, cross-compiling using PyO3 is
|
|||
* A toolchain for your target.
|
||||
* The appropriate options in your Cargo `.config` for the platform you're targeting and the toolchain you are using.
|
||||
* A Python interpreter that's already been compiled for your target (optional when building "abi3" extension modules).
|
||||
* A Python interpreter that is built for your host and available through the `PATH` or setting the [`PYO3_PYTHON`](#python-version) variable (optional when building "abi3" extension modules).
|
||||
* A Python interpreter that is built for your host and available through the `PATH` or setting the [`PYO3_PYTHON`](#configuring-the-python-version) variable (optional when building "abi3" extension modules).
|
||||
|
||||
After you've obtained the above, you can build a cross-compiled PyO3 module by using Cargo's `--target` flag. PyO3's build script will detect that you are attempting a cross-compile based on your host machine and the desired target.
|
||||
|
|
@ -85,7 +85,7 @@ This `#[cfg]` marks code which is running on PyPy.
|
|||
|
||||
## Checking the Python version at runtime
|
||||
|
||||
When building with PyO3's `abi3` feature, your extension module will be compiled against a specific [minimum version](../building_and_distribution.html#minimum-python-version-for-abi3) of Python, but may be running on newer Python versions.
|
||||
When building with PyO3's `abi3` feature, your extension module will be compiled against a specific [minimum version](../building-and-distribution.md#minimum-python-version-for-abi3) of Python, but may be running on newer Python versions.
|
||||
|
||||
For example with PyO3's `abi3-py38` feature, your extension will be compiled as if it were for Python 3.8. If you were using `pyo3-build-config`, `#[cfg(Py_3_8)]` would be present. Your user could freely install and run your abi3 extension on Python 3.9.
|
||||
|
||||
|
@ -104,5 +104,5 @@ Python::with_gil(|py| {
|
|||
});
|
||||
```
|
||||
|
||||
[`Python::version()`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Python.html#method.version
|
||||
[`Python::version_info()`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Python.html#method.version_info
|
||||
[`Python::version()`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.version
|
||||
[`Python::version_info()`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.version_info
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
PyO3 exposes a group of attributes powered by Rust's proc macro system for defining Python classes as Rust structs.
|
||||
|
||||
The main attribute is `#[pyclass]`, which is placed upon a Rust `struct` or a fieldless `enum` (a.k.a. C-like enum) to generate a Python type for it. They will usually also have *one* `#[pymethods]`-annotated `impl` block for the struct, which is used to define Python methods and constants for the generated Python type. (If the [`multiple-pymethods`] feature is enabled, each `#[pyclass]` is allowed to have multiple `#[pymethods]` blocks.) `#[pymethods]` may also have implementations for Python magic methods such as `__str__`.
|
||||
The main attribute is `#[pyclass]`, which is placed upon a Rust `struct` or `enum` to generate a Python type for it. They will usually also have *one* `#[pymethods]`-annotated `impl` block for the struct, which is used to define Python methods and constants for the generated Python type. (If the [`multiple-pymethods`] feature is enabled, each `#[pyclass]` is allowed to have multiple `#[pymethods]` blocks.) `#[pymethods]` may also have implementations for Python magic methods such as `__str__`.
|
||||
|
||||
This chapter will discuss the functionality and configuration these attributes offer. Below is a list of links to the relevant section of this chapter for each:
|
||||
|
||||
|
@ -16,18 +16,18 @@ This chapter will discuss the functionality and configuration these attributes o
|
|||
- [`#[classmethod]`](#class-methods)
|
||||
- [`#[classattr]`](#class-attributes)
|
||||
- [`#[args]`](#method-arguments)
|
||||
- [Magic methods and slots](class/protocols.html)
|
||||
- [Magic methods and slots](class/protocols.md)
|
||||
- [Classes as function arguments](#classes-as-function-arguments)
|
||||
|
||||
## Defining a new class
|
||||
|
||||
To define a custom Python class, add the `#[pyclass]` attribute to a Rust struct or a fieldless enum.
|
||||
To define a custom Python class, add the `#[pyclass]` attribute to a Rust struct or enum.
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass]
|
||||
struct Integer {
|
||||
struct MyClass {
|
||||
inner: i32,
|
||||
}
|
||||
|
||||
|
@ -35,8 +35,18 @@ struct Integer {
|
|||
#[pyclass]
|
||||
struct Number(i32);
|
||||
|
||||
// PyO3 supports custom discriminants in enums
|
||||
#[pyclass]
|
||||
// PyO3 supports unit-only enums (which contain only unit variants)
|
||||
// These simple enums behave similarly to Python's enumerations (enum.Enum)
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
enum MyEnum {
|
||||
Variant,
|
||||
OtherVariant = 30, // PyO3 supports custom discriminants.
|
||||
}
|
||||
|
||||
// PyO3 supports custom discriminants in unit-only enums
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
enum HttpResponse {
|
||||
Ok = 200,
|
||||
NotFound = 404,
|
||||
|
@ -44,14 +54,22 @@ enum HttpResponse {
|
|||
// ...
|
||||
}
|
||||
|
||||
// PyO3 also supports enums with Struct and Tuple variants
|
||||
// These complex enums have sligtly different behavior from the simple enums above
|
||||
// They are meant to work with instance checks and match statement patterns
|
||||
// The variants can be mixed and matched
|
||||
// Struct variants have named fields while tuple enums generate generic names for fields in order _0, _1, _2, ...
|
||||
// Apart from this both types are functionally identical
|
||||
#[pyclass]
|
||||
enum MyEnum {
|
||||
Variant,
|
||||
OtherVariant = 30, // PyO3 supports custom discriminants.
|
||||
enum Shape {
|
||||
Circle { radius: f64 },
|
||||
Rectangle { width: f64, height: f64 },
|
||||
RegularPolygon(u32, f64),
|
||||
Nothing(),
|
||||
}
|
||||
```
|
||||
|
||||
The above example generates implementations for [`PyTypeInfo`] and [`PyClass`] for `MyClass` and `MyEnum`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter.
|
||||
The above example generates implementations for [`PyTypeInfo`] and [`PyClass`] for `MyClass`, `Number`, `MyEnum`, `HttpResponse`, and `Shape`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter.
|
||||
|
||||
### Restrictions
|
||||
|
||||
|
@ -69,6 +87,38 @@ When you need to share ownership of data between Python and Rust, instead of usi
|
|||
|
||||
A Rust `struct Foo<T>` with a generic parameter `T` generates new compiled implementations each time it is used with a different concrete type for `T`. These new implementations are generated by the compiler at each usage site. This is incompatible with wrapping `Foo` in Python, where there needs to be a single compiled implementation of `Foo` which is integrated with the Python interpreter.
|
||||
|
||||
Currently, the best alternative is to write a macro which expands to a new `#[pyclass]` for each instantiation you want:
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
struct GenericClass<T> {
|
||||
data: T,
|
||||
}
|
||||
|
||||
macro_rules! create_interface {
|
||||
($name: ident, $type: ident) => {
|
||||
#[pyclass]
|
||||
pub struct $name {
|
||||
inner: GenericClass<$type>,
|
||||
}
|
||||
#[pymethods]
|
||||
impl $name {
|
||||
#[new]
|
||||
pub fn new(data: $type) -> Self {
|
||||
Self {
|
||||
inner: GenericClass { data: data },
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
create_interface!(IntClass, i64);
|
||||
create_interface!(FloatClass, String);
|
||||
```
|
||||
|
||||
#### Must be Send
|
||||
|
||||
Because Python objects are freely shared between threads by the Python interpreter, there is no guarantee which thread will eventually drop the object. Therefore all types annotated with `#[pyclass]` must implement `Send` (unless annotated with [`#[pyclass(unsendable)]`](#customizing-the-class)).
|
||||
|
@ -80,6 +130,7 @@ To declare a constructor, you need to define a method and annotate it with the `
|
|||
attribute. Only Python's `__new__` method can be specified, `__init__` is not available.
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# use pyo3::prelude::*;
|
||||
# #[pyclass]
|
||||
# struct Number(i32);
|
||||
|
@ -96,6 +147,7 @@ impl Number {
|
|||
Alternatively, if your `new` method may fail you can return `PyResult<Self>`.
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::exceptions::PyValueError;
|
||||
# #[pyclass]
|
||||
|
@ -130,37 +182,33 @@ For arguments, see the [`Method arguments`](#method-arguments) section below.
|
|||
The next step is to create the module initializer and add our class to it:
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# use pyo3::prelude::*;
|
||||
# #[pyclass]
|
||||
# struct Number(i32);
|
||||
#
|
||||
#[pymodule]
|
||||
fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<Number>()?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## PyCell and interior mutability
|
||||
## Bound<T> and interior mutability
|
||||
|
||||
You sometimes need to convert your `pyclass` into a Python object and access it
|
||||
from Rust code (e.g., for testing it).
|
||||
[`PyCell`] is the primary interface for that.
|
||||
Often is useful to turn a `#[pyclass]` type `T` into a Python object and access it from Rust code. The [`Py<T>`] and [`Bound<'py, T>`] smart pointers are the ways to represent a Python object in PyO3's API. More detail can be found about them [in the Python objects](./types.md#pyo3s-smart-pointers) section of the guide.
|
||||
|
||||
`PyCell<T: PyClass>` is always allocated in the Python heap, so Rust doesn't have ownership of it.
|
||||
In other words, Rust code can only extract a `&PyCell<T>`, not a `PyCell<T>`.
|
||||
Most Python objects do not offer exclusive (`&mut`) access (see the [section on Python's memory model](./python-from-rust.md#pythons-memory-model)). However, Rust structs wrapped as Python objects (called `pyclass` types) often *do* need `&mut` access. Due to the GIL, PyO3 *can* guarantee exclusive access to them.
|
||||
|
||||
Thus, to mutate data behind `&PyCell` safely, PyO3 employs the
|
||||
[Interior Mutability Pattern](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html)
|
||||
like [`RefCell`].
|
||||
The Rust borrow checker cannot reason about `&mut` references once an object's ownership has been passed to the Python interpreter. This means that borrow checking is done at runtime using with a scheme very similar to `std::cell::RefCell<T>`. This is known as [interior mutability](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html).
|
||||
|
||||
Users who are familiar with `RefCell` can use `PyCell` just like `RefCell`.
|
||||
Users who are familiar with `RefCell<T>` can use `Py<T>` and `Bound<'py, T>` just like `RefCell<T>`.
|
||||
|
||||
For users who are not very familiar with `RefCell`, here is a reminder of Rust's rules of borrowing:
|
||||
For users who are not very familiar with `RefCell<T>`, here is a reminder of Rust's rules of borrowing:
|
||||
- At any given time, you can have either (but not both of) one mutable reference or any number of immutable references.
|
||||
- References must always be valid.
|
||||
- References can never outlast the data they refer to.
|
||||
|
||||
`PyCell`, like `RefCell`, ensures these borrowing rules by tracking references at runtime.
|
||||
`Py<T>` and `Bound<'py, T>`, like `RefCell<T>`, ensure these borrowing rules by tracking references at runtime.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
|
@ -170,7 +218,7 @@ struct MyClass {
|
|||
num: i32,
|
||||
}
|
||||
Python::with_gil(|py| {
|
||||
let obj = PyCell::new(py, MyClass { num: 3 }).unwrap();
|
||||
let obj = Bound::new(py, MyClass { num: 3 }).unwrap();
|
||||
{
|
||||
let obj_ref = obj.borrow(); // Get PyRef
|
||||
assert_eq!(obj_ref.num, 3);
|
||||
|
@ -185,15 +233,13 @@ Python::with_gil(|py| {
|
|||
assert!(obj.try_borrow_mut().is_err());
|
||||
}
|
||||
|
||||
// You can convert `&PyCell` to a Python object
|
||||
// You can convert `Bound` to a Python object
|
||||
pyo3::py_run!(py, obj, "assert obj.num == 5");
|
||||
});
|
||||
```
|
||||
|
||||
`&PyCell<T>` is bounded by the same lifetime as a [`GILGuard`].
|
||||
To make the object longer lived (for example, to store it in a struct on the
|
||||
Rust side), you can use `Py<T>`, which stores an object longer than the GIL
|
||||
lifetime, and therefore needs a `Python<'_>` token to access.
|
||||
A `Bound<'py, T>` is restricted to the GIL lifetime `'py`. To make the object longer lived (for example, to store it in a struct on the
|
||||
Rust side), use `Py<T>`. `Py<T>` needs a `Python<'_>` token to allow access:
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
|
@ -208,9 +254,9 @@ fn return_myclass() -> Py<MyClass> {
|
|||
|
||||
let obj = return_myclass();
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let cell = obj.as_ref(py); // Py<MyClass>::as_ref returns &PyCell<MyClass>
|
||||
let obj_ref = cell.borrow(); // Get PyRef<T>
|
||||
Python::with_gil(move |py| {
|
||||
let bound = obj.bind(py); // Py<MyClass>::bind returns &Bound<'py, MyClass>
|
||||
let obj_ref = bound.borrow(); // Get PyRef<T>
|
||||
assert_eq!(obj_ref.num, 1);
|
||||
});
|
||||
```
|
||||
|
@ -219,7 +265,7 @@ Python::with_gil(|py| {
|
|||
|
||||
As detailed above, runtime borrow checking is currently enabled by default. But a class can opt of out it by declaring itself `frozen`. It can still use interior mutability via standard Rust types like `RefCell` or `Mutex`, but it is not bound to the implementation provided by PyO3 and can choose the most appropriate strategy on field-by-field basis.
|
||||
|
||||
Classes which are `frozen` and also `Sync`, e.g. they do use `Mutex` but not `RefCell`, can be accessed without needing the Python GIL via the `PyCell::get` and `Py::get` methods:
|
||||
Classes which are `frozen` and also `Sync`, e.g. they do use `Mutex` but not `RefCell`, can be accessed without needing the Python GIL via the `Bound::get` and `Py::get` methods:
|
||||
|
||||
```rust
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
@ -239,13 +285,15 @@ let py_counter: Py<FrozenCounter> = Python::with_gil(|py| {
|
|||
});
|
||||
|
||||
py_counter.get().value.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
Python::with_gil(move |_py| drop(py_counter));
|
||||
```
|
||||
|
||||
Frozen classes are likely to become the default thereby guiding the PyO3 ecosystem towards a more deliberate application of interior mutability. Eventually, this should enable further optimizations of PyO3's internals and avoid downstream code paying the cost of interior mutability when it is not actually required.
|
||||
|
||||
## Customizing the class
|
||||
|
||||
{{#include ../pyclass_parameters.md}}
|
||||
{{#include ../pyclass-parameters.md}}
|
||||
|
||||
These parameters are covered in various sections of this guide.
|
||||
|
||||
|
@ -279,8 +327,12 @@ explicitly.
|
|||
|
||||
To get a parent class from a child, use [`PyRef`] instead of `&self` for methods,
|
||||
or [`PyRefMut`] instead of `&mut self`.
|
||||
Then you can access a parent class by `self_.as_ref()` as `&Self::BaseClass`,
|
||||
or by `self_.into_super()` as `PyRef<Self::BaseClass>`.
|
||||
Then you can access a parent class by `self_.as_super()` as `&PyRef<Self::BaseClass>`,
|
||||
or by `self_.into_super()` as `PyRef<Self::BaseClass>` (and similar for the `PyRefMut`
|
||||
case). For convenience, `self_.as_ref()` can also be used to get `&Self::BaseClass`
|
||||
directly; however, this approach does not let you access base clases higher in the
|
||||
inheritance hierarchy, for which you would need to chain multiple `as_super` or
|
||||
`into_super` calls.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
|
@ -297,7 +349,7 @@ impl BaseClass {
|
|||
BaseClass { val1: 10 }
|
||||
}
|
||||
|
||||
pub fn method(&self) -> PyResult<usize> {
|
||||
pub fn method1(&self) -> PyResult<usize> {
|
||||
Ok(self.val1)
|
||||
}
|
||||
}
|
||||
|
@ -315,8 +367,8 @@ impl SubClass {
|
|||
}
|
||||
|
||||
fn method2(self_: PyRef<'_, Self>) -> PyResult<usize> {
|
||||
let super_ = self_.as_ref(); // Get &BaseClass
|
||||
super_.method().map(|x| x * self_.val2)
|
||||
let super_ = self_.as_super(); // Get &PyRef<BaseClass>
|
||||
super_.method1().map(|x| x * self_.val2)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -333,14 +385,54 @@ impl SubSubClass {
|
|||
}
|
||||
|
||||
fn method3(self_: PyRef<'_, Self>) -> PyResult<usize> {
|
||||
let base = self_.as_super().as_super(); // Get &PyRef<'_, BaseClass>
|
||||
base.method1().map(|x| x * self_.val3)
|
||||
}
|
||||
|
||||
fn method4(self_: PyRef<'_, Self>) -> PyResult<usize> {
|
||||
let v = self_.val3;
|
||||
let super_ = self_.into_super(); // Get PyRef<'_, SubClass>
|
||||
SubClass::method2(super_).map(|x| x * v)
|
||||
}
|
||||
|
||||
fn get_values(self_: PyRef<'_, Self>) -> (usize, usize, usize) {
|
||||
let val1 = self_.as_super().as_super().val1;
|
||||
let val2 = self_.as_super().val2;
|
||||
(val1, val2, self_.val3)
|
||||
}
|
||||
|
||||
fn double_values(mut self_: PyRefMut<'_, Self>) {
|
||||
self_.as_super().as_super().val1 *= 2;
|
||||
self_.as_super().val2 *= 2;
|
||||
self_.val3 *= 2;
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn factory_method(py: Python<'_>, val: usize) -> PyResult<PyObject> {
|
||||
let base = PyClassInitializer::from(BaseClass::new());
|
||||
let sub = base.add_subclass(SubClass { val2: val });
|
||||
if val % 2 == 0 {
|
||||
Ok(Py::new(py, sub)?.to_object(py))
|
||||
} else {
|
||||
let sub_sub = sub.add_subclass(SubSubClass { val3: val });
|
||||
Ok(Py::new(py, sub_sub)?.to_object(py))
|
||||
}
|
||||
}
|
||||
}
|
||||
# Python::with_gil(|py| {
|
||||
# let subsub = pyo3::PyCell::new(py, SubSubClass::new()).unwrap();
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000")
|
||||
# let subsub = pyo3::Py::new(py, SubSubClass::new()).unwrap();
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.method1() == 10");
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.method2() == 150");
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.method3() == 200");
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.method4() == 3000");
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.get_values() == (10, 15, 20)");
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.double_values() == None");
|
||||
# pyo3::py_run!(py, subsub, "assert subsub.get_values() == (20, 30, 40)");
|
||||
# let subsub = SubSubClass::factory_method(py, 2).unwrap();
|
||||
# let subsubsub = SubSubClass::factory_method(py, 3).unwrap();
|
||||
# let cls = py.get_type_bound::<SubSubClass>();
|
||||
# pyo3::py_run!(py, subsub cls, "assert not isinstance(subsub, cls)");
|
||||
# pyo3::py_run!(py, subsubsub cls, "assert isinstance(subsubsub, cls)");
|
||||
# });
|
||||
```
|
||||
|
||||
|
@ -348,8 +440,9 @@ You can inherit native types such as `PyDict`, if they implement
|
|||
[`PySizedLayout`]({{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PySizedLayout.html).
|
||||
This is not supported when building for the Python limited API (aka the `abi3` feature of PyO3).
|
||||
|
||||
However, because of some technical problems, we don't currently provide safe upcasting methods for types
|
||||
that inherit native types. Even in such cases, you can unsafely get a base class by raw pointer conversion.
|
||||
To convert between the Rust type and its native base class, you can take
|
||||
`slf` as a Python object. To access the Rust fields use `slf.borrow()` or
|
||||
`slf.borrow_mut()`, and to access the base class use `slf.downcast::<BaseClass>()`.
|
||||
|
||||
```rust
|
||||
# #[cfg(not(Py_LIMITED_API))] {
|
||||
|
@ -370,15 +463,14 @@ impl DictWithCounter {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
fn set(mut self_: PyRefMut<'_, Self>, key: String, value: &PyAny) -> PyResult<()> {
|
||||
self_.counter.entry(key.clone()).or_insert(0);
|
||||
let py = self_.py();
|
||||
let dict: &PyDict = unsafe { py.from_borrowed_ptr_or_err(self_.as_ptr())? };
|
||||
fn set(slf: &Bound<'_, Self>, key: String, value: Bound<'_, PyAny>) -> PyResult<()> {
|
||||
slf.borrow_mut().counter.entry(key.clone()).or_insert(0);
|
||||
let dict = slf.downcast::<PyDict>()?;
|
||||
dict.set_item(key, value)
|
||||
}
|
||||
}
|
||||
# Python::with_gil(|py| {
|
||||
# let cnt = pyo3::PyCell::new(py, DictWithCounter::new()).unwrap();
|
||||
# let cnt = pyo3::Py::new(py, DictWithCounter::new()).unwrap();
|
||||
# pyo3::py_run!(py, cnt, "cnt.set('abc', 10); assert cnt['abc'] == 10")
|
||||
# });
|
||||
# }
|
||||
|
@ -427,14 +519,14 @@ struct MyDict {
|
|||
impl MyDict {
|
||||
#[new]
|
||||
#[pyo3(signature = (*args, **kwargs))]
|
||||
fn new(args: &PyAny, kwargs: Option<&PyAny>) -> Self {
|
||||
fn new(args: &Bound<'_, PyAny>, kwargs: Option<&Bound<'_, PyAny>>) -> Self {
|
||||
Self { private: 0 }
|
||||
}
|
||||
|
||||
// some custom methods that use `private` here...
|
||||
}
|
||||
# Python::with_gil(|py| {
|
||||
# let cls = py.get_type::<MyDict>();
|
||||
# let cls = py.get_type_bound::<MyDict>();
|
||||
# pyo3::py_run!(py, cls, "cls(a=1, b=2)")
|
||||
# });
|
||||
# }
|
||||
|
@ -628,7 +720,7 @@ This is the equivalent of the Python decorator `@classmethod`.
|
|||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[classmethod]
|
||||
fn cls_method(cls: &PyType) -> PyResult<i32> {
|
||||
fn cls_method(cls: &Bound<'_, PyType>) -> PyResult<i32> {
|
||||
Ok(10)
|
||||
}
|
||||
}
|
||||
|
@ -638,7 +730,7 @@ Declares a class method callable from Python.
|
|||
|
||||
* The first parameter is the type object of the class on which the method is called.
|
||||
This may be the type object of a derived class.
|
||||
* The first parameter implicitly has type `&PyType`.
|
||||
* The first parameter implicitly has type `&Bound<'_, PyType>`.
|
||||
* For details on `parameter-list`, see the documentation of `Method arguments` section.
|
||||
* The return type must be `PyResult<T>` or `T` for some `T` that implements `IntoPy<PyObject>`.
|
||||
|
||||
|
@ -646,6 +738,7 @@ Declares a class method callable from Python.
|
|||
|
||||
To create a constructor which takes a positional class argument, you can combine the `#[classmethod]` and `#[new]` modifiers:
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyType;
|
||||
# #[pyclass]
|
||||
|
@ -655,10 +748,10 @@ To create a constructor which takes a positional class argument, you can combine
|
|||
impl BaseClass {
|
||||
#[new]
|
||||
#[classmethod]
|
||||
fn py_new<'p>(cls: &'p PyType, py: Python<'p>) -> PyResult<Self> {
|
||||
fn py_new(cls: &Bound<'_, PyType>) -> PyResult<Self> {
|
||||
// Get an abstract attribute (presumably) declared on a subclass of this class.
|
||||
let subclass_attr = cls.getattr("a_class_attr")?;
|
||||
Ok(Self(subclass_attr.to_object(py)))
|
||||
let subclass_attr: Bound<'_, PyAny> = cls.getattr("a_class_attr")?;
|
||||
Ok(Self(subclass_attr.unbind()))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -703,7 +796,7 @@ impl MyClass {
|
|||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let my_class = py.get_type::<MyClass>();
|
||||
let my_class = py.get_type_bound::<MyClass>();
|
||||
pyo3::py_run!(py, my_class, "assert my_class.my_attribute == 'hello'")
|
||||
});
|
||||
```
|
||||
|
@ -737,23 +830,23 @@ struct MyClass {
|
|||
my_field: i32,
|
||||
}
|
||||
|
||||
// Take a GIL-bound reference when the underlying `PyCell` is irrelevant.
|
||||
// Take a reference when the underlying `Bound` is irrelevant.
|
||||
#[pyfunction]
|
||||
fn increment_field(my_class: &mut MyClass) {
|
||||
my_class.my_field += 1;
|
||||
}
|
||||
|
||||
// Take a GIL-bound reference wrapper when borrowing should be automatic,
|
||||
// but interaction with the underlying `PyCell` is desired.
|
||||
// Take a reference wrapper when borrowing should be automatic,
|
||||
// but interaction with the underlying `Bound` is desired.
|
||||
#[pyfunction]
|
||||
fn print_field(my_class: PyRef<'_, MyClass>) {
|
||||
println!("{}", my_class.my_field);
|
||||
}
|
||||
|
||||
// Take a GIL-bound reference to the underlying cell
|
||||
// Take a reference to the underlying Bound
|
||||
// when borrowing needs to be managed manually.
|
||||
#[pyfunction]
|
||||
fn increment_then_print_field(my_class: &PyCell<MyClass>) {
|
||||
fn increment_then_print_field(my_class: &Bound<'_, MyClass>) {
|
||||
my_class.borrow_mut().my_field += 1;
|
||||
|
||||
println!("{}", my_class.borrow().my_field);
|
||||
|
@ -812,9 +905,9 @@ impl MyClass {
|
|||
fn method(
|
||||
&mut self,
|
||||
num: i32,
|
||||
py_args: &PyTuple,
|
||||
py_args: &Bound<'_, PyTuple>,
|
||||
name: &str,
|
||||
py_kwargs: Option<&PyDict>,
|
||||
py_kwargs: Option<&Bound<'_, PyDict>>,
|
||||
) -> String {
|
||||
let num_before = self.num;
|
||||
self.num = num;
|
||||
|
@ -837,9 +930,7 @@ py_args=('World', 666), py_kwargs=Some({'x': 44, 'y': 55}), name=Hello, num=44,
|
|||
py_args=(), py_kwargs=None, name=World, num=-1, num_before=44
|
||||
```
|
||||
|
||||
## Making class method signatures available to Python
|
||||
|
||||
The [`text_signature = "..."`](./function.md#text_signature) option for `#[pyfunction]` also works for `#[pymethods]`:
|
||||
The [`#[pyo3(text_signature = "...")`](./function/signature.md#overriding-the-generated-signature) option for `#[pyfunction]` also works for `#[pymethods]`.
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
|
@ -864,7 +955,7 @@ impl MyClass {
|
|||
// similarly for classmethod arguments, use $cls
|
||||
#[classmethod]
|
||||
#[pyo3(text_signature = "($cls, e, f)")]
|
||||
fn my_class_method(cls: &PyType, e: i32, f: i32) -> i32 {
|
||||
fn my_class_method(cls: &Bound<'_, PyType>, e: i32, f: i32) -> i32 {
|
||||
e + f
|
||||
}
|
||||
#[staticmethod]
|
||||
|
@ -876,8 +967,8 @@ impl MyClass {
|
|||
#
|
||||
# fn main() -> PyResult<()> {
|
||||
# Python::with_gil(|py| {
|
||||
# let inspect = PyModule::import(py, "inspect")?.getattr("signature")?;
|
||||
# let module = PyModule::new(py, "my_module")?;
|
||||
# let inspect = PyModule::import_bound(py, "inspect")?.getattr("signature")?;
|
||||
# let module = PyModule::new_bound(py, "my_module")?;
|
||||
# module.add_class::<MyClass>()?;
|
||||
# let class = module.getattr("MyClass")?;
|
||||
#
|
||||
|
@ -886,7 +977,7 @@ impl MyClass {
|
|||
# assert_eq!(doc, "");
|
||||
#
|
||||
# let sig: String = inspect
|
||||
# .call1((class,))?
|
||||
# .call1((&class,))?
|
||||
# .call_method0("__str__")?
|
||||
# .extract()?;
|
||||
# assert_eq!(sig, "(c, d)");
|
||||
|
@ -894,7 +985,7 @@ impl MyClass {
|
|||
# let doc: String = class.getattr("__doc__")?.extract()?;
|
||||
# assert_eq!(doc, "");
|
||||
#
|
||||
# inspect.call1((class,)).expect_err("`text_signature` on classes is not compatible with compilation in `abi3` mode until Python 3.10 or greater");
|
||||
# inspect.call1((&class,)).expect_err("`text_signature` on classes is not compatible with compilation in `abi3` mode until Python 3.10 or greater");
|
||||
# }
|
||||
#
|
||||
# {
|
||||
|
@ -941,13 +1032,58 @@ impl MyClass {
|
|||
Note that `text_signature` on `#[new]` is not compatible with compilation in
|
||||
`abi3` mode until Python 3.10 or greater.
|
||||
|
||||
## #[pyclass] enums
|
||||
### Method receivers and lifetime elision
|
||||
|
||||
Currently PyO3 only supports fieldless enums. PyO3 adds a class attribute for each variant, so you can access them in Python without defining `#[new]`. PyO3 also provides default implementations of `__richcmp__` and `__int__`, so they can be compared using `==`:
|
||||
PyO3 supports writing instance methods using the normal method receivers for shared `&self` and unique `&mut self` references. This interacts with [lifetime elision][lifetime-elision] insofar as the lifetime of a such a receiver is assigned to all elided output lifetime parameters.
|
||||
|
||||
This is a good default for general Rust code where return values are more likely to borrow from the receiver than from the other arguments, if they contain any lifetimes at all. However, when returning bound references `Bound<'py, T>` in PyO3-based code, the GIL lifetime `'py` should usually be derived from a GIL token `py: Python<'py>` passed as an argument instead of the receiver.
|
||||
|
||||
Specifically, signatures like
|
||||
|
||||
```rust,ignore
|
||||
fn frobnicate(&self, py: Python) -> Bound<Foo>;
|
||||
```
|
||||
|
||||
will not work as they are inferred as
|
||||
|
||||
```rust,ignore
|
||||
fn frobnicate<'a, 'py>(&'a self, py: Python<'py>) -> Bound<'a, Foo>;
|
||||
```
|
||||
|
||||
instead of the intended
|
||||
|
||||
```rust,ignore
|
||||
fn frobnicate<'a, 'py>(&'a self, py: Python<'py>) -> Bound<'py, Foo>;
|
||||
```
|
||||
|
||||
and should usually be written as
|
||||
|
||||
```rust,ignore
|
||||
fn frobnicate<'py>(&self, py: Python<'py>) -> Bound<'py, Foo>;
|
||||
```
|
||||
|
||||
The same problem does not exist for `#[pyfunction]`s as the special case for receiver lifetimes does not apply and indeed a signature like
|
||||
|
||||
```rust,ignore
|
||||
fn frobnicate(bar: &Bar, py: Python) -> Bound<Foo>;
|
||||
```
|
||||
|
||||
will yield compiler error [E0106 "missing lifetime specifier"][compiler-error-e0106].
|
||||
|
||||
## `#[pyclass]` enums
|
||||
|
||||
Enum support in PyO3 comes in two flavors, depending on what kind of variants the enum has: simple and complex.
|
||||
|
||||
### Simple enums
|
||||
|
||||
A simple enum (a.k.a. C-like enum) has only unit variants.
|
||||
|
||||
PyO3 adds a class attribute for each variant, so you can access them in Python without defining `#[new]`. PyO3 also provides default implementations of `__richcmp__` and `__int__`, so they can be compared using `==`:
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
enum MyEnum {
|
||||
Variant,
|
||||
OtherVariant,
|
||||
|
@ -956,7 +1092,7 @@ enum MyEnum {
|
|||
Python::with_gil(|py| {
|
||||
let x = Py::new(py, MyEnum::Variant).unwrap();
|
||||
let y = Py::new(py, MyEnum::OtherVariant).unwrap();
|
||||
let cls = py.get_type::<MyEnum>();
|
||||
let cls = py.get_type_bound::<MyEnum>();
|
||||
pyo3::py_run!(py, x y cls, r#"
|
||||
assert x == cls.Variant
|
||||
assert y == cls.OtherVariant
|
||||
|
@ -965,24 +1101,23 @@ Python::with_gil(|py| {
|
|||
})
|
||||
```
|
||||
|
||||
You can also convert your enums into `int`:
|
||||
You can also convert your simple enums into `int`:
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
enum MyEnum {
|
||||
Variant,
|
||||
OtherVariant = 10,
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let cls = py.get_type::<MyEnum>();
|
||||
let cls = py.get_type_bound::<MyEnum>();
|
||||
let x = MyEnum::Variant as i32; // The exact value is assigned by the compiler.
|
||||
pyo3::py_run!(py, cls x, r#"
|
||||
assert int(cls.Variant) == x
|
||||
assert int(cls.OtherVariant) == 10
|
||||
assert cls.OtherVariant == 10 # You can also compare against int.
|
||||
assert 10 == cls.OtherVariant
|
||||
"#)
|
||||
})
|
||||
```
|
||||
|
@ -991,14 +1126,15 @@ PyO3 also provides `__repr__` for enums:
|
|||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
enum MyEnum{
|
||||
Variant,
|
||||
OtherVariant,
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let cls = py.get_type::<MyEnum>();
|
||||
let cls = py.get_type_bound::<MyEnum>();
|
||||
let x = Py::new(py, MyEnum::Variant).unwrap();
|
||||
pyo3::py_run!(py, cls x, r#"
|
||||
assert repr(x) == 'MyEnum.Variant'
|
||||
|
@ -1011,7 +1147,8 @@ All methods defined by PyO3 can be overridden. For example here's how you overri
|
|||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
enum MyEnum {
|
||||
Answer = 42,
|
||||
}
|
||||
|
@ -1024,7 +1161,7 @@ impl MyEnum {
|
|||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let cls = py.get_type::<MyEnum>();
|
||||
let cls = py.get_type_bound::<MyEnum>();
|
||||
pyo3::py_run!(py, cls, "assert repr(cls.Answer) == '42'")
|
||||
})
|
||||
```
|
||||
|
@ -1033,7 +1170,8 @@ Enums and their variants can also be renamed using `#[pyo3(name)]`.
|
|||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass(name = "RenamedEnum")]
|
||||
#[pyclass(eq, eq_int, name = "RenamedEnum")]
|
||||
#[derive(PartialEq)]
|
||||
enum MyEnum {
|
||||
#[pyo3(name = "UPPERCASE")]
|
||||
Variant,
|
||||
|
@ -1041,7 +1179,7 @@ enum MyEnum {
|
|||
|
||||
Python::with_gil(|py| {
|
||||
let x = Py::new(py, MyEnum::Variant).unwrap();
|
||||
let cls = py.get_type::<MyEnum>();
|
||||
let cls = py.get_type_bound::<MyEnum>();
|
||||
pyo3::py_run!(py, x cls, r#"
|
||||
assert repr(x) == 'RenamedEnum.UPPERCASE'
|
||||
assert x == cls.UPPERCASE
|
||||
|
@ -1049,6 +1187,32 @@ Python::with_gil(|py| {
|
|||
})
|
||||
```
|
||||
|
||||
Ordering of enum variants is optionally added using `#[pyo3(ord)]`.
|
||||
*Note: Implementation of the `PartialOrd` trait is required when passing the `ord` argument. If not implemented, a compile time error is raised.*
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass(eq, ord)]
|
||||
#[derive(PartialEq, PartialOrd)]
|
||||
enum MyEnum{
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let cls = py.get_type_bound::<MyEnum>();
|
||||
let a = Py::new(py, MyEnum::A).unwrap();
|
||||
let b = Py::new(py, MyEnum::B).unwrap();
|
||||
let c = Py::new(py, MyEnum::C).unwrap();
|
||||
pyo3::py_run!(py, cls a b c, r#"
|
||||
assert (a < b) == True
|
||||
assert (c <= b) == False
|
||||
assert (c > a) == True
|
||||
"#)
|
||||
})
|
||||
```
|
||||
|
||||
You may not use enums as a base class or let enums inherit from other classes.
|
||||
|
||||
```rust,compile_fail
|
||||
|
@ -1073,6 +1237,115 @@ enum BadSubclass {
|
|||
|
||||
`#[pyclass]` enums are currently not interoperable with `IntEnum` in Python.
|
||||
|
||||
### Complex enums
|
||||
|
||||
An enum is complex if it has any non-unit (struct or tuple) variants.
|
||||
|
||||
PyO3 supports only struct and tuple variants in a complex enum. Unit variants aren't supported at present (the recommendation is to use an empty tuple enum instead).
|
||||
|
||||
PyO3 adds a class attribute for each variant, which may be used to construct values and in match patterns. PyO3 also provides getter methods for all fields of each variant.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
enum Shape {
|
||||
Circle { radius: f64 },
|
||||
Rectangle { width: f64, height: f64 },
|
||||
RegularPolygon(u32, f64),
|
||||
Nothing { },
|
||||
}
|
||||
|
||||
# #[cfg(Py_3_10)]
|
||||
Python::with_gil(|py| {
|
||||
let circle = Shape::Circle { radius: 10.0 }.into_py(py);
|
||||
let square = Shape::RegularPolygon(4, 10.0).into_py(py);
|
||||
let cls = py.get_type_bound::<Shape>();
|
||||
pyo3::py_run!(py, circle square cls, r#"
|
||||
assert isinstance(circle, cls)
|
||||
assert isinstance(circle, cls.Circle)
|
||||
assert circle.radius == 10.0
|
||||
|
||||
assert isinstance(square, cls)
|
||||
assert isinstance(square, cls.RegularPolygon)
|
||||
assert square[0] == 4 # Gets _0 field
|
||||
assert square[1] == 10.0 # Gets _1 field
|
||||
|
||||
def count_vertices(cls, shape):
|
||||
match shape:
|
||||
case cls.Circle():
|
||||
return 0
|
||||
case cls.Rectangle():
|
||||
return 4
|
||||
case cls.RegularPolygon(n):
|
||||
return n
|
||||
case cls.Nothing():
|
||||
return 0
|
||||
|
||||
assert count_vertices(cls, circle) == 0
|
||||
assert count_vertices(cls, square) == 4
|
||||
"#)
|
||||
})
|
||||
```
|
||||
|
||||
WARNING: `Py::new` and `.into_py` are currently inconsistent. Note how the constructed value is _not_ an instance of the specific variant. For this reason, constructing values is only recommended using `.into_py`.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
enum MyEnum {
|
||||
Variant { i: i32 },
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let x = Py::new(py, MyEnum::Variant { i: 42 }).unwrap();
|
||||
let cls = py.get_type_bound::<MyEnum>();
|
||||
pyo3::py_run!(py, x cls, r#"
|
||||
assert isinstance(x, cls)
|
||||
assert not isinstance(x, cls.Variant)
|
||||
"#)
|
||||
})
|
||||
```
|
||||
|
||||
The constructor of each generated class can be customized using the `#[pyo3(constructor = (...))]` attribute. This uses the same syntax as the [`#[pyo3(signature = (...))]`](function/signature.md)
|
||||
attribute on function and methods and supports the same options. To apply this attribute simply place it on top of a variant in a `#[pyclass]` complex enum as shown below:
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
enum Shape {
|
||||
#[pyo3(constructor = (radius=1.0))]
|
||||
Circle { radius: f64 },
|
||||
#[pyo3(constructor = (*, width, height))]
|
||||
Rectangle { width: f64, height: f64 },
|
||||
#[pyo3(constructor = (side_count, radius=1.0))]
|
||||
RegularPolygon { side_count: u32, radius: f64 },
|
||||
Nothing { },
|
||||
}
|
||||
|
||||
# #[cfg(Py_3_10)]
|
||||
Python::with_gil(|py| {
|
||||
let cls = py.get_type_bound::<Shape>();
|
||||
pyo3::py_run!(py, cls, r#"
|
||||
circle = cls.Circle()
|
||||
assert isinstance(circle, cls)
|
||||
assert isinstance(circle, cls.Circle)
|
||||
assert circle.radius == 1.0
|
||||
|
||||
square = cls.Rectangle(width = 1, height = 1)
|
||||
assert isinstance(square, cls)
|
||||
assert isinstance(square, cls.Rectangle)
|
||||
assert square.width == 1
|
||||
assert square.height == 1
|
||||
|
||||
hexagon = cls.RegularPolygon(6)
|
||||
assert isinstance(hexagon, cls)
|
||||
assert isinstance(hexagon, cls.RegularPolygon)
|
||||
assert hexagon.side_count == 6
|
||||
assert hexagon.radius == 1
|
||||
"#)
|
||||
})
|
||||
```
|
||||
|
||||
## Implementation details
|
||||
|
||||
The `#[pyclass]` macros rely on a lot of conditional code generation: each `#[pyclass]` can optionally have a `#[pymethods]` block.
|
||||
|
@ -1091,8 +1364,15 @@ struct MyClass {
|
|||
# #[allow(dead_code)]
|
||||
num: i32,
|
||||
}
|
||||
unsafe impl pyo3::type_object::PyTypeInfo for MyClass {
|
||||
|
||||
impl pyo3::types::DerefToPyAny for MyClass {}
|
||||
|
||||
# #[allow(deprecated)]
|
||||
# #[cfg(feature = "gil-refs")]
|
||||
unsafe impl pyo3::type_object::HasPyGilRef for MyClass {
|
||||
type AsRefTarget = pyo3::PyCell<Self>;
|
||||
}
|
||||
unsafe impl pyo3::type_object::PyTypeInfo for MyClass {
|
||||
const NAME: &'static str = "MyClass";
|
||||
const MODULE: ::std::option::Option<&'static str> = ::std::option::Option::None;
|
||||
#[inline]
|
||||
|
@ -1112,7 +1392,7 @@ impl<'a, 'py> pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a
|
|||
type Holder = ::std::option::Option<pyo3::PyRef<'py, MyClass>>;
|
||||
|
||||
#[inline]
|
||||
fn extract(obj: &'py pyo3::PyAny, holder: &'a mut Self::Holder) -> pyo3::PyResult<Self> {
|
||||
fn extract(obj: &'a pyo3::Bound<'py, PyAny>, holder: &'a mut Self::Holder) -> pyo3::PyResult<Self> {
|
||||
pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder)
|
||||
}
|
||||
}
|
||||
|
@ -1122,7 +1402,7 @@ impl<'a, 'py> pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a
|
|||
type Holder = ::std::option::Option<pyo3::PyRefMut<'py, MyClass>>;
|
||||
|
||||
#[inline]
|
||||
fn extract(obj: &'py pyo3::PyAny, holder: &'a mut Self::Holder) -> pyo3::PyResult<Self> {
|
||||
fn extract(obj: &'a pyo3::Bound<'py, PyAny>, holder: &'a mut Self::Holder) -> pyo3::PyResult<Self> {
|
||||
pyo3::impl_::extract_argument::extract_pyclass_ref_mut(obj, holder)
|
||||
}
|
||||
}
|
||||
|
@ -1136,6 +1416,8 @@ impl pyo3::IntoPy<PyObject> for MyClass {
|
|||
impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
|
||||
const IS_BASETYPE: bool = false;
|
||||
const IS_SUBCLASS: bool = false;
|
||||
const IS_MAPPING: bool = false;
|
||||
const IS_SEQUENCE: bool = false;
|
||||
type BaseType = PyAny;
|
||||
type ThreadChecker = pyo3::impl_::pyclass::SendablePyClass<MyClass>;
|
||||
type PyClassMutability = <<pyo3::PyAny as pyo3::impl_::pyclass::PyClassBaseType>::PyClassMutability as pyo3::impl_::pycell::PyClassMutability>::MutableChild;
|
||||
|
@ -1158,27 +1440,26 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
|
|||
|
||||
fn doc(py: Python<'_>) -> pyo3::PyResult<&'static ::std::ffi::CStr> {
|
||||
use pyo3::impl_::pyclass::*;
|
||||
static DOC: pyo3::once_cell::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = pyo3::once_cell::GILOnceCell::new();
|
||||
static DOC: pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = pyo3::sync::GILOnceCell::new();
|
||||
DOC.get_or_try_init(py, || {
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, "", None.or_else(|| collector.new_text_signature()))
|
||||
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, pyo3::ffi::c_str!(""), collector.new_text_signature())
|
||||
}).map(::std::ops::Deref::deref)
|
||||
}
|
||||
}
|
||||
|
||||
# Python::with_gil(|py| {
|
||||
# let cls = py.get_type::<MyClass>();
|
||||
# let cls = py.get_type_bound::<MyClass>();
|
||||
# pyo3::py_run!(py, cls, "assert cls.__name__ == 'MyClass'")
|
||||
# });
|
||||
# }
|
||||
```
|
||||
|
||||
|
||||
[`GILGuard`]: {{#PYO3_DOCS_URL}}/pyo3/struct.GILGuard.html
|
||||
[`PyTypeInfo`]: {{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PyTypeInfo.html
|
||||
|
||||
[`Py`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Py.html
|
||||
[`PyCell`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyCell.html
|
||||
[`Bound<'_, T>`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Bound.html
|
||||
[`PyClass`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass/trait.PyClass.html
|
||||
[`PyRef`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html
|
||||
[`PyRefMut`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRefMut.html
|
||||
|
@ -1190,3 +1471,6 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
|
|||
[classattr]: https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables
|
||||
|
||||
[`multiple-pymethods`]: features.md#multiple-pymethods
|
||||
|
||||
[lifetime-elision]: https://doc.rust-lang.org/reference/lifetime-elision.html
|
||||
[compiler-error-e0106]: https://doc.rust-lang.org/error_codes/E0106.html
|
||||
|
|
|
@ -75,8 +75,8 @@ A [previous implementation] used a normal `u64`, which meant it required a `&mut
|
|||
fn __call__(
|
||||
&mut self,
|
||||
py: Python<'_>,
|
||||
args: &PyTuple,
|
||||
kwargs: Option<&PyDict>,
|
||||
args: &Bound<'_, PyTuple>,
|
||||
kwargs: Option<&Bound<'_, PyDict>>,
|
||||
) -> PyResult<Py<PyAny>> {
|
||||
self.count += 1;
|
||||
let name = self.wraps.getattr(py, "__name__")?;
|
||||
|
|
|
@ -35,7 +35,7 @@ and cast it to an `i32`.
|
|||
# #![allow(dead_code)]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
|
||||
fn wrap(obj: &Bound<'_, PyAny>) -> PyResult<i32> {
|
||||
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
|
||||
let val: u32 = val.extract()?;
|
||||
// 👇 This intentionally overflows!
|
||||
|
@ -48,7 +48,7 @@ We also add documentation, via `///` comments, which are visible to Python users
|
|||
# #![allow(dead_code)]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
|
||||
fn wrap(obj: &Bound<'_, PyAny>) -> PyResult<i32> {
|
||||
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
|
||||
let val: u32 = val.extract()?;
|
||||
Ok(val as i32)
|
||||
|
@ -170,8 +170,8 @@ impl Number {
|
|||
self.0 as f64
|
||||
}
|
||||
|
||||
fn __complex__<'py>(&self, py: Python<'py>) -> &'py PyComplex {
|
||||
PyComplex::from_doubles(py, self.0 as f64, 0.0)
|
||||
fn __complex__<'py>(&self, py: Python<'py>) -> Bound<'py, PyComplex> {
|
||||
PyComplex::from_doubles_bound(py, self.0 as f64, 0.0)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -206,14 +206,13 @@ assert hash_djb2('l50_50') == Number(-1152549421)
|
|||
```rust
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::convert::TryInto;
|
||||
|
||||
use pyo3::exceptions::{PyValueError, PyZeroDivisionError};
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::class::basic::CompareOp;
|
||||
use pyo3::types::PyComplex;
|
||||
use pyo3::types::{PyComplex, PyString};
|
||||
|
||||
fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
|
||||
fn wrap(obj: &Bound<'_, PyAny>) -> PyResult<i32> {
|
||||
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
|
||||
let val: u32 = val.extract()?;
|
||||
Ok(val as i32)
|
||||
|
@ -230,9 +229,9 @@ impl Number {
|
|||
Self(value)
|
||||
}
|
||||
|
||||
fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
|
||||
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
|
||||
// Get the class name dynamically in case `Number` is subclassed
|
||||
let class_name: &str = slf.get_type().name()?;
|
||||
let class_name: Bound<'_, PyString> = slf.get_type().qualname()?;
|
||||
Ok(format!("{}({})", class_name, slf.borrow().0))
|
||||
}
|
||||
|
||||
|
@ -321,13 +320,13 @@ impl Number {
|
|||
self.0 as f64
|
||||
}
|
||||
|
||||
fn __complex__<'py>(&self, py: Python<'py>) -> &'py PyComplex {
|
||||
PyComplex::from_doubles(py, self.0 as f64, 0.0)
|
||||
fn __complex__<'py>(&self, py: Python<'py>) -> Bound<'py, PyComplex> {
|
||||
PyComplex::from_doubles_bound(py, self.0 as f64, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<Number>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -387,10 +386,10 @@ fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
|||
#
|
||||
# fn main() -> PyResult<()> {
|
||||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
# let globals = PyModule::import(py, "__main__")?.dict();
|
||||
# globals.set_item("Number", Number::type_object(py))?;
|
||||
# let globals = PyModule::import_bound(py, "__main__")?.dict();
|
||||
# globals.set_item("Number", Number::type_object_bound(py))?;
|
||||
#
|
||||
# py.run(SCRIPT, Some(globals), None)?;
|
||||
# py.run_bound(SCRIPT, Some(&globals), None)?;
|
||||
# Ok(())
|
||||
# })
|
||||
# }
|
||||
|
@ -412,8 +411,8 @@ the contracts of this function. Let's review those contracts:
|
|||
- The GIL must be held. If it's not, calling this function causes a data race.
|
||||
- The pointer must be valid, i.e. it must be properly aligned and point to a valid Python object.
|
||||
|
||||
Let's create that helper function. The signature has to be `fn(&PyAny) -> PyResult<T>`.
|
||||
- `&PyAny` represents a checked borrowed reference, so the pointer derived from it is valid (and not null).
|
||||
Let's create that helper function. The signature has to be `fn(&Bound<'_, PyAny>) -> PyResult<T>`.
|
||||
- `&Bound<'_, PyAny>` represents a checked borrowed reference, so the pointer derived from it is valid (and not null).
|
||||
- Whenever we have borrowed references to Python objects in scope, it is guaranteed that the GIL is held. This reference is also where we can get a [`Python`] token to use in our call to [`PyErr::take`].
|
||||
|
||||
```rust
|
||||
|
@ -422,7 +421,7 @@ use std::os::raw::c_ulong;
|
|||
use pyo3::prelude::*;
|
||||
use pyo3::ffi;
|
||||
|
||||
fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
|
||||
fn wrap(obj: &Bound<'_, PyAny>) -> Result<i32, PyErr> {
|
||||
let py: Python<'_> = obj.py();
|
||||
|
||||
unsafe {
|
||||
|
@ -441,6 +440,6 @@ fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
|
|||
```
|
||||
|
||||
[`PyErr::take`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/struct.PyErr.html#method.take
|
||||
[`Python`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Python.html
|
||||
[`Python`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html
|
||||
[`FromPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.FromPyObject.html
|
||||
[`pyo3::ffi::PyLong_AsUnsignedLongMask`]: {{#PYO3_DOCS_URL}}/pyo3/ffi/fn.PyLong_AsUnsignedLongMask.html
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
Recall the `Number` class from the previous chapter:
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass]
|
||||
|
@ -17,7 +18,7 @@ impl Number {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<Number>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -75,19 +76,20 @@ In the `__repr__`, we used a hard-coded class name. This is sometimes not ideal,
|
|||
because if the class is subclassed in Python, we would like the repr to reflect
|
||||
the subclass name. This is typically done in Python code by accessing
|
||||
`self.__class__.__name__`. In order to be able to access the Python type information
|
||||
*and* the Rust struct, we need to use a `PyCell` as the `self` argument.
|
||||
*and* the Rust struct, we need to use a `Bound` as the `self` argument.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
#
|
||||
# #[pyclass]
|
||||
# struct Number(i32);
|
||||
#
|
||||
#[pymethods]
|
||||
impl Number {
|
||||
fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
|
||||
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
|
||||
// This is the equivalent of `self.__class__.__name__` in Python.
|
||||
let class_name: &str = slf.get_type().name()?;
|
||||
let class_name: Bound<'_, PyString> = slf.get_type().qualname()?;
|
||||
// To access fields of the Rust struct, we need to borrow the `PyCell`.
|
||||
Ok(format!("{}({})", class_name, slf.borrow().0))
|
||||
}
|
||||
|
@ -120,6 +122,19 @@ impl Number {
|
|||
}
|
||||
}
|
||||
```
|
||||
To implement `__hash__` using the Rust [`Hash`] trait implementation, the `hash` option can be used.
|
||||
This option is only available for `frozen` classes to prevent accidental hash changes from mutating the object. If you need
|
||||
an `__hash__` implementation for a mutable class, use the manual method from above. This option also requires `eq`: According to the
|
||||
[Python docs](https://docs.python.org/3/reference/datamodel.html#object.__hash__) "If a class does not define an `__eq__()`
|
||||
method it should not define a `__hash__()` operation either"
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#
|
||||
#[pyclass(frozen, eq, hash)]
|
||||
#[derive(PartialEq, Hash)]
|
||||
struct Number(i32);
|
||||
```
|
||||
|
||||
|
||||
> **Note**: When implementing `__hash__` and comparisons, it is important that the following property holds:
|
||||
>
|
||||
|
@ -216,8 +231,8 @@ impl Number {
|
|||
|
||||
# fn main() -> PyResult<()> {
|
||||
# Python::with_gil(|py| {
|
||||
# let x = PyCell::new(py, Number(4))?;
|
||||
# let y = PyCell::new(py, Number(4))?;
|
||||
# let x = &Bound::new(py, Number(4))?;
|
||||
# let y = &Bound::new(py, Number(4))?;
|
||||
# assert!(x.eq(y)?);
|
||||
# assert!(!x.ne(y)?);
|
||||
# Ok(())
|
||||
|
@ -225,6 +240,26 @@ impl Number {
|
|||
# }
|
||||
```
|
||||
|
||||
To implement `__eq__` using the Rust [`PartialEq`] trait implementation, the `eq` option can be used.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#
|
||||
#[pyclass(eq)]
|
||||
#[derive(PartialEq)]
|
||||
struct Number(i32);
|
||||
```
|
||||
|
||||
To implement `__lt__`, `__le__`, `__gt__`, & `__ge__` using the Rust `PartialOrd` trait implementation, the `ord` option can be used. *Note: Requires `eq`.*
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#
|
||||
#[pyclass(eq, ord)]
|
||||
#[derive(PartialEq, PartialOrd)]
|
||||
struct Number(i32);
|
||||
```
|
||||
|
||||
### Truthyness
|
||||
|
||||
We'll consider `Number` to be `True` if it is nonzero:
|
||||
|
@ -251,6 +286,7 @@ use std::hash::{Hash, Hasher};
|
|||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::class::basic::CompareOp;
|
||||
use pyo3::types::PyString;
|
||||
|
||||
#[pyclass]
|
||||
struct Number(i32);
|
||||
|
@ -262,8 +298,8 @@ impl Number {
|
|||
Self(value)
|
||||
}
|
||||
|
||||
fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
|
||||
let class_name: &str = slf.get_type().name()?;
|
||||
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
|
||||
let class_name: Bound<'_, PyString> = slf.get_type().qualname()?;
|
||||
Ok(format!("{}({})", class_name, slf.borrow().0))
|
||||
}
|
||||
|
||||
|
@ -294,7 +330,7 @@ impl Number {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<Number>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -304,3 +340,4 @@ fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
|||
[`Hasher`]: https://doc.rust-lang.org/std/hash/trait.Hasher.html
|
||||
[`DefaultHasher`]: https://doc.rust-lang.org/std/collections/hash_map/struct.DefaultHasher.html
|
||||
[SipHash]: https://en.wikipedia.org/wiki/SipHash
|
||||
[`PartialEq`]: https://doc.rust-lang.org/stable/std/cmp/trait.PartialEq.html
|
||||
|
|
|
@ -1,23 +1,30 @@
|
|||
# Magic methods and slots
|
||||
# Class customizations
|
||||
|
||||
Python's object model defines several protocols for different object behavior, such as the sequence, mapping, and number protocols. You may be familiar with implementing these protocols in Python classes by "magic" methods, such as `__str__` or `__repr__`. Because of the double-underscores surrounding their name, these are also known as "dunder" methods.
|
||||
Python's object model defines several protocols for different object behavior, such as the sequence, mapping, and number protocols. Python classes support these protocols by implementing "magic" methods, such as `__str__` or `__repr__`. Because of the double-underscores surrounding their name, these are also known as "dunder" methods.
|
||||
|
||||
In the Python C-API which PyO3 is implemented upon, many of these magic methods have to be placed into special "slots" on the class type object, as covered in the previous section.
|
||||
PyO3 makes it possible for every magic method to be implemented in `#[pymethods]` just as they would be done in a regular Python class, with a few notable differences:
|
||||
- `__new__` and `__init__` are replaced by the [`#[new]` attribute](../class.md#constructor).
|
||||
- `__del__` is not yet supported, but may be in the future.
|
||||
- `__buffer__` and `__release_buffer__` are currently not supported and instead PyO3 supports [`__getbuffer__` and `__releasebuffer__`](#buffer-objects) methods (these predate [PEP 688](https://peps.python.org/pep-0688/#python-level-buffer-protocol)), again this may change in the future.
|
||||
- PyO3 adds [`__traverse__` and `__clear__`](#garbage-collector-integration) methods for controlling garbage collection.
|
||||
- The Python C-API which PyO3 is implemented upon requires many magic methods to have a specific function signature in C and be placed into special "slots" on the class type object. This limits the allowed argument and return types for these methods. They are listed in detail in the section below.
|
||||
|
||||
If a function name in `#[pymethods]` is a recognised magic method, it will be automatically placed into the correct slot in the Python type object. The function name is taken from the usual rules for naming `#[pymethods]`: the `#[pyo3(name = "...")]` attribute is used if present, otherwise the Rust function name is used.
|
||||
If a magic method is not on the list above (for example `__init_subclass__`), then it should just work in PyO3. If this is not the case, please file a bug report.
|
||||
|
||||
The magic methods handled by PyO3 are very similar to the standard Python ones on [this page](https://docs.python.org/3/reference/datamodel.html#special-method-names) - in particular they are the the subset which have slots as [defined here](https://docs.python.org/3/c-api/typeobj.html). Some of the slots do not have a magic method in Python, which leads to a few additional magic methods defined only in PyO3:
|
||||
- Magic methods for garbage collection
|
||||
- Magic methods for the buffer protocol
|
||||
## Magic Methods handled by PyO3
|
||||
|
||||
If a function name in `#[pymethods]` is a magic method which is known to need special handling, it will be automatically placed into the correct slot in the Python type object. The function name is taken from the usual rules for naming `#[pymethods]`: the `#[pyo3(name = "...")]` attribute is used if present, otherwise the Rust function name is used.
|
||||
|
||||
The magic methods handled by PyO3 are very similar to the standard Python ones on [this page](https://docs.python.org/3/reference/datamodel.html#special-method-names) - in particular they are the subset which have slots as [defined here](https://docs.python.org/3/c-api/typeobj.html).
|
||||
|
||||
When PyO3 handles a magic method, a couple of changes apply compared to other `#[pymethods]`:
|
||||
- The Rust function signature is restricted to match the magic method.
|
||||
- The `#[pyo3(signature = (...)]` and `#[pyo3(text_signature = "...")]` attributes are not allowed.
|
||||
|
||||
The following sections list of all magic methods PyO3 currently handles. The
|
||||
The following sections list all magic methods for which PyO3 implements the necessary special handling. The
|
||||
given signatures should be interpreted as follows:
|
||||
- All methods take a receiver as first argument, shown as `<self>`. It can be
|
||||
`&self`, `&mut self` or a `PyCell` reference like `self_: PyRef<'_, Self>` and
|
||||
`&self`, `&mut self` or a `Bound` reference like `self_: PyRef<'_, Self>` and
|
||||
`self_: PyRefMut<'_, Self>`, as described [here](../class.md#inheritance).
|
||||
- An optional `Python<'py>` argument is always allowed as the first argument.
|
||||
- Return values can be optionally wrapped in `PyResult`.
|
||||
|
@ -31,7 +38,6 @@ given signatures should be interpreted as follows:
|
|||
checked by the Python interpreter. For example, `__str__` needs to return a
|
||||
string object. This is indicated by `object (Python type)`.
|
||||
|
||||
|
||||
### Basic object customization
|
||||
|
||||
- `__str__(<self>) -> object (str)`
|
||||
|
@ -207,7 +213,7 @@ impl Container {
|
|||
|
||||
# Python::with_gil(|py| {
|
||||
# let container = Container { iter: vec![1, 2, 3, 4] };
|
||||
# let inst = pyo3::PyCell::new(py, container).unwrap();
|
||||
# let inst = pyo3::Py::new(py, container).unwrap();
|
||||
# pyo3::py_run!(py, inst, "assert list(inst) == [1, 2, 3, 4]");
|
||||
# pyo3::py_run!(py, inst, "assert list(iter(iter(inst))) == [1, 2, 3, 4]");
|
||||
# });
|
||||
|
@ -450,7 +456,7 @@ Usually, an implementation of `__traverse__` should do nothing but calls to `vis
|
|||
Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`,
|
||||
i.e. `Python::with_gil` will panic.
|
||||
|
||||
> Note: these methods are part of the C API, PyPy does not necessarily honor them. If you are building for PyPy you should measure memory consumption to make sure you do not have runaway memory growth. See [this issue on the PyPy bug tracker](https://foss.heptapod.net/pypy/pypy/-/issues/3899).
|
||||
> Note: these methods are part of the C API, PyPy does not necessarily honor them. If you are building for PyPy you should measure memory consumption to make sure you do not have runaway memory growth. See [this issue on the PyPy bug tracker](https://github.com/pypy/pypy/issues/3848).
|
||||
|
||||
[`IterNextOutput`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass/enum.IterNextOutput.html
|
||||
[`PySequence`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PySequence.html
|
||||
|
|
|
@ -12,49 +12,50 @@ The table below contains the Python type and the corresponding function argument
|
|||
|
||||
| Python | Rust | Rust (Python-native) |
|
||||
| ------------- |:-------------------------------:|:--------------------:|
|
||||
| `object` | - | `&PyAny` |
|
||||
| `str` | `String`, `Cow<str>`, `&str`, `OsString`, `PathBuf`, `Path` | `&PyString`, `&PyUnicode` |
|
||||
| `bytes` | `Vec<u8>`, `&[u8]`, `Cow<[u8]>` | `&PyBytes` |
|
||||
| `bool` | `bool` | `&PyBool` |
|
||||
| `int` | `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize`, `num_bigint::BigInt`[^1], `num_bigint::BigUint`[^1] | `&PyLong` |
|
||||
| `float` | `f32`, `f64` | `&PyFloat` |
|
||||
| `complex` | `num_complex::Complex`[^2] | `&PyComplex` |
|
||||
| `list[T]` | `Vec<T>` | `&PyList` |
|
||||
| `dict[K, V]` | `HashMap<K, V>`, `BTreeMap<K, V>`, `hashbrown::HashMap<K, V>`[^3], `indexmap::IndexMap<K, V>`[^4] | `&PyDict` |
|
||||
| `tuple[T, U]` | `(T, U)`, `Vec<T>` | `&PyTuple` |
|
||||
| `set[T]` | `HashSet<T>`, `BTreeSet<T>`, `hashbrown::HashSet<T>`[^3] | `&PySet` |
|
||||
| `frozenset[T]` | `HashSet<T>`, `BTreeSet<T>`, `hashbrown::HashSet<T>`[^3] | `&PyFrozenSet` |
|
||||
| `bytearray` | `Vec<u8>`, `Cow<[u8]>` | `&PyByteArray` |
|
||||
| `slice` | - | `&PySlice` |
|
||||
| `type` | - | `&PyType` |
|
||||
| `module` | - | `&PyModule` |
|
||||
| `object` | - | `PyAny` |
|
||||
| `str` | `String`, `Cow<str>`, `&str`, `char`, `OsString`, `PathBuf`, `Path` | `PyString`, `PyUnicode` |
|
||||
| `bytes` | `Vec<u8>`, `&[u8]`, `Cow<[u8]>` | `PyBytes` |
|
||||
| `bool` | `bool` | `PyBool` |
|
||||
| `int` | `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize`, `num_bigint::BigInt`[^1], `num_bigint::BigUint`[^1] | `PyLong` |
|
||||
| `float` | `f32`, `f64` | `PyFloat` |
|
||||
| `complex` | `num_complex::Complex`[^2] | `PyComplex` |
|
||||
| `fractions.Fraction`| `num_rational::Ratio`[^8] | - |
|
||||
| `list[T]` | `Vec<T>` | `PyList` |
|
||||
| `dict[K, V]` | `HashMap<K, V>`, `BTreeMap<K, V>`, `hashbrown::HashMap<K, V>`[^3], `indexmap::IndexMap<K, V>`[^4] | `PyDict` |
|
||||
| `tuple[T, U]` | `(T, U)`, `Vec<T>` | `PyTuple` |
|
||||
| `set[T]` | `HashSet<T>`, `BTreeSet<T>`, `hashbrown::HashSet<T>`[^3] | `PySet` |
|
||||
| `frozenset[T]` | `HashSet<T>`, `BTreeSet<T>`, `hashbrown::HashSet<T>`[^3] | `PyFrozenSet` |
|
||||
| `bytearray` | `Vec<u8>`, `Cow<[u8]>` | `PyByteArray` |
|
||||
| `slice` | - | `PySlice` |
|
||||
| `type` | - | `PyType` |
|
||||
| `module` | - | `PyModule` |
|
||||
| `collections.abc.Buffer` | - | `PyBuffer<T>` |
|
||||
| `datetime.datetime` | - | `&PyDateTime` |
|
||||
| `datetime.date` | - | `&PyDate` |
|
||||
| `datetime.time` | - | `&PyTime` |
|
||||
| `datetime.tzinfo` | - | `&PyTzInfo` |
|
||||
| `datetime.timedelta` | - | `&PyDelta` |
|
||||
| `decimal.Decimal` | `rust_decimal::Decimal`[^5] | - |
|
||||
| `datetime.datetime` | `SystemTime`, `chrono::DateTime<Tz>`[^5], `chrono::NaiveDateTime`[^5] | `PyDateTime` |
|
||||
| `datetime.date` | `chrono::NaiveDate`[^5] | `PyDate` |
|
||||
| `datetime.time` | `chrono::NaiveTime`[^5] | `PyTime` |
|
||||
| `datetime.tzinfo` | `chrono::FixedOffset`[^5], `chrono::Utc`[^5], `chrono_tz::TimeZone`[^6] | `PyTzInfo` |
|
||||
| `datetime.timedelta` | `Duration`, `chrono::Duration`[^5] | `PyDelta` |
|
||||
| `decimal.Decimal` | `rust_decimal::Decimal`[^7] | - |
|
||||
| `ipaddress.IPv4Address` | `std::net::IpAddr`, `std::net::IpV4Addr` | - |
|
||||
| `ipaddress.IPv6Address` | `std::net::IpAddr`, `std::net::IpV6Addr` | - |
|
||||
| `os.PathLike ` | `PathBuf`, `Path` | `&PyString`, `&PyUnicode` |
|
||||
| `pathlib.Path` | `PathBuf`, `Path` | `&PyString`, `&PyUnicode` |
|
||||
| `os.PathLike ` | `PathBuf`, `Path` | `PyString`, `PyUnicode` |
|
||||
| `pathlib.Path` | `PathBuf`, `Path` | `PyString`, `PyUnicode` |
|
||||
| `typing.Optional[T]` | `Option<T>` | - |
|
||||
| `typing.Sequence[T]` | `Vec<T>` | `&PySequence` |
|
||||
| `typing.Sequence[T]` | `Vec<T>` | `PySequence` |
|
||||
| `typing.Mapping[K, V]` | `HashMap<K, V>`, `BTreeMap<K, V>`, `hashbrown::HashMap<K, V>`[^3], `indexmap::IndexMap<K, V>`[^4] | `&PyMapping` |
|
||||
| `typing.Iterator[Any]` | - | `&PyIterator` |
|
||||
| `typing.Union[...]` | See [`#[derive(FromPyObject)]`](traits.html#deriving-a-hrefhttpsdocsrspyo3latestpyo3conversiontraitfrompyobjecthtmlfrompyobjecta-for-enums) | - |
|
||||
| `typing.Iterator[Any]` | - | `PyIterator` |
|
||||
| `typing.Union[...]` | See [`#[derive(FromPyObject)]`](traits.md#deriving-frompyobject-for-enums) | - |
|
||||
|
||||
There are also a few special types related to the GIL and Rust-defined `#[pyclass]`es which may come in useful:
|
||||
It is also worth remembering the following special types:
|
||||
|
||||
| What | Description |
|
||||
| ------------- | ------------------------------------- |
|
||||
| `Python` | A GIL token, used to pass to PyO3 constructors to prove ownership of the GIL |
|
||||
| `Py<T>` | A Python object isolated from the GIL lifetime. This can be sent to other threads. |
|
||||
| `PyObject` | An alias for `Py<PyAny>` |
|
||||
| `&PyCell<T>` | A `#[pyclass]` value owned by Python. |
|
||||
| `PyRef<T>` | A `#[pyclass]` borrowed immutably. |
|
||||
| `PyRefMut<T>` | A `#[pyclass]` borrowed mutably. |
|
||||
| What | Description |
|
||||
| ---------------- | ------------------------------------- |
|
||||
| `Python<'py>` | A GIL token, used to pass to PyO3 constructors to prove ownership of the GIL. |
|
||||
| `Bound<'py, T>` | A Python object connected to the GIL lifetime. This provides access to most of PyO3's APIs. |
|
||||
| `Py<T>` | A Python object isolated from the GIL lifetime. This can be sent to other threads. |
|
||||
| `PyObject` | An alias for `Py<PyAny>` |
|
||||
| `PyRef<T>` | A `#[pyclass]` borrowed immutably. |
|
||||
| `PyRefMut<T>` | A `#[pyclass]` borrowed mutably. |
|
||||
|
||||
For more detail on accepting `#[pyclass]` values as function arguments, see [the section of this guide on Python Classes](../class.md).
|
||||
|
||||
|
@ -72,9 +73,9 @@ For most PyO3 usage the conversion cost is worth paying to get these benefits. A
|
|||
|
||||
### Returning Rust values to Python
|
||||
|
||||
When returning values from functions callable from Python, Python-native types (`&PyAny`, `&PyDict` etc.) can be used with zero cost.
|
||||
When returning values from functions callable from Python, [PyO3's smart pointers](../types.md#pyo3s-smart-pointers) (`Py<T>`, `Bound<'py, T>`, and `Borrowed<'a, 'py, T>`) can be used with zero cost.
|
||||
|
||||
Because these types are references, in some situations the Rust compiler may ask for lifetime annotations. If this is the case, you should use `Py<PyAny>`, `Py<PyDict>` etc. instead - which are also zero-cost. For all of these Python-native types `T`, `Py<T>` can be created from `T` with an `.into()` conversion.
|
||||
Because `Bound<'py, T>` and `Borrowed<'a, 'py, T>` have lifetime parameters, the Rust compiler may ask for lifetime annotations to be added to your function. See the [section of the guide dedicated to this](../types.md#function-argument-lifetimes).
|
||||
|
||||
If your function is fallible, it should return `PyResult<T>` or `Result<T, E>` where `E` implements `From<E> for PyErr`. This will raise a `Python` exception if the `Err` variant is returned.
|
||||
|
||||
|
@ -95,7 +96,8 @@ Finally, the following Rust types are also able to convert to Python as return v
|
|||
| `BTreeMap<K, V>` | `Dict[K, V]` |
|
||||
| `HashSet<T>` | `Set[T]` |
|
||||
| `BTreeSet<T>` | `Set[T]` |
|
||||
| `&PyCell<T: PyClass>` | `T` |
|
||||
| `Py<T>` | `T` |
|
||||
| `Bound<T>` | `T` |
|
||||
| `PyRef<T: PyClass>` | `T` |
|
||||
| `PyRefMut<T: PyClass>` | `T` |
|
||||
|
||||
|
@ -107,4 +109,10 @@ Finally, the following Rust types are also able to convert to Python as return v
|
|||
|
||||
[^4]: Requires the `indexmap` optional feature.
|
||||
|
||||
[^5]: Requires the `rust_decimal` optional feature.
|
||||
[^5]: Requires the `chrono` optional feature.
|
||||
|
||||
[^6]: Requires the `chrono-tz` optional feature.
|
||||
|
||||
[^7]: Requires the `rust_decimal` optional feature.
|
||||
|
||||
[^8]: Requires the `num-rational` optional feature.
|
||||
|
|
|
@ -13,7 +13,7 @@ fails, so usually you will use something like
|
|||
# use pyo3::types::PyList;
|
||||
# fn main() -> PyResult<()> {
|
||||
# Python::with_gil(|py| {
|
||||
# let list = PyList::new(py, b"foo");
|
||||
# let list = PyList::new_bound(py, b"foo");
|
||||
let v: Vec<i32> = list.extract()?;
|
||||
# assert_eq!(&v, &[102, 111, 111]);
|
||||
# Ok(())
|
||||
|
@ -54,7 +54,7 @@ struct RustyStruct {
|
|||
#
|
||||
# fn main() -> PyResult<()> {
|
||||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
# let module = PyModule::from_code(
|
||||
# let module = PyModule::from_code_bound(
|
||||
# py,
|
||||
# "class Foo:
|
||||
# def __init__(self):
|
||||
|
@ -86,7 +86,7 @@ struct RustyStruct {
|
|||
# use pyo3::types::PyDict;
|
||||
# fn main() -> PyResult<()> {
|
||||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
# let dict = PyDict::new(py);
|
||||
# let dict = PyDict::new_bound(py);
|
||||
# dict.set_item("my_string", "test")?;
|
||||
#
|
||||
# let rustystruct: RustyStruct = dict.extract()?;
|
||||
|
@ -111,7 +111,7 @@ struct RustyStruct {
|
|||
#
|
||||
# fn main() -> PyResult<()> {
|
||||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
# let module = PyModule::from_code(
|
||||
# let module = PyModule::from_code_bound(
|
||||
# py,
|
||||
# "class Foo(dict):
|
||||
# def __init__(self):
|
||||
|
@ -155,7 +155,7 @@ struct RustyStruct {
|
|||
#
|
||||
# fn main() -> PyResult<()> {
|
||||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
# let py_dict = py.eval("{'foo': 'foo', 'bar': 'bar', 'foobar': 'foobar'}", None, None)?;
|
||||
# let py_dict = py.eval_bound("{'foo': 'foo', 'bar': 'bar', 'foobar': 'foobar'}", None, None)?;
|
||||
# let rustystruct: RustyStruct = py_dict.extract()?;
|
||||
# assert_eq!(rustystruct.foo, "foo");
|
||||
# assert_eq!(rustystruct.bar, "bar");
|
||||
|
@ -181,7 +181,7 @@ struct RustyTuple(String, String);
|
|||
# use pyo3::types::PyTuple;
|
||||
# fn main() -> PyResult<()> {
|
||||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
# let tuple = PyTuple::new(py, vec!["test", "test2"]);
|
||||
# let tuple = PyTuple::new_bound(py, vec!["test", "test2"]);
|
||||
#
|
||||
# let rustytuple: RustyTuple = tuple.extract()?;
|
||||
# assert_eq!(rustytuple.0, "test");
|
||||
|
@ -204,7 +204,7 @@ struct RustyTuple((String,));
|
|||
# use pyo3::types::PyTuple;
|
||||
# fn main() -> PyResult<()> {
|
||||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
# let tuple = PyTuple::new(py, vec!["test"]);
|
||||
# let tuple = PyTuple::new_bound(py, vec!["test"]);
|
||||
#
|
||||
# let rustytuple: RustyTuple = tuple.extract()?;
|
||||
# assert_eq!((rustytuple.0).0, "test");
|
||||
|
@ -236,7 +236,7 @@ struct RustyTransparentStruct {
|
|||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
# let s = PyString::new(py, "test");
|
||||
# let s = PyString::new_bound(py, "test");
|
||||
#
|
||||
# let tup: RustyTransparentTupleStruct = s.extract()?;
|
||||
# assert_eq!(tup.0, "test");
|
||||
|
@ -265,7 +265,7 @@ use pyo3::prelude::*;
|
|||
|
||||
#[derive(FromPyObject)]
|
||||
# #[derive(Debug)]
|
||||
enum RustyEnum<'a> {
|
||||
enum RustyEnum<'py> {
|
||||
Int(usize), // input is a positive int
|
||||
String(String), // input is a string
|
||||
IntTuple(usize, usize), // input is a 2-tuple with positive ints
|
||||
|
@ -284,7 +284,7 @@ enum RustyEnum<'a> {
|
|||
b: usize,
|
||||
},
|
||||
#[pyo3(transparent)]
|
||||
CatchAll(&'a PyAny), // This extraction never fails
|
||||
CatchAll(Bound<'py, PyAny>), // This extraction never fails
|
||||
}
|
||||
#
|
||||
# use pyo3::types::{PyBytes, PyString};
|
||||
|
@ -303,7 +303,7 @@ enum RustyEnum<'a> {
|
|||
# );
|
||||
# }
|
||||
# {
|
||||
# let thing = PyString::new(py, "text");
|
||||
# let thing = PyString::new_bound(py, "text");
|
||||
# let rust_thing: RustyEnum<'_> = thing.extract()?;
|
||||
#
|
||||
# assert_eq!(
|
||||
|
@ -339,7 +339,7 @@ enum RustyEnum<'a> {
|
|||
# );
|
||||
# }
|
||||
# {
|
||||
# let module = PyModule::from_code(
|
||||
# let module = PyModule::from_code_bound(
|
||||
# py,
|
||||
# "class Foo(dict):
|
||||
# def __init__(self):
|
||||
|
@ -364,7 +364,7 @@ enum RustyEnum<'a> {
|
|||
# }
|
||||
#
|
||||
# {
|
||||
# let module = PyModule::from_code(
|
||||
# let module = PyModule::from_code_bound(
|
||||
# py,
|
||||
# "class Foo(dict):
|
||||
# def __init__(self):
|
||||
|
@ -388,13 +388,13 @@ enum RustyEnum<'a> {
|
|||
# }
|
||||
#
|
||||
# {
|
||||
# let thing = PyBytes::new(py, b"text");
|
||||
# let thing = PyBytes::new_bound(py, b"text");
|
||||
# let rust_thing: RustyEnum<'_> = thing.extract()?;
|
||||
#
|
||||
# assert_eq!(
|
||||
# b"text",
|
||||
# match rust_thing {
|
||||
# RustyEnum::CatchAll(i) => i.downcast::<PyBytes>()?.as_bytes(),
|
||||
# RustyEnum::CatchAll(ref i) => i.downcast::<PyBytes>()?.as_bytes(),
|
||||
# other => unreachable!("Error extracting: {:?}", other),
|
||||
# }
|
||||
# );
|
||||
|
@ -482,9 +482,9 @@ If the input is neither a string nor an integer, the error message will be:
|
|||
- retrieve the field from a mapping, possibly with the custom key specified as an argument.
|
||||
- can be any literal that implements `ToBorrowedObject`
|
||||
- `pyo3(from_py_with = "...")`
|
||||
- apply a custom function to convert the field from Python the desired Rust type.
|
||||
- apply a custom function to convert the field from Python the desired Rust type.
|
||||
- the argument must be the name of the function as a string.
|
||||
- the function signature must be `fn(&PyAny) -> PyResult<T>` where `T` is the Rust type of the argument.
|
||||
- the function signature must be `fn(&Bound<PyAny>) -> PyResult<T>` where `T` is the Rust type of the argument.
|
||||
|
||||
### `IntoPy<T>`
|
||||
|
||||
|
@ -499,7 +499,7 @@ _without_ having a unique python type.
|
|||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
# #[allow(dead_code)]
|
||||
struct MyPyObjectWrapper(PyObject);
|
||||
|
||||
impl IntoPy<PyObject> for MyPyObjectWrapper {
|
||||
|
|
|
@ -16,7 +16,7 @@ You can also debug classic `!`-macros by adding `-Z trace-macros`:
|
|||
cargo rustc --profile=check -- -Z unstable-options --pretty=expanded -Z trace-macros > expanded.rs; rustfmt expanded.rs
|
||||
```
|
||||
|
||||
See [cargo expand](https://github.com/dtolnay/cargo-expand) for a more elaborate version of those commands.
|
||||
Note that those commands require using the nightly build of rust and may occasionally have bugs. See [cargo expand](https://github.com/dtolnay/cargo-expand) for a more elaborate and stable version of those commands.
|
||||
|
||||
## Running with Valgrind
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Using `async` and `await`
|
||||
|
||||
*`async`/`await` support is currently being integrated in PyO3. See the [dedicated documentation](../async-await.md)*
|
||||
|
||||
If you are working with a Python library that makes use of async functions or wish to provide
|
||||
Python bindings for an async Rust library, [`pyo3-asyncio`](https://github.com/awestlake87/pyo3-asyncio)
|
||||
likely has the tools you need. It provides conversions between async functions in both Python and
|
||||
|
@ -118,7 +120,7 @@ Export an async function that makes use of `async-std`:
|
|||
use pyo3::{prelude::*, wrap_pyfunction};
|
||||
|
||||
#[pyfunction]
|
||||
fn rust_sleep(py: Python<'_>) -> PyResult<&PyAny> {
|
||||
fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>> {
|
||||
pyo3_asyncio::async_std::future_into_py(py, async {
|
||||
async_std::task::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(Python::with_gil(|py| py.None()))
|
||||
|
@ -126,10 +128,8 @@ fn rust_sleep(py: Python<'_>) -> PyResult<&PyAny> {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn my_async_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
|
||||
|
||||
Ok(())
|
||||
fn my_async_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(rust_sleep, m)?)
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -141,7 +141,7 @@ If you want to use `tokio` instead, here's what your module should look like:
|
|||
use pyo3::{prelude::*, wrap_pyfunction};
|
||||
|
||||
#[pyfunction]
|
||||
fn rust_sleep(py: Python<'_>) -> PyResult<&PyAny> {
|
||||
fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>>> {
|
||||
pyo3_asyncio::tokio::future_into_py(py, async {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(Python::with_gil(|py| py.None()))
|
||||
|
@ -149,9 +149,8 @@ fn rust_sleep(py: Python<'_>) -> PyResult<&PyAny> {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn my_async_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
|
||||
Ok(())
|
||||
fn my_async_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(rust_sleep, m)?)
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -195,7 +194,7 @@ to do something special with the object that it returns.
|
|||
Normally in Python, that something special is the `await` keyword, but in order to await this
|
||||
coroutine in Rust, we first need to convert it into Rust's version of a `coroutine`: a `Future`.
|
||||
That's where `pyo3-asyncio` comes in.
|
||||
[`pyo3_asyncio::into_future`](https://docs.rs/pyo3-asyncio/latest/pyo3_asyncio/fn.into_future.html)
|
||||
[`pyo3_asyncio::async_std::into_future`](https://docs.rs/pyo3-asyncio/latest/pyo3_asyncio/async_std/fn.into_future.html)
|
||||
performs this conversion for us.
|
||||
|
||||
The following example uses `into_future` to call the `py_sleep` function shown above and then await the
|
||||
|
@ -231,7 +230,7 @@ a coroutine argument:
|
|||
|
||||
```rust
|
||||
#[pyfunction]
|
||||
fn await_coro(coro: &PyAny) -> PyResult<()> {
|
||||
fn await_coro(coro: &Bound<'_, PyAny>>) -> PyResult<()> {
|
||||
// convert the coroutine into a Rust future using the
|
||||
// async_std runtime
|
||||
let f = pyo3_asyncio::async_std::into_future(coro)?;
|
||||
|
@ -255,11 +254,11 @@ async def py_sleep():
|
|||
await_coro(py_sleep())
|
||||
```
|
||||
|
||||
If for you wanted to pass a callable function to the `#[pyfunction]` instead, (i.e. the last line becomes `await_coro(py_sleep))`, then the above example needs to be tweaked to first call the callable to get the coroutine:
|
||||
If you wanted to pass a callable function to the `#[pyfunction]` instead, (i.e. the last line becomes `await_coro(py_sleep))`, then the above example needs to be tweaked to first call the callable to get the coroutine:
|
||||
|
||||
```rust
|
||||
#[pyfunction]
|
||||
fn await_coro(callable: &PyAny) -> PyResult<()> {
|
||||
fn await_coro(callable: &Bound<'_, PyAny>>) -> PyResult<()> {
|
||||
// get the coroutine by calling the callable
|
||||
let coro = callable.call0()?;
|
||||
|
||||
|
@ -315,7 +314,7 @@ async fn rust_sleep() {
|
|||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn call_rust_sleep(py: Python<'_>) -> PyResult<&PyAny> {
|
||||
fn call_rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>>> {
|
||||
pyo3_asyncio::async_std::future_into_py(py, async move {
|
||||
rust_sleep().await;
|
||||
Ok(Python::with_gil(|py| py.None()))
|
||||
|
@ -347,7 +346,7 @@ implementations _prefer_ control over the main thread, this can still make some
|
|||
|
||||
Because Python needs to control the main thread, we can't use the convenient proc macros from Rust
|
||||
runtimes to handle the `main` function or `#[test]` functions. Instead, the initialization for PyO3 has to be done from the `main` function and the main
|
||||
thread must block on [`pyo3_asyncio::run_forever`](https://docs.rs/pyo3-asyncio/latest/pyo3_asyncio/fn.run_forever.html) or [`pyo3_asyncio::async_std::run_until_complete`](https://docs.rs/pyo3-asyncio/latest/pyo3_asyncio/async_std/fn.run_until_complete.html).
|
||||
thread must block on [`pyo3_asyncio::async_std::run_until_complete`](https://docs.rs/pyo3-asyncio/latest/pyo3_asyncio/async_std/fn.run_until_complete.html).
|
||||
|
||||
Because we have to block on one of those functions, we can't use [`#[async_std::main]`](https://docs.rs/async-std/latest/async_std/attr.main.html) or [`#[tokio::main]`](https://docs.rs/tokio/1.1.0/tokio/attr.main.html)
|
||||
since it's not a good idea to make long blocking calls during an async function.
|
||||
|
@ -465,7 +464,7 @@ tokio = "1.4"
|
|||
use pyo3::{prelude::*, wrap_pyfunction};
|
||||
|
||||
#[pyfunction]
|
||||
fn rust_sleep(py: Python<'_>) -> PyResult<&PyAny> {
|
||||
fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>>> {
|
||||
pyo3_asyncio::tokio::future_into_py(py, async {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(Python::with_gil(|py| py.None()))
|
||||
|
@ -473,7 +472,7 @@ fn rust_sleep(py: Python<'_>) -> PyResult<&PyAny> {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn my_async_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
fn my_async_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -30,11 +30,11 @@ fn log_something() {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
// A good place to install the Rust -> Python logger.
|
||||
pyo3_log::init();
|
||||
|
||||
m.add_function(wrap_pyfunction!(log_something))?;
|
||||
m.add_function(wrap_pyfunction!(log_something, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
|
|
@ -24,7 +24,7 @@ use pyo3::exceptions::PyException;
|
|||
create_exception!(mymodule, CustomError, PyException);
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let ctx = [("CustomError", py.get_type::<CustomError>())].into_py_dict(py);
|
||||
let ctx = [("CustomError", py.get_type_bound::<CustomError>())].into_py_dict_bound(py);
|
||||
pyo3::py_run!(
|
||||
py,
|
||||
*ctx,
|
||||
|
@ -44,9 +44,9 @@ use pyo3::exceptions::PyException;
|
|||
pyo3::create_exception!(mymodule, CustomError, PyException);
|
||||
|
||||
#[pymodule]
|
||||
fn mymodule(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
fn mymodule(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
// ... other elements added to module ...
|
||||
m.add("CustomError", py.get_type::<CustomError>())?;
|
||||
m.add("CustomError", py.get_type_bound::<CustomError>())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ fn mymodule(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
|||
|
||||
## Raising an exception
|
||||
|
||||
As described in the [function error handling](./function/error_handling.md) chapter, to raise an exception from a `#[pyfunction]` or `#[pymethods]`, return an `Err(PyErr)`. PyO3 will automatically raise this exception for you when returning the result to Python.
|
||||
As described in the [function error handling](./function/error-handling.md) chapter, to raise an exception from a `#[pyfunction]` or `#[pymethods]`, return an `Err(PyErr)`. PyO3 will automatically raise this exception for you when returning the result to Python.
|
||||
|
||||
You can also manually write and fetch errors in the Python interpreter's global state:
|
||||
|
||||
|
@ -75,12 +75,12 @@ Python has an [`isinstance`](https://docs.python.org/3/library/functions.html#is
|
|||
In PyO3 every object has the [`PyAny::is_instance`] and [`PyAny::is_instance_of`] methods which do the same thing.
|
||||
|
||||
```rust
|
||||
use pyo3::Python;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyBool, PyList};
|
||||
|
||||
Python::with_gil(|py| {
|
||||
assert!(PyBool::new(py, true).is_instance_of::<PyBool>());
|
||||
let list = PyList::new(py, &[1, 2, 3, 4]);
|
||||
assert!(PyBool::new_bound(py, true).is_instance_of::<PyBool>());
|
||||
let list = PyList::new_bound(py, &[1, 2, 3, 4]);
|
||||
assert!(!list.is_instance_of::<PyBool>());
|
||||
assert!(list.is_instance_of::<PyList>());
|
||||
});
|
||||
|
@ -111,7 +111,7 @@ mod io {
|
|||
pyo3::import_exception!(io, UnsupportedOperation);
|
||||
}
|
||||
|
||||
fn tell(file: &PyAny) -> PyResult<u64> {
|
||||
fn tell(file: &Bound<'_, PyAny>) -> PyResult<u64> {
|
||||
match file.call_method0("tell") {
|
||||
Err(_) => Err(io::UnsupportedOperation::new_err("not supported: tell")),
|
||||
Ok(x) => x.extract::<u64>(),
|
||||
|
@ -128,5 +128,5 @@ defines exceptions for several standard library modules.
|
|||
[`PyErr`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html
|
||||
[`PyResult`]: {{#PYO3_DOCS_URL}}/pyo3/type.PyResult.html
|
||||
[`PyErr::from_value`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html#method.from_value
|
||||
[`PyAny::is_instance`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#method.is_instance
|
||||
[`PyAny::is_instance_of`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#method.is_instance_of
|
||||
[`PyAny::is_instance`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.is_instance
|
||||
[`PyAny::is_instance_of`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.is_instance_of
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue