https://github.com/google/python-fire

 

import fire

class Calculator(object):
 “”"A simple calculator class.“”"

 def double(self, number):
   return 2 * number

 def hello(self, name):
     return “hello ” + name + “!”

if __name__ == ‘__main__‘:
   fire.Fire(Calculator)


$ python calculator.py double --number=15 

30

 

$ python calculator.py double 10 

20

 

$ python test.py hello --name=“sam”
hello sam!

 

 

Posted by 김용환 '김용환'



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 김용환 '김용환'