DigitalOcean Functions Challenge를 완료하기 위한 Python CLI 작성

전주곡



내 에서 계속하기 위해 동일한 작업을 수행하는 Python CLI 작성에 대해 자세히 설명하겠습니다DigitalOcean Functions Challenge.

코드



이전에 작성한 Go 코드와 마찬가지로 이 예제에서는 모든 것을 main.py에 집어넣었습니다. 따라서 시작하려면 간단하게 main.py 파일을 만들고 Pipenv과 함께 사용하고 싶은 처음 몇 가지 종속 항목을 설치했습니다. 그래서:
pipenv install requests structlog click

  • Requests Python 응용 프로그램에서 웹과 인터페이스해야 할 때 항상 사용합니다.

  • Structlog은 Python 애플리케이션용으로 선택한 구조화된 로깅 패키지입니다.

  • Click은 Python 애플리케이션용으로 선택한 CLI 프레임워크입니다.

  • 이렇게 하면 이제 Pipfile과 Pipfile.lock이 있고 실제 개발을 수행할 수 있습니다.

    먼저 요청 및 응답 클래스를 정의하고 싶었고 이전 예제에서 알고 있는 내용을 알고 있었기 때문에 클래스를 작성할 수 있었습니다.

    #!/usr/bin/env python3
    import logging
    import sys
    
    import requests
    import structlog
    import click
    
    
    class Request:
        """
        The Request class will contain all of our methods to handle a request to the DigitalOcean Function Challenge api.
    
        Attributes
        ----------
        API_URL : str
            The API_URL attribute is a static string to hold the URL of the API to access.
    
        name: str
            The name attribute is the name of the Sammy that we wish to create.
    
        t : str
            The "t" attribute is the type of the Sammy that we wish to create. This attribute is named "t" to avoid
            conflicting with the "type" keywork.
    
        log : structlog.BoundLogger
            The log attribute is not an attribute to be dealt with directly, rather the _get_log(self) method will retrieve
            the bound logger for us.
    
        Methods
        -------
        _set_name(name: str)
            This method sets the value of the name attribute.
    
        _set_type(t: str)
            This method sets the value of the t attribute.
    
        _set_log()
            This method gets the application logger and sets it to the log attribute.
    
        _get_url() -> str
            This method returns the value of the API_URL attribute.
    
        _get_name() -> str
            This method returns the value of the name attribute.
    
        _get_type() -> str
            This method returns the value of the t attribute.
    
        _build_headers() -> dict
            This static method returns a dictionary to be used as a request header.
    
        _build_request_body() -> dict
            This method returns a dictionary to be used as the request body.
    
        do() -> requests.Response
            This method is the primary class method and is used to perform the HTTP POST request.
        """
        def __init__(self, name: str, t: str):
            """
            Parameters
            ----------
            :param name: str
                The name to give to your new Sammy.
            :param t: str
                The type to give to your new Sammy.
            """
            self.API_URL = "https://functionschallenge.digitalocean.com/api/sammy"
            self.name = None
            self.t = None
            self.log: structlog.BoundLogger = None
            self._set_name(name)
            self._set_type(t)
            self._set_log()
    
        def _set_name(self, name: str):
            """This method sets the value of the name attribute.
    
            Parameters
            ----------
            :param name: str
                The name of the new sammy to create.
            """
            self.name = name
    
        def _set_type(self, t: str):
            """This method sets the value of the t attribute.
    
            Input validation is handled at the CLI layer. We will need to do further input validation if we don't do the
            validation at the CLI layer.
    
            Parameters
            ----------
            :param t: str
                The type of the new sammy to create.
            """
            self.t = t
    
        def _set_log(self):
            """This method gets the application logger and sets it to the log attribute."""
            self.log = structlog.stdlib.get_logger()
    
        def _get_url(self) -> str:
            """This method returns the value of the API_URL attribute."""
            return self.API_URL
    
        def _get_name(self) -> str:
            """This method returns the value of the name attribute."""
            return self.name
    
        def _get_type(self) -> str:
            """This method returns the value of the t attribute."""
            return self.t
    
        def _build_request_body(self) -> dict:
            """This method returns a dictionary to be used as the request body."""
            return {
                "name": self.name,
                "type": self.t,
            }
    
        @staticmethod
        def _build_headers() -> dict:
            """This static method returns a dictionary to be used as a request header."""
            return {
                "Accept": "application/json",
                "Content-Type": "application/json"
            }
    
        def do(self) -> requests.Response:
            """This method is the primary class method and is used to perform the HTTP POST request."""
            return requests.post(url=self._get_url(), json=self._build_request_body(), headers=self._build_headers())
    
    
    class Response:
        """
        The Response class will contain all of our methods to handle a response from the DigitalOcean Functions Challenge
        api.
    
        Attributes
        ----------
        resp : requests:Response
            The actual response that is retrieved from performing the do() function from our Request class.
        log: : structlog.BoundLogger
            The log attribute is not an attribute to be dealt with directly, rather the _get_log(self) method will retrieve
            the bound logger for us.
    
        Methods
        -------
        _set_log() -> None
            Gets the application logger.
    
        _get_status_code() -> int
            Gets the HTTP status code from the requests.Response object the class was initialized with.
    
        _has_errors() -> bool
            A helper to let us know if errors exist in the requests.Response object the class was initialized with.
    
        do() -> None
            The primary method of this class. Do is used to print the information from the response to the terminal.
        """
        def __init__(self, resp: requests.Response):
            """
            Parameters
            ----------
            :param resp: requests.Response
                A response object from our Request class do() method.
            """
            self.resp = resp
            self.log: structlog.BoundLogger = None
    
            self._set_log()
    
        def _set_log(self):
            """Gets the application logger."""
            self.log = structlog.stdlib.get_logger()
    
        def _get_status_code(self) -> int:
            """Gets the HTTP status code from the requests.Response object the class was initialized with."""
            return self.resp.status_code
    
        def _has_errors(self) -> bool:
            """A helper to let us know if errors exist in the requests.Response object the class was initialized with."""
            respj: dict = self.resp.json()
            if "errors" in respj:
                return True
            else:
                return False
    
        def do(self) -> None:
            """The primary method of this class. Do is used to print the information from the response to the terminal."""
            if self._has_errors():
                for e in self.resp.json()["errors"]:
                    for ee in self.resp.json()["errors"][e]:
                        self.log.error(ee)
                    return
            else:
                self.log.info(self.resp.json()["message"])
    


    참고: 파일 맨 위에 있는 shebang을 확인하십시오. 저는 개인적으로 파일을 직접 호출할 수 있도록 Python 애플리케이션으로 이 작업을 수행하는 것을 좋아합니다. 하지만 이 작업을 수행할 때 실행할 적절한 권한을 파일에 제공해야 합니다. 내 경우(유닉스 계열 OS에서) 실행을 허용하기 위해 실행chmod +x main.py만 하면 되었습니다.

    이제 요청과 응답을 처리할 몇 가지 클래스가 있습니다.

    나는 structlog가 나를 위해 좀 더 json-y를 처리하기를 원했기 때문에 다음과 같이 로거 구성 기능을 추가해야 했습니다.

    ...
    
    def configure_logger():
        """Basic application level logging configuration"""
        logging.basicConfig(
            format="%(message)s",
            stream=sys.stdout,
            level=logging.INFO,
        )
        structlog.configure(
            processors=[
                structlog.processors.add_log_level,
                structlog.processors.JSONRenderer(),
            ],
            logger_factory=structlog.stdlib.LoggerFactory(),
        )
    


    이제 로거가 구성되었으므로 CLI를 사용하여 모두 가져오기만 하면 됩니다.

    @click.command()
    @click.option('-n', '--name', "name", help='The name to give to your new Sammy.')
    @click.option('-t', '--type', "t", type=click.Choice([
        "sammy",
        "punk",
        "dinosaur",
        "retro",
        "pizza",
        "robot",
        "pony",
        "bootcamp",
        "xray"
    ], case_sensitive=False), help='The type to give to your new Sammy.')
    def main(name, t):
        configure_logger()
        req = Request(name, t)
        response = req.do()
        resp = Response(response)
        resp.do()
    
    
    if __name__ == "__main__":
        main()
    


    참고: 다시 말하지만 if __name__ == "__main__" 문에 주목하세요. 이것은 "파일이 직접 호출되면 이 작업을 수행하십시오"라는 줄입니다.

    실제로 이 모든 main() 기능은 (일부 클릭 데코레이터의 도움으로) 유형에 대한 name 플래그, t 플래그를 설정한 다음 설계한 대로 기능을 실행하는 것입니다.

    새 앱을 실행하는 것은 ./main.py --name <my name> --type <my type> 와 같이 간단합니다.

    평소와 같이 전체 파일을 보려면 다음 저장소에서 자유롭게 확인하십시오.
    https://github.com/j4ng5y/digitalocean-functions-challenge/tree/main/python

    좋은 웹페이지 즐겨찾기