flask 코드에서 post 바디에 있는 json을 출력하는 코드이다.



요청의 헤더가 content-type:text/html이라면 아래 코드에서 request.get_json()의 값은 None이 된다.





from flask import Flask, request
import json
app = Flask(__name__)

@app.route("/", methods=['GET', 'POST'])
def hello():
maybe_json = request.get_json(silent=True, cache=False)
if maybe_json:
thejson = json.dumps(maybe_json)
else:
thejson = "no json"
print(thejson)
return "good job"



요청 헤더를 json으로 수정하거나 서버 코드를 아래과 같이 get_json(force=True)를 추가하면 잘 동작한다.





from flask import Flask, request
import json
app = Flask(__name__)

@app.route("/", methods=['GET', 'POST'])
def hello():
maybe_json = request.get_json(silent=True, cache=False, force=True)
if maybe_json:
thejson = json.dumps(maybe_json)
else:
thejson = "no json"
print(thejson)
return "good job"


Posted by 김용환 '김용환'



파이썬 3 예제이다.


파이썬 2의 subprocess의 결과는 이전에는 string이었지만 python2.6? 또는 python 3부터는 bytes로 리턴한다.

TypeError: startswith first arg must be bytes or a tuple of bytes, not str 이런 에러가 난다면 이슈이다.



이해가 되는 예시이다. 



>>> print(subprocess.Popen("echo hellow", shell=True, stdout=subprocess.PIPE))

b'hellow\n'


>>> print(subprocess.Popen("echo hellow", shell=True, stdout=subprocess.PIPE))

<subprocess.Popen object at 0x10cfb2908>


>>> print(subprocess.Popen("echo hellow", shell=True, stdout=subprocess.PIPE).communicate())

(b'hellow\n', None)


>>> print(subprocess.Popen("echo hellow", shell=True, stdout=subprocess.PIPE, universal_newlines=True).communicate()[0])

hellow



>>> print(subprocess.Popen("echo hellow", shell=True,stdout=subprocess.PIPE).communicate()[0].decode('utf-8').strip())

hellow





마찬가지로..subprocess 모듈의 check_out도 바이트이기에..


status = subprocess.check_output(cmd.split()).rstrip() 코드는




print(subprocess.check_output(cmd.split()).decode('utf-8').rstrip()) 로 변경하면 string으로 리턴한다.







<참고 예시>


import subprocess

import paramiko

cmd = "vagrant ssh-config vagrant2"

p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, universal_newlines=True)

config = paramiko.SSHConfig()

config.parse(p.stdout)

config.lookup("vagrant2")



Posted by 김용환 '김용환'



코드를 커밋하기 전에 coding convention에 맞지 않으면 에러를 내고 commit을 안되게 하는 pre-commit이란 툴이 있다. 


pre-commit이 지원하는 언어/툴은 다음과 같다. 자바, 스칼라는 없지만, go, python, ruby, rust, swift 등이 있다.




docker

docker_image

fail

golang

node

python

python_venv

ruby

rust

swift

pcre

pygrep

script

system






아래와 같이 설치를 진행한다.




$ pip install pre-commit


$ cd kemi_api_디렉토리


$ pre-commit


$ vi .pre-commit-config.yaml

repos:

-   repo: https://github.com/pre-commit/pre-commit-hooks

    rev: v2.0.0

    hooks:

    -   id: trailing-whitespace

    -   id: end-of-file-fixer

    -   id: fix-encoding-pragma

    -   id: double-quote-string-fixer

    -   id: requirements-txt-fixer


$ pre-commit install




/common/exceptions.py 파일에 '# -*- coding: utf-8 -*-' 이 포함되어 있는데. 

일부러 그 주석만 삭제하고 다음 커맨드를 실행한다.




$ git add .

$ git commit -m 'test'

gTrim Trailing Whitespace.................................................Passed

Fix End of Files.........................................................Passed

Fix python encoding pragma...............................................Failed

hookid: fix-encoding-pragma


Files were modified by this hook. Additional output:


Added `# -*- coding: utf-8 -*-` to /common/exceptions.py


Fix double quoted strings................................................Passed

Fix requirements.txt.................................(no files to check)Skipped



파이썬 인코딩 부분에서 failed되고 알려준다. 

때로는 고쳐주기도 한다.




프로젝트 디렉토리의 .git/hook/pre-commit 파일이 하나 생긴다. 


[프로젝트/.git/hooks] ls -al pre-commit

-rwxr-xr-x  1 samuel.kim  staff  5257 11  9 18:19 pre-commit




이 파일로 인해서 git commit을 미리 체크한다.


hook에 대한 내용은 https://git-scm.com/book/ko/v1/Git%EB%A7%9E%EC%B6%A4-Git-%ED%9B%85를 참조한다.



#!/usr/bin/env python

"""File generated by pre-commit: https://pre-commit.com"""

from __future__ import print_function


import distutils.spawn

import os

import subprocess

import sys


# work around https://github.com/Homebrew/homebrew-core/issues/30445

os.environ.pop('__PYVENV_LAUNCHER__', None)


HERE = os.path.dirname(os.path.abspath(__file__))

Z40 = '0' * 40

ID_HASH = '138fd403232d2ddd5efb44317e38bf03'

# start templated

CONFIG = '.pre-commit-config.yaml'

HOOK_TYPE = 'pre-commit'

INSTALL_PYTHON = '/Users/samuel.kim/.pyenv/versions/3.7.0/bin/python3.7'

SKIP_ON_MISSING_CONFIG = False

# end templated



class EarlyExit(RuntimeError):

    pass



class FatalError(RuntimeError):

    pass



def _norm_exe(exe):

    """Necessary for shebang support on windows.


    roughly lifted from `identify.identify.parse_shebang`

    """

    with open(exe, 'rb') as f:

        if f.read(2) != b'#!':

            return ()

        try:

            first_line = f.readline().decode('UTF-8')

        except UnicodeDecodeError:

            return ()


        cmd = first_line.split()

        if cmd[0] == '/usr/bin/env':

            del cmd[0]

        return tuple(cmd)



def _run_legacy():

    if HOOK_TYPE == 'pre-push':

        stdin = getattr(sys.stdin, 'buffer', sys.stdin).read()

    else:

        stdin = None


    legacy_hook = os.path.join(HERE, '{}.legacy'.format(HOOK_TYPE))

    if os.access(legacy_hook, os.X_OK):

        cmd = _norm_exe(legacy_hook) + (legacy_hook,) + tuple(sys.argv[1:])

        proc = subprocess.Popen(cmd, stdin=subprocess.PIPE if stdin else None)

        proc.communicate(stdin)

        return proc.returncode, stdin

    else:

        return 0, stdin



def _validate_config():

    cmd = ('git', 'rev-parse', '--show-toplevel')

    top_level = subprocess.check_output(cmd).decode('UTF-8').strip()

    cfg = os.path.join(top_level, CONFIG)

    if os.path.isfile(cfg):

        pass

    elif SKIP_ON_MISSING_CONFIG or os.getenv('PRE_COMMIT_ALLOW_NO_CONFIG'):

        print(

            '`{}` config file not found. '

            'Skipping `pre-commit`.'.format(CONFIG),

        )

        raise EarlyExit()

    else:

        raise FatalError(

            'No {} file was found\n'

            '- To temporarily silence this, run '

            '`PRE_COMMIT_ALLOW_NO_CONFIG=1 git ...`\n'

            '- To permanently silence this, install pre-commit with the '

            '--allow-missing-config option\n'

            '- To uninstall pre-commit run '

            '`pre-commit uninstall`'.format(CONFIG),

        )



def _exe():

    with open(os.devnull, 'wb') as devnull:

        for exe in (INSTALL_PYTHON, sys.executable):

            try:

                if not subprocess.call(

                        (exe, '-c', 'import pre_commit.main'),

                        stdout=devnull, stderr=devnull,

                ):

                    return (exe, '-m', 'pre_commit.main', 'run')

            except OSError:

                pass


    if distutils.spawn.find_executable('pre-commit'):

        return ('pre-commit', 'run')


    raise FatalError(

        '`pre-commit` not found.  Did you forget to activate your virtualenv?',

    )



def _rev_exists(rev):

    return not subprocess.call(('git', 'rev-list', '--quiet', rev))



def _pre_push(stdin):

    remote = sys.argv[1]


    opts = ()

    for line in stdin.decode('UTF-8').splitlines():

        _, local_sha, _, remote_sha = line.split()

        if local_sha == Z40:

            continue

        elif remote_sha != Z40 and _rev_exists(remote_sha):

            opts = ('--origin', local_sha, '--source', remote_sha)

        else:

            # ancestors not found in remote

            ancestors = subprocess.check_output((

                'git', 'rev-list', local_sha, '--topo-order', '--reverse',

                '--not', '--remotes={}'.format(remote),

            )).decode().strip()

            if not ancestors:

                continue

            else:

                first_ancestor = ancestors.splitlines()[0]

                cmd = ('git', 'rev-list', '--max-parents=0', local_sha)

                roots = set(subprocess.check_output(cmd).decode().splitlines())

                if first_ancestor in roots:

                    # pushing the whole tree including root commit

                    opts = ('--all-files',)

                else:

                    cmd = ('git', 'rev-parse', '{}^'.format(first_ancestor))

                    source = subprocess.check_output(cmd).decode().strip()

                    opts = ('--origin', local_sha, '--source', source)


    if opts:

        return opts

    else:

        # An attempt to push an empty changeset

        raise EarlyExit()



def _opts(stdin):

    fns = {

        'commit-msg': lambda _: ('--commit-msg-filename', sys.argv[1]),

        'pre-commit': lambda _: (),

        'pre-push': _pre_push,

    }

    stage = HOOK_TYPE.replace('pre-', '')

    return ('--config', CONFIG, '--hook-stage', stage) + fns[HOOK_TYPE](stdin)



def main():

    retv, stdin = _run_legacy()

    try:

        _validate_config()

        return retv | subprocess.call(_exe() + _opts(stdin))

    except EarlyExit:

        return retv

    except FatalError as e:

        print(e.args[0])

        return 1



if __name__ == '__main__':

    exit(main())




만약 flake8라는 파이썬 코딩 컨벤션 강제 툴을 적용하려면. .pre-commit-config.yaml 파일의 hooks id를 추가한다.



$ vi .pre-commit-config.yaml

repos:

-   repo: https://github.com/pre-commit/pre-commit-hooks

    rev: v2.0.0

    hooks:

    -   id: trailing-whitespace

    -   id: end-of-file-fixer

    -   id: fix-encoding-pragma

    -   id: double-quote-string-fixer

    -   id: requirements-txt-fixer

    -   id: flake8






Posted by 김용환 '김용환'



flask에서는 json encoder를 사용해서 json 응답을 보내줘야 한다.


@app.route("/getEmployeeList") def getEmployeeList(): try: # Initialize a employee list employeeList = [] # create a instances for filling up employee list for i in range(0,2): empDict = { 'firstName': 'Roy', 'lastName': 'Augustine'} employeeList.append(empDict) # convert to json data jsonStr = json.dumps(employeeList) except Exception ,e: print str(e) return jsonify(Employees=jsonStr)


https://codehandbook.org/create-json-using-python-flask/





그러나 flask에 flask-restful을 추가해 설치한후,, 아래와 같이 설정한다면..


from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}

api.add_resource(HelloWorld, '/')

if __name__ == '__main__':
    app.run(debug=True)



그냥 기본 타입과 collection은 자동으로 json으로 변환한다. 그 이유가 멀까?


json.dump(aaa) 이런 코드가 필요없어서 참 좋았다.







https://github.com/flask-restful/flask-restful/blob/master/flask_restful/__init__.py#L474


make_response()에서 default decorator로 json을 출력한다.





아래 코드를 보면, indent 4칸에 newline으로 예쁘게 출력하는 코드가 있다. 


https://github.com/flask-restful/flask-restful/blob/master/flask_restful/representations/json.py


from __future__ import absolute_import
from flask import make_response, current_app
from flask_restful.utils import PY3
from json import dumps


def output_json(data, code, headers=None):
    """Makes a Flask response with a JSON encoded body"""

    settings = current_app.config.get('RESTFUL_JSON', {})

    # If we're in debug mode, and the indent is not set, we set it to a
    # reasonable value here.  Note that this won't override any existing value
    # that was set.  We also set the "sort_keys" value.
    if current_app.debug:
        settings.setdefault('indent', 4)
        settings.setdefault('sort_keys', not PY3)

    # always end the json dumps with a new line
    # see https://github.com/mitsuhiko/flask/pull/1262
    dumped = dumps(data, **settings) + "\n"

    resp = make_response(dumped, code)
    resp.headers.extend(headers or {})
    return resp






Posted by 김용환 '김용환'




python으로 해결하는 JSONP 파싱 예시이다.



>>> import requests

>>> url = '...'

>>> jsonp = requests.get(url % 1000)

>>> jsonp.content

b'callback({"status":{

...

})' 

>>> import json

>>> pure_json = jsonp.text[jsonp.text.index('(') + 1 : jsonp.text.rindex(')')]

>>> dealers = json.loads(pure_json)

>>> dealers.keys()

dict_keys(['status'])

>>> dealers['count']

10 



Posted by 김용환 '김용환'




pip로 어떤 패키지를 설치했는지 목록을 볼 수 있다. freeze 커맨드를 사용한다.


# pip freeze

celery==3.1.7

certifi==2018.8.24

...

selenium==3.14.1

six==1.11.0

urllib3==1.22

w3lib==1.19.0

websocket-client==0.51.0

Werkzeug==0.14.1

zope.interface==4.4.3






# pip freeze | grep sel


selenium==3.14.1


Posted by 김용환 '김용환'




파이썬에서 모듈 프로그래밍(디렉토리 , ___init__.py)를 진행할 때,


ModuleNotFoundError를 부딪힐 일이 있다.



$ python util/scrapers.py

Traceback (most recent call last):

  File "util/scrapers.py", line 3, in <module>

    from util.all_scrapers import re_scraper, bs_scraper, \

ModuleNotFoundError: No module named 'util'



이 이유는 파이썬 패스를 못찾다 보니 모듈을 찾지 못한 것이다. 

PYTHONPATH를 bash 설정 파일(예, bash_profile)에 지정하면 된다.



$ cat ~/bash_profile

PYTHONPATH=$PYTHONPATH:/~/dev/python/scraping/code

export PYTHONPATH


Posted by 김용환 '김용환'


파이썬에서 selenium과 phantomjs를 연동한 간단 예시이다.


>>> from selenium import webdriver

>>> driver = webdriver.Firefox() 



이전 커맨드를 실행하면 빈 브라우저 창이 열린다. 에러가 발생하면 geckodriver(https://github.com/mozilla/geckodriver/releases)를 설치하고 geckodriver를 사용할 수 있도록  PATH 변수에 추가해야 한다.



geckodriver 파일을 압축을 푼 후 바이너르를 PATH에 추가한 후, executable(예, chmod 755)로 변경한다.





 

 

 >>> driver.get('https://www.google.com')

 

driver를 사용해 엘리먼트를 파싱하거나, 파이어폭스가 커맨드를 따라 변경되는 것을 볼 수 있다. 


 

 


phantomjs와 같은 헤드리스 브라우져와 연동할 수 있다.

 

http://phantomjs.org/download.html




>>> from selenium import webdriver

>>> driver = webdriver.PhantomJS()  



패스에 넣거나 다음처럼 phantomjs 경로를 사용할 수 있다.



>>> driver = webdriver.PhantomJS('utils/phantomsjs')  

>>> driver.get('http://python.org')

>>> driver.save_screenshot('python_website.png')

True



이렇게 파일을 확인할 수 있다.


스크린 샷 파일이 긴 윈도우이다. maximize_window를 사용하거나 set_window_size로 윈도우 크기를 설정해 윈도우 크기를 변경할 수 있다.



https://selenium-python.readthedocs.io/api.html

Posted by 김용환 '김용환'

import 실패를 방지하는 파이썬의 try ... import .. except: 문이다.

다른 언어에서는 못 본 것 같다.



try:

   from PySide.QtGui import *

   from PySide.QtCore import *

   from PySide.QtWebKit import *

except ImportError:

   from PyQt4.QtGui import *

   from PyQt4.QtCore import *

   from PyQt4.QtWebKit import * 

Posted by 김용환 '김용환'



파이썬의 선(zen of python)를 보려면 파이썬 인터프리터에서 this를 임포트하면 된다. 이스터 에그(Easter Egg)..




>>> import this

The Zen of Python, by Tim Peters


Beautiful is better than ugly.

Explicit is better than implicit.

Simple is better than complex.

Complex is better than complicated.

Flat is better than nested.

Sparse is better than dense.

Readability counts.

Special cases aren't special enough to break the rules.

Although practicality beats purity.

Errors should never pass silently.

Unless explicitly silenced.

In the face of ambiguity, refuse the temptation to guess.

There should be one-- and preferably only one --obvious way to do it.

Although that way may not be obvious at first unless you're Dutch.

Now is better than never.

Although never is often better than *right* now.

If the implementation is hard to explain, it's a bad idea.

If the implementation is easy to explain, it may be a good idea.

Namespaces are one honking great idea -- let's do more of those!




그동안의 경험을 기반으로 의역했다.



파이썬의 선, 팀 피터


아름다운 코드는 지저분한 코드보다 낫다.


명확한 코드는 암시적인 코드보다 낫다.


단순한 코드가 복잡한 코드보다 낫다.


복잡한 코드가 난해한 코드보다 낫다.


단조로운 코드가 복잡한 코드보다 낫다.


읽기 쉬운 코드는 읽기 어려운 코드보다 낫다.


가독성은 중요하다.


규칙을 깰 정도로 특별한 경우란 없다.


하지만 실용성은 이상을 능가한다.


에러를 결코 조용히 넘어가지 않도록 한다.


명시적으로 조용히 넘어가라고 하더라도 조용히 넘어가지 않는다.


모호한 코드를 대면할 때마다 추측하고 싶은 유혹을 거절하라.


문제를 해결할 단 하나의 명확하고 바람직한 방법이 있을 것이다. 


하지만 처음에 코딩을 할 때는 잘 모를 수 있기에 코드의 동작 방법을 정확히 알지 못 할 수 있다.


아무 것도 안하는 것보다 지금 하는 게 낫다.


하지만 아무 것도 하지 않는 것이 지금 *당장* 하는 것보다 나을 수도 있다.


설명하기 어려운 구현이라면 좋은 아이디어는 아니다.


쉽게 설명할 수 있는 구현이라면 좋은 아이디어일 것이다.


네임스페이스는 매우 훌륭한 아이디어이다. 많이 사용하자




Posted by 김용환 '김용환'