
ãã®èšäºã«èå³ããã人ã®ããã«-Catã®äžã«è¡ã£ãŠãã ããã
1.ãããžã§ã¯ãã®ã¢ã€ãã¢
ãã®ã¢ã€ãã¢ã¯2015幎4æäžæ¬ã«çãŸããŸãããååãšä»äºãããŠããŠãååãšäžç·ã«ãªãã£ã¹ã§ä»äºãããŠããŸãã ããã°ã©ãã³ã°ã«çŽæ¥é¢ãã£ãŠããéã«ãã©ããããããèªåã楜ããŸããããã«ãããŸããŸãªèå³æ·±ãPythonãããžã§ã¯ãã«ã€ããŠè©±ãããšã«ããŸããã ã³ãã¥ãã±ãŒã·ã§ã³ã®éçšã§ã圌ãã¯ã©ããããããèªçºçã«èªåã®ãããžã§ã¯ãã®ãããã¯ã«åãçµã¿ããããžã§ã¯ãã§ããã«äœ¿çšããã®ã«èå³æ·±ããã®ã«ã¢ãããŒãããŸããïŒå¿ ãããä»äºã«é¢é£ããŠããããã§ã¯ãããŸããïŒã çŽæ¥è°è«ãããšããåé¡ãªãåæ¹åã«ããŒã¿ã転éã§ããWebãœã±ããã䜿çšããããªããæè»ãªããã¬ãŒã ã¯ãŒã¯ãçšæããã®ãè¯ããšããã¢ã€ãã¢ãçãŸããŸããã åãªã¯ãšã¹ãã¯JSON圢åŒã§æäŸãããRESTããã³HTTPãããã³ã«ã䜿çšããéã«ããç¥ãããŠããããããŒãå«ãŸããŠããŸãã ãŸããå¿«é©ãªè¿œå æ©èœãšããŠãäœããã®ã€ãã³ã/ã¿ã€ã ã¢ãŠãæã«ãµãŒããŒããã¯ã©ã€ã¢ã³ãã«éç¥ïŒéç¥ïŒãéä¿¡ããæ©èœãæäŸããŸãã
åœç¶ã®ããšãªããããã®ãããªé·ãè°è«ã®åŸãç§ã¯ãã®ã¢ã€ãã¢ãå®çŸããããšã«ããŸããïŒãããŠãªãããã§ã¯ãªãã®ã§ããïŒïŒã èªèº«ã®èå³ãç±æããããŠç¬¬3 Python'aã®ãšã³ã·ã¹ãã ã®éçºã«åœ¹ç«ã€äœããããããšããé¡æã¯ãããã«ããžãã¹ã«åããããããã®ç¹å¥ãªåæ©ãäžããŸããã
2.ç®æšãèšå®ãã
ç§èªèº«ãå人çã«ã¯ãåè¿°ã®å 容ã«å ããŠãã©ã€ãã©ãªãäœæãããšãã«èªåã®åªåã«éäžããããšã決å®ãããããã€ãã®è¿œå ãã€ã³ãã匷調ããŸããã
- ã¯ã©ã€ã¢ã³ãèŠæ±ãåŠçãããšãã«asyncioã䜿çšããŠã¿ãŠãã ãã
- 1-2å以äžã®äŸåã¢ãžã¥ãŒã«ïŒå°ããã»ã©è¯ãïŒ
- é£ããããŠç解ã§ããªãã¯ãã§ãã
- 䜿ããããïŒDjango RESTãFlaskãã¬ãŒã ã¯ãŒã¯ãåç §ããŠãã ãããããã¯ããªãã·ã³ãã«ã§æè»ã§ãïŒ
- ããã°ã©ãã¯ãå¿ èŠãªãšãã«ã»ãŒãã¹ãŠã®ã³ã³ããŒãã³ãã亀æã§ããŸã
åœç¶ãæåã®ããŒãžã§ã³ã§ãªãªãŒã¹ããããšã¯ãéçºããã»ã¹ãé¢ããããšã¯ãªãã£ãã®ã§ãäžèšã®ãã¹ãŠã¯å®å šã«éçŸå®çã§ããããã®ãããå°ãç°¡ç¥åããããã«ããã¹ãŠãå°ããªãããŒã¹ãã«å解ããããšã«ããŸããã ããããäœæãããã¹ããããªãªãŒã¹ããåæ§ã®ã¹ããŒã ã«åŸã£ãŠæ®ããå®è¡ããŸãã æåã«ãã©ã€ãã©ãªã«ãšã£ãŠæãéèŠãªãã®ïŒã«ãŒãã£ã³ã°ããã¥ãŒãèªèšŒãªã©ïŒãèšè¿°ããåŸã§å¯èœãªéãæ°ããæ©èœãè¿œå ããŸãã
3.éçºã®æºåïŒAiohttp察Gevent察Autobahn.wsã®éžæ
éçºã¯2015幎4ææ«é ã«éå§ãããŸããããããžã§ã¯ãã®äœæ¥ãäœããã®åœ¢ã§ä¿é²ããããã«ãæ¢è£œã®ãœãªã¥ãŒã·ã§ã³ïŒãŸãã¯ä»¥åã¯äºæ³ããŠããªãã£ãæ¢åã®ã©ã€ãã©ãªïŒã®æ€çŽ¢ãéå§ããŸããã ç§ãšäŒŒããããªã¢ã€ãã¢ãæã£ãŠããã©ã€ãã©ãªã¯ãããŸããã§ããã å¿ èŠãªã³ã³ããŒãã³ãã®ã»ãšãã©ã¯ãè¡ãããŠãããã¹ãŠã®ããã»ã¹ã®ç¬èªã®ç解ã«åºã¥ããŠãç¬ç«ããŠäœæããå¿ èŠããããããããã¯ã¿ã¹ã¯ã®è€éåã«ã€ãªãããŸããã
ç§ã¯ãWebãœã±ããã䜿çšã§ããããã«ããã©ã€ãã©ãªãŒããçŽæ¥å§ããããšã«ããŸããã åœæãaiohttpãgeventãautobahn.wsãªã©ã®ããã±ãŒãžãããã€ãèŠã€ãããŸããã åã©ã€ãã©ãªã«ã¯ããããé·æãšçæããããŸããããŸãã¯ãç¹ã«å¿ èŠã®ãªãå Žæã§èªè»¢è»ãåã³é§è»ããå¿ èŠããªãããã«ãæ©èœãšã³ãŒãã®åå©çšã®å¯èœæ§ããå§ããŸããã
Aiohttpã¯ãasyncioæšæºã©ã€ãã©ãªã«åºã¥ããsvetlovã«ãã£ãŠéçºãããWebéçºã©ã€ãã©ãªã§ã ã ãã®ã©ã€ãã©ãªã䜿ã£ãŠæ¬åœã«çŽ æŽãããçµéšããããšã¯èšããŸããããå€ãã®ããšãéåžžã«ã¯ãŒã«ã«è¡ãããŠããããšã¯æ³šç®ã«å€ããŸãã ãã ããWebãœã±ããã䜿çšããææ¡ããããœãªã¥ãŒã·ã§ã³ã¯ãããäœã¬ãã«ã®ããã«æããŸããïŒãã ããå Žåã«ãã£ãŠã¯æ¬åœã«äŸ¿å©ã§ãïŒã ç§ã¯ããå°ãæœè±¡åããããšãæãã§ããŸããïŒäŸãã°ãã¯ã©ã€ã¢ã³ã/ãµãŒããŒãonMessageãsendMessageã®ãããªã¡ãœãããæã£ãŠããgevent-websocketãautobahn.wsã®ããã«ãã€ãã³ãæåã®Twistedãã¬ãŒã ã¯ãŒã¯ã®ã¡ãœããã«äŒŒãŠããŸãïŒã æ®ãã¯ãå³æžé€šã¯çŸããã§ãã
æåã®ã¬ãã¥ãŒã§ã®Geventã¯ã泚ç®ãéããæåã®ããã±ãŒãžã®1ã€ã§ããã ãŸããããã«ããã䜿çšãããšããã¢ã€ãã¢ã¯æåŠãããŸããããããžã§ã¯ããéå§ããããšãïŒ2015幎4æïŒãgeventã¯Pythonèšèªã®3çªç®ã®ãã©ã³ãã«ç§»æ€ãããŠããŸããã§ããã ãã ãããã¹ãŠåãããã«ç§»æ€ãããå Žåã¯ãgevent-websocketæ¡åŒµæ©èœã䜿çšããŠäœ¿çšãããšããã¹ãŠãéåžžã«ããŸãæ©èœããŸãã ãã®ã©ã€ãã©ãªãæžããŠããæç¹ã§ã¯ããã®ã©ã€ãã©ãªã¯ãã§ã«3çªç®ã®ãã©ã³ãããµããŒãããŠããŸãããä»ã¯ãããåãæ¿ããŠãæå³ããããŸããã
Autobahn.wsã¯ãå°ããªããããããžã§ã¯ããäœæããéã«æ°å察åŠããªããã°ãªããªãã£ãã©ã€ãã©ãªã§ããã䜿çšã«é¢ããŠã¯ãã§ã«æå°éã®çµéšãããããŸããã ããªãè¯ãã³ãã¥ããã£ã«å ããã©ã€ãã©ãªã®äœæè ã¯ãåé¡ãçºçããå Žåã«ãã€ã§ãæ¯æŽããæºåãã§ããŠããŸãïŒããšãã°ãTwisted + wxPythonãšçµã¿åãããããšãã§ããªãã£ããšããTobiasã¯ãããã©ã®ããã«è¡ãããšãã§ããããéåžžã«ãã説æããŸããïŒã ææ°ããŒãžã§ã³ã¯asyncioãšäºææ§ããããå¿ èŠãªå Žæã«ãã³ã¬ãŒã¿ãè¿œå ããã ãã§ãã ãã1ã€ã®åªããæ©èœã¯ã RFC6455ãžã®æºæ ãšãçä¿¡/çºä¿¡ããŒã¿ã®ãã§ãã¯ã®å¯çšæ§ã§ãïŒå°çããããUTF-8ãšã³ã³ãŒãã§éä¿¡ããããã¯éåžžã«äŸ¿å©ã ãšæããŸãïŒã ãããã£ãŠãå°æ¥ã®ã©ã€ãã©ãªã®åºç€ãšããŠäœ¿çšããããšã決å®ãããŸããã
4.éçºäžã«çºçããåé¡
ã©ã€ãã©ãªã®æåã®ããŒãžã§ã³ãæžããšããç§ã¯åã«å€ãã®ã¿ã¹ã¯ã«ã¢ãããŒãããæ¹æ³ãç¥ããŸããã§ããã å°ãèããŠããããµãŒããŒãã¯ã©ã€ã¢ã³ãããã®çä¿¡èŠæ±ãã©ã®ããã«åŠçããããšãããã¹ã«æ²¿ã£ãŠãå®è£ ãšäžç·ã«é²ãããšã«ããŸããã
1ïŒãªã¯ãšã¹ããåä¿¡ãã
2ïŒå¿ èŠãªããŒã¿ãå°çããããšã確èªããŸãããããã«åºã¥ããŠããªã¯ãšã¹ãã®åŠçæ¹æ³ïŒæäœã®çš®é¡ãé£çµ¡å ãªã©ïŒãæããã«ãªããŸãã
3ïŒçä¿¡èŠæ±ïŒç¹å®ã®ãšã³ããªãã€ã³ããšåŒã³åºãããã¡ãœããïŒã«äžèŽãããã³ãã©ãæ¢ããŠããŸãã äœãèŠã€ãããªãã£ãå Žåããšã©ãŒãè¿ããŸãã ãã¹ãŠãæ£åžžã§ããã°ãé©åãªãã³ãã©ãŒãéžæããåãåã£ãåŒæ°ãããã«æž¡ããŸã
4ïŒçæãããå¿çã¯ãç¹å®ã®åœ¢åŒïŒJSONãXMLãªã©ïŒã«ã€ãªãããŸãã
5ïŒã¯ã©ã€ã¢ã³ãã«åçãè¿ããŸãã
çè«çã«ã¯ããã¹ãŠãéåžžã«åçŽã«èãããŸãããå®éã«ã¯ããã¹ãŠããŸã£ããéã§ããããšãå€æããŸããã ç§ã«èµ·ãã£ãå¯äžã®ããšã¯ãé«ã¬ãã«ã®æœè±¡åããäœã¬ãã«ã®æœè±¡åã«è¡ãããšã§ããã ã€ãŸããAutobahn.wsãšasyncioã«ãŒãã䜿çšããŠæ¬¡ã®ããã«äœæ¥ããŸããã
1ïŒããã¡ã¯ããªãŒãã®ã€ã³ã¹ã¿ã³ã¹ãäœæããŸããããã¯ãasyncioã«ãŒãã䜿çšããçä¿¡æ¥ç¶ãåãå ¥ããŠããããåŠçããŸãã ããã³ãã·ã§ã€ã¯ããã»ã¹ããå®äºãããšãã¯ã©ã€ã¢ã³ããããªã¯ãšã¹ããåä¿¡ããŠââåŠçããæºåãæŽããŸãã
2ïŒç¹å®ã®åœ¢åŒã§ã¯ã©ã€ã¢ã³ããããªã¯ãšã¹ããåä¿¡ããŸããã ãã®å Žåã次ã®ããã«JSONã®åœ¢åŒã§åä¿¡ããŸãã
{ 'method': 'POST', 'url': '/users/create', 'args': { 'token': 'aGFicmFoYWJyX2FkbWlu' }, 'data': { 'username': 'habrahabr', 'password': 'mysupersecretpassword', } }
ãã®JSONã®æ§é ã¯ããªãåçŽã§ãã ã¯ã©ã€ã¢ã³ãã¯ãç§ãã¡ã«ãšã£ãŠéèŠãªããã€ãã®ãã©ã¡ãŒã¿ãŒãå®çŸ©ããã ãã§ååã§ãã
- method-ãªãœãŒã¹ã«å¯Ÿããæäœã®ã¿ã€ãïŒHTTPã§è¡ãããæ¹æ³ãšåæ§ïŒã
- url-åŠçããããªãœãŒã¹ãžã®ãã¹ã
- argsïŒãªãã·ã§ã³ïŒ-ãµãŒããŒã«éä¿¡ããããã©ã¡ãŒã¿ãŒã®ã»ããã æãè¿ãé¡æšã¯ããïŒãã䜿çšããHTTPèŠæ±URLã§å®çŸ©ããããã©ã¡ãŒã¿ãŒã§ãã ã habrahabr.ru/?page=2&paginate_by=25 ãã®ãããªãïŒãæåã ããã¯ãæ¢è£œã®ããŒã¿ã®ãªã¹ãïŒããšãã°ãç¹å®ã®ã°ã«ãŒããå²ãåœãŠãå¿ èŠããããŠãŒã¶ãŒã®èå¥åïŒããŸãã¯èŠæ±åŠçäžã«ãµãŒããŒåŽã§äœ¿çšããããã£ã«ã¿ãŒã®åãªãåŒæ°ã®ã»ããã§ãã
- ããŒã¿ïŒãªãã·ã§ã³ïŒ-ãªãœãŒã¹ãæäœãããšãã«äœ¿çšãããããŒã¿ã®ã»ããã äžè¬ã«ãããã¯HTTPãªã¯ãšã¹ãã®æ¬æã«é¡äŒŒãããã®ã§ãããšæ³å®ã§ããŸãã
- event_nameïŒãªãã·ã§ã³ïŒ-ããŒã¿ãã©ã®ãšã³ããã€ã³ãããè¿ãããããç解ã§ããäžæã®èå¥åã
ãã®çš®ã®ãªã¯ãšã¹ãã«ã€ããŠããµãŒããŒã¯åä¿¡ãæåŸ ããŠããŸãã å¿ èŠãªåŒæ°ããªãå Žåã¯ãããã«èª¬æããŸãïŒããšãã°ãã¡ãœãããè¿œå ããã®ãå¿ããŸããïŒã ãã以å€ã®å Žåã¯ããªã¹ããããã«é²ããŸãã
3ïŒãã®ãããèŠæ±ã¯ãµãŒããŒã«é ä¿¡ãããæ£ãã圢åŒã§æ£ãããã®ã«ãªããŸãã 次ã«ãããã«å¿ããŠåŠçããçããè¿ããŸãã ããããããã«ã¯äœãå¿ èŠã§ããïŒ ç§ã®èŠ³ç¹ããã¯ãåããŠãã«ãŒãã£ã³ã°ã·ã¹ãã ã§ç¹å®ã®URLã«å¿ èŠãªãã³ãã©ãŒãç»é²ããã ãã§ååã§ããããã«ããã察å¿ããå¿çãçæãããJSONãXMLïŒãŸãã¯ãã®ä»ã®åœ¢åŒïŒã«å€æãããã¯ã©ã€ã¢ã³ãã«è¿ãããŸãã
ãã®æç¹ã§ãã«ãŒãã£ã³ã°ã«æ³šæãåããããšæããŸãã ããã¯éåžžã«éèŠãªãã€ã³ãã§ããããšãã°ãçŸåšã®ãŠãŒã¶ãŒã®ãªã¹ãïŒã/ users /ããªã©ïŒãååŸããããã«ãããåºå®URLã§ã¢ã¯ã»ã¹ãæäŸãããããã§ãã äžæ¹ãã/ users //ããšãã圢åŒã®URLãä»ããŠã¢ã¯ã»ã¹ããããšãã§ããŸãããããã«ã¯ãŠãŒã¶ãŒã«é¢ãã詳现æ å ±ãå¿ èŠã§ãã ã€ãŸããæåã®ã¿ã€ãã®ã«ãŒãã£ã³ã°ã¯åçŽã§éçã2çªç®ã®ã¿ã€ãã¯åçã§ãããšèŠãªããŸããããã¯ããªã¯ãšã¹ããããªã¯ãšã¹ããžãšå€åãããªãœãŒã¹ãžã®ãã¹ã«ãã©ã¡ãŒã¿ãŒãããããã§ãã
ãã®åé¡ã解決ããã«ã¯ãæ£èŠè¡šçŸã圹ç«ã¡ãŸãã ãªãœãŒã¹ãã¹ã宣èšããããã³ã«ãããšãã°ïŒ
router = SimpleRouter() router.register('/auth/login', LogIn, 'POST') router.register('/users/{pk}', UserDetail, ['GET', 'PATCH'])
ãã®ãããªãªãœãŒã¹ãžã®ãã¹ãåæããŸãã ãããŠãç¹å®ã®ã¿ã€ãã®ãªã¯ãšã¹ãã®ã¿ããæå®ããããã¹ã«æ²¿ã£ãŠã®ã¿åŠçãããšã³ããã€ã³ããäœæããŸãã ãã®ãªãœãŒã¹ãžã®ãªã¯ãšã¹ããå°çãããšãããŒããã¹ãå€ããã³ãã©ãŒãšãããã£ã¯ã·ã§ããªãééããã ãã§ååã§ãã èŠæ±ã®åä¿¡æã«åçãã¹ãæ€åºãããå¿ èŠãªãã³ãã©ãŒãèŠã€ãã£ãå Žåãæ€åºãããåçãã©ã¡ãŒã¿ãŒãèŠæ±ã®åŠçå Žæã«è»¢éããŠãããŒã§ãªããžã§ã¯ããååŸãããããã®ãã©ã¡ãŒã¿ãŒã䜿çšããŠä»ã®æäœãå®è¡ãããã§ããããã«ããŸãã
ãããŠãã¡ããããªã¯ãšã¹ããååšããªãURLã«å°éããå Žåãèæ ®ããŸãã 圌ã«ãšã£ãŠã¯ãå ·äœçãªèª¬æãšãšãã«ãšã©ãŒãè¿ãã ãã§ååã§ãã
4ïŒçŽ æŽããããä»äœããã¯ãªã¢ãããŸããã å¿ èŠãªãã¹ãšãããã®ãã³ãã©ãŒãèŠã€ãããã©ã¡ãŒã¿ãŒãèŠã€ããŠè»¢éããããã«ã¬ã®ã¥ã©ãŒã䜿çšã§ããŸãïŒåçãã¹ããã£ãããããå ŽåïŒã 次ã«ãJSONã§æå®ãããã¡ãœãããã©ã¡ãŒã¿ãŒã調ã¹ãŠããã¥ãŒãã察å¿ããã¯ã©ã¹ã¡ãœãããèŠã€ããŸãã ååšããªãå Žåã¯ãããã«ããã«ã€ããŠè©±ããæäœãå®è¡ããŸããã ãã以å€ã®å Žåã¯ãæ€åºãããã¡ãœãããåŒã³åºããŠåçãäœæããŸãã
5ïŒæ¬¡ã«ãããŒã¿ïŒãšã©ãŒã®ããå Žåãå«ãïŒãç¹å®ã®åœ¢åŒã«ã·ãªã¢ã«åããŸãã ããã©ã«ãã§ã¯ããã¹ãŠãJSON圢åŒã«å€æãããŸãã
6ïŒçæãããå¿çãWebãœã±ããçµç±ã§ã¯ã©ã€ã¢ã³ãã«è»¢éããŸãã
ãã®ãµã³ãã«èšç»ã«ãããšããªãªãŒã¹1.0ã®åã«ç¶ããŸããã ç¬èªã®ãã¥ãŒãã«ãŒãã£ã³ã°ã·ã¹ãã ãããã³ãã®ä»ã®èå³æ·±ãæ©èœãäœæããããšã¯éåžžã«èå³æ·±ãããšã§ããã æåã®ãªãªãŒã¹ãäœæããéçšã§ã¯ããã®pet-projectã®éçºäžã«ãæ§æã®ããã¢ãžã¥ãŒã«ãå¿ èŠã§ããïŒãã®å ŽåãDjangoã®ã¢ãžã¥ãŒã«ã«äŒŒãã¢ãžã¥ãŒã«ã§ããïŒã ãŸãã¯ãããšãã°ãç§ã«ãšã£ãŠéåžžã«å¿ èŠãªèªèšŒã«ãããããã«ãŠã§ã¢ããã³JSON Web Tokenã¢ãžã¥ãŒã«ã®ãµããŒããåŸã ã«å®è£ ãããŸããã åã«è¿°ã¹ãããã«ãç§ãã¡ã¯ãã¹ãŠã®çš®é¡ã®ã¢ãžã¥ãŒã«ãèªåã§è¡ããäœåãªãã®ãåŒã£åŒµãããšããªãã§ãã ããã
äœããã®åœ¢ã§ãã次ã®èªè»¢è»ããšæžããŠãããã®ã§ãè¿œå ã®åŽåãšæéã®ã³ã¹ããããããŸããã ççŽã«èšã£ãŠãç§ã¯ãã®æ¹æ³ã§è¡ã£ãããšããŸã£ããåŸæããŠããŸãããæžã蟌ã¿ããããã°ãããã³å®æçãªåŸ®èª¿æŽã«è²»ããããæéãèªåèªèº«ãæããŠããããã§ãã
æåã®ããŒãžã§ã³ãæžããšããã³ãŒãã®èšè¿°ãšãããã°ãããªãããŸããã£ãããããŒãžã§ã³1.1ãå®è£ ãããšãã«ãé·ãéãããã°ã«ãšã©ãŸããŸããã ã³ãŒãã®èšè¿°ãšç§»æ€ã¯ãäœãèµ·ãã£ãŠããã®ããæ€çŽ¢ããŠè©³çŽ°ã«åæããã»ã©æéã¯ããããŸããã§ãããäŸãã°ïŒ
1ïŒãå éšãã§äœãã©ã®ããã«çºçããŠãããã«ã€ããŠã®Django RESTãã¬ãŒã ã¯ãŒã¯ã®ãœãŒã¹ã³ãŒãããŒã¹ã®åæïŒç¹å®ã®ãªããžã§ã¯ãã®æžã蟌ã¿ãŸãã¯èªã¿åããè¡ãå Žåã®åŠçââã åä¿¡ãããã£ãŒã«ãïŒããã³ä»ã®ã¢ãã«ãšã®æ¥ç¶ãããïŒãšãã·ãªã¢ã«å/éã·ãªã¢ã«åã®å¿ èŠæ§ããã€ãã©ã®ããã«ç解ãããã
2ïŒSQLAlchemyã¢ãã«ã®ã·ãªã¢ã«åãDjangoRESTã³ãŒããšDjango ORMã®éã§çºçããæ¹æ³ã«äŒŒãŠããŸãã
3ïŒæ¢ã«äœæãããAPIãä»ããŠäœããã®ãªããžã§ã¯ããžã®ãã¹ãååŸã§ããããã«ïŒåä¿¡ããURLã䜿çšããŠäžéšã®ããŒã¿ãèªã¿æžãã§ããããã«ïŒã«ãŒãã£ã³ã°ã§äœæ¥ããæ©äŒãæã€ã
æ©èœã®ãã®éšåãéçºãããšããã©ã€ãã©ãªã®ãœãŒã¹ã³ãŒããDjango RESTïŒããã¯äž»ã«æ¬¡ã®ããŒãžã§ã³ã®åºç€ã§ããïŒãããã³SQLAlchemy + marshmallow-sqlalchemyã©ã€ãã©ãªã®ãœãŒã¹ã³ãŒãã®äž¡æ¹ãããã¹ãŠã®ã¢ã€ãã¢ãå®çŸããã®ã«åœ¹ç«ã¡ãŸããã
å€ãã®ãªãœãŒã¹ãæ¶è²»ãããŸããããæçµçµæã¯ãã¹ãŠã®ã³ã¹ããå®å šã«æ£åœåããŸãã-ããã§ãDjango RESTã§äœ¿çšããŠããæ¹æ³ã§SQLAlchemyãæäœã§ããããã«ãªããŸããã ããŒã¿ã®æäœã¯åãã§ãããå®è³ªçã«å€§ããªéãã¯ãããŸããã å®è³ªçã«èªåèªèº«ãååŠç¿ããå¿ èŠããªãããšã¯çŽ æŽãããããšã§ããã¢ã¯ã»ã¹å¯èœãªAPIã¯ãå€ãã®ç¹ã§Django RESTã§äœ¿çšãããŠãããã®ãšåãã§ãã
5.ãããžã§ã¯ãã®çŸåšã®ã¹ããŒã¿ã¹
çŸåšãã©ã€ãã©ãªã«ã¯æ¬¡ã®æ©èœããããŸãã
- ã«ãŒãã£ã³ã°
- é¢æ°ããŒã¹ããã³ã¯ã©ã¹ããŒã¹ã®ãã¥ãŒã®ãµããŒã
- JSON Web Tokenã«ããèªèšŒïŒå°ãå¶éãããŠããŸããïŒ
- Django Frameworkã®èšå®ãšåæ§ã®èšå®ãæã€ãã¡ã€ã«ã®ãµããŒã
- éä¿¡ãããã¡ãã»ãŒãžã®å§çž®ïŒãã©ãŠã¶ã§ãµããŒããããå¿ èŠãªæ¡åŒµæ©èœãã€ã³ã¹ããŒã«ãããŠããå ŽåïŒ
- Djangoããã³SQLAlchemy ORMã¢ãã«ã®ã·ãªã¢ã«å
- SSLãµããŒã
6.䜿çšäŸ
ç°¡åãªäŸãšããŠã次ã®ã³ãŒãã䜿çšããŠãŠãŒã¶ãŒãšã¡ãŒã«ã¢ãã¬ã¹ãæäœã§ããŸãã SQLAlchemy ORMã䜿çšããŠèª¬æããããŒãã«ãéå§ããŸãããã
# -*- coding: utf-8 -*- from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship, validates Base = declarative_base() class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String(50), unique=True) fullname = Column(String(50), default='Unknown') password = Column(String(512)) addresses = relationship("Address", back_populates="user") @validates('name') def validate_name(self, key, name): assert '@' not in name return name def __repr__(self): return "<User(name='%s', fullname='%s', password='%s')>" % (self.name, self.fullname, self.password) class Address(Base): __tablename__ = 'addresses' id = Column(Integer, primary_key=True) email_address = Column(String, nullable=False) user_id = Column(Integer, ForeignKey('users.id')) user = relationship("User", back_populates="addresses") def __repr__(self): return "<Address(email_address='%s')>" % self.email_address
次ã«ãããã2ã€ã®ã¢ãã«ã«é©ããã·ãªã¢ã©ã€ã¶ãŒã«ã€ããŠèª¬æããŸãã
# -*- coding: utf-8 -*- from app.db import User, Address from aiorest_ws.db.orm.sqlalchemy import serializers from sqlalchemy.orm import Query class AddressSerializer(serializers.ModelSerializer): class Meta: model = Address fields = ('id', 'email_address') class UserSerializer(serializers.ModelSerializer): addresses = serializers.PrimaryKeyRelatedField(queryset=Query(Address), many=True, required=False) class Meta: model = User
å€ãã®äººãæ°ã¥ããããã«ããŠãŒã¶ãŒãã·ãªã¢ã«åããããã®ã¯ã©ã¹ãå®çŸ©ããå Žæã§ãã¢ãã¬ã¹ãã£ãŒã«ããæå®ãããPrimaryKeyRelatedFieldã¯ã©ã¹ã®ã³ã³ã¹ãã©ã¯ã¿ãŒã§åŒæ°queryset = QueryïŒAddressïŒãæå®ãããŠããŸãã ããã¯ãSQLAlchemy ORMã®ã·ãªã¢ã©ã€ã¶ãŒãã¢ãã¬ã¹ãã£ãŒã«ããšããŒãã«ã®éã«é¢ä¿ãæ§ç¯ããã·ãªã¢ã«åäžã«ãã®ã¯ã©ã¹ã«äž»ããŒãæž¡ãããšãã§ããããã«ããããã«è¡ãããŸãã ããçšåºŠãããã¯Djangoãã¬ãŒã ã¯ãŒã¯ã®QuerySetã«äŒŒãŠããŸãã
次ã«ãå©çšå¯èœãªAPIã䜿çšããŠãããã®ããŒãã«ã®ããŒã¿ãæäœã§ããããã«ãããã¥ãŒãå®è£ ããŸãã
# -*- coding: utf-8 -*- from aiorest_ws.conf import settings from aiorest_ws.db.orm.exceptions import ValidationError from aiorest_ws.views import MethodBasedView from app.db import User from app.serializers import AddressSerializer, UserSerializer class UserListView(MethodBasedView): def get(self, request, *args, **kwargs): session = settings.SQLALCHEMY_SESSION() users = session.query(User).all() data = UserSerializer(users, many=True).data session.close() return data def post(self, request, *args, **kwargs): if not request.data: raise ValidationError('You must provide arguments for create.') if not isinstance(request.data, list): raise ValidationError('You must provide a list of objects.') serializer = UserSerializer(data=request.data, many=True) serializer.is_valid(raise_exception=True) serializer.save() return serializer.data class UserView(MethodBasedView): def get(self, request, id, *args, **kwargs): session = settings.SQLALCHEMY_SESSION() instance = session.query(User).filter(User.id == id).first() data = UserSerializer(instance).data session.close() return data def put(self, request, id, *args, **kwargs): if not request.data: raise ValidationError('You must provide an updated instance.') session = settings.SQLALCHEMY_SESSION() instance = session.query(User).filter(User.id == id).first() if not instance: raise ValidationError('Object does not exist.') serializer = UserSerializer(instance, data=request.data, partial=True) serializer.is_valid(raise_exception=True) serializer.save() session.close() return serializer.data class CreateUserView(MethodBasedView): def post(self, request, *args, **kwargs): serializer = UserSerializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() return serializer.data class AddressView(MethodBasedView): def get(self, request, id, *args, **kwargs): session = settings.SQLALCHEMY_SESSION() instance = session.query(User).filter(User.id == id).first() session.close() return AddressSerializer(instance).data class CreateAddressView(MethodBasedView): def post(self, request, *args, **kwargs): serializer = AddressSerializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() session.close() return serializer.data
çŸæç¹ã§ã¯ããªããžã§ã¯ããæäœããããã®åå¥ã®ãã¥ãŒãšããªããžã§ã¯ãã®ãªã¹ããåå¥ã«äœæããŠããŸãã MethodBasedViewããç¶æ¿ããããããã®åãµãã¯ã©ã¹ã¯ã䜿çšãããç¹å®ã®ãã³ãã©ãŒãå®è£ ããŸãã ãªã¯ãšã¹ãã®åã¿ã€ãïŒget / post / put / patch /ãªã©ïŒã«å¯ŸããŠãç¬èªã®ãã³ãã©ãŒãäœæãããŸãã
æåŸã®ã¹ãããã¯ããã®APIãç»é²ããå€éšããã¢ã¯ã»ã¹ã§ããããã«ããããšã§ãã
# -*- coding: utf-8 -*- from aiorest_ws.routers import SimpleRouter from app.views import UserListView, UserView, CreateUserView, AddressView, \ CreateAddressView router = SimpleRouter() router.register('/user/list', UserListView, 'GET') router.register('/user/{id}', UserView, ['GET', 'PUT'], name='user-detail') router.register('/user/', CreateUserView, ['POST']) router.register('/address/{id}', AddressView, ['GET', 'PUT'], name='address-detail') router.register('/address/', CreateAddressView, ['POST'])
å®éãããã§ãã¹ãŠã®æºåãæŽããŸããããµãŒããŒãèµ·åããŠã¯ã©ã€ã¢ã³ããä»ããŠæ¥ç¶ããã ãã§ãïŒPython + Autobahn.wsãJavaScriptã䜿çšãããªã©ãå€ãã®ãªãã·ã§ã³ããããŸãïŒã ããšãã°ãPython + Authobahn.wsã䜿çšããããã€ãã®ç°¡åãªã¯ãšãªã衚瀺ããŸãïŒäºåã«äºçŽããŸãããã¯ã©ã€ã¢ã³ãã®äŸã¯å®ç§ã§ã¯ãããŸãããããã§ã®ã¿ã¹ã¯ã¯ããããè¡ãæ¹æ³ã瀺ãããšã§ãïŒã
# -*- coding: utf-8 -*- import asyncio import json from hashlib import sha256 from autobahn.asyncio.websocket import WebSocketClientProtocol, \ WebSocketClientFactory def hash_password(password): return sha256(password.encode('utf-8')).hexdigest() class HelloClientProtocol(WebSocketClientProtocol): def onOpen(self): # Create new address request = { 'method': 'POST', 'url': '/address/', 'data': { "email_address": 'some_address@google.com' }, 'event_name': 'create-address' } self.sendMessage(json.dumps(request).encode('utf8')) # Get users list request = { 'method': 'GET', 'url': '/user/list/', 'event_name': 'get-user-list' } self.sendMessage(json.dumps(request).encode('utf8')) # Create new user with address request = { 'method': 'POST', 'url': '/user/', 'data': { 'name': 'Neyton', 'fullname': 'Neyton Drake', 'password': hash_password('123456'), 'addresses': [{"id": 1}, ] }, 'event_name': 'create-user' } self.sendMessage(json.dumps(request).encode('utf8')) # Trying to create new user with same info, but we have taken an error self.sendMessage(json.dumps(request).encode('utf8')) # Update existing object request = { 'method': 'PUT', 'url': '/user/6/', 'data': { 'fullname': 'Definitely not Neyton Drake', 'addresses': [{"id": 1}, {"id": 2}] }, 'event_name': 'partial-update-user' } self.sendMessage(json.dumps(request).encode('utf8')) def onMessage(self, payload, isBinary): print("Result: {0}".format(payload.decode('utf8'))) if __name__ == '__main__': factory = WebSocketClientFactory("ws://localhost:8080") factory.protocol = HelloClientProtocol loop = asyncio.get_event_loop() coro = loop.create_connection(factory, '127.0.0.1', 8080) loop.run_until_complete(coro) loop.run_forever() loop.close()
ããã§ããµã³ãã«ã®ãœãŒã¹ã³ãŒãå šäœãããã«è©³ããèŠãããšãã§ããŸã ã
7.ãããªãéçº
çŸåšã®ã©ã€ãã©ãªæ©èœãæ¡åŒµããæ¹æ³ã«ã€ããŠã¯ãããªãã®æ°ã®ã¢ã€ãã¢ããããŸãã ããšãã°ã次ã®åéã§ãã®ã¢ãžã¥ãŒã«ãéçºã§ããŸãã
- éç¥ãµããŒã
- ãã©ãŠã¶ãä»ããŠAPIããã¥ã¡ã³ãã衚瀺ããïŒããããSwaggerã®ãã©ã°ã€ã³ãšããŠïŒ
- APIãã¹ãã¢ãžã¥ãŒã«
- Pythonããã³JavaScriptã®ã¯ã©ã€ã¢ã³ã
- Ponyããã³Peewee ORMã®ãµããŒã
ç¹°ãè¿ãã«ãªããŸãããå€ãã®æ©èœã¯1ã€ã®ãªãªãŒã¹ã§ã¯ãªããç°ãªããªãªãŒã¹ã§èšç»ãããŠããããšãæãåºãããŠãã ããã ããã¯æå³çã«è¡ããããã®ã§ããã極端ãªãã®ããå¥ã®ãã®ãžãšæ¥ãã§è¡ããªãããã«ã䞊è¡ããŠäœããããŸãã ãããŠãç§ã«ãšã£ãŠãããªãã«ãšã£ãŠãç°¡åã§ãã
8.ãããŠçµè«ãšããŠ...
ç¬èªã®ã©ã€ãã©ãªãäœæããçµéšããªãã«ãããããããåããŠããªãè¯ãçµæã«ãªã£ããšæããŸãã ãããŠãPythonèšèªã®éçºã«éåžžã«åŒ·ãè²¢ç®ããŸãïŒããšãå°ããªãã®ã§ãïŒã è²»ããããæéã«é©ããªãã§ãã ããïŒãã¹ãŠãèªç±æéã§å®æçã«è¡ãããŸãïŒãããŠç¶ç¶ãããŸãïŒïŒ1ã€ã®ãããžã§ã¯ãã§ã®å®æçãªäœæ¥ã¯éåžžã«ç²ããŸãããåæã«è€æ°ã®æ¹åã§éçºãããããïŒã
äœããã®åœ¢ã§ããã®ã©ã€ãã©ãªã«é¢ãããã¹ãŠã®ææ¡ãã¢ã€ãã¢ãæ¹åç¹ãã³ã¡ã³ãïŒãŸãã¯GitHubã®ãªã¯ãšã¹ãããŒã«ã®åœ¢åŒïŒã§èããŠããããã§ãã ã©ã€ãã©ãªãå®è£ ã®æ©èœã«é¢ããã質åã¯ãæ°è»œã«ãå¯ããã ããããã£ãŒãããã¯ããåŸ ã¡ããŠãããŸãã
äžèšã®ãã¹ãŠã®ã³ãŒããšaiorest-wsã©ã€ãã©ãªã®ãœãŒã¹ã¯ã GitHubã§è¡šç€ºã§ããŸãã ãµã³ãã«ã¯ããããžã§ã¯ãã®ã«ãŒããexamplesãã£ã¬ã¯ããªã«ãããŸãã ããã¥ã¡ã³ãã¯ããã§èŠãããšãã§ããŸã ã