{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 작성할 애플리케이션" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## LGTM 이미지를 자동 생성하는 커맨드 라인 도구" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "Notebook 환경은 이미 파이썬 환경 안에 있기 때문에 가상 환경은 활성화 할 수 없습니다." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Collecting lgtm\n", " Cloning https://github.com/yeonsookim-wt/lgtm to /private/var/folders/xt/5rx58mwn1p5g_dghf90jzwbw0000gp/T/pip-install-0ug3fe0x/lgtm_b658156aad80408aae1b187f2bb08d25\n", " Running command git clone -q https://github.com/yeonsookim-wt/lgtm /private/var/folders/xt/5rx58mwn1p5g_dghf90jzwbw0000gp/T/pip-install-0ug3fe0x/lgtm_b658156aad80408aae1b187f2bb08d25\n", "Collecting Click~=7.0\n", " Using cached click-7.1.2-py2.py3-none-any.whl (82 kB)\n", "Collecting Pillow~=6.2.0\n", " Using cached Pillow-6.2.2-cp38-cp38-macosx_10_9_x86_64.whl (2.1 MB)\n", "Collecting requests~=2.22.0\n", " Using cached requests-2.22.0-py2.py3-none-any.whl (57 kB)\n", "Collecting idna<2.9,>=2.5\n", " Using cached idna-2.8-py2.py3-none-any.whl (58 kB)\n", "Requirement already satisfied: certifi>=2017.4.17 in /Users/yeonsookim/Workspaces/python-src/venv/lib/python3.8/site-packages (from requests~=2.22.0->lgtm) (2020.12.5)\n", "Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1\n", " Using cached urllib3-1.25.11-py2.py3-none-any.whl (127 kB)\n", "Collecting chardet<3.1.0,>=3.0.2\n", " Using cached chardet-3.0.4-py2.py3-none-any.whl (133 kB)\n", "Using legacy 'setup.py install' for lgtm, since package 'wheel' is not installed.\n", "Installing collected packages: urllib3, idna, chardet, requests, Pillow, Click, lgtm\n", " Attempting uninstall: urllib3\n", " Found existing installation: urllib3 1.26.3\n", " Uninstalling urllib3-1.26.3:\n", " Successfully uninstalled urllib3-1.26.3\n", " Attempting uninstall: idna\n", " Found existing installation: idna 2.10\n", " Uninstalling idna-2.10:\n", " Successfully uninstalled idna-2.10\n", " Attempting uninstall: chardet\n", " Found existing installation: chardet 4.0.0\n", " Uninstalling chardet-4.0.0:\n", " Successfully uninstalled chardet-4.0.0\n", " Attempting uninstall: requests\n", " Found existing installation: requests 2.25.1\n", " Uninstalling requests-2.25.1:\n", " Successfully uninstalled requests-2.25.1\n", " Running setup.py install for lgtm ... \u001b[?25ldone\n", "\u001b[?25hSuccessfully installed Click-7.1.2 Pillow-6.2.2 chardet-3.0.4 idna-2.8 lgtm-1.0.0 requests-2.22.0 urllib3-1.25.11\n" ] } ], "source": [ "!pip install git+https://github.com/yeonsookim-wt/lgtm#egg=lgtm" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Usage: lgtm [OPTIONS] KEYWORD\n", "\n", " LGTM 이미지 생성 도구\n", "\n", "Options:\n", " -m, --message TEXT 이미지에 추가할 문자열 [default: LGTM]\n", " --help Show this message and exit.\n" ] } ], "source": [ "# 도움말 표시\n", "!lgtm --help" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# 'book'으로 이미지 검색을 수행해 output.png를 생성\n", "# 키워드 대신 이미지 경로나 이미지 URL도 지정할 수 있음\n", "!lgtm book" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 이용하는 주요 패키지" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### requests ── HTTP 클라이언트 라이브러리" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: requests==2.22.0 in /Users/yeonsookim/Workspaces/python-src/venv/lib/python3.8/site-packages (2.22.0)\n", "Requirement already satisfied: certifi>=2017.4.17 in /Users/yeonsookim/Workspaces/python-src/venv/lib/python3.8/site-packages (from requests==2.22.0) (2020.12.5)\n", "Requirement already satisfied: idna<2.9,>=2.5 in /Users/yeonsookim/Workspaces/python-src/venv/lib/python3.8/site-packages (from requests==2.22.0) (2.8)\n", "Requirement already satisfied: chardet<3.1.0,>=3.0.2 in /Users/yeonsookim/Workspaces/python-src/venv/lib/python3.8/site-packages (from requests==2.22.0) (3.0.4)\n", "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /Users/yeonsookim/Workspaces/python-src/venv/lib/python3.8/site-packages (from requests==2.22.0) (1.25.11)\n", "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "# requests 설치\n", "%pip install requests==2.22.0" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "ename": "URLError", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mSSLCertVerificationError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/urllib/request.py\u001b[0m in \u001b[0;36mdo_open\u001b[0;34m(self, http_class, req, **http_conn_args)\u001b[0m\n\u001b[1;32m 1318\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1319\u001b[0;31m h.request(req.get_method(), req.selector, req.data, headers,\n\u001b[0m\u001b[1;32m 1320\u001b[0m encode_chunked=req.has_header('Transfer-encoding'))\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/http/client.py\u001b[0m in \u001b[0;36mrequest\u001b[0;34m(self, method, url, body, headers, encode_chunked)\u001b[0m\n\u001b[1;32m 1229\u001b[0m \u001b[0;34m\"\"\"Send a complete request to the server.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1230\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_send_request\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbody\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheaders\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mencode_chunked\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1231\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/http/client.py\u001b[0m in \u001b[0;36m_send_request\u001b[0;34m(self, method, url, body, headers, encode_chunked)\u001b[0m\n\u001b[1;32m 1275\u001b[0m \u001b[0mbody\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_encode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbody\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'body'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1276\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mendheaders\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbody\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mencode_chunked\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mencode_chunked\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1277\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/http/client.py\u001b[0m in \u001b[0;36mendheaders\u001b[0;34m(self, message_body, encode_chunked)\u001b[0m\n\u001b[1;32m 1224\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mCannotSendHeader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1225\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_send_output\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmessage_body\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mencode_chunked\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mencode_chunked\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1226\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/http/client.py\u001b[0m in \u001b[0;36m_send_output\u001b[0;34m(self, message_body, encode_chunked)\u001b[0m\n\u001b[1;32m 1003\u001b[0m \u001b[0;32mdel\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_buffer\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1004\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1005\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/http/client.py\u001b[0m in \u001b[0;36msend\u001b[0;34m(self, data)\u001b[0m\n\u001b[1;32m 943\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mauto_open\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 944\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconnect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 945\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/http/client.py\u001b[0m in \u001b[0;36mconnect\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1398\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1399\u001b[0;31m self.sock = self._context.wrap_socket(self.sock,\n\u001b[0m\u001b[1;32m 1400\u001b[0m server_hostname=server_hostname)\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ssl.py\u001b[0m in \u001b[0;36mwrap_socket\u001b[0;34m(self, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, session)\u001b[0m\n\u001b[1;32m 499\u001b[0m \u001b[0;31m# ctx._wrap_socket()\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 500\u001b[0;31m return self.sslsocket_class._create(\n\u001b[0m\u001b[1;32m 501\u001b[0m \u001b[0msock\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0msock\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ssl.py\u001b[0m in \u001b[0;36m_create\u001b[0;34m(cls, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, context, session)\u001b[0m\n\u001b[1;32m 1039\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"do_handshake_on_connect should not be specified for non-blocking sockets\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1040\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdo_handshake\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1041\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mOSError\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ssl.py\u001b[0m in \u001b[0;36mdo_handshake\u001b[0;34m(self, block)\u001b[0m\n\u001b[1;32m 1308\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msettimeout\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1309\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_sslobj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdo_handshake\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1310\u001b[0m \u001b[0;32mfinally\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mSSLCertVerificationError\u001b[0m: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1108)", "\nDuring handling of the above exception, another exception occurred:\n", "\u001b[0;31mURLError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0murl\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34mf'https://httpbin.org/get?{query}'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0;32mwith\u001b[0m \u001b[0mrequest\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0murlopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0murl\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 9\u001b[0m \u001b[0mres\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'utf-8'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mHTTPError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/urllib/request.py\u001b[0m in \u001b[0;36murlopen\u001b[0;34m(url, data, timeout, cafile, capath, cadefault, context)\u001b[0m\n\u001b[1;32m 220\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 221\u001b[0m \u001b[0mopener\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_opener\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 222\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mopener\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 223\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 224\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0minstall_opener\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mopener\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/urllib/request.py\u001b[0m in \u001b[0;36mopen\u001b[0;34m(self, fullurl, data, timeout)\u001b[0m\n\u001b[1;32m 523\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 524\u001b[0m \u001b[0msys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0maudit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'urllib.Request'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreq\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfull_url\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreq\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreq\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mheaders\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreq\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 525\u001b[0;31m \u001b[0mresponse\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_open\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mreq\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 526\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 527\u001b[0m \u001b[0;31m# post-process response\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/urllib/request.py\u001b[0m in \u001b[0;36m_open\u001b[0;34m(self, req, data)\u001b[0m\n\u001b[1;32m 540\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 541\u001b[0m \u001b[0mprotocol\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mreq\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtype\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 542\u001b[0;31m result = self._call_chain(self.handle_open, protocol, protocol +\n\u001b[0m\u001b[1;32m 543\u001b[0m '_open', req)\n\u001b[1;32m 544\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/urllib/request.py\u001b[0m in \u001b[0;36m_call_chain\u001b[0;34m(self, chain, kind, meth_name, *args)\u001b[0m\n\u001b[1;32m 500\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mhandler\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mhandlers\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 501\u001b[0m \u001b[0mfunc\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mgetattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mhandler\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmeth_name\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 502\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 503\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 504\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/urllib/request.py\u001b[0m in \u001b[0;36mhttps_open\u001b[0;34m(self, req)\u001b[0m\n\u001b[1;32m 1360\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1361\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mhttps_open\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreq\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1362\u001b[0;31m return self.do_open(http.client.HTTPSConnection, req,\n\u001b[0m\u001b[1;32m 1363\u001b[0m context=self._context, check_hostname=self._check_hostname)\n\u001b[1;32m 1364\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/urllib/request.py\u001b[0m in \u001b[0;36mdo_open\u001b[0;34m(self, http_class, req, **http_conn_args)\u001b[0m\n\u001b[1;32m 1320\u001b[0m encode_chunked=req.has_header('Transfer-encoding'))\n\u001b[1;32m 1321\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mOSError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# timeout error\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1322\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mURLError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0merr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1323\u001b[0m \u001b[0mr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mh\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgetresponse\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1324\u001b[0m \u001b[0;32mexcept\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mURLError\u001b[0m: " ] } ], "source": [ "from urllib import request, parse, error\n", "import json\n", "query = parse.urlencode({'q': 'python'})\n", "\n", "# httpbin은 요청 내용을 반환해 줌\n", "url = f'https://httpbin.org/get?{query}'\n", "try:\n", " with request.urlopen(url) as f:\n", " res = f.read().decode('utf-8')\n", "except error.HTTPError as e:\n", " print(e)\n", "\n", "json.loads(res)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'args': {'q': 'python'},\n", " 'headers': {'Accept': '*/*',\n", " 'Accept-Encoding': 'gzip, deflate',\n", " 'Host': 'httpbin.org',\n", " 'User-Agent': 'python-requests/2.22.0'},\n", " 'origin': '153.182.176.137, 153.182.176.137',\n", " 'url': 'https://httpbin.org/get?q=python'}" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import requests\n", "res = requests.get('https://httpbin.org/get',\n", " params={'q': 'python'})\n", "res.json()" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "res = requests.post('https://httpbin.org/post',\n", " data={'q': 'python'})" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'q': 'python'}" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "res.json()['form']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Click ── 커맨드 라인 도구 작성 라이브러리" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Collecting Click==7.0\n", " Downloading Click-7.0-py2.py3-none-any.whl (81 kB)\n", "\u001b[K |████████████████████████████████| 81 kB 1.7 MB/s eta 0:00:011\n", "\u001b[?25hInstalling collected packages: Click\n", " Attempting uninstall: Click\n", " Found existing installation: click 7.1.2\n", " Uninstalling click-7.1.2:\n", " Successfully uninstalled click-7.1.2\n", "Successfully installed Click-7.0\n", "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "# Click 설치\n", "%pip install Click==7.0" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "import click\n", "\n", "@click.command()\n", "@click.option('--words', default='Hello')\n", "@click.argument('name')\n", "def greet(name, words):\n", " click.echo(f'{words}, {name}!')\n", "\n", "if __name__ == '__main__':\n", " greet()" ] } ], "source": [ "!cat greet.py" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, user!\n" ] } ], "source": [ "!python3 greet.py user" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hi, user!\n" ] } ], "source": [ "!python3 greet.py user --words Hi" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Pillow ── 이미지 처리 라이브러리" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Collecting Pillow==6.2.1\n", " Downloading Pillow-6.2.1-cp38-cp38-macosx_10_9_x86_64.whl (2.1 MB)\n", "\u001b[K |████████████████████████████████| 2.1 MB 2.8 MB/s eta 0:00:01\n", "\u001b[?25hInstalling collected packages: Pillow\n", " Attempting uninstall: Pillow\n", " Found existing installation: Pillow 6.2.2\n", " Uninstalling Pillow-6.2.2:\n", " Successfully uninstalled Pillow-6.2.2\n", "Successfully installed Pillow-6.2.1\n", "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "# Pillow 설치\n", "%pip install Pillow==6.2.1" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "import os\n", "from PIL import Image\n", "def thumbnail(infile, size=(128, 128)):\n", " outfile = os.path.splitext(\n", " infile)[0] + \".thumbnail\"\n", " try:\n", " im = Image.open(infile)\n", " im.thumbnail(size)\n", " im.save(outfile, \"JPEG\")\n", " except IOError:\n", " print(\"cannot create thumbnail for\", infile)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "# 임의의 JPEG 파일을 지정함\n", "thumbnail('dog.jpg')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 프로젝트 작성" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Git 이용" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "!mkdir workspace" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/Users/yeonsookim/Workspaces/personal/python-src/13-application/workspace\n" ] } ], "source": [ "%cd workspace" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Initialized empty Git repository in /Users/yeonsookim/Workspaces/personal/python-src/13-application/workspace/.git/\n" ] } ], "source": [ "!git init" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### .gitignore 파일 작성" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "# 아래 URL의 내용을 .gitignore라는 이름의 파일에 저장\n", "# 환경에 따라 키워드를 바꾸어 내용을 편집함\n", "# https://www.gitignore.io/api/macos,python\n", "!git add ." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "# git 설치 직후라면 다음을 실행함\n", "!git config --global user.email \"you@example.com\"\n", "!git config --global user.name \"Your Name\"" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "!git commit -m '.gitignore를 추가'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### GitHub에서의 소스 코드 관리" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "$ git remote add origin \\\n", " git@github.com:/lgtm.git\n", "$ git push -u origin master" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 패키지 모형 작성" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Click==7.0\n", "Pillow==6.2.1\n", "requests==2.22.0" ] } ], "source": [ "!cat requirements.txt" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "Notebook 환경은 이미 파이썬 환경 안에 있기 때문에 가상 환경은 활성화 할 수 없습니다." ] }, { "cell_type": "raw", "metadata": {}, "source": [ "$ python3 -m venv venv\n", "$ . venv/bin/activate" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: Click==7.0 in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from -r requirements.txt (line 1)) (7.0)\n", "Requirement already satisfied: Pillow==6.2.1 in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from -r requirements.txt (line 2)) (6.2.1)\n", "Requirement already satisfied: requests==2.22.0 in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from -r requirements.txt (line 3)) (2.22.0)\n", "Requirement already satisfied: chardet<3.1.0,>=3.0.2 in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from requests==2.22.0->-r requirements.txt (line 3)) (3.0.4)\n", "Requirement already satisfied: certifi>=2017.4.17 in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from requests==2.22.0->-r requirements.txt (line 3)) (2019.11.28)\n", "Requirement already satisfied: idna<2.9,>=2.5 in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from requests==2.22.0->-r requirements.txt (line 3)) (2.8)\n", "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from requests==2.22.0->-r requirements.txt (line 3)) (1.25.7)\n", "\u001b[33mWARNING: You are using pip version 19.2.3, however version 19.3.1 is available.\n", "You should consider upgrading via the 'pip install --upgrade pip' command.\u001b[0m\n", "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip install -r requirements.txt" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip freeze > requirements.lock" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### lgtm 패키지 작성" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "!mkdir lgtm" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "# 빈 __init__.py를 작성\n", "# Windows에서는 type nul > lgtm/__init__.py\n", "!touch lgtm/__init__.py" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(주:lgtm/core.py)\n", "import click\n", "\n", "@click.command()\n", "def cli():\n", " \"\"\"LGTM 이미지 생성 도구\"\"\"\n", " lgtm()\n", " click.echo('lgtm') # 동작 확인용\n", "\n", "\n", "def lgtm():\n", " # 여기에 로직을 추가함\n", " pass" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(주:main.py)\n", "from lgtm import core\n", "\n", "if __name__ == '__main__':\n", " core.cli()" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(venv) $ python3 main.py\n", "lgtm" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "!git add ." ] }, { "cell_type": "raw", "metadata": {}, "source": [ "!git commit -m '프로젝트 모형 작성'" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(venv) $ git push" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 테스트 코드 작성" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(주:tests/test_core.py)\n", "import unittest\n", "\n", "class LgtmTest(unittest.TestCase):\n", " def test_lgtm(self):\n", " from lgtm.core import lgtm\n", " self.assertIsNone(lgtm())" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "# 단위 테스트 실행\n", "(venv) $ python -m unittest -v\n", "test_lgtm (test_core.LgtmTest) ... ok\n", "\n", "----------------------------------------------------------------------\n", "Ran 1 test in 0.017s\n", "\n", "OK" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "!git add ." ] }, { "cell_type": "raw", "metadata": {}, "source": [ "!git commit -m 'core 모듈 테스트 작성'" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(venv) $ git push" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 지속적인 통합 도입" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## CircleCI에서의 테스트 자동화" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 프로젝트 추가" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### config.yml 추가" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "version: 2\n", "jobs:\n", " setup_dependencies:\n", " docker:\n", " - image: circleci/python:3.8.1\n", " steps:\n", " - checkout\n", " - restore_cache:\n", " key: deps-{{ checksum \"requirements.lock\" }}\n", " - run:\n", " command: |\n", " pip install --user -r requirements.lock\n", " - save_cache:\n", " key: deps-{{ checksum \"requirements.lock\" }}\n", " paths:\n", " - \"~/.local\"\n", " test:\n", " docker:\n", " - image: circleci/python:3.8.1\n", " steps:\n", " - checkout\n", " - restore_cache:\n", " key: deps-{{ checksum \"requirements.lock\" }}\n", " - run:\n", " command: |\n", " python3 -m unittest -v\n", "workflows:\n", " version: 2\n", " all:\n", " jobs:\n", " - setup_dependencies\n", " - test:\n", " requires:\n", " - setup_dependencies\n" ] } ], "source": [ "!cat .circleci/config.yml" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 테스트 실행과 결과 확인" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "!git add ." ] }, { "cell_type": "raw", "metadata": {}, "source": [ "!git commit -m 'CircleCI 설정 파일 추가'" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(venv) $ git push" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 애플리케이션 개발" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 커맨드 라인 인수 얻기" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 이미지 파일 소스 정보와 메시지 받기" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(주:lgtm/core.py)\n", "@click.command()\n", "@click.option('--message', '-m', default='LGTM',\n", " show_default=True, help='이미지에 추가할 문자열')\n", "@click.argument('keyword')\n", "def cli(keyword, message):\n", " \"\"\"LGTM 이미지 생성 도구\"\"\"\n", " lgtm(keyword, message)\n", " click.echo('lgtm') # 동작 확인용\n", "\n", "\n", "def lgtm(keyword, message):\n", " # 여기에 로직을 추가함\n", " pass" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Usage: main.py [OPTIONS] KEYWORD\n", "\n", " LGTM画像生成ツール\n", "\n", "Options:\n", " -m, --message TEXT 画像に乗せる文字列 [default: LGTM]\n", " --help Show this message and exit.\n" ] } ], "source": [ "!python3 main.py --help" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "!git add ." ] }, { "cell_type": "raw", "metadata": {}, "source": [ "!git commit -m '커맨드 라인 인수를 받음'" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(venv) $ git push" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 테스트 코드 수정" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(주:tests/test_core.py)\n", "(생략)\n", " def test_lgtm(self):\n", " from lgtm.core import lgtm\n", " self.assertIsNone(lgtm('./python.jpeg', 'LGTM'))" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(venv) $ python3 -m unittest -v\n", "test_lgtm (test_core.LgtmTest) ... ok\n", "\n", "----------------------------------------------------------------------\n", "Ran 1 test in 0.017s\n", "\n", "OK" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "!git add ." ] }, { "cell_type": "raw", "metadata": {}, "source": [ "!git commit -m 'lgtm/core.py 변경에 따른 테스트 수정'" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(venv) $ git push" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 이미지 얻기" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 파일 경로로부터 이미지를 얻는 클래스 구현" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(주:lgtm/image_source.py)\n", "from io import BytesIO\n", "import requests\n", "\n", "class LocalImage:\n", " \"\"\"파일로부터 이미지를 얻음\"\"\"\n", "\n", " def __init__(self, path):\n", " self._path = path\n", "\n", " def get_image(self):\n", " return open(self._path, 'rb')\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### URL로부터 이미지를 얻는 클래스 구현" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(주:lgtm/image_source.py)\n", "(생략)\n", "class RemoteImage:\n", " \"\"\"URL로부터 이미지를 얻음\"\"\"\n", "\n", " def __init__(self, path):\n", " self._url = path\n", "\n", " def get_image(self):\n", " data = requests.get(self._url)\n", " # 바이트 데이터를 파일 객체로 변환\n", " return BytesIO(data.content)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 검색 키워드로부터 이미지를 얻는 클래스 구현" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "(주:lgtm/image_source.py)\n", "(생략)\n", "class _LoremFlickr(RemoteImage):\n", " \"\"\"검색 키워드로부터 이미지를 얻음\"\"\"\n", " LOREM_FLICKR_URL = 'https://loremflickr.com'\n", " WIDTH = 800\n", " HEIGHT = 600\n", "\n", " def __init__(self, keyword):\n", " super().__init__(self._build_url(keyword))\n", "\n", " def _build_url(self, keyword):\n", " return (f'{self.LOREM_FLICKR_URL}/'\n", " f'{self.WIDTH}/{self.HEIGHT}/{keyword}')\n", "\n", "\n", "KeywordImage = _LoremFlickr" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 이미지를 얻는 클래스 이용" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "from io import BytesIO\n", "import requests\n", "from pathlib import Path\n", "\n", "class LocalImage:\n", " \"\"\"ファイルから画像を取得する\"\"\"\n", "\n", " def __init__(self, path):\n", " self._path = path\n", "\n", " def get_image(self):\n", " return open(self._path, 'rb')\n", "\n", "\n", "class RemoteImage:\n", " \"\"\"URLから画像を取得する\"\"\"\n", "\n", " def __init__(self, path):\n", " self._path = path\n", "\n", " def get_image(self):\n", " data = requests.get(self._path)\n", " # バイトデータをファイルオブジェクトに変換\n", " return BytesIO(data.content)\n", "\n", "class RemoteImage:\n", " \"\"\"URLから画像を取得する\"\"\"\n", "\n", " def __init__(self, path):\n", " self._url = path\n", "\n", " def get_image(self):\n", " data = requests.get(self._url)\n", " # バイトデータをファイルオブジェクトに変換\n", " return BytesIO(data.content)\n", "\n", "class _LoremFlickr(RemoteImage):\n", " \"\"\"キーワード検索で画像を取得する\"\"\"\n", " LOREM_FLICKR_URL = 'https://loremflickr.com'\n", " WIDTH = 800\n", " HEIGHT = 600\n", "\n", " def __init__(self, keyword):\n", " super().__init__(self._build_url(keyword))\n", "\n", " def _build_url(self, keyword):\n", " return (f'{self.LOREM_FLICKR_URL}/'\n", " f'{self.WIDTH}/{self.HEIGHT}/{keyword}')\n", "\n", "\n", "KeywordImage = _LoremFlickr\n", "\n", "# 関数だがコンストラクタとして利用する\n", "def ImageSource(keyword):\n", " \"\"\"最適なイメージソースクラスを返す\"\"\"\n", " if keyword.startswith(('http://', 'https://')):\n", " return RemoteImage(keyword)\n", " elif Path(keyword).exists():\n", " return LocalImage(keyword)\n", " else:\n", " return KeywordImage(keyword)\n", "\n", "\n", "def get_image(keyword):\n", " \"\"\"画像のファイルオブジェクトを返す\"\"\"\n", " return ImageSource(keyword).get_image()" ] } ], "source": [ "!cat lgtm/image_source.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 이미지 처리" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 문자열을 이미지 위에 표시하는 최소한의 구현 예시" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "from importlib import reload\n", "reload(Image)\n", "reload(ImageDraw)\n", "from PIL import Image, ImageDraw\n", "# ローカル環境にある任意の画像パスを指定\n", "file_path = '../dog.jpg'\n", "\n", "# ここではファイルパスの文字列を渡しているが\n", "# ファイルオブジェクトを渡してもよい\n", "image = Image.open(file_path)\n", "\n", "draw = ImageDraw.Draw(image)\n", "\n", "# 左上に\"LGTM\"を描画\n", "draw.text((0, 0), 'LGTM')\n", "\n", "image.save('output.png', 'PNG')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 문자열을 중앙의 가장 적절한 크기로 표시함" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "from PIL import Image, ImageDraw, ImageFont\n", "\n", "# 画像全体に対するメッセージ描画可能エリアの比率\n", "MAX_RATIO = 0.8\n", "\n", "# フォント関連の定数\n", "FONT_MAX_SIZE = 256\n", "FONT_MIN_SIZE = 24\n", "\n", "# WindowsやLinuxではパスが異なる\n", "FONT_NAME = '/Library/Fonts/Arial Bold.ttf'\n", "FONT_COLOR_WHITE = (255, 255, 255, 0)\n", "\n", "# アウトプット関連の定数\n", "OUTPUT_NAME = 'output.png'\n", "OUTPUT_FORMAT = 'PNG'\n", "\n", "\n", "def save_with_message(fp, message):\n", " image = Image.open(fp)\n", " draw = ImageDraw.Draw(image)\n", " # メッセージを描画できる領域のサイズ\n", " # タプルの要素ごとに計算する\n", " image_width, image_height = image.size\n", " message_area_width = image_width * MAX_RATIO\n", " message_area_height = image_height * MAX_RATIO\n", "\n", " # フォントサイズを決める\n", " for font_size in range(FONT_MAX_SIZE, FONT_MIN_SIZE,\n", " -1):\n", " font = ImageFont.truetype(FONT_NAME, font_size)\n", " # 描画に必要なサイズ\n", " text_width, text_height = draw.textsize(\n", " message, font=font)\n", " w = message_area_width - text_width\n", " h = message_area_height - text_height\n", "\n", " # 幅、高さともに領域内におさまる値を採用\n", " if w > 0 and h > 0:\n", " position = ((image_width - text_width) / 2,\n", " (image_height - text_height) / 2)\n", " # メッセージの描画\n", " draw.text(position, message,\n", " fill=FONT_COLOR_WHITE, font=font)\n", " break\n", "\n", " # 画像の保存\n", " image.save(OUTPUT_NAME, OUTPUT_FORMAT)" ] } ], "source": [ "!cat lgtm/drawer.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 각 처리 호출" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "import click\n", "\n", "from lgtm.drawer import save_with_message\n", "from lgtm.image_source import get_image\n", "\n", "@click.command()\n", "@click.option('--message', '-m', default='LGTM',\n", " show_default=True, help='画像に乗せる文字列')\n", "@click.argument('keyword')\n", "def cli(keyword, message):\n", " \"\"\"LGTM画像生成ツール\"\"\"\n", " lgtm(keyword, message)\n", "\n", "\n", "def lgtm(keyword, message):\n", " with get_image(keyword) as fp:\n", " save_with_message(fp, message)" ] } ], "source": [ "!cat lgtm/core.py" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "# 결과 이미지는 얻는 이미지에 따라 다름\n", "!python3 main.py book" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 명령어로 실행하기" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## setup.py 작성" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "from setuptools import find_packages, setup\n", "\n", "setup(\n", " name='lgtm',\n", " version='1.0.0',\n", " packages=find_packages(exclude=('tests',)),\n", " install_requires=[\n", " 'Click',\n", " 'Pillow',\n", " 'requests',\n", " ],\n", " entry_points={\n", " 'console_scripts': [\n", " 'lgtm=lgtm.core:cli'\n", " ]\n", " }\n", ")" ] } ], "source": [ "!cat setup.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### entry\\_points ── 스크립트 인터페이스 등록을 수행하는 인수" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 동작 확인" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Obtaining file:///Users/suyamar/github/python-practice-book/src/13-application/workspace\n", "Requirement already satisfied: Click in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from lgtm==1.0.0) (7.0)\n", "Requirement already satisfied: Pillow in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from lgtm==1.0.0) (6.2.1)\n", "Requirement already satisfied: requests in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from lgtm==1.0.0) (2.22.0)\n", "Requirement already satisfied: certifi>=2017.4.17 in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from requests->lgtm==1.0.0) (2019.11.28)\n", "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from requests->lgtm==1.0.0) (1.25.7)\n", "Requirement already satisfied: idna<2.9,>=2.5 in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from requests->lgtm==1.0.0) (2.8)\n", "Requirement already satisfied: chardet<3.1.0,>=3.0.2 in /Users/suyamar/github/python-practice-book/src/13-application/venv/lib/python3.8/site-packages (from requests->lgtm==1.0.0) (3.0.4)\n", "Installing collected packages: lgtm\n", " Found existing installation: lgtm 1.0.0\n", " Not uninstalling lgtm at /Users/suyamar/github/python-practice-book/src/13-application/workspace, outside environment /Users/suyamar/github/python-practice-book/src/13-application/venv\n", " Can't uninstall 'lgtm'. No files were found to uninstall.\n", " Running setup.py develop for lgtm\n", "Successfully installed lgtm-1.0.0\n", "\u001b[33mWARNING: You are using pip version 19.2.3, however version 19.3.1 is available.\n", "You should consider upgrading via the 'pip install --upgrade pip' command.\u001b[0m\n", "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip install -e ." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Usage: lgtm [OPTIONS] KEYWORD\n", "Try \"lgtm --help\" for help.\n", "\n", "Error: Missing argument \"KEYWORD\".\n" ] } ], "source": [ "# 명령어가 등록됨\n", "!lgtm" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [], "source": [ "!lgtm book" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 정리" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.7" } }, "nbformat": 4, "nbformat_minor": 4 }