ïŒ2018幎çïŒ
ãã²ã«ã»ã°ãªã³ããŒã°
ããã« æ»ã 
ããã¯Mega-Tutorialã®22çªç®ã®éšåã§ããããã§ã¯ãWebãµãŒããŒãšã¯ç¬ç«ããŠæ©èœããããã¯ã°ã©ãŠã³ããžã§ããäœæããæ¹æ³ã説æããŸãã
ãã¿ãã¬ã®äžã«ã¯ã2018幎ã·ãªãŒãºã®ãã¹ãŠã®èšäºã®ãªã¹ãããããŸãã
泚1ïŒãã®ã³ãŒã¹ã®å€ãããŒãžã§ã³ããæ¢ãã®å Žåã¯ããã¡ããã芧ãã ãã ã
泚2ïŒçªç¶ãç§ã®ïŒãã²ã«ïŒã®ä»äºãæ¯æããŠè©±ããããå ŽåããŸãã¯1é±éèšäºãåŸ
ã€å¿èããªãå Žåãç§ïŒãã²ã«ã°ãªãŒã³ããŒã°ïŒã¯ãã®ã¬ã€ãã®å®å
šçïŒè±èªïŒãé»åæžç±ãŸãã¯ãããªã®åœ¢åŒã§æäŸããŸãã 詳现ã«ã€ããŠã¯ã learn.miguelgrinberg.comãã芧ãã ãã ã
ãã®ç« ã§ã¯ãã¢ããªã±ãŒã·ã§ã³ã®äžéšãšããŠå®è¡ããå¿
èŠã®ããé·ãããã»ã¹ãŸãã¯è€éãªããã»ã¹ã®å®è£
ã«çŠç¹ãåœãŠãŸãã ãããã®ããã»ã¹ã¯ãã¿ã¹ã¯ã®å®è¡äžã¯ã¯ã©ã€ã¢ã³ããžã®å¿çããããã¯ããããããªã¯ãšã¹ãã®ã³ã³ããã¹ãã§åæçã«å®è¡ããããšã¯ã§ããŸããã ã¯ã©ã€ã¢ã³ããã¡ãŒã«ãéä¿¡ããã®ã«å¿
èŠãª3ã4ç§åŸ
æ©ããå¿
èŠããªãããã«ãã¡ãŒã«ã¡ãã»ãŒãžã®éä¿¡ãããã¯ã°ã©ãŠã³ãã¹ããªãŒã ã«ç§»åãã第10ç« ã§ãã®ãããã¯ã«ã€ããŠç°¡åã«è§ŠããŸããã é»åã¡ãŒã«ã¡ãã»ãŒãžã«ã¹ããªãŒã ã䜿çšããŠãåé¡ãããŸããããåé¡ã®ããã»ã¹ãéåžžã«é·ãå Žåããã®ãœãªã¥ãŒã·ã§ã³ã¯ããŸãæ¡åŒµã§ããŸããã äžè¬çãªæ¹æ³ã¯ãé·ãã¿ã¹ã¯ãã¯ãŒã¯ãããŒããŸãã¯ã»ãšãã©ã®å ŽåããŒã«ã«ã¢ããããŒãããããšã§ãã
é·ãã¿ã¹ã¯ã®å¿
èŠæ§ãæ£åœåããããã«ããã€ã¯ãããã°ã«ãšã¯ã¹ããŒãæ©èœãå°å
¥ããŸããããã«ããããŠãŒã¶ãŒã¯ãã¹ãŠã®ããã°æçš¿ãå«ãããŒã¿ãã¡ã€ã«ãèŠæ±ã§ããŸãã ãŠãŒã¶ãŒããã®ãªãã·ã§ã³ã䜿çšããå Žåãã¢ããªã±ãŒã·ã§ã³ã¯ãã¹ãŠã®ãŠãŒã¶ãŒã¡ãã»ãŒãžãJSONãã¡ã€ã«ã«ãšã¯ã¹ããŒãããŠãããé»åã¡ãŒã«ã§ãŠãŒã¶ãŒã«éä¿¡ããå¿
èŠããããŸãã ããããã¹ãŠè¡ãããŠããéããŠãŒã¶ãŒã«ã¯å®äºã®å²åã瀺ãéç¥ã衚瀺ãããŸãã
ãã®ç« ã®GitHubãªã³ã¯ïŒ Browse ã Zip ã Diff ã
ã¿ã¹ã¯ãã¥ãŒã®æŠèŠ
ã¿ã¹ã¯ãã¥ãŒã¯ã ã¿ã¹ã¯ãå®äºããããã®ã¯ãŒã¯ãããŒãèŠæ±ããããã®äŸ¿å©ãªãœãªã¥ãŒã·ã§ã³ãã¢ããªã±ãŒã·ã§ã³ã«æäŸããŸãã ã¯ãŒã¯ãããŒã¯ã¢ããªã±ãŒã·ã§ã³ãšã¯ç¬ç«ããŠå®è¡ãããå¥ã®ã·ã¹ãã ã«åžžé§ããããšãããããŸãã ã¢ããªã±ãŒã·ã§ã³ãšãã³ãã©ãŒéã®éä¿¡ã¯ã ã¡ãã»ãŒãžãã¥ãŒãä»ããŠè¡ãããŸã ã ã¢ããªã±ãŒã·ã§ã³ã¯ã¿ã¹ã¯ãéä¿¡ããå®è¡ãç£èŠããŠãã¥ãŒãšå¯Ÿè©±ããŸãã 次ã®å³ã¯ãå
žåçãªå®è£
ã瀺ããŠããŸãã

Pythonã§æã人æ°ã®ããã¿ã¹ã¯ãã¥ãŒã¯Celeryã§ãã ããã¯å€ãã®ãªãã·ã§ã³ããããè€æ°ã®ã¡ãã»ãŒãžãã¥ãŒããµããŒãããããªãè€éãªããã±ãŒãžã§ãã Pythonã¿ã¹ã¯ãã¥ãŒã®ãã1ã€ã®äžè¬çãªãªãã·ã§ã³ã¯ã Redisãã¥ãŒãŸãã¯åã«RQã§ããããã¯ãRedisã¡ãã»ãŒãžãã¥ãŒã®ã¿ããµããŒãããŸãããCeleryãããæ§æãã¯ããã«ç°¡åã§ãã
CeleryãšRQã¯ã©ã¡ããFlaskã¢ããªã±ãŒã·ã§ã³ã®ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ããµããŒãããã®ã«éåžžã«é©ããŠãããããRQã®ã·ã³ãã«ãã¯ãã®ã¢ããªã±ãŒã·ã§ã³ã®éžæã«åœ¹ç«ã¡ãŸãã ãã ããåãæ©èœãCeleryã§å®è£
ããããšã¯ããã»ã©è€éã§ã¯ãããŸããã RQãããCeleryã«èå³ãããå Žåã¯ãããã°ã§æžããèšäºã Using Celery with Flask ã ãèªãããšãã§ããŸãã
rqã䜿çšãã
RQã¯pip
ãä»ããŠã€ã³ã¹ããŒã«ãããæšæºã®Pythonããã±ãŒãžã§ãïŒ
(venv) $ pip install rq (venv) $ pip freeze > requirements.txt
åè¿°ããããã«ãã¢ããªã±ãŒã·ã§ã³ãšRQãã³ãã©ãŒã®éã®æ¥ç¶ã¯Redisã¡ãã»ãŒãžãã¥ãŒã«ãããããRedisãµãŒããŒãèµ·åããå¿
èŠããããŸãã ã¯ã³ã¯ãªãã¯ã§RedisãµãŒããŒãã€ã³ã¹ããŒã«ããã³èµ·åããŠããœãŒã¹ã³ãŒãã€ã³ã¹ããŒã©ãŒãããŠã³ããŒãããã·ã¹ãã ã§çŽæ¥ã³ã³ãã€ã«ããããã®å€ãã®ãªãã·ã§ã³ããããŸãã Windowsã䜿çšããŠããå ŽåãMicrosoftã¯ããã§ã€ã³ã¹ããŒã©ãŒããµããŒãããŸã ã Linuxã§ã¯ããããããªãã¬ãŒãã£ã³ã°ã·ã¹ãã ã®ããã±ãŒãžãããŒãžã£ãŒãä»ããŠããã±ãŒãžãšããŠååŸã§ããŸãã Mac OS XãŠãŒã¶ãŒã¯brew install redis
ãéå§ããŠããã redis-server
ã³ãã³ãã䜿çšããŠæåã§ãµãŒãã¹ãéå§redis-server
ã
ãµãŒãã¹ãå®è¡ãããRQã§å©çšå¯èœã§ããããšã確èªããå Žåãé€ãããã¹ãŠã§Redisãšå¯Ÿè©±ããå¿
èŠã¯ãããŸããã
ã¿ã¹ã¯ãäœæãã
RQã䜿çšããŠç°¡åãªã¿ã¹ã¯ãå®äºããæ¹æ³ã玹ä»ããŸããããã«ãããã¿ã¹ã¯ã«æ
£ããããšãã§ããŸãã ã¿ã¹ã¯ã¯Pythoné¢æ°ã«ãããŸããã æ°ããapp / tasks.pyã¢ãžã¥ãŒã«ã«å®è£
ããã¿ã¹ã¯ã®äŸã次ã«ç€ºããŸã ã
app / tasks.py ïŒããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ã®äŸã
import time def example(seconds): print('Starting task') for i in range(seconds): print(i) time.sleep(1) print('Task completed')
ãã®ã¿ã¹ã¯ã¯åŒæ°ãšããŠç§æ°ãåãã1ç§ã«1åã«ãŠã³ã¿ãŒãå°å·ããŠãã®æéãåŸ
æ©ããŸãã
RQã¯ãŒã«ãŒãèµ·å
ã¿ã¹ã¯ã®æºåãã§ããã®ã§ããã³ãã©ãŒãéå§ã§ããŸãã ããã¯ã rq worker
ã³ãã³ãã䜿çšããŠè¡ãããŸãã
(venv) $ rq worker microblog-tasks 18:55:06 RQ worker 'rq:worker:miguelsmac.90369' started, version 0.9.1 18:55:06 Cleaning registries for queue: microblog-tasks 18:55:06 18:55:06 *** Listening on microblog-tasks...
ããã§ãã¯ãŒã¯ãããŒã¯Redisã«æ¥ç¶ãããmicroblog microblog-tasks
ãšããååã®ãã¥ãŒã§å²ãåœãŠå¯èœãªãã¹ãŠã®ã¿ã¹ã¯ãç£èŠããŸãã è€æ°ã®ãã³ãã©ãŒã«ããå€ãã®åž¯åå¹
ãæããããå Žåãå¿
èŠãªããšã¯rq worker
ããå€ãã®ã€ã³ã¹ã¿ã³ã¹ãå®è¡ããããšã ãã§ããã¹ãŠåããã¥ãŒã«æ¥ç¶ãããŸãã 次ã«ããžã§ãããã¥ãŒã«è¡šç€ºããããšã䜿çšå¯èœãªã¯ãŒã¯ãããŒã®ããããããžã§ããéžæããŸãã å®çšŒåç°å¢ã§ã¯ãå°ãªããšãCPUã§äœ¿çšå¯èœãªããã»ããµãšåãæ°ã®ããã»ããµãå¿
èŠã«ãªãã§ãããã
ã¿ã¹ã¯ã®éæ
次ã«ã2çªç®ã®ã¿ãŒããã«ãŠã£ã³ããŠãéãããã®ä»®æ³ç°å¢ãã¢ã¯ãã£ãã«ããŸãã ã·ã§ã«ã»ãã·ã§ã³ã䜿çšããŠãworkerã§example()
ã¿ã¹ã¯ãå®è¡ããŸãã
>>> from redis import Redis >>> import rq >>> queue = rq.Queue('microblog-tasks', connection=Redis.from_url('redis://')) >>> job = queue.enqueue('app.tasks.example', 23) >>> job.get_id() 'c651de7f-21a8-4068-afd5-8b982a6f6d32'
RQã®Queue
ã¯ã©ã¹ã¯ãã¢ããªã±ãŒã·ã§ã³ãã¥ãŒãè¡šããŸãã ããã¯2ã€ã®åŒæ°ãåããŸããããã¯ãã¥ãŒåãšRedis
æ¥ç¶ãªããžã§ã¯ãã§ããããã®å Žåã¯ããã©ã«ãã®URLã§åæåããŸãã RedisãµãŒããŒãå¥ã®ãã¹ããŸãã¯ããŒãã§å®è¡ãããŠããå Žåã¯ãå¥ã®URLã䜿çšããå¿
èŠããããŸãã
enqueue()
ã¡ãœããã¯ããã¥ãŒã«ãžã§ããè¿œå ããããã«äœ¿çšãããŸãã æåã®åŒæ°ã¯ãå®è¡ããã¿ã¹ã¯ã®ååã§ãããé¢æ°ãªããžã§ã¯ããŸãã¯ã€ã³ããŒãæååãšããŠçŽæ¥æå®ãããŸãã ããã«ãããã¢ããªã±ãŒã·ã§ã³åŽã§é¢æ°ãã€ã³ããŒãããå¿
èŠããªããªããããæååãªãã·ã§ã³ã®ã»ããã¯ããã«äŸ¿å©ã§ãã enqueue()
æå®ãããæ®ãã®åŒæ°ã¯ãã¹ãŠãworkerã§å®è¡ãããŠããé¢æ°ã«æž¡ãããŸãã
enqueue()
ãåŒã³åºããããšããã«ãã¯ãŒã«ãŒRQãå®è¡ãããŠããã¿ãŒããã«ã®æåã®ãŠã£ã³ããŠã§ã¢ã¯ãã£ããã£ã«æ°ä»ãã§ãããã example()
é¢æ°ãæ©èœãã1ç§ã«1åã«ãŠã³ã¿ãŒãåºåããããšãããããŸãã åæã«ãä»ã®ç«¯æ«ã¯ãããã¯ããããã·ã§ã«ã§åŒãè©äŸ¡ãç¶ããããšãã§ããŸãã äžèšã®äŸã§ã¯ã job.get_id()
ã¡ãœãããjob.get_id()
ãã¿ã¹ã¯ã®äžæã®èå¥åãååŸããŸããã job
ãªããžã§ã¯ãã§äœ¿çšã§ããå¥ã®èå³æ·±ãè¡šçŸã¯ãé¢æ°ãè·å Žã§ã®äœæ¥ãçµäºãããã©ããã確èªããããšã§ãã
>>> job.is_finished False
äžèšã®äŸã§è¡ã£ãããã«23
ãæž¡ããå Žåãé¢æ°ã¯çŽ23ç§éæ©èœããŸãã ãã®æéãjob.is_finished
ãããšã job.is_finished
ã¯True
ã«ãªãTrue
ã ããã¯çŽ æŽãããããšã§ã¯ãããŸãããïŒïŒ RQã®ã·ã³ãã«ããæ¬åœã«æ°ã«å
¥ã£ãŠããŸãïŒ
é¢æ°ãå®äºãããšããã«ã ã¯ãŒã«ãŒã¯æ°ãããžã§ãã®åŸ
æ©ã«æ»ããããããã«å®éšããå Žåã¯ãä»ã®åŒæ°ãæå®ããŠenqueue()
åŒã³åºããç¹°ãè¿ãããšãã§ããŸãã ã¿ã¹ã¯ã«é¢é£ãããã¥ãŒã«ä¿åãããããŒã¿ã¯ããã°ããã®éïŒããã©ã«ãã§ã¯500ç§ïŒããã«æ®ããŸãããæçµçã«ã¯åé€ãããŸãã ããã¯éèŠã§ã;ã¿ã¹ã¯ãã¥ãŒã¯å®äºããã¿ã¹ã¯ã®å±¥æŽãä¿åããŸããã
ã¿ã¹ã¯é²æã¬ããŒã
äžèšã§äœ¿çšããã¿ã¹ã¯ã®äŸã¯ãéçŸå®çã«åçŽã§ãã ååãšããŠãé·ãã¿ã¹ã¯ã®å®è¡äžã«ãå®è¡ã®é²è¡ç¶æ³ã«é¢ããæ
å ±ãã¢ããªã±ãŒã·ã§ã³ã§å©çšã§ããããã«ãããã®æ
å ±ããŠãŒã¶ãŒã«è¡šç€ºã§ããŸãã RQã¯ã meta
ãžã§ããªããžã§ã¯ãå±æ§ã§ããããµããŒãããŸãã example()
ã¿ã¹ã¯ãæžãçŽããŠãé²æã¬ããŒããèšé²ããŸãã
app / tasks.py ïŒé²æã¬ããŒãä»ãã®ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ã®äŸã
import time from rq import get_current_job def example(seconds): job = get_current_job() print('Starting task') for i in range(seconds): job.meta['progress'] = 100.0 * i / seconds job.save_meta() print(i) time.sleep(1) job.meta['progress'] = 100 job.save_meta() print('Task completed')
example()
ãã®æ°ããããŒãžã§ã³ã¯ãRQ get_current_job()
é¢æ°ã䜿çšããŠãã¿ã¹ã¯ãget_current_job()
ãšãã«ã¢ããªã±ãŒã·ã§ã³ã«è¿ããããã®ãšåæ§ã®ãžã§ãã€ã³ã¹ã¿ã³ã¹ãååŸããŸãã meta
ãžã§ããªããžã§ã¯ãå±æ§ã¯ãã¿ã¹ã¯ãã¢ããªã±ãŒã·ã§ã³ã«æž¡ããŠãŒã¶ãŒããŒã¿ãèšé²ã§ããèŸæžã§ãã ãã®äŸã§ã¯ãèšé²ã®ããã«ãã¿ã¹ã¯ã®å®äºã®å²åãè¡šãprogress
èŠçŽ ã䜿çšããŸãã é²è¡ç¶æ³ãæŽæ°ããããã³ã«ã job.save_meta()
ãåŒã³åºããŠãã¢ããªã±ãŒã·ã§ã³ãèŠã€ããããšãã§ããRedisã«ããŒã¿ãæžã蟌ãjob.save_meta()
RQã«job.save_meta()
ããŸãã
ã¢ããªã±ãŒã·ã§ã³åŽïŒçŸåšã¯Pythonã·ã§ã«ã®ã¿ïŒã§ã¯ããã®ã¿ã¹ã¯ãå®è¡ããŠã次ã®ããã«é²è¡ç¶æ³ã远跡ã§ããŸãã
>>> job = queue.enqueue('app.tasks.example', 23) >>> job.meta {} >>> job.refresh() >>> job.meta {'progress': 13.043478260869565} >>> job.refresh() >>> job.meta {'progress': 69.56521739130434} >>> job.refresh() >>> job.meta {'progress': 100} >>> job.is_finished True
äžèšã§ãããããã«ããã¡ãåŽã§ã¯meta
å±æ§ãèªã¿åãå¯èœã§ãã Redisããã³ã³ãã³ããæŽæ°ããã«ã¯ã refresh()
ã¡ãœãããåŒã³åºãå¿
èŠããããŸãã
ããŒã¿ããŒã¹å
ã®ã¿ã¹ã¯ã®æåº
äžèšã®äŸã§ã¯ãã¿ã¹ã¯ãå®è¡ããŠããã®å®è¡æ¹æ³ã確èªããã ãã§ååã§ãã Webã¢ããªã±ãŒã·ã§ã³ã®å Žåããããã®ã¿ã¹ã¯ã®1ã€ããªã¯ãšã¹ãã®äžéšãšããŠéå§ããããšããã«ãã®ãªã¯ãšã¹ããçµäºãããã®ã¿ã¹ã¯ã®ã³ã³ããã¹ãå
šäœã倱ããããããäºæ
ã¯ããå°ãè€éã«ãªããŸãã ã¢ããªã±ãŒã·ã§ã³ã§åãŠãŒã¶ãŒãå®è¡ããã¿ã¹ã¯ã远跡ããå¿
èŠããããããããŒã¿ããŒã¹ããŒãã«ã䜿çšããŠç¶æ
ãç¶æããå¿
èŠããããŸãã 以äžã«ã Task
ã¢ãã«ã®æ°ããå®è£
ã瀺ããŸãã
app / models.py ïŒã¿ã¹ã¯ã¢ãã«ã
# ... import redis import rq class User(UserMixin, db.Model): # ... tasks = db.relationship('Task', backref='user', lazy='dynamic') # ... class Task(db.Model): id = db.Column(db.String(36), primary_key=True) name = db.Column(db.String(128), index=True) description = db.Column(db.String(128)) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) complete = db.Column(db.Boolean, default=False) def get_rq_job(self): try: rq_job = rq.job.Job.fetch(self.id, connection=current_app.redis) except (redis.exceptions.RedisError, rq.exceptions.NoSuchJobError): return None return rq_job def get_progress(self): job = self.get_rq_job() return job.meta.get('progress', 0) if job is not None else 100
ãã®ã¢ãã«ãšä»¥åã®ã¢ãã«ã®èå³æ·±ãéãã¯ãäž»ããŒãã£ãŒã«ãid
ãæŽæ°ã§ã¯ãªãæååã§ããããšã§ãã ããã¯ããã®ã¢ãã«ã§ã¯ãããŒã¿ããŒã¹ã«ããç¬èªã®ãã©ã€ããªããŒçæã«äŸåããã®ã§ã¯ãªããRQã«ãã£ãŠäœæããããžã§ãèå¥åã䜿çšããããã§ãã
ã¢ãã«ã«ã¯ãã¿ã¹ã¯ã®ãã«ããŒã ïŒRQã«æž¡ãããïŒããŠãŒã¶ãŒãžã®è¡šç€ºã«é©ããã¿ã¹ã¯ã®èª¬æãã¿ã¹ã¯ãèŠæ±ãããŠãŒã¶ãŒãšã®éä¿¡ãããã³ã¿ã¹ã¯ãå®äºãããã©ããã瀺ãè«çå€ãæ ŒçŽãããŸãã complete
ãã£ãŒã«ãã®ç®çã¯ãå®è¡äžã®ã¿ã¹ã¯ãæŽæ°ã®é²è¡ç¶æ³ã衚瀺ããããã«ç¹å¥ãªåŠçãå¿
èŠãšãããããå®äºããã¿ã¹ã¯ãã¢ã¯ãã£ãã«å®è¡ãããŠããã¿ã¹ã¯ããåé¢ããããšã§ãã
get_rq_job()
ã¡ãœããã¯ãã¢ãã«ããååŸã§ãããæå®ãããã¿ã¹ã¯èå¥åããRQ Job
ã€ã³ã¹ã¿ã³ã¹ãããŒããããã«ããŒã¡ãœããã§ãã ããã¯ãRedisã«ååšããããŒã¿ãããžã§ãã®ã€ã³ã¹ã¿ã³ã¹ãããŒãããJob.fetch()
ã䜿çšããŠè¡ãããŸãã get_progress()
ã¡ãœããã¯get_progress()
ã¡ãœããã®äžã«æ§ç¯ãããã¿ã¹ã¯ã®å®äºçãè¿ããŸãã ãã®æ¹æ³ã«ã¯èå³æ·±ãææ¡ãããã€ããããŸãã ã¢ãã«ããã®ãžã§ãIDãRQãã¥ãŒã«ååšããªãå Žåãããã¯ã¿ã¹ã¯ãæ¢ã«å®äºããŠãããããŒã¿ãæéåãã§ãã¥ãŒããåé€ãããŠããããšãæå³ããŸãããããã£ãŠããã®å Žåã¯100ïŒ
ãè¿ãããŸãã äžæ¹ãã¿ã¹ã¯ãååšãããã meta
å±æ§ã«é¢é£ããæ
å ±ããªãå Žåãã¿ã¹ã¯ãå®äºããããã«ã¹ã±ãžã¥ãŒã«ãããŠãããšå®å
šã«æ³å®ã§ããŸãããéå§ããæ©äŒããªãã£ãããããã®ç¶æ³ã§ã¯é²æãšããŠ0ãè¿ãããŸãã
ããŒã¿ããŒã¹ã¹ããŒãã«å€æŽãé©çšããã«ã¯ãæ°ãã移è¡ãäœæããããŒã¿ããŒã¹ãæŽæ°ããå¿
èŠããããŸãã
(venv) $ flask db migrate -m "tasks" (venv) $ flask db upgrade
æ°ããã¢ãã«ãã·ã§ã«ã³ã³ããã¹ãã«è¿œå ããŠãã€ã³ããŒãããããšãªãã·ã§ã«ã»ãã·ã§ã³ã§äœ¿çšã§ããããã«ããããšãã§ããŸãã
microblog.py ïŒã¿ã¹ã¯ã¢ãã«ãã·ã§ã«ã³ã³ããã¹ãã«è¿œå ããŸãã
from app import create_app, db, cli from app.models import User, Post, Message, Notification, Task app = create_app() cli.register(app) @app.shell_context_processor def make_shell_context(): return {'db': db, 'User': User, 'Post': Post, 'Message': Message, 'Notification': Notification, 'Task': Task}
Flaskãšã®RQçµ±å
RedisãµãŒãã¹ã®æ¥ç¶URLãæ§æã«è¿œå ããå¿
èŠããããŸãã
class Config(object): # ... REDIS_URL = os.environ.get('REDIS_URL') or 'redis://'
ãã€ãã®ããã«ãRedisæ¥ç¶URLã¯ç°å¢å€æ°ããååŸãããå€æ°ãå®çŸ©ãããŠããªãå Žåãããã©ã«ãã®URLã䜿çšãããŸããããã¯ããµãŒãã¹ãããã©ã«ãã§åããã¹ããšããŒãã§å®è¡ãããããšãåæãšããŠããŸãã
ã¢ããªã±ãŒã·ã§ã³ãã¡ã¯ããªé¢æ°ã¯ãRedisãšRQã®åæåãæ
åœããŸãã
app / _ init_ .py ïŒRQçµ±åã
# ... from redis import Redis import rq # ... def create_app(config_class=Config): # ... app.redis = Redis.from_url(app.config['REDIS_URL']) app.task_queue = rq.Queue('microblog-tasks', connection=app.redis) # ...
app.task_queue
ã¯ãã¿ã¹ã¯ãæ瀺ããããã¥ãŒã«ãªããŸãã ã¢ããªã±ãŒã·ã§ã³ã®ã©ãã«ããŠãcurrent_app.task_queue
ã䜿çšããŠã¢ã¯ã»ã¹ã§ãããããã¢ããªã±ãŒã·ã§ã³ã«ãã¥ãŒãã¢ã¿ãããããšäŸ¿å©ã§ãã ã¢ããªã±ãŒã·ã§ã³ã®äžéšãéä¿¡ãŸãã¯ãã§ãã¯ããããããããã«ã User
ã¢ãã«ã«ããã€ãã®ãã«ããŒã¡ãœãããäœæã§ããŸãã
app / models.py ïŒãŠãŒã¶ãŒã¢ãã«ã®ã¿ã¹ã¯ã®ãã«ããŒã¡ãœããã
# ... class User(UserMixin, db.Model): # ... def launch_task(self, name, description, *args, **kwargs): rq_job = current_app.task_queue.enqueue('app.tasks.' + name, self.id, *args, **kwargs) task = Task(id=rq_job.get_id(), name=name, description=description, user=self) db.session.add(task) return task def get_tasks_in_progress(self): return Task.query.filter_by(user=self, complete=False).all() def get_task_in_progress(self, name): return Task.query.filter_by(name=name, user=self, complete=False).first()
launch_task()
ã¡ãœããã¯ãã¿ã¹ã¯ãRQãã¥ãŒã«æž¡ããããŒã¿ããŒã¹ã«è¿œå ããŸãã name
åŒæ°ã¯ã app / tasks.pyã§å®çŸ©ãããŠããé¢æ°ã®ååã§ãã RQã«app.tasks
ãããšãé¢æ°ã¯app.tasks
è¿œå ããapp.tasks
ã é¢æ°ã®å®å
šãªname
ãäœæããååã description
åŒæ°ã¯ããŠãŒã¶ãŒã«æ瀺ã§ããã¿ã¹ã¯ã®æ確ãªèª¬æã§ãã ããã°æçš¿ããšã¯ã¹ããŒãããé¢æ°ã§ã¯ã export_posts
ãšããååãšExporting posts...
ã®Exporting posts...
ã®èª¬æã䜿çšãExporting posts...
æ®ãã®åŒæ°ã¯ãã¿ã¹ã¯ã«æž¡ãããäœçœ®åŒæ°ãšããŒåŒæ°ã§ãã ãã®é¢æ°ã¯ã enqueue()
ãã¥ãŒã¡ãœãããåŒã³åºããŠãžã§ããéä¿¡ããããšããå§ãŸããŸãã è¿ãããã¿ã¹ã¯ãªããžã§ã¯ãã«ã¯RQã«ãã£ãŠå²ãåœãŠãããã¿ã¹ã¯IDãå«ãŸããŠãããããããã䜿çšããŠãããŒã¿ããŒã¹ã«å¯Ÿå¿ããã¿ã¹ã¯ãªããžã§ã¯ããäœæã§ããŸãã
launch_task()
ã¯ãã»ãã·ã§ã³ã«æ°ããTask
ãªããžã§ã¯ããè¿œå ããŸãããã³ãããããŸããã äžè¬çãªã±ãŒã¹ã§ã¯ã1ã€ã®ãã©ã³ã¶ã¯ã·ã§ã³ã§äžäœã¬ãã«ã®é¢æ°ã«ãã£ãŠè¡ãããè€æ°ã®æŽæ°ãçµã¿åãããããšãã§ãããããäžäœã¬ãã«ã®é¢æ°ã§ããŒã¿ããŒã¹ã»ãã·ã§ã³ãæäœããã®ãæé©ã§ãã ããã¯å³å¯ãªã«ãŒã«ã§ã¯ãããŸããããã®ç« ã®åŸåã§ãåé¢æ°ã§ã³ããããå®è¡ãããäŸå€ã確èªããŸãã
get_tasks_in_progress()
ã¡ãœããã¯ããŠãŒã¶ãŒã«çºè¡ãããé¢æ°ã®å®å
šãªãªã¹ããè¿ããŸãã åŸã§ããã®ã¡ãœããã䜿çšããŠããŠãŒã¶ãŒã«è¡šç€ºãããããŒãžã§å®è¡ãããã¿ã¹ã¯ã«é¢ããæ
å ±ãå«ããããšãããããŸãã
æåŸã«ã get_task_in_progress()
ã¯ãç¹å®ã®ã¿ã¹ã¯ãè¿ã以åã®ããŒãžã§ã³ã®åçŽãªããŒãžã§ã³ã§ãã ãŠãŒã¶ãŒãåãã¿ã€ãã®è€æ°ã®ã¿ã¹ã¯ãåæã«å®è¡ããããšãçŠæ¢ããŠãããããã¿ã¹ã¯ãéå§ããåã«ããã®ã¡ãœããã䜿çšããŠåã®ã¿ã¹ã¯ãçŸåšå®è¡ãããŠãããã©ããã確èªã§ããŸãã
RQã¿ã¹ã¯ããã¡ãŒã«ãéä¿¡ãã
ããã¯ã¡ã€ã³ãããã¯ããã®éžè±ã®ããã«æãããããããŸããããäžèšã§è¿°ã¹ãããã«ãããã¯ã°ã©ãŠã³ããšã¯ã¹ããŒãã¿ã¹ã¯ãå®äºãããšããã¹ãŠã®ã¡ãã»ãŒãžãå«ãJSONãã¡ã€ã«ãå«ãã¡ãŒã«ããŠãŒã¶ãŒã«éä¿¡ãããŸãã 第11ç« ã§çŽ¹ä»ããé»åã¡ãŒã«æ©èœã¯ã2ã€ã®æ¹æ³ã§æ¡åŒµããå¿
èŠããããŸãã ãŸããJSONãã¡ã€ã«ãæ·»ä»ã§ããããã«ãæ·»ä»ãã¡ã€ã«ã®ãµããŒããè¿œå ããå¿
èŠããããŸãã 次ã«ã send_email()
é¢æ°ã¯ãããã¯ã°ã©ãŠã³ãã¹ã¬ããã䜿çšããŠéåæã§ã¬ã¿ãŒãéä¿¡ããŸãã æ¢ã«éåæã®ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ããé»åã¡ãŒã«ãéä¿¡ããå Žåãã¹ããªãŒã ã«åºã¥ã第2ã¬ãã«ã®ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ã¯ããŸãæå³ããªããããé»åã¡ãŒã«ã®åæéä¿¡ãšéåæéä¿¡ã®äž¡æ¹ããµããŒãããå¿
èŠããããŸãã
幞ããªããšã«ãFlask-Mailã¯æ·»ä»ãã¡ã€ã«ããµããŒãããŠããã®ã§ã send_email()
é¢æ°ãæ¡åŒµããŠããããè¿œå ã®åŒæ°ãšããŠååŸãã Message
ãªããžã§ã¯ãã§èšå®ããã ãã§ãã ãããŠãåªå
ã¿ã¹ã¯ãšããŠé»åã¡ãŒã«ãéä¿¡ããããšã«å ããŠãè«çsync
åŒæ°ãè¿œå ããã ãã§ãã
app / email.py ïŒæ·»ä»ãã¡ã€ã«ä»ãã®ã¡ãŒã«ãéä¿¡ããŸãã
# ... def send_email(subject, sender, recipients, text_body, html_body, attachments=None, sync=False): msg = Message(subject, sender=sender, recipients=recipients) msg.body = text_body msg.html = html_body if attachments: for attachment in attachments: msg.attach(*attachment) if sync: mail.send(msg) else: Thread(target=send_async_email, args=(current_app._get_current_object(), msg)).start()
Message
ã¯ã©ã¹ã®attach()
ã¡ãœããã¯ãæ·»ä»ãã¡ã€ã«ãå®çŸ©ãã3ã€ã®åŒæ°ïŒãã¡ã€ã«åãã¡ãã£ã¢ã¿ã€ããããã³å®éã®ãã¡ã€ã«ããŒã¿attach()
åããŸãã ãã¡ã€ã«åã¯ãæ·»ä»ãã¡ã€ã«ã«é¢é£ä»ããããåä¿¡è
ã«è¡šç€ºãããåãªãååã§ãããå®éã®ãã¡ã€ã«ã§ãã£ãŠã¯ãªããŸããã ã¡ãã£ã¢ã¿ã€ãã«ãã£ãŠæ·»ä»ãã¡ã€ã«ã®ã¿ã€ãã決å®ããããããé»åã¡ãŒã«ãªãŒããŒã¯é©åã«è¡šç€ºã§ããŸãã ããšãã°ãã¡ãã£ã¢ã¿ã€ããšããŠjpg/png
ãéä¿¡ãããšãé»åã¡ãŒã«ãªãŒããŒã¯æ·»ä»ãã¡ã€ã«ãç»åã§ããããšãèªèããŸãããã®å Žåããã®ããã«è¡šç€ºã§ããŸãã ããã°æçš¿ããŒã¿ãã¡ã€ã«ã«ã¯ãã¡ãã£ã¢ã¿ã€ãapplication/json
ã䜿çšããJSON圢åŒã䜿çšããŸãã 3çªç®ã®æåŸã®åŒæ°ã¯ãæ·»ä»ãã¡ã€ã«ã®å
容ãå«ãæååãŸãã¯ãã€ãã·ãŒã±ã³ã¹ã§ãã
ç°¡åã«ããããã«ã send_email()
attachments
åŒæ°ã¯ã¿ãã«ã®ãªã¹ãã«ãªããåã¿ãã«ã«ã¯3ã€ã®attach()
åŒæ°ã«å¯Ÿå¿ãã3ã€ã®èŠçŽ ããããŸãã ãããã£ãŠããã®ãªã¹ãã®åèŠçŽ ã«å¯ŸããŠãã¿ãã«ãåŒæ°ãšããŠattach()
éä¿¡ããå¿
èŠããããŸãã Pythonã§ã¯ãé¢æ°ã«éä¿¡ããåŒæ°ãå«ããªã¹ããŸãã¯ã¿ãã«ãããå Žåã次ã®ãããªéå±ãªæ§æã䜿çšãã代ããã«ã func(*args)
ã䜿çšããŠãã®ãªã¹ããåŒæ°ã®å®éã®ãªã¹ãã«å±éã§ããŸãfunc(args[0], args[1], args[2])
ã ããšãã°ã args = [1, 'foo']
å ŽåãåŒã³åºãã¯func (1, 'foo')
åŒã³åºãããã®ããã«2ã€ã®åŒæ°ãéä¿¡ããŸãã *
ãªãå Žå*
åŒã³åºãã«ã¯1ã€ã®åŒæ°ãå«ãŸãããªã¹ãã«ãªããŸãã
é»åã¡ãŒã«ã®åæéä¿¡ã«é¢ããŠã¯ã sync
ã True
ãšãã«çŽæ¥mail.send(msg)
åŒã³åºãã«æ»ãå¿
èŠããããŸããã
ã¿ã¹ã¯ãã«ããŒ
äžèšã§äœ¿çšããexample()
ã¿ã¹ã¯ã¯åçŽãªã¹ã¿ã³ãã¢ãã³é¢æ°ã§ããããããã°æçš¿ããšã¯ã¹ããŒãããé¢æ°ã«ã¯ãããŒã¿ããŒã¹ãžã®ã¢ã¯ã»ã¹ãã¡ãŒã«æ©èœã®éä¿¡ãªã©ãã¢ããªã±ãŒã·ã§ã³ã«ããæ©èœã®äžéšãå¿
èŠã«ãªããŸãã ããã¯å¥ã®ããã»ã¹ã§è¡ããããããFlask-SQLAlchemyãšFlask-Mailãåæåããå¿
èŠããããŸããããããèšå®ããã«ã¯ãFlaskã¢ããªã±ãŒã·ã§ã³ã®ã€ã³ã¹ã¿ã³ã¹ãå¿
èŠã§ãã ãã®ãããFlaskã¢ããªã±ãŒã·ã§ã³ã®ã€ã³ã¹ã¿ã³ã¹ãšapp / tasks.pyã¢ãžã¥ãŒã«ã®äžéšã«ã¢ããªã±ãŒã·ã§ã³ã³ã³ããã¹ããè¿œå ããŸã ã
app / tasks.py ïŒã¢ããªã±ãŒã·ã§ã³ãšã³ã³ããã¹ããäœæããŸãã
from app import create_app app = create_app() app.app_context().push()
ããã¯RQã¯ãŒã«ãŒãã€ã³ããŒãããå¯äžã®ã¢ãžã¥ãŒã«ã§ããããããã®ã¢ãžã¥ãŒã«ã§ã¢ããªã±ãŒã·ã§ã³ãäœæãããŸãã flask
ã³ãã³ãã䜿çšããå Žåãã«ãŒããã£ã¬ã¯ããªã®microblog.pyã¢ãžã¥ãŒã«ãã¢ããªã±ãŒã·ã§ã³ãäœæããŸãããRQã¯ãŒã«ãŒã¯ããã«ã€ããŠäœãç¥ããªããããã¿ã¹ã¯æ©èœã«å¿
èŠãªå Žåã¯ã¢ããªã±ãŒã·ã§ã³ã®ç¬èªã®ã€ã³ã¹ã¿ã³ã¹ãäœæããå¿
èŠããããŸãã app.app_context()
ã¡ãœããã¯ãã§ã«ããã€ãã®å Žæã§èŠãããã³ã³ããã¹ããæŒããšãã¢ããªã±ãŒã·ã§ã³ãã¢ããªã±ãŒã·ã§ã³ã®ãçŸåšã®ãã€ã³ã¹ã¿ã³ã¹ã«ãªããFlask-SQLAlchemyãªã©ã®æ¡åŒµæ©èœãcurrent_app.config
ã䜿çšããŠèšå®ãååŸã§ããããã«ãªããŸãã ã³ã³ããã¹ãããªãå ŽåãåŒcurrent_app
ã¯ãšã©ãŒãè¿ããŸãã
次ã«ããã®æ©èœã®é²æç¶æ³ãã©ã®ããã«å ±åããããèããŸããã job.meta
ãã£ã¯ã·ã§ããªãä»ããŠé²è¡æ
å ±ãéä¿¡ããããšã«å ããŠãã¯ã©ã€ã¢ã³ãã«éç¥ãéä¿¡ããŠãããŒãžãæŽæ°ããå¿
èŠãªãå®äºçãåçã«æŽæ°ã§ããããã«ããŸãã ãã®ããã«ã 第21ç« ã§äœæãããã®ãšåæ§ã®éç¥ã¡ã«ããºã ã䜿çšããŸãã æŽæ°ã¯ãæªèªã¡ãã»ãŒãžã¢ã€ã³ã³ãšåæ§ã«æ©èœããŸãã ãµãŒããŒããã³ãã¬ãŒãã衚瀺ãããšã job.meta
ããååŸãããéçãªãé²ææ
å ±ãå«ãŸããŸãããã¯ã©ã€ã¢ã³ãã®ãã©ãŠã¶ãŒã«ããŒãžã衚瀺ããããšããã«ãéç¥ã䜿çšããŠéç¥ãããŒã»ã³ããŒãžãåçã«æŽæ°ããŸãã éç¥ã®ãããå®è¡äžã®ã¿ã¹ã¯ã®é²è¡ç¶æ³ã®æŽæ°ã¯ãåã®äŸã§è¡ã£ãæ¹æ³ãããå°ãè€éã«ãªããããã¿ã¹ã¯ã®é²è¡ç¶æ³ã®æŽæ°å°çšã®ãã³ã¬ãŒã¿ãŒé¢æ°ãäœæããŸãã
app / tasks.py ïŒã¿ã¹ã¯ã®é²è¡ç¶æ³ãèšå®ããŸãã
from rq import get_current_job from app import db from app.models import Task # ... def _set_task_progress(progress): job = get_current_job() if job: job.meta['progress'] = progress job.save_meta() task = Task.query.get(job.get_id()) task.user.add_notification('task_progress', {'task_id': job.get_id(), 'progress': progress}) if progress >= 100: task.complete = True db.session.commit()
ãšã¯ã¹ããŒãã¿ã¹ã¯ã¯ã _set_task_progress()
ãåŒã³åºããŠãå®äºã®å²åãèšé²ã§ããŸãã job.meta
Redis, task task.user
, , add_notification()
. task_progress
, , , , (progress number). JavaScript, .
, , , complete
. , , add_notification()
, . , , - , .
. :
app/tasks.py : .
def export_posts(user_id): try: # # except: #
try/except? , , , Flask , , , . , , , RQ, Flask, , , , RQ , . , RQ worker -, , .
, , :
app/tasks.py : .
import sys # ... def export_posts(user_id): try: # ... except: _set_task_progress(100) app.logger.error('Unhandled exception', exc_info=sys.exc_info())
, , , 100%, logger Flask , sys.exc_info()
. , flask Application logger , Flask . , 7 . app.logger
.
, , :
app/tasks.py : .
import time from app.models import User, Post # ... def export_posts(user_id): try: user = User.query.get(user_id) _set_task_progress(0) data = [] i = 0 total_posts = user.posts.count() for post in user.posts.order_by(Post.timestamp.asc()): data.append({'body': post.body, 'timestamp': post.timestamp.isoformat() + 'Z'}) time.sleep(5) i += 1 _set_task_progress(100 * i // total_posts) # except: # ...
, . ISO 8601. datetime
Python, , , ISO "Z", UTC.
- . i
, , total_posts
, . i
total_posts
0 100.
, , time.sleep(5)
. , sleep, , , .
, , data
:
app/tasks.py : .
import json from flask import render_template from app.email import send_email # ... def export_posts(user_id): try: # ... send_email('[Microblog] Your blog posts', sender=app.config['ADMINS'][0], recipients=[user.email], text_body=render_template('email/export_posts.txt', user=user), html_body=render_template('email/export_posts.html', user=user), attachments=[('posts.json', 'application/json', json.dumps({'posts': data}, indent=4))], sync=True) except: # ...
send_email()
. , attach()
Flask-Mail's Message
. - , Python json.dumps()
.
, , HTML . :
app/templates/email/export_posts.txt : Export posts text email template.
Dear {{ user.username }}, Please find attached the archive of your posts that you requested. Sincerely, The Microblog Team
HTML- :
app/templates/email/export_posts.html: Export posts HTML email template.
<p>Dear {{ user.username }},</p> <p>Please find attached the archive of your posts that you requested.</p> <p>Sincerely,</p> <p>The Microblog Team</p>
. , .
export_posts
:
app/main/routes.py : Export posts route and view function.
@bp.route('/export_posts') @login_required def export_posts(): if current_user.get_task_in_progress('export_posts'): flash(_('An export task is currently in progress')) else: current_user.launch_task('export_posts', _('Exporting posts...')) db.session.commit() return redirect(url_for('main.user', username=current_user.username))
, , . , . , get_task_in_progress()
, .
, launch_task()
. - , RQ worker app.tasks.
ã - , . Task
. .
, . , , , " ":
app/templates/user.html : .
... <p> <a href="{{ url_for('main.edit_profile') }}"> {{ _('Edit your profile') }} </a> </p> {% if not current_user.get_task_in_progress('export_posts') %} <p> <a href="{{ url_for('main.export_posts') }}"> {{ _('Export your posts') }} </a> </p> ... {% endif %}
, , , .
, . , RQ worker :
- , Redis
- , RQ.
rq worker microblog-tasks
- Flask,
flask run
( FLASK_APP
)
, . Bootstrap, . - , . - , . , . , :

app/templates/base.html : .
... {% block content %} <div class="container"> {% if current_user.is_authenticated %} {% with tasks = current_user.get_tasks_in_progress() %} {% if tasks %} {% for task in tasks %} <div class="alert alert-success" role="alert"> {{ task.description }} <span id="{{ task.id }}-progress">{{ task.get_progress() }}</span>% </div> {% endfor %} {% endif %} {% endwith %} {% endif %} ... {% endblock %} ...
. . , get_tasks_in_progress()
, . , , , , .
alert . CSS, alert-success
, alert-info. Bootstrap HTML . , , .
<span>
, id
. , JavaScript . , , -progress
. , , <span>
#<task.id> - progress
.
, "" , . , , .
<span>
, JavaScript:
app/templates/base.html : .
... {% block scripts %} ... <script> ... function set_task_progress(task_id, progress) { $('#' + task_id + '-progress').text(progress); } </script> ... {% endblock %}
id
jQuery <span>
. , , jQuery , .
, _set_task_progress()
app/tasks.py add_notification()
. , - , , 21 , . , add_notification()
, , .
JavaScript, , , unread_message_count
, . , task_progress
, set_task_progress()
, . , JavaScript:
app/templates/base.html : .
for (var i = 0; i < notifications.length; i++) { switch (notifications[i].name) { case 'unread_message_count': set_message_count(notifications[i].data); break; case 'task_progress': set_task_progress( notifications[i].data.task_id, notifications[i].data.progress); break; } since = notifications[i].timestamp; }
, , if
, unread_message_count
, switch
, , . "C", , switch . , if/elseif
. , .
, , RQ task_progress
, task_id
progress
, set_task_progress()
.
, 10 , .
, , . , , Flask-Babel , :
(venv) $ flask translate update
, , app/translations/es/LC_MESSAGES/messages.po .
, , :
(venv) $ flask translate compile
. : Redis RQ. , , , , , .
Linux
Linux, Redis , . Ubuntu Linux sudo apt-get install redis-server
.
RQ, " Gunicorn Supervisor" 17 , Supervisor, rq worker-tasks
gunicorn
. (, , production), numprocs
, , .
Heroku
Heroku, Redis . , Postgres. Redis , :
$ heroku addons:create heroku-redis:hobby-dev
URL- redis Heroku REDIS_URL
, , .
Heroku web-dyno worker dyno, rq , . procfile:
web: flask db upgrade; flask translate compile; gunicorn microblog:app worker: rq worker microblog-tasks
:
$ heroku ps:scale worker=1
Docker
Docker Redis. Redis Docker:
$ docker run --name redis -d -p 6379:6379 redis:3-alpine
redis REDIS_URL
, , MySQL. , redis:
$ docker run --name microblog -d -p 8000:5000 --rm -e SECRET_KEY=my-secret-key \ -e MAIL_SERVER=smtp.googlemail.com -e MAIL_PORT=587 -e MAIL_USE_TLS=true \ -e MAIL_USERNAME=<your-gmail-username> -e MAIL_PASSWORD=<your-gmail-password> \ --link mysql:dbserver --link redis:redis-server \ -e DATABASE_URL=mysql+pymysql://microblog:<database-password>@dbserver/microblog \ -e REDIS_URL=redis://redis-server:6379/0 \ microblog:latest
, RQ. , , , , start up, -. docker run
, worker:
$ docker run --name rq-worker -d --rm -e SECRET_KEY=my-secret-key \ -e MAIL_SERVER=smtp.googlemail.com -e MAIL_PORT=587 -e MAIL_USE_TLS=true \ -e MAIL_USERNAME=<your-gmail-username> -e MAIL_PASSWORD=<your-gmail-password> \ --link mysql:dbserver --link redis:redis-server \ -e DATABASE_URL=mysql+pymysql://microblog:<database-password>@dbserver/microblog \ -e REDIS_URL=redis://redis-server:6379/0 \ --entrypoint venv/bin/rq \ microblog:latest worker -u redis://redis-server:6379/0 microblog-tasks
ã³ãã³ãã¯2ã€ã®éšåã§æå®ããå¿
èŠããããããDockerã€ã¡ãŒãžã®ããã©ã«ãã®èµ·åã³ãã³ãããªãŒããŒã©ã€ãããã®ã¯ããå°ãè€éã§ããåŒæ°--entrypoint
ã¯ãå®è¡å¯èœãã¡ã€ã«ã®ååã®ã¿ãåãå
¥ããŸãããåŒæ°ïŒååšããå ŽåïŒã¯ãã³ãã³ãã©ã€ã³ã®æåŸã®ã€ã¡ãŒãžãšã¿ã°ã®åŸã«æå®ããå¿
èŠããããŸããä»®æ³ç°å¢ãã¢ã¯ãã£ãã«ããã«æ©èœããrq
ããã«ãäœãæå®ããå¿
èŠããããã«æ³šæããŠãã ããvenv/bin/rq
ã
ããã« æ»ã 