python에서 XML을 파싱할 때 주로 elementtree 라이브러리를 사용한다. 나 같이 XML에 대해 잘 몰라도 쉽게 노드에 접근 및 추가할 수 있다.

요넘을 이용한 간단한 rss 파서를 만들어봤다. 아주 간단히...ㅋㅋ

#-*-encoding:utf-8
import socket
from urllib2 import Request, urlopen
import elementtree.ElementTree as ET

class Rss(object):
    id = int()
    link = str()
    title = str()
    description = str()
    item_list = list()

class RssItem(object):
    id = int()
    title = str()
    link = str()
    description = str()
    pub_date = str()
    site_id = int()

def get_rss(rss_url):
    rss = Rss()
    req = Request(rss_url)
    rss_content = str()
    response = None

    try:
        timeout = 3
        socket.setdefaulttimeout(timeout)
        response = urlopen(req)
    except IOError, e:
        if hasattr(e, 'reason'):
            print 'We failed to reach a server.'
            print 'Reason: ', e.reason
        elif hasattr(e, 'code'):
            print 'The server couldn\'t fulfill the request.'
            print 'Error code: ', e.code
        sys.exit(0)

    try:
        rss_content = response.read()
        tree = ET.fromstring(rss_content)
        channel = tree[0]
        rss.title = channel.find('title').text.strip()
        rss.link = channel.find('link').text.strip()
        rss.description = channel.find('description').text.strip()
        items = channel.findall('item')

        for item in items:
            rss_item = RssItem()
            rss_item.title = item.find('title').text.strip()
            rss_item.link = item.find('link').text.strip()
            rss_item.description = item.find('description').text.strip()
            rss_item.pub_date = item.find('pubDate').text.strip()
            rss.item_list.append(rss_item)
    except Exception, e:
        print e

    return rss

if __name__ == '__main__':
    site = get_rss("http://no99.tistory.com/rss")
    print site.title


참고문서
(참고하기 보다는 그냥 복사해왔다는 말이 정확하겠다^^)
urllib2 - http://www.voidspace.org.uk/python/articles/urllib2.shtml
elementtree - http://effbot.org/zone/element-index.htm



Posted by xHuro
,
#서론
TurboGears의 TGWebService는 현재 1.2.0까지 릴리즈된 상태이다. decorator를 이용하여 간단하게 WSDL을 자동으로 생성해주는 기능이 강력하지만(이전 포스트 참조), wsvalidate decorator를 사용할 때 Custom Objects에 대해서는 WSDL을 생성해주지 않는 문제가 발생한다. (편법으로 전혀 사용하지 않는 메소드를 하나 만들고, 그 메소드의 wsexpose에 Custom Objects 넣어주면 문제점을 피해갈 수 있으나, 정상적인 방법도 아닐 뿐더러 소스 코드도 깔끔해지지 않는다. 이전 포스트가 이런 편법이었다.)

이번 포스트에서는 Java와 연동에 앞서, 위의 WSValidate 문제점을 살펴보고, 커스터마이징해보도록 한다.

#필요한 Library
TGWebService(http://code.google.com/p/tgws/) : 사이트가 code google로 이사했다

#WsValidate의 문제점
TGWebService의 README 파일의 Custom Objects as Input 항목에서 설명하는데로 실행하면 WSDL에 반영되지 않는 문제점이 생긴다.

아래의 소스는 이전 포스트의 WebService 클래스를 조금 수정한 것이다. (wsexpose에는 아무것도 없고, wsvalidate에만 Custom Objects를 넣어줬다)

  @CodeGeass
from tgwebservices.controllers import WebServicesRoot, \
    WebServicesController, wsexpose, wsvalidate

from tgwebservices.runtime import typedproperty, unsigned
class Person(object):        
    name = ''
    id = ''

class CodeGeass(WebServicesRoot):
    @wsexpose()
    @wsvalidate(Person)
    def getPerson(self, value):
        person = value
        print person.id
        print person.name

이전 포스트와 동일하게 WSDL을 확인해보자.
http://localhost:8080/codegeass/soap/api.wsdl
   <wsdl:types>
     <xsd:schema elementFormDefault="qualified" targetNamespace="http://localhost:8080/codegeass/soap/types">
          <xsd:element name="getPerson">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="types:Person"/>
              </xsd:sequence>
            </xsd:complexType>

          </xsd:element>
          <xsd:element name="getPersonResponse">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="result" type="xsd:string"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
      </xsd:schema>

   </wsdl:types>
   <wsdl:message name="getPersonRequest" xmlns="http://localhost:8080/codegeass/soap/types">
       <wsdl:part name="parameters" element="types:getPerson"/>
   </wsdl:message>
   <wsdl:message name="getPersonResponse" xmlns="http://localhost:8080/codegeass/soap/types">
      <wsdl:part name="parameters" element="types:getPersonResponse"/>
   </wsdl:message>
... 후략
: 눈 씻고 찾아봐도, getPerson의 value type인 types:Data를 찾을 수 없다. 결국 이 WSDL을 사용하는 Client들은 types:Data에 대한 정보를 알 수 없게 되어, 이 메소드를 사용할 수 없게 된다.

#WSDL에 wsvalidate Decorator의 Custom Objects Type을 넣어주기 위한 해결책
편법적인 방법을 말고 좀더 근본적인 문제를 해결해 보도록 한다. TGWebService가 설치되어 있는 디렉토리를 둘러보면, controller.py가 있다. 이 안은 WebService 클래스안 각 메소드의 decorator 기능이 정의되어 있다.

  @wsexpose에 대한 정의
def wsexpose(return_type=basestring):
    """Exposes a method for access via web services. Only one value
    can be returned because many languages only support a single
    return value from methods.
    
    @param return_type the class that will be returned. This is used to
                       help statically typed clients."""
  ... 중략

        # a list denotes that the user is returning an array of the type
        # given by the first element in the array
        # we need to keep track of the complex types so that they can be
        # defined as appropriate for the given protocol
        if isinstance(return_type, list):
            rt = return_type[0]
            if rt not in primitives:
                register_complex_type(fi, rt)
        elif return_type not in primitives:
            register_complex_type(fi, return_type)

        return newfunc
    return entangle
: 볼드 표시된 부분을 자세히 살펴보자.
이전 포스트에서 언급하였듯이 wsexpose는 해당 메소드의 return_type을 설정하는 decorator이다. 볼드 표시한 부분은 return_type을 확인해서 알맞게 type을 등록하는 것을 알 수 있다.(return_type이 list인지, primitives인지, not primitives인지...)

  @wsvalidate에 대한 정의
def wsvalidate(*args, **kw):
    """Validates and converts incoming parameters. Also registers the
    parameters used by the method for use by statically typed languages.
    Method parameters can be specified via positional or keyword arguments.
    You should pass in the class used for each parameter."""
    def entangle(func):
        fi = register(func)
        input_types = dict()

        # match up the validators with the function parameters
        # the validators list doesn't include self, but the parameters list
        # does. So, the parameters list is offset by one higher.
        for i in range(0, len(args)):
            argtype = args[i]
            input_types[fi.params[i]] = argtype

        input_types.update(kw)
        fi.input_types = input_types
        return func
    return entangle
: wsvalidate는 위 wsexpose처럼 inpyt_type을 체크하고 type을 등록하는 부분이 없다. 그래서 아래와 같이 추가해준다. (wsexpose와 비슷하다.)

  @수정된 wsvalidate에 대한 정의
        # the validators list doesn't include self, but the parameters list
        # does. So, the parameters list is offset by one higher.
        for i in range(0, len(args)):
            argtype = args[i]
            input_types[fi.params[i]] = argtype

            if isinstance(argtype, list):
                rt = argtype[0]

                if rt not in primitives:
                    register_complex_type(fi, rt)
            elif argtype not in primitives:
                register_complex_type(fi, argtype)
 ... 후략

TurboGears를 멈추고, 리스타트하고 다시 WSDL을 확인해보자
<wsdl:types>
     <xsd:schema elementFormDefault="qualified" targetNamespace="http://localhost:8080/codegeass/soap/types">
        <xsd:complexType name="Person">
          <xsd:sequence>
            <xsd:element name="id" type="xsd:string"/>
            <xsd:element name="name" type="xsd:string"/>

          </xsd:sequence>
        </xsd:complexType>
          <xsd:element name="getPerson">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="types:Person"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="getPersonResponse">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="result" type="xsd:string"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
      </xsd:schema>
   </wsdl:types>
: 볼드 표시된 부분이 추가되었음을 알 수 있다.

#또 하나의 문제점...
이로써, 프로젝트 진행 중에 발생한 Custom Object는 해결이 되었으나, 또하나의 문제점이 발생하였다. wsvalidate에 List type([int], [float], [custom]...)을 적용하면 역시 WSDL에 반영되지 않는 것이다.

다음과 같이 array로 선언하고, WSDL을 확인해보자.
  @List type에 대한 CodeGeass
from tgwebservices.controllers import WebServicesRoot, \
    WebServicesController, wsexpose, wsvalidate

from tgwebservices.runtime import typedproperty, unsigned
class Person(object):        
    name = ''
    id = ''

class CodeGeass(WebServicesRoot):
    @wsexpose()
    @wsvalidate([Person])
    def getPerson(self, value):
        person = value
        print person.id
        print person.name

  @List type에 대한 WSDL
<wsdl:types>
     <xsd:schema elementFormDefault="qualified" targetNamespace="http://localhost:8080/codegeass/soap/types">
        <xsd:complexType name="Person">
          <xsd:sequence>
            <xsd:element name="id" type="xsd:string"/>
            <xsd:element name="name" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
          <xsd:element name="getPerson">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="types:Person_Array"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="getPersonResponse">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="result" type="xsd:string"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
      </xsd:schema>
   </wsdl:types>
: 도대체 types:Person_Array에 대한 정의는 어디에 숨었을까?

#WSDL에 wsvalidate Decorator의 List type 적용하기 위한 해결책
TGWebservice 소스중 soap.py를 분석해보자. 소스의 soapController 클래스는 일반 WebService 클래스의 wsexpose, wsvalidate Decorator의 분석 결과(위의 controller.py에서 체크한 결과)를 wsdl.html 템플릿으로 내뱉어주는 클래스다. 소스 코드의 마지막 부분에 있는 내부 메소드, _initialize_arrays 메소드를 잠시 들여다 보자.

  @soap.py 소스
...전략

    def _initialize_arrays(self):
        arrays = set()

        for func in self.wscontroller._ws_funcs.values():
            input_types = func._ws_func_info.input_types
            soap_type(func._ws_func_info.return_type, arrays)

        for cls in self.wscontroller._ws_complex_types:
            for key in ctvalues(cls):         
                soap_type(getattr(cls, key), arrays)
        self.arrays = arrays
: 일반 WebService 클래스(self.wscontroller)에 정의되어 있는 메소드의 return type을 arrays에 담는 작업(첫번째 loop)과 Custom Object type(두번째 2중 loop)을 arrays에 담는 작업을 이 _initialize_arrays에서 담당한다. 이 arrays는 이후 wsdl.html에서 loop를 돌면서 wsdl:type에 대해 출력한다.

이 메소드를 기준으로 소스를 분석해보면 return type(wsexpose)에 대해서만 처리할 뿐, input type(wsvalidate)에 대한 처리가 없음을 알 수 있다. 소스를 다음과 같이 추가하여 해당 메소드의 input type에 대한 처리하도록 한다.

  @수정된 soap.py 소스
    def _initialize_arrays(self):
        arrays = set()

        for func in self.wscontroller._ws_funcs.values():
            input_types = func._ws_func_info.input_types
            soap_type(func._ws_func_info.return_type, arrays)

            for it_key in input_types:        
                soap_type(input_types[it_key], arrays)

        for cls in self.wscontroller._ws_complex_types:
            for key in ctvalues(cls):         
                soap_type(getattr(cls, key), arrays)
        self.arrays = arrays

다시 한번 WSDL을 확인해보자
  @수정된 List type에 대한 WSDL
  <wsdl:types>
     <xsd:schema elementFormDefault="qualified" targetNamespace="http://localhost:8080/codegeass/soap/types">
        <xsd:complexType name="Person">
          <xsd:sequence>
            <xsd:element name="id" type="xsd:string"/><xsd:element name="name" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
          <xsd:element name="getPerson">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="types:Person_Array"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="getPersonResponse">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="result" type="xsd:string"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
        <xsd:complexType name="Person_Array">
          <xsd:sequence>
            <xsd:element maxOccurs="unbounded" name="item" nillable="true" type="types:Person"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:schema>
   </wsdl:types>
: 전에 없었던 Person_Array에 대한 정의가 생겼음을 확인할 수 있다. Person_Array 안 element의 type 역시 Person으로 정의된 걸 알 수 있다.

#결론
지금까지 wsvalidate Decorator에 Custom Object나 List Type을 적용할 경우 WSDL에 반영되지 않는 문제점과 그 해결책을 확인해보았다. Python 자체가 Dynamic Typed Language라서 Input Type에 대한 체크를 하지 않는 것 같은데, 실질적으로 내가 참여한 프로젝트는 Java와 Python 사이를 WebService
로 연동하기 때문에, 수정전의 WSDL로는 Java로 클라이언트를 생성할 수가 없었다.

앞으로 TGWebServices가 버전업될 때 마다 패치하기 보다는, 반영되는 것을 기대하는 것이 좋을 듯 싶다.
Posted by xHuro
,
cx_Oracle을 사용하다보면, utf-8로 인코딩된 데이터들을 table에 insert하면 모조리 ???로 바뀌는 상황이 발생한다.
cx_Oracle의 encoding이 US-ASCII 로 잡혀있기 때문이다.
다음 코드로 encoding을 확인할 수 있다.

import cx_Oracle
conn = cx_Oracle.connect('test/test@TYPED')
conn.encoding

US-ASCII 일 경우, 다음 처럼 NLS을 설정하면 쉽게 해결된다.

import os
os.environ['NLS_LANG'] = '.UTF8'

import cx_Oracle
conn = cx_Oracle.connect('test/test@TYPED')
conn.encoding
Posted by xHuro
,
사용자 삽입 이미지

내 로컬에서의 vim 환경

python을 위한 vim 플러그인들 : runscript.vim, taglist.vim, python.vim

이클립스 플러그인 중의 하나인 pydev를 사용해도 되긴하지만, 서버에서 직접 코딩하는 경우가 많기 때문에 여러가지 플러그인을 설치했다. (다시 한번 vim의 확장성에 감탄하는 중...)
Posted by xHuro
,
TurboGears에서 SQLObject를 이용한 Unit Test시 dburi을 설정하지 않으면, 기본적으로 sqlite(memory DB)로 설정된다. Unit Test시에는 아래와 같이 초기에 설정해준다.

from turbogears import testutil, database
database.set_db_uri("db2://test:test@dada.pe.kr")


여기서 db2 대신, 각각 쓰고 있는 DB로 설정하면된다.
Posted by xHuro
,
class RegisterTest(unittest.TestCase):
    pass

def suite():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(RegisterTest))
    return suite


unittest.TextTestRunner(verbosity=2).run(suite())

unittest.TestCase를 사용할 때 TestSuite에 넣고 Test 실행하기.
각 test case에 대해 comment를 달면, comment도 같이 출력되어 보기가 좋다.

시간날때 좀더 업데이트 해야겠다.

Posted by xHuro
,
from time import time

def test:
    pass

start = time()

for index in range(0,1000):
    test()

end = time()

print 'total time : ' + str(end - start)


간단한 python code. 별거 아닌데, 익숙치가 않아서 모듈들이 헷갈린다.
기억력을 믿지 말고 검색과 기록을 생활화 해야한다^^
Posted by xHuro
,