<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>:)</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 12 Mar 2023 09:04:59 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>:)</title>
            <url>https://velog.velcdn.com/images/gyuseok-dev/profile/d083928d-2003-497b-a905-26123d8bef1c/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. :). All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/gyuseok-dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[AWS CDK 적용기]]></title>
            <link>https://velog.io/@gyuseok-dev/AWS-CDK%EB%A1%9C-IaC-%EB%BF%8C%EC%88%98%EA%B8%B0</link>
            <guid>https://velog.io/@gyuseok-dev/AWS-CDK%EB%A1%9C-IaC-%EB%BF%8C%EC%88%98%EA%B8%B0</guid>
            <pubDate>Sun, 12 Mar 2023 09:04:59 GMT</pubDate>
            <description><![CDATA[<p>회사에서 AWS Cloud Development Kit (이하 CDK)를 적용한 지도 1년이 되어가고 있습니다. 사용하면서 매우 유용하다는 생각이 들었지만, 생각보다 한국에서는 사용이 많이 이루어지지 않는 것 같아서 해당 경험을 공유해보려고 합니다.</p>
<h1 id="cdk란-무엇일까요">CDK란 무엇일까요?</h1>
<p>CDK는 AWS Cloud Development Kit의 약어로, AWS 인프라 관리를 개발자가 사용하는 언어(예: Python, Java, TypeScript 등)로 관리할 수 있게 해주는 도구입니다.</p>
<p>핵심만 말하자면</p>
<blockquote>
<p>내가 사용하는 언어로 AWS 인프라 관리를 도와주는 도구 </p>
</blockquote>
<h1 id="누구에게-필요할까요">누구에게 필요할까요</h1>
<p>개발자가 AWS 인프라 관리를 자신이 사용하는 언어로 관리하고 싶을 때, CDK를 사용할 수 있습니다. 특히 TypeScript를 사용하는 경우 프론트엔드, 백엔드, 인프라 관리 등 다양한 작업을 동시에 할 수 있습니다.</p>
<h1 id="cdk는-어떤-문제를-해결해줄-수-있을까요">CDK는 어떤 문제를 해결해줄 수 있을까요</h1>
<ul>
<li>우선 laC의 장점과 동일한 부분은 간단하게만 적겠습니다<ul>
<li>일관성: <ul>
<li>IaC를 사용하면 코드로 인프라를 구성하기 때문에 모든 인프라 구성이 코드화 가능.</li>
<li>반복적인 인프라 작업을 자동화.</li>
</ul>
</li>
<li>효율성: <ul>
<li>코드 재사용성과 모듈화가 가능. 또한, </li>
<li>코드 버전 관리, 변경 내역 추적, 빠른 배포와 롤백 가능</li>
<li>작업 시간 단축 = 개발자 행복</li>
</ul>
</li>
<li>안정성: <ul>
<li>자동화된 테스트와 검증 절차를 통해 오류 감지, 오류 방지 가능.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>이거를 이제 내가 잘 쓰는 언어로 할 수 있다는 건데
유명한 terraform 같은 경우에도 새로 언어를 배워한다는게, 
특히 <strong>스타트업</strong> 입장에서는 러닝커브라는건 시간적인 리스크가 있어서 &quot;이렇게 할바에는 그냥 수동으로 하는게 빠르겠다. 도입 포기!&quot; 이런 결과로 가기 쉽다는 거죠.</p>
<p>요약을 하자면 IaC 도입을 가장 쉽게 할 수 있는 방법인거 같아요</p>
<h1 id="예시">예시</h1>
<pre><code class="language-typescript">import * as cdk from &#39;aws-cdk-lib&#39;;
import * as ec2 from &#39;aws-cdk-lib/aws-ec2&#39;;
import * as ecs from &#39;aws-cdk-lib/aws-ecs&#39;;
import * as ecsPatterns from &#39;aws-cdk-lib/aws-ecs-patterns&#39;;

const app = new cdk.App();
const stack = new cdk.Stack(app, &#39;MyStack&#39;);

const vpc = new ec2.Vpc(stack, &#39;MyVpc&#39;, { maxAzs: 2 });

const cluster = new ecs.Cluster(stack, &#39;MyCluster&#39;, { vpc });
const fargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(stack, &#39;MyFargateService&#39;, {
  cluster,
  memoryLimitMiB: 512,
  cpu: 256,
  taskImageOptions: {
    image: ecs.ContainerImage.fromRegistry(&#39;amazon/amazon-ecs-sample&#39;),
  },
});

new cdk.CfnOutput(stack, &#39;LoadBalancerDNS&#39;, { value: fargateService.loadBalancer.loadBalancerDnsName });</code></pre>
<p>위 코드는 AWS CDK를 사용해 Amazon ECS 클러스터와 Fargate 서비스를 생성하는 TypeScript 코드 예시입니다.</p>
<h1 id="어떻게-쓰고-계시나요">어떻게 쓰고 계시나요</h1>
<p>지금 적용되어있는 형태</p>
<ul>
<li>lambda + apigateway Restful API</li>
<li>codepipeline</li>
<li>vpc</li>
<li>elastic cache</li>
<li>eventbridge(스케쥴러용)</li>
</ul>
<h1 id="단점은-없나요">단점은 없나요</h1>
<p>CDK를 사용하면 인프라 관리가 코드 형태로 되므로, IaC(Infrastructure as Code)를 사용하지 않을 때 발생할 수 있는 문제들을 방지할 수 있습니다. 그러나 CDK 역시 일정한 학습 곡선이 필요하고, AWS 서비스가 업데이트되면서 CDK에서 지원되지 않는 경우도 있습니다.</p>
<p>실제로 제 경험상, API Gateway에서 리소스를 삭제하고 코드를 변경 후 배포하면, 삭제된 리소스가 남아있어 배포가 실패할 수 있습니다. 또한, 많은 리소스를 한 번에 변경하려다가 실패하여 롤백하는 경우에는 롤백 시간이 길어지는 등의 문제가 발생할 수 있습니다.</p>
<p>하지만 이러한 문제들은 CDK의 원리와 사용 방법을 숙지하고 있으면 충분히 해결할 수 있습니다.</p>
<h1 id="결론">결론</h1>
<p>CDK는 개발자가 AWS 인프라를 코드로 관리할 수 있게 해주는 도구로, 코드 재사용성과 모듈화가 가능하며, 코드 버전 관리와 변경 내역 추적, 빠른 배포와 롤백이 가능하다는 장점이 있습니다. 또한, CDK를 사용하면 AWS 인프라 관리에 대한 이해도가 높아질 수 있다는 점도 큰 장점입니다.</p>
<p>그러나 AWS 서비스가 업데이트되면서 CDK에서 지원되지 않는 경우도 있으므로, CDK를 사용하기 전에 AWS 공식 문서를 꼼꼼하게 확인하는 것이 중요합니다.</p>
<p>시간이 날때마다 그동안 적용해왔던 경험을 시리즈로 공유해보겠습니다. 감사합니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[elastic search - 날짜 범위 별 쿼리 ]]></title>
            <link>https://velog.io/@gyuseok-dev/elastic-search-%EB%82%A0%EC%A7%9C-%EB%B2%94%EC%9C%84-%EB%B3%84-%EC%BF%BC%EB%A6%AC-</link>
            <guid>https://velog.io/@gyuseok-dev/elastic-search-%EB%82%A0%EC%A7%9C-%EB%B2%94%EC%9C%84-%EB%B3%84-%EC%BF%BC%EB%A6%AC-</guid>
            <pubDate>Fri, 14 Oct 2022 16:25:29 GMT</pubDate>
            <description><![CDATA[<ul>
<li>하루 전, 일주일 전 row 개수 가져오기<h2 id="query">QUERY</h2>
<pre><code>GET my_index/_search
{
&quot;aggs&quot;: {
  &quot;byDate&quot;: {
    &quot;date_range&quot;: { 
        &quot;field&quot;: &quot;createdAt&quot;,
        &quot;ranges&quot;: [
            {&quot;from&quot;: &quot;now-1w&quot;, &quot;to&quot;: &quot;now&quot;, &quot;key&quot;: &quot;week&quot; },
            {&quot;from&quot;: &quot;now-1d&quot;, &quot;to&quot;: &quot;now&quot;, &quot;key&quot;: &quot;today&quot;}
        ],
        &quot;keyed&quot;: true // 키 네이밍을 가능하게 해줌
    }
  }
}
}</code></pre><h2 id="response">RESPONSE</h2>
<pre><code>{
&quot;took&quot; : 3,
&quot;timed_out&quot; : false,
&quot;_shards&quot; : {
  &quot;total&quot; : 5,
  &quot;successful&quot; : 5,
  &quot;skipped&quot; : 0,
  &quot;failed&quot; : 0
},
&quot;hits&quot; : {
  &quot;total&quot; : {
    &quot;value&quot; : 14,
    &quot;relation&quot; : &quot;eq&quot;
  },
  &quot;max_score&quot; : null,
  &quot;hits&quot; : [ ]
},
&quot;aggregations&quot; : {
  &quot;byDate&quot; : {
    &quot;buckets&quot; : {
      &quot;week&quot; : {
        &quot;from&quot; : 1.665159166158E12,
        &quot;from_as_string&quot; : &quot;2022-10-07T16:12:46.158Z&quot;,
        &quot;to&quot; : 1.665763966158E12,
        &quot;to_as_string&quot; : &quot;2022-10-14T16:12:46.158Z&quot;,
        &quot;doc_count&quot; : 14
      },
      &quot;today&quot; : {
        &quot;from&quot; : 1.665677566158E12,
        &quot;from_as_string&quot; : &quot;2022-10-13T16:12:46.158Z&quot;,
        &quot;to&quot; : 1.665763966158E12,
        &quot;to_as_string&quot; : &quot;2022-10-14T16:12:46.158Z&quot;,
        &quot;doc_count&quot; : 3
      }
    }
  }
}
}</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 - 트러블슈팅 - control plain]]></title>
            <link>https://velog.io/@gyuseok-dev/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-controlplain-1</link>
            <guid>https://velog.io/@gyuseok-dev/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-controlplain-1</guid>
            <pubDate>Mon, 21 Mar 2022 14:28:29 GMT</pubDate>
            <description><![CDATA[<h1 id="1">1</h1>
<p>k8s 환경 안에서 app이 제대로 동작되지 않는 것으로 보입니다.
원인을 찾아 복구해 보겠습니다.</p>
<pre><code>kubectl get all --all-namespaces</code></pre><p><img src="https://images.velog.io/images/gyuseok-dev/post/9cdfb3e6-f884-45c6-b884-54dc815242b9/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2010.39.50.png" alt=""></p>
<p>describe로 좀 더 자세히 보겠습니다.</p>
<pre><code>kubectl describe pod -n kube-system kube-scheduler-controlplane</code></pre><p><img src="https://images.velog.io/images/gyuseok-dev/post/c10fadad-bdff-42a5-9ec5-0aa20cd42a05/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2010.41.20.png" alt=""></p>
<p>이부분에서 문제가 있는 것 같습니다.</p>
<p>kube-scheduler의 manifestfile을 수정하도록하겠습니다.</p>
<pre><code>vi /etc/kubernetes/manifests/kube-scheduler.yaml </code></pre><pre><code># in kube-scheduler.yaml
spec:
  containers:
  - command:
    - kube-scheduler
    - --authentication-kubeconfig=/etc/kubernetes/scheduler.conf
    - --authorization-kubeconfig=/etc/kubernetes/scheduler.conf
    - --bind-address=127.0.0.1
    - --kubeconfig=/etc/kubernetes/scheduler.conf
    - --leader-elect=true
    - --port=0</code></pre><p>다시 체크해서 복구된 것을 확인합니다.</p>
<pre><code>kubectl get all --all-namespaces</code></pre><p><img src="https://images.velog.io/images/gyuseok-dev/post/8e5e7c02-1ba9-4d37-8668-c4add3cb19bc/image.png" alt=""></p>
<p>good~</p>
<h1 id="2">2</h1>
<p>이번에는 deployement의 pod를 1개에서 2개로 scale out 한 이후 이상현상이 발생했습니다.</p>
<pre><code>kubectl get all --all-namespaces</code></pre><p><img src="https://images.velog.io/images/gyuseok-dev/post/08c92633-1973-44a5-9983-44cd0efbbaf3/image.png" alt="">
자세히 보겠습니다.</p>
<pre><code>kubectl describe pod -n kube-system kube-controller-manager-controlplane </code></pre><p>그러나 descibe 만으로는 원인을 파악하기 어려웠습니다.
로그를 보겠습니다.</p>
<pre><code>kubectl logs -n kube-system kube-controller-manager-controlplane  </code></pre><p><img src="https://images.velog.io/images/gyuseok-dev/post/6a7ffe8e-c14d-4e74-8a1e-c022314351ba/image.png" alt="">
config file 이름에 정확하지 않은 것으로 판단됩니다.</p>
<pre><code>root@controlplane:~# ls /etc/kubernetes/          
admin.conf               kubelet.conf             pki/                     
controller-manager.conf  manifests/               scheduler.conf   </code></pre><p><code>controller-managet.conf</code>로 정정해줍니다.</p>
<pre><code> vi /etc/kubernetes/manifests/kube-controller-manager.yaml </code></pre><pre><code>spec:
  containers:
  - command:
    ...중략...
    - --kubeconfig=/etc/kubernetes/controller-manager.conf
    ...중략...</code></pre><p>다시 잘돌아가는지 확인해줍니다.</p>
<pre><code>kubectl get all --all-namespaces</code></pre><p><img src="https://images.velog.io/images/gyuseok-dev/post/27798fc0-9281-4448-b3fd-132f7aa6c616/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.26.19.png" alt=""></p>
<p>good~</p>
<h1 id="3">3</h1>
<pre><code>kubectl logs -n kube-system kube-controller-manager-controlplane  </code></pre><p><img src="https://images.velog.io/images/gyuseok-dev/post/deae4780-7f05-4fef-b165-53badd4370f6/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.32.13.png" alt="">
로그를 보면 무엇인가 또 경로에 문제가 있어보입니다.</p>
<pre><code>kubectl describe pod -n kube-system kube-controller-manager-controlplane </code></pre><p><img src="https://images.velog.io/images/gyuseok-dev/post/040aadb2-977a-4758-b9d9-e7324341009e/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.34.31.png" alt="">
이상한 경로가 포함되어있는 것을 확인하였습니다.
올바른 경로로 수정합니다.</p>
<pre><code> vi /etc/kubernetes/manifests/kube-controller-manager.yaml </code></pre><pre><code>  volumes:
  ...중략...
  - hostPath:
      path: /etc/kubernetes/pki
      type: DirectoryOrCreate
    name: k8s-certs
  ...중략...</code></pre><p>확인해보자</p>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/e851878c-33f4-4a61-aead-4aed5f5538c0/image.png" alt=""></p>
<p>good!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS CloudWatch Log 남기기]]></title>
            <link>https://velog.io/@gyuseok-dev/AWS-CloudWatch-Log</link>
            <guid>https://velog.io/@gyuseok-dev/AWS-CloudWatch-Log</guid>
            <pubDate>Tue, 04 Jan 2022 14:42:19 GMT</pubDate>
            <description><![CDATA[<p>python flask aws cloudwatch에 로그를 남기는 방법에 대해서 알아보겠습니다.</p>
<h2 id="tldr">TL;DR</h2>
<ul>
<li>log를 킹갓 AWS에 맡겨볼겁니다.</li>
<li>watchtower를 사용할 겁니다.</li>
<li>flask에 적용할겁니다.(다른 framework면 미리 죄송합니다.)</li>
</ul>
<h2 id="들어가며">들어가며</h2>
<p>회사에서는 로그를 특정 파일에 남긴 뒤 해당 파일을 s3의 올리는 방식을 사용하고 있습니다.
원하는 로그를 검색도 하기도 어렵고 가독성도 매우 떨어져서 &quot;이게 맞나...&quot; 싶었습니다.
또 로그를 확인하기 위해서 s3에서 파일을 다운받고 에디터로 열고 검색하고 하는 과정이 심히 불편쓰..
그러다보니 로그가 있어도 잘 이용을 하지 않는 느낌을 많이 받아서 고민하던 중
간단하게 적용할 수 있는 라이브러리가 있어서 회사 적용해보았더니 기존에 비해 편리하다는 느낌을 많이 받아서 정리 겸 공유 해보려고 합니다.</p>
<h2 id="iam-사용자-생성">IAM 사용자 생성</h2>
<h3 id="사용자-생성">사용자 생성</h3>
<p>&#39;AWS Console - IAM - 사용자&#39;에서 &#39;사용자 추가&#39;를 해주세요.
<img src="https://images.velog.io/images/gyuseok-dev/post/b599808c-c4e2-4e6a-a4ca-2fc42c1830ff/iam_1.png" alt="">
*&#39;AWS 액세스 유형 선택&#39;에서 &#39;엑세스 키-프로그래밍 방식 액세스&#39; 선택 해주세요.</p>
<h3 id="권한-부여">권한 부여</h3>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/689db7d4-9495-4477-9af4-d682c736c38e/iam_2.png" alt=""></p>
<p><a href="https://github.com/kislyuk/watchtower">watchtower</a>에서 다음과 같이 적절한 권한을 추가하도록 권장하고 있습니다.</p>
<blockquote>
<p>we recommend that you use the arn:aws:iam::aws:policy/AWSOpsWorksCloudWatchLogs managed policy, which has just the right permissions without being overly broad.
-&gt; 지나치게 광범위하지 않으면서 적절한 &quot;AWSOpsWorksCloudWatchLogs&quot; 정책을 사용하는 것을 추천합니다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/3cfef49f-f678-4a0e-a19d-b463729e1c07/IMG_0034.jpg" alt="IAM 생성완료"></p>
<p>IAM 생성이 완료된 모습입니다. 액세스 키와 비밀 액세스 키를 잘 가지고 있어 주세요.</p>
<h2 id="watchtower-추가">watchtower 추가</h2>
<pre><code class="language-bash">pip install --upgrade pip
pip install watchtower</code></pre>
<p>끝입니다.</p>
<h2 id="handler-적용">handler 적용</h2>
<pre><code class="language-python"># app.py
import logging

from flask import Flask, current_app
import watchtower

app = Flask(__name__)

handler = watchtower.CloudWatchLogHandler(
        log_group_name=&quot;my-service&quot;,
        log_stream_name=&quot;dev&quot;, 
)

formatter = logging.Formatter(&quot;[%(levelname)s] %(message)s&quot;)
handler.setFormatter(formatter)
handler.setLevel(logging.INFO)

app.logger.addHandler(handler)

@app.route(&quot;/&quot;)
def hello_world():
    current_app.logger.info(&quot;It`s working!!&quot;)
    return &quot;&lt;p&gt;Hello, World!&lt;/p&gt;&quot;</code></pre>
<p>적용을 위한 간단한 flask app을 만들어보았습니다.</p>
<p>그럼 테스트를 한번 해볼까요?</p>
<pre><code>export FLASK_APP=app.py
flask run</code></pre><p>이런... 에러가 나오네요.</p>
<pre><code>botocore.exceptions.NoRegionError: You must specify a region.</code></pre><p>이때! 1번에서 생성한 IAM 액세스 키를 추가해주어야합니다.</p>
<pre><code>export AWS_DEFAULT_REGION=default_region
export AWS_ACCESS_KEY_ID=your_access_key_id
export AWS_SECRET_ACCESS_KEY=your_secret_access_key</code></pre><p>AWS_DEFAULT_REGION = default region을 설정해주어야합니다. 서울은 &#39;ap-northeast-2&#39;입니다.
AWS_ACCESS_KEY_ID = IAM 생성 시 받은 액세스 키를 넣어줍니다.
AWS_SECRET_ACCESS_KEY = IAM 생성 시 받은 비밀 액세스 키를 넣어줍니다.</p>
<p>적용 후 다시 flask run 하면 이번에는 정상동작하는 것을 볼 수 있습니다.</p>
<h2 id="확인하기">확인하기</h2>
<p>API를 호출 해볼까요?
<a href="http://localhost:5000/">http://localhost:5000/</a>
<img src="https://images.velog.io/images/gyuseok-dev/post/1588b325-fd5d-46d1-b9a7-2ac9c4f636ff/%EA%B2%B0%EA%B3%BC_1.png" alt="">
<img src="https://images.velog.io/images/gyuseok-dev/post/600812d0-9998-49fb-a0c2-a3934574b60c/%EA%B2%B0%EA%B3%BC3.png" alt=""></p>
<p>호출 후 AWS Console로 가보면 다음과 같이 코드에 설정한 로그 그룹과 로그 스트림 정보가 나타나게됩니다.</p>
<blockquote>
<p>로그 전송 시간이 있어서 1분정도 딜레이가 있을 수 있습니다.
물론 해당 시간은 로그핸들러 생성시 조정할 수 있는 인자가 있습니다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/baa6928e-9dbd-4ab5-89b6-d021c66f4ece/%EA%B2%B0%EA%B3%BC2.png" alt="">
짠! log가 정상적으로 들어왔네요~ 이제 cloudwatch에서 서비스의 로그를 확인할 수 있습니다.</p>
<h2 id="실제-운영-시-고려한-점">실제 운영 시 고려한 점</h2>
<ul>
<li>로그스트림에는 개발환경(dev, staging, prod)를 넣어서 구분했다.</li>
<li>실제로는 환경변수가 아닌 aws credential을 사용했다.(보안)</li>
<li>기존에 boto3를 사용하고 있었으면 boto3.client(&quot;logs&quot;)로 credential을 부여할 수 있는 인자기 있다.(라이브러리가 boto3 기반)</li>
<li>핸들러 생성시 aws credential이 적절하지 않으면 서버가 구동이 안되기 때문에 적용하지 않도록 에러 처리를 했다. (예: CI 테스트 서버)</li>
</ul>
<h2 id="마치며">마치며</h2>
<p>서비스를 운영하면서 당장의 대응만 생각하다보면 로그 구축은 생각보다 우선순위에서 멀어지게 되는거 같습니다.
때문에 미래를 대비하기 위해서 읽기 쉽고, 사용하기 쉬운 로깅에 대해서 고민이 필요할 것 같습니다.</p>
<h2 id="reference">Reference</h2>
<p><a href="https://github.com/kislyuk/watchtower">watchtower github</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git Commit Convention]]></title>
            <link>https://velog.io/@gyuseok-dev/Git-Commit-Convention</link>
            <guid>https://velog.io/@gyuseok-dev/Git-Commit-Convention</guid>
            <pubDate>Tue, 06 Jul 2021 13:58:02 GMT</pubDate>
            <description><![CDATA[<p>팀 워크에서 가장 중요한 것은 같은 언어로 이야기해야하는 것이라고 생각합니다.
같은 언어로 이야기 한다는 것은 의미를 해석하는 시간을 줄여 생산성을 높이기 위함이라고 생각하는데요.</p>
<p>그렇기 때문에 회사에서 convention에 신경을 쓰는 편입니다.
하나 딱! 정해놓고 평생동안 울궈먹거나 이것을 기준삼아 발전시키기 위해 개인적으로 정리해보았습니다.</p>
<h2 id="commit-formats"><strong>Commit Formats</strong></h2>
<h3 id="default"><strong>Default</strong></h3>
<pre><code>&lt;type&gt;(&lt;optional scope&gt;):&lt;subject&gt;empty separator line&lt;optional body&gt;empty separator line&lt;optional footer&gt;</code></pre><ul>
<li><code>subject</code>와 <code>body</code>에는 한글/영어를 혼용이 가능한 것으로 하겠습니다.</li>
<li>혹시 팀원 전원이 찬성한다면 <code>subject</code>와 <code>body</code> 역시 영어를 사용하는 것을 권장합니다. </li>
</ul>
<h3 id="types"><strong>Types</strong></h3>
<blockquote>
<p><code>Type</code>을 통해 해당 커밋의 가장 기본적을 목적을 파악합니다.</p>
</blockquote>
<ul>
<li><code>feat</code> : 기획과 관련한 기능 추가 및 삭제</li>
<li><code>fix</code>  : 버그 수정 및 기존 기능 수정</li>
<li><code>refactor</code> : 리팩토링 (기능 수정 X) * 기능 수정과 리팩토링 구분해서 올려주세요.<ul>
<li><code>perf</code> : 성능 향상을 위한 리팩토링</li>
</ul>
</li>
<li><code>style</code> : code style 변경 (white-space, formatting, missing semi-colons, etc)</li>
<li><code>test</code> : 테스트 추가</li>
<li><code>docs</code> : 문서 추가/변경 (ReadMe.md)</li>
<li><code>build</code>: 빌드 관련 변경 (build tool, ci pipeline, dependencies, project version)</li>
<li><code>chore</code> : 그외 변경 ( <code>.gitignore</code> )</li>
</ul>
<h3 id="scope"><strong>Scope</strong></h3>
<blockquote>
<p><code>scope</code>는 하나의 repo에 다양한 앱이 있을 경우 구분을 짓기 위함입니다.</p>
</blockquote>
<ul>
<li>request과 response이 있다면 <code>fix(request): fix typo</code>, <code>feat(response): fix typo</code></li>
<li>비지니스 로직을 구분하기 위해서 사용</li>
</ul>
<h3 id="subject"><strong>Subject</strong></h3>
<blockquote>
<p><code>subject</code>에서는 좀더 명확한 변경 내용을 담습니다.</p>
</blockquote>
<ul>
<li>required</li>
<li>59자 이하로 작성해주세요.</li>
<li><code>추가</code>, <code>변경</code>, <code>수정</code>, <code>삭제</code>, <code>개선</code> 등 명사형 문장으로 작성</li>
<li>영문의 경우 명령형으로 작성</li>
<li>끝에 온점(.) 생략</li>
<li>파일명이나 함수명은 사용 X (코드에서 보여줄것임)</li>
<li>ex) oo 기능 추가, 불필요한 코드 삭제</li>
</ul>
<h3 id="body"><strong>Body</strong></h3>
<ul>
<li>optional</li>
<li>한 줄에 59자 이하</li>
<li><code>추가</code>, <code>변경</code>, <code>수정</code>, <code>삭제</code>, <code>개선</code> 등 명사형 문장으로 작성</li>
<li>subject 외 자세한 내용이 필요하다면 작성</li>
</ul>
<h3 id="examples"><strong>Examples</strong></h3>
<ul>
<li><code>feat: 엄청난 버튼 추가</code></li>
<li><code>fix: &lt;어떤 이유&gt;인한 oo 컴포넌트 버그 수정</code></li>
<li><code>fix: 오타 수정</code></li>
<li><code>build: package.json dependency 업데이트</code></li>
<li><code>refactor: oo 컴포넌트 구조 리팩토링</code></li>
<li><code>style: white space 제거</code></li>
</ul>
<h3 id="todo"><strong>Todo</strong></h3>
<ul>
<li><code>BREAKING CHANGES</code>나 이슈번호를 남기는 방법에 대해서 이해하기</li>
<li>commitlint 적용하기</li>
<li>좀더 실무적인 예시 추가하기</li>
</ul>
<h2 id="references"><strong>References</strong></h2>
<ul>
<li><a href="https://gist.github.com/qoomon/5dfcdf8eec66a051ecd85625518cfd13">Conventional Commit Messages</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python:) Black으로 코드 스타일 자동화]]></title>
            <link>https://velog.io/@gyuseok-dev/Python.-Black-the-Code-Formatter</link>
            <guid>https://velog.io/@gyuseok-dev/Python.-Black-the-Code-Formatter</guid>
            <pubDate>Sun, 07 Mar 2021 10:34:18 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/gyuseok-dev/post/1e4acc75-5b8c-4aad-bf00-eb7a449d5c08/image.png" alt=""></p>
<p>회사에서 python code formatte로 Black을 사용하고 있어 정리해보려고 합니다</p>
<h3 id="왜-코드-스타일을-자동화-해야할까요">왜 코드 스타일을 자동화 해야할까요?</h3>
<blockquote>
<p>마치 한사람이 만든 것과 같은 코드</p>
</blockquote>
<p>여러사람이 코드를 짜다보면 저마다 자신이 편한 코드 스타일이 있습니다.
어떤사람은 <code>&quot;double quotes&quot;</code> 를 선호하고, 다른 누군가는 <code>&#39;single quote&#39;</code>를 선호할 수 있습니다.</p>
<p>서로 다른 스타일이 적용된 코드를 보면 다른 누군가이 코드를 수정하려고 할 때 혼란이 올 수 있습니다.
&#39;어떤 스타일에 맞춰야되는 거지?&#39;</p>
<p>또한, 가독성 높이기 위해서는 <code>마치 한사람이 만든 것과 같은 코드</code> 가 중요하다고 생각합니다.
설령, 서로 어떤 스타일로 통일 하자고 결정하고 문서를 만들어서 약속을 정하는 방법이 있지만 그것으로는 부족합니다.</p>
<p>문서를 매번 확인 해야하는 것은 번거롭고, 거의 그럴일은 없겠지만 만약 코드 스타일에 수정사항이 있으면 일일히 확인해서 기존의 것을 수정하기도 힘듭니다.</p>
<p>따라서 간단한 명령으로 코드 스타일을 통일시켜주는 방법을 찾아야할 필요가 있습니다.</p>
<h1 id="black">Black</h1>
<hr>
<blockquote>
<p><strong>Black</strong> is the uncompromising Python code formatter</p>
</blockquote>
<p><strong>Black</strong>은 엄격한 파이썬 코드 포맷터 입니다.</p>
<blockquote>
<p><strong>Black</strong> gives you speed, determinism, and freedom from pycodestyle nagging about formatting. You will save time and mental energy for more important matters.</p>
</blockquote>
<p>여러분은 <strong>Black</strong>을 통해 코드 스타일에 대한 잔소리로부터 자유를 얻을 수 있고, 더 중요한 문제에 시간과 에너지를 쏟을 수 있도록 해줍니다. (의역)</p>
<blockquote>
<p>Black makes code review faster by producing the smallest diffs possible</p>
</blockquote>
<p><strong>Black</strong>은 </p>
<p><a href="https://black.now.sh">Black Playground</a>에서 설치없이 어떻게 작용하는지 확인 해볼 수 있습니다.</p>
<h2 id="설치">설치</h2>
<pre><code class="language-bash">pip install black</code></pre>
<h2 id="사용방법">사용방법</h2>
<ul>
<li>변경전<pre><code class="language-python">from seven_dwwarfs import Grumpy, Happy, Sleepy, Bashful, Sneezy, Dopey, Doc
x = {  &#39;a&#39;:37,&#39;b&#39;:42,
</code></pre>
</li>
</ul>
<p>&#39;c&#39;:927}</p>
<p>x = 123456789.123456789E1234567891111111111111</p>
<p>if very_long_variable_name is not None and <br> very_long_variable_name.field &gt; 0 or <br> very_long_variable_name.is_debug:
 z = &#39;hello &#39;+&#39;world&#39;
else:
 world = &#39;world&#39;
 a = &#39;hello {}&#39;.format(world)
 f = rf&#39;hello {world}&#39;
if (this
and that): y = &#39;hello &#39;&#39;world&#39;#FIXME: <a href="https://github.com/python/black/issues/26">https://github.com/python/black/issues/26</a>
class Foo  (     object  ):
  def f    (self   ):
    return       37<em>-2
  def g(self, x,y=42):
      return y
def f  (   a: List[ int ]) :
  return      37-a[42-u :  y*</em>3]
def very_important_function(template: str,*variables,file: os.PathLike,debug:bool=False,):
    &quot;&quot;&quot;Applies <code>variables</code> to the <code>template</code> and writes to <code>file</code>.&quot;&quot;&quot;
    with open(file, &quot;w&quot;) as f:
     ...</p>
<h1 id="fmt-off">fmt: off</h1>
<p>custom_formatting = [
    0,  1,  2,
    3,  4,  5,
    6,  7,  8,
]</p>
<h1 id="fmt-on">fmt: on</h1>
<p>regular_formatting = [
    0,  1,  2,
    3,  4,  5,
    6,  7,  8,
]</p>
<pre><code>다음과 같이 보기만해도 불편함으로 가득한 코드가 있습니다.
해당 코드를 작성한뒤 터미널에 다음과 같이 입력해줍니다.
```bash
black {파일 또는 폴더 이름}</code></pre><p><img src="https://images.velog.io/images/gyuseok-dev/post/7f5b9227-f3c6-42b6-8e71-c1e1f51341cc/image.png" alt=""></p>
<ul>
<li>변경 후<pre><code class="language-python">from seven_dwwarfs import Grumpy, Happy, Sleepy, Bashful, Sneezy, Dopey, Doc
</code></pre>
</li>
</ul>
<p>x = {&quot;a&quot;: 37, &quot;b&quot;: 42, &quot;c&quot;: 927}</p>
<p>x = 123456789.123456789e1234567891111111111111</p>
<p>if (
    very_long_variable_name is not None
    and very_long_variable_name.field &gt; 0
    or very_long_variable_name.is_debug
):
    z = &quot;hello &quot; + &quot;world&quot;
else:
    world = &quot;world&quot;
    a = &quot;hello {}&quot;.format(world)
    f = rf&quot;hello {world}&quot;
if this and that:
    y = &quot;hello &quot; &quot;world&quot;  # FIXME: <a href="https://github.com/python/black/issues/26">https://github.com/python/black/issues/26</a></p>
<p>class Foo(object):
    def f(self):
        return 37 * -2</p>
<pre><code>def g(self, x, y=42):
    return y</code></pre><p>def f(a: List[int]):
    return 37 - a[42 - u : y ** 3]</p>
<p>def very_important_function(
    template: str,
    *variables,
    file: os.PathLike,
    debug: bool = False,
):
    &quot;&quot;&quot;Applies <code>variables</code> to the <code>template</code> and writes to <code>file</code>.&quot;&quot;&quot;
    with open(file, &quot;w&quot;) as f:
        ...</p>
<h1 id="fmt-off-1">fmt: off</h1>
<p>custom_formatting = [
    0,  1,  2,
    3,  4,  5,
    6,  7,  8,
]</p>
<h1 id="fmt-on-1">fmt: on</h1>
<p>regular_formatting = [
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
]</p>
<pre><code>확실히 이전 보다 깔끔해졌다고 느껴집니다.
이렇게 **Black**은 내가 어떻게 코드를 작성하든 일관된 스타일로 변경해줍니다.

다음과 같이 `fmt: on` 통해 원하는 구분에는 포맷팅이 적용되지 않게 할 수 있습니다.
```python
## 해당 구문은 오히려 포맷을 적용하지 않았을 때 가독성이 좋습니다.
# fmt: off
custom_formatting = [
    0,  1,  2,
    3,  4,  5,
    6,  7,  8,
]
# fmt: on
regular_formatting = [
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
]</code></pre><h2 id="옵션">옵션</h2>
<p>여러 옵션을 통해 팀의 코드 스타일에 맞춰 custom 할 수 있습니다.</p>
<p><code>-l</code>  : 한 라인에 최대 글자 수 (초기값 88)</p>
<p><code>—diff</code> : 파일을 변경하지 않고 변경되는 부분을 콘솔로 보여준다.</p>
<p><code>—color</code> : <code>—diff</code>를 사용했을 때 변경점에 색을 입힌다.</p>
<p><code>black {파일명 또는 폴더명} -l 80 --diff --color</code></p>
<p>다음과 같이 사용하면 변경사항을 preview 할 수 있습니다.</p>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/4a9b2b60-3f9b-4443-b669-1fc86502fb28/image.png" alt=""></p>
<h2 id="참고">참고</h2>
<p><a href="https://github.com/psf/black">Black</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 안전한 땅]]></title>
            <link>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%95%88%EC%A0%84%ED%95%9C-%EB%95%85</link>
            <guid>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%95%88%EC%A0%84%ED%95%9C-%EB%95%85</guid>
            <pubDate>Sun, 07 Feb 2021 07:00:55 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/5d04ae03-5fa8-4fca-abdf-4b202d85add1/image.png" alt=""></p>
<h3 id="코드">코드</h3>
<pre><code class="language-python">def detect_mine(ground, scope):
    size = len(ground)
    index = { 
        &#39;x&#39;: 0, 
        &#39;y&#39;: 0
    }
    result = 0
    while True:
        sum = 0
        # scope 내부 검사
        for x in range(index[&#39;y&#39;], index[&#39;y&#39;] + scope):
            for y in range(index[&#39;x&#39;], index[&#39;x&#39;] + scope):
                if ground[y][x]:
                    sum += 1
        # 최대값 판별
        result = max(result, sum)
        # index 변경
        if index[&#39;x&#39;] + scope == size:
            if index[&#39;y&#39;] + scope == size:
                return result
            index[&#39;x&#39;] = 0
            index[&#39;y&#39;] += 1
        else:
            index[&#39;x&#39;] += 1


if __name__ == &#39;__main__&#39;:

    matrix = [
                [1, 0, 0, 1, 0],
                [0, 1, 0, 0, 1],
                [0, 0, 0, 1, 0],
                [0, 0, 0, 0, 0],
                [0, 0, 1, 0, 0],
    ]
    print(detect_mine(matrix, 3))
    #3</code></pre>
<h3 id="reference">Reference</h3>
<p><a href="https://www.notion.so/Python-100-6ee1860ce29a41bc8eb6b9cfa7d7f06c">제주 코딩 베이스 캠프 코딩 페스티벌 python 100제</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 최단 경로 구하기]]></title>
            <link>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B5%9C%EB%8B%A8-%EA%B2%BD%EB%A1%9C-%EA%B5%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B5%9C%EB%8B%A8-%EA%B2%BD%EB%A1%9C-%EA%B5%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 06 Feb 2021 09:05:36 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/27264baf-f7dc-4eba-aadc-67e3c5d3b0fb/image.png" alt=""></p>
<h3 id="개념">개념</h3>
<ol>
<li>시작위치를 q 넣고, depth를 -1로 초기화</li>
<li>depth를 1 증가</li>
<li>depth별로 loop를 진행 (for i in 0~size(q))</li>
<li>해당 depth의 node를 pop </li>
<li>목표점에 도달했는지 비교</li>
<li>목표점에 도달 했다면 depth <strong>return</strong></li>
<li>목표점에 도달하지 못했다면 이동 경로에 node를 넣고 다음경로를 q에 push</li>
<li>동일한 depth의 검사가 끝날 때까지 4~7 반복</li>
<li>더이상 q가 없을 때까지 진행(while)</li>
</ol>
<blockquote>
<p>해당 방식은 알고리즘은 트리 구조에서만 가능하다.</p>
</blockquote>
<h3 id="코드">코드</h3>
<pre><code class="language-python">from collections import deque 

def shortest_path(graph, start, end):
    result = -1
    visited = []
    q = deque([start])
    while q:
        result += 1
        size = len(q)

        for _ in range(size):
            node = q.popleft()
            if node == end:
                return result

            visited.append(node)
            q += graph[node] - set(visited)

if __name__ == &#39;__main__&#39;:
    graph = {
        &#39;A&#39;: set([&#39;B&#39;, &#39;C&#39;]),
        &#39;B&#39;: set([&#39;A&#39;, &#39;D&#39;, &#39;E&#39;]),
        &#39;C&#39;: set([&#39;A&#39;, &#39;F&#39;]),
        &#39;D&#39;: set([&#39;B&#39;]),
        &#39;E&#39;: set([&#39;B&#39;, &#39;F&#39;]),
        &#39;F&#39;: set([&#39;C&#39;, &#39;E&#39;])}

    print(shortest_path(graph, &#39;A&#39;, &#39;F&#39;))
    # 2</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 이상한 369]]></title>
            <link>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%9D%B4%EC%83%81%ED%95%9C-369</link>
            <guid>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%9D%B4%EC%83%81%ED%95%9C-369</guid>
            <pubDate>Fri, 05 Feb 2021 11:12:54 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/b12346cd-f196-45ba-b940-3e3dfc072ed9/image.png" alt=""></p>
<h3 id="개념">개념</h3>
<ol>
<li>1~n까지 loop</li>
<li>해당 숫자를 문자열로 분리 loop</li>
<li>해당 글자가 0이거나 3의 배수가 아니면 return 0</li>
<li>3번 조건이 아니면 return 1 </li>
<li>2~4을 카운트</li>
</ol>
<h3 id="코드">코드</h3>
<pre><code class="language-python">def game_369(n):

    def check_only_369(n):
        for c in str(n):
            if int(c) == 0 or int(c) % 3 != 0:
                return 0
        return 1

    cnt = 0
    for i in range(1,n+1):
        cnt += check_only_369(i) 
    return cnt 

if __name__ == &#39;__main__&#39;:

    print(game_369(93))
    # 10</code></pre>
<h3 id="정규식으로도-풀어보자">정규식으로도 풀어보자</h3>
<h3 id="개념-1">개념</h3>
<ol>
<li>정규표현식 = ^[369]+$ =&gt; 3,6,9만 포함하는 문자열</li>
<li>해당 문자열이 정규표현식과 일치하면 카운트</li>
</ol>
<h3 id="코드-1">코드</h3>
<pre><code class="language-python">import re
def game_369(n):

    cnt = 0
    p = re.compile(&#39;^[369]+$&#39;)
    for i in range(1,n+1):
        if re.search(p, str(i)):
            cnt += 1 
    return cnt 

if __name__ == &#39;__main__&#39;:

    print(game_369(93))
    # 10
</code></pre>
<p>정규표현식을 사용하면 조건이 있는 문자열를 판단할 때 더 간단하게 해결할 수 있다.</p>
<h3 id="reference">Reference</h3>
<p><a href="https://www.notion.so/Python-100-6ee1860ce29a41bc8eb6b9cfa7d7f06c">제주 코딩 베이스 캠프 코딩 페스티벌 python 100제</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 엘레베이터에 짐 싣기]]></title>
            <link>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%97%98%EB%A0%88%EB%B2%A0%EC%9D%B4%ED%84%B0%EC%97%90-%EC%A7%90-%EC%8B%A3%EA%B8%B0</link>
            <guid>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%97%98%EB%A0%88%EB%B2%A0%EC%9D%B4%ED%84%B0%EC%97%90-%EC%A7%90-%EC%8B%A3%EA%B8%B0</guid>
            <pubDate>Fri, 05 Feb 2021 04:07:59 GMT</pubDate>
            <description><![CDATA[<h3 id="코드">코드</h3>
<pre><code class="language-python">def elevator(dumps, weight):
    result = 0
    dumps.sort()

    while (dumps):
        dump = dumps.pop()
        div = weight // dump
        mod = weight % dump

        if dumps == []:
            if mod is not 0:
                return -1
            else:
                result += div
        else:
            if div:
                result += div
                weight -= div * dump                

    return result

if __name__ == &#39;__main__&#39;:
    print(elevator([3,9],24))
    # 4
    print(elevator([2,3],10))
    # -1</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 골드바흐의 추측]]></title>
            <link>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B3%A8%EB%93%9C%EB%B0%94%ED%9D%90%EC%9D%98-%EC%B6%94%EC%B8%A1</link>
            <guid>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B3%A8%EB%93%9C%EB%B0%94%ED%9D%90%EC%9D%98-%EC%B6%94%EC%B8%A1</guid>
            <pubDate>Thu, 04 Feb 2021 12:21:13 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<blockquote>
<p>골드바흐의 추측(Goldbach&#39;s conjecture)은 오래전부터 알려진 정수론의 미해결 문제로, 2보다 큰 모든 짝수는 두 개의 소수(Prime number)의 합으로 표시할 수 있다는 것이다. 이때 하나의 소수를 두 번 사용하는 것은 허용한다. - 위키백과</p>
</blockquote>
<p>위 설명에서 2보다 큰 모든 짝수를 두 소수의 합으로 나타낸 것을 골드바흐 파티션이라고 합니다.</p>
<blockquote>
</blockquote>
<p>예)
100 == 47 + 53
56 == 19 + 37</p>
<blockquote>
</blockquote>
<p>2보다 큰 짝수 n이 주어졌을 때, 골드바흐 파티션을 출력하는 코드를 작성하세요. </p>
<h3 id="개념">개념</h3>
<ol>
<li>n이 주어졌을 때 n까지의 모든 소수 출력</li>
<li>(n - 출력된 소수)가 소수인지 확인</li>
<li>오름차순 정렬</li>
<li>중복제거
 예를들어 n = 100 이면 [47, 53], [53, 47]은 중복됨</li>
</ol>
<h3 id="코드">코드</h3>
<pre><code class="language-python">import math

def goldbach(n):
    if n &lt;= 2 or n % 2 != 0:
        return &#39;적절하지 못한 입력(2보다 큰 짝수)&#39;

    def prime(n):
        array = [True for i in range(n+1)]
        # 에라토스테네스의 체
        for i in range(2, int(math.sqrt(n)) + 1):
            if array[i] == True:
                j = 2
                while i * j &lt;= n:
                    array[i * j] = False
                    j += 1
        return [ i for i in range(2, n+1) if array[i] ]

    result = []

    prime_arr = prime(n) # n까지 전체 소수 리스트
    for i in prime_arr:
        if (n - i) in prime_arr: # n-소수가 소수인지 판별
            values = sorted([i, n-i])
            if values in result: # 중복 제거
                return result
            result.append(values)

    return result

if __name__ == &#39;__main__&#39;:

    print(goldbach(100))
    # [[3, 97], [11, 89], [17, 83], [29, 71], [41, 59], [47, 53]]
    print(goldbach(56))
    # [[3, 53], [13, 43], [19, 37]]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 행렬 곱하기]]></title>
            <link>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%96%89%EB%A0%AC-%EA%B3%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%96%89%EB%A0%AC-%EA%B3%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 04 Feb 2021 12:08:50 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<blockquote>
<p>행렬 2개가 주어졌을 때 곱할 수 있는 행렬인지 확인하고 곱할 수 있다면 그 결과를, 곱할 수 없다면 -1을 출력하는 프로그램을 만들어주세요.</p>
<pre><code class="language-python">input = [[1, 2],[2, 4]],[[1, 0],[0, 3]]
output = [[1,6], [2,12]]</code></pre>
</blockquote>
<pre><code>
### 개념

1. 행렬 치환
2. 곱의 합
3. 연산

### 코드
```python
def matrix_mul(matrix_1, matrix_2):
    #곱의 합
    def sum_product(arr1, arr2):
        return sum([a * b for a, b in zip(arr1, arr2)])

    #행렬 뒤집기
    def tran(matrix):           
        return [[matrix[j][i] for j in range(len(matrix))] for i in range(len(matrix[0]))]

    return [[sum_product(arr1, arr2) for arr2 in tran(matrix_2)] for arr1 in matrix_1]

if __name__ == &#39;__main__&#39;:
    print(matrix_mul([[1, 2],[2, 4]],[[1, 0],[0, 3]]))
    # [[1,6], [2,12]]</code></pre><h3 id="reference">Reference</h3>
<p><a href="https://www.notion.so/Python-100-6ee1860ce29a41bc8eb6b9cfa7d7f06c">제주 코딩 베이스 캠프 코딩 페스티벌 python 100제</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 가장 비슷한 면적 찾기]]></title>
            <link>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B0%80%EC%9E%A5-%EB%B9%84%EC%8A%B7%ED%95%9C-%EB%A9%B4%EC%A0%81-%EC%B0%BE%EA%B8%B0</link>
            <guid>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B0%80%EC%9E%A5-%EB%B9%84%EC%8A%B7%ED%95%9C-%EB%A9%B4%EC%A0%81-%EC%B0%BE%EA%B8%B0</guid>
            <pubDate>Thu, 04 Feb 2021 11:59:14 GMT</pubDate>
            <description><![CDATA[<h3 id="개념">개념</h3>
<ol>
<li>기준이되는 값에서 나머지의 값의 절대차를 구함</li>
<li>오름차순 정렬 후 가장 첫번째 값 추출</li>
</ol>
<h3 id="코드">코드</h3>
<pre><code class="language-python">def near_country(countries, name):
    base = countries[name]
    del countries[name]

    for k, v in countries.items():
        countries[k] = abs(countries[k] - base)

    sorted_contries = sorted(countries.items(), key = lambda item: item[1])

    print(a[0][0], a[0][1], sep=&#39; &#39;)


if __name__ == &#39;__main__&#39;:
    nationWidth = {
        &#39;korea&#39;: 220877,
        &#39;Rusia&#39;: 17098242,
        &#39;China&#39;: 9596961,
        &#39;France&#39;: 543965,
        &#39;Japan&#39;: 377915,
        &#39;England&#39; : 242900 }

    near_country(nationWidth, &#39;korea&#39;)

    # England 22023
</code></pre>
<h3 id="reference">Reference</h3>
<p><a href="https://www.notion.so/Python-100-6ee1860ce29a41bc8eb6b9cfa7d7f06c">제주 코딩 베이스 캠프 코딩 페스티벌 python 100제</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 퀵 정렬]]></title>
            <link>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%80%B5-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%80%B5-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Thu, 04 Feb 2021 11:55:35 GMT</pubDate>
            <description><![CDATA[<h3 id="퀵-정렬quick-sort">퀵 정렬(Quick Sort)</h3>
<blockquote>
<p>퀵 정렬(Quicksort)은 찰스 앤터니 리처드 호어가 개발한 정렬 알고리즘이다.</p>
</blockquote>
<p>다른 원소와의 비교만으로 정렬을 수행하는 비교 정렬에 속한다.</p>
<blockquote>
</blockquote>
<p>퀵 정렬은 n개의 데이터를 정렬할 때, 최악의 경우에는 O(n2)번의 비교를 수행하고, 평균적으로 O(n log n)번의 비교를 수행한다.</p>
<h3 id="개념">개념</h3>
<ol>
<li>두 개의 배열을 생성(front, back)</li>
<li>중간 값(pivot) 보다 크면 뒤로 작으면 앞으로 이동</li>
<li>더이상 중간 값이 없을 때 까지 1~2번 반복(재귀함수 사용) </li>
</ol>
<h3 id="코드">코드</h3>
<pre><code class="language-python">import collections

def quick_sort(arr):
    length = len(arr)

    if length &lt;= 1:
      return arr

    mid = arr.pop(length//2)
    front = []
    back = []

    for i in arr:
      if i &lt; mid:
        front.append(i)
      else:
        back.append(i)

      return quick_sort(front) + [mid] + quick_sort(back)

print(quick_sort([4,3,2,1]))
# [1, 2, 3, 4]</code></pre>
<h3 id="reference">Reference</h3>
<p><a href="https://www.notion.so/Python-100-6ee1860ce29a41bc8eb6b9cfa7d7f06c">제주 코딩 베이스 캠프 코딩 페스티벌 python 100제</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 하노이탑]]></title>
            <link>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%95%98%EB%85%B8%EC%9D%B4%ED%83%91</link>
            <guid>https://velog.io/@gyuseok-dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%95%98%EB%85%B8%EC%9D%B4%ED%83%91</guid>
            <pubDate>Wed, 03 Feb 2021 05:40:57 GMT</pubDate>
            <description><![CDATA[<h3 id="개념">개념</h3>
<blockquote>
<ol>
<li>처음에 모든 원판은 A기둥에 꽂혀 있다.</li>
<li>모든 원판의 지름은 다르다.</li>
<li>이 원반은 세 개의 기둥 중 하나에 반드시 꽂혀야 한다.</li>
<li>작은 원반 위에 큰 원반을 놓을 수 없다.</li>
<li>한 번에 하나의 원판(가장 위에 있는 원판) 만을 옮길 수 있다.</li>
</ol>
</blockquote>
<h3 id="코드">코드</h3>
<pre><code class="language-python">def hanoi(n, from_obj, to_obj, aux_obj):
    if n == 1:
        print(f&#39;{from_obj}-&gt;{to_obj}&#39;)
        return
    hanoi(n-1, from_obj, aux_obj, to_obj)
    print(f&#39;{from_obj}-&gt;{to_obj}&#39;)
    hanoi(n-1, aux_obj, to_obj, from_obj)

if __name__ == &#39;__main__&#39;:
    print(&#39;n=======&gt;1&#39;)
    hanoi(1, &#39;A&#39;, &#39;B&#39;, &#39;C&#39;)
    print(&#39;n=======&gt;2&#39;)
    hanoi(2, &#39;A&#39;, &#39;B&#39;, &#39;C&#39;)</code></pre>
<h3 id="결과">결과</h3>
<pre><code class="language-shell">» python3 playground.py
n=======&gt;1
A-&gt;B
n=======&gt;2
A-&gt;C
A-&gt;B
C-&gt;B
n=======&gt;3
A-&gt;B
A-&gt;C
B-&gt;C
A-&gt;B
C-&gt;A
C-&gt;B
A-&gt;B</code></pre>
<h3 id="reference">Reference</h3>
<p><a href="https://www.notion.so/Python-100-6ee1860ce29a41bc8eb6b9cfa7d7f06c">제주 코딩 베이스 캠프 코딩 페스티벌 python 100제</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Python] pytest.fixture()]]></title>
            <link>https://velog.io/@gyuseok-dev/pytest-pytest.fixture</link>
            <guid>https://velog.io/@gyuseok-dev/pytest-pytest.fixture</guid>
            <pubDate>Sun, 03 Jan 2021 12:20:00 GMT</pubDate>
            <description><![CDATA[<p><code>pytest</code>를 사용해서 테스트를 하다보면 <code>fixture</code> 대한 이해가 중요다.</p>
<p>unittest의 setUp처럼 테스트 실행 DB 등록같이 전처리가 필요할 때 사용한다.</p>
<p>특히 <code>@pytest.fixture(scope=??)</code> scope에 따라 실행되는 범위가 다르기 때문에
직접 실행하면서 이해해보자</p>
<h2 id="pytestfixture">@pytest.fixture()</h2>
<pre><code class="language-python">import pytest

@pytest.fixture(scope=&#39;function&#39;)
def fixture():
    print(&#39;\nTest_1 Fixture!!&#39;)

def test_a(fixture):
    print(&#39;\ntest_a&#39;)

def test_b(fixture):
    print(&#39;\ntest_b&#39;)

def test_c(fixture):
    print(&#39;\ntest_c&#39;)

# in test_2.py
from test_1 import fixture 

def test_2_a(fixture):
    print(&#39;\ntest_a&#39;)

def test_2_d(fixture):
    print(&#39;\ntest_b&#39;)

def test_2_c(fixture):
    print(&#39;\ntest_c&#39;)</code></pre>
<p>아무 scope=function일 때는 모든 test 함수에서 실행된다.</p>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/7cef27a4-1d17-48d6-b2ab-77b80caa91d6/image.png" alt=""></p>
<h2 id="pytestfixturescopemodule">@pytest.fixture(scope=&#39;module&#39;)</h2>
<pre><code class="language-python"># in test_1.py
import pytest

@pytest.fixture(scope=&#39;module&#39;)
def fixture():
    print(&#39;\nTest_1 Fixture!!&#39;)

def test_a(fixture):
    print(&#39;\ntest_a&#39;)

def test_b(fixture):
    print(&#39;\ntest_b&#39;)

def test_c(fixture):
    print(&#39;\ntest_c&#39;)

# in test_2.py
from test_1 import fixture 

def test_2_a(fixture):
    print(&#39;\ntest_a&#39;)

def test_2_d(fixture):
    print(&#39;\ntest_b&#39;)

def test_2_c(fixture):
    print(&#39;\ntest_c&#39;)</code></pre>
<p>다음과 같이 파일을 하나 더생성한뒤 fixture함수를 import 하였다.
<img src="https://images.velog.io/images/gyuseok-dev/post/f6f7253d-e4a1-4984-b307-e09bfd0eb6da/image.png" alt=""></p>
<p>scope를 module로 하면 fixture가 파일 단위로 실행됨을 알 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flask] aws에서 gunicorn 으로 실행하기]]></title>
            <link>https://velog.io/@gyuseok-dev/flask-aws%EC%97%90%EC%84%9C-gunicorn-%EC%9C%BC%EB%A1%9C-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@gyuseok-dev/flask-aws%EC%97%90%EC%84%9C-gunicorn-%EC%9C%BC%EB%A1%9C-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 03 Jan 2021 11:46:37 GMT</pubDate>
            <description><![CDATA[<p>기업협업 프로젝트를 하면서 프론트엔드에게 안정적으로 서버를 제공하기 위해서는 <code>gunicorn</code>을 이용해서 서버를 24시간 켜주어야 한다.</p>
<pre><code class="language-bash"> gunicorn --bind 0.0.0.0:8000 app:app</code></pre>
<p>이렇게 해서 사용할 port를 지정해준다.</p>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/dd124ecb-f8d1-4444-854b-e5575800440c/image.png" alt=""></p>
<p>안된다.. 아무래도 나의 앱에서는 <code>Aplication Factory</code>를 사용해서 그런것같다.</p>
<p>app.py에서 create_app으로 app을 만들어 주기 때문에 다음과 같이 사용하자.</p>
<pre><code class="language-bash"> gunicorn --bind 0.0.0.0:8000 app:create_app()</code></pre>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/b32c110c-435c-4800-8cdc-bfa2d7bd1f6f/image.png" alt=""></p>
<p>안되는데?</p>
<p>당황하지말고 구글링을 좀 더한뒤 다음과 같이 해보자 ㅎㅎ</p>
<pre><code class="language-bash"> gunicorn --bind 0.0.0.0:8000 &quot;app:create_app()&quot;</code></pre>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/9d5a890a-670e-4542-afde-0f722ed8e9d4/image.png" alt=""></p>
<p>잘된다.</p>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/4a95e488-bfa3-4a25-9059-bf8e1718c322/image.png" alt=""></p>
<p>통신도 원활히 되는 것을 확인할 수 있다.</p>
<p>good~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flask] Pytest로 테스트하기]]></title>
            <link>https://velog.io/@gyuseok-dev/flask-test</link>
            <guid>https://velog.io/@gyuseok-dev/flask-test</guid>
            <pubDate>Sun, 27 Dec 2020 13:27:54 GMT</pubDate>
            <description><![CDATA[<p>살다보면 이래저래 시간이 없다는 핑계로 그냥 넘어가자하는 순간들이 많다.
하지만 넘기고 나서 계속해서 신경 쓰이고 불편해서 &#39;아...그냥 그때라도 할걸..&#39; 하는 순간들이 생각보다 많은데 고것이 나에게는 Test다. 바쁘다..진도도 나가고 싶고 문서도 만들고 싶고 Validation 라이브러리도 적용하고 싶다. 하지만 그런 자유를 얻으려면 반드시 테스트가 필요하다. 우리는 할 수 있다. 테스트를 하자.</p>
<h3 id="목표">목표</h3>
<p>flask로 user CR 만들기</p>
<ul>
<li>준비물<ul>
<li>flask</li>
<li>mysql</li>
<li>pytest</li>
<li>sqlalchemy<h4 id="하나-시작은-항상-가상환경부터">하나. 시작은 항상 가상환경부터</h4>
<pre><code class="language-bash">sudo apt-get install python3-venv #설치
python3 -m venv venv #생성
. venv/bin/activate #활성화</code></pre>
이쯤되면 외울때도 됫겠다.</li>
</ul>
</li>
</ul>
<h4 id="둘-requirments-설치">둘. requirments 설치</h4>
<pre><code class="language-python">pip install flask
pip install sqlalchemy
pip install mysqlclient</code></pre>
<h4 id="셋-app-작성">셋. app 작성</h4>
<pre><code class="language-python"># in main.py
from flask import Flask, jsonify, request
from flask.views import MethodView

from sqlalchemy    import create_engine, text

import config

def create_app(test_config=None):
    app = Flask(__name__)

    if test_config:
        app.config.update(test_config)
    database = create_engine(app.config[&#39;DB_URL&#39;], encoding = &#39;utf-8&#39;, max_overflow = 0)

    # 뷰
    class UserListView(MethodView):
        def __init__(self, database):
            self.db = database

        def get(self):
            users = self.db.execute(&quot;SELECT * FROM users&quot;)
            return jsonify({&#39;users&#39;: [dict(row) for row in users] }), 200

    # 라우팅
    app.add_url_rule(&#39;/user&#39;, view_func=UserListView.as_view(&#39;user&#39;,db))
    return app

if __name__ == &#39;__main__&#39;:
    app = create_app()
    app.run(host=&#39;0.0.0.0&#39;, debug=True, port=8000)</code></pre>
<p>간단하게 sqlachemy를 이용해서 유저정보를 작성하는 앱을 만들었다.</p>
<p>여기서 테스트 시 test_config를 통해 DB_URL를 TEST용 DB로 바꿔 줄 것이다.</p>
<h4 id="넷-test-작성">넷. test 작성</h4>
<pre><code class="language-python"></code></pre>
<p>테스트를 작성하기 위해서는 다음을 고려해야한다.</p>
<ol>
<li>테스트용 DB를 따로 사용해야한다.</li>
<li>테스트가 돌때마다 테스트용 DB의 테이블이 초기화되어야한다.</li>
</ol>
<ul>
<li>테스트용 DB 적용하기</li>
</ul>
<pre><code class="language-python"># in test_main.py
...

import pytest
from main import create_app
from sqlalchemy import create_engine, text
import config

TEST_CONFIG = {
    &#39;TESTING&#39;: True,
    &#39;DB_URL&#39;: config.TEST_DB_URL
}

@pytest.fixture(scope=&#39;session&#39;) # 테스트 실행시 한번만 실행
def app():
    app = create_app(TEST_CONFIG)
    return app

@pytest.fixture(scope=&#39;session&#39;)
def db():
    db = create_engine(TEST_CONFIG[&#39;DB_URL&#39;], encoding = &#39;utf-8&#39;, max_overflow = 0)
    return db

@pytest.fixture # 매 테스트 실행 마다 실행
def client(app, db):
    db_file(db, &#39;test.sql&#39;)
    client = app.test_client()
    return client

...
</code></pre>
<p>여기서 create_app에 설정해둔 TEST_CONFIG를 넣어주면서 테스트용 DB에서 테스트가 진행될 수 있도록 바꾸었다.</p>
<p>또한 db_file이라는 파일이라는 함수를 작성하여 &#39;test.sql&#39; 정보를 읽어 테스트 함수마다 초기화되도록 fixture function 단위인 client에 넣어주었다.</p>
<blockquote>
<ul>
<li>@pytest.fixture
함수형 테스트에서 사전작업이 필요한 부분들을 묶어 줄 수 있다.
아무 설정도 하지않으면 매 테스트 마다 실행된다.</li>
</ul>
</blockquote>
<ul>
<li>@pytest.fixture(scope=&#39;session&#39;)
테스트 전반에 한번만 수행된다.
app을 생성하거나 db를 지정하는 동작은 테스트시 한번만 하면되므로 <code>session</code>으로 지정하였다.</li>
</ul>
<p>test 파일에 다음 항목을 추가해주었다.</p>
<h4 id="다섯-sql-파일-만들기">다섯. sql 파일 만들기</h4>
<p>UserView를 테스트 하기 위해서
users 테이블를 만들고 한명의 유저를 Insert하는 sql를 넣었다.</p>
<pre><code class="language-sql">-- in test.sql
DROP TABLE users;
CREATE TABLE users
(
    `id`             INT             NOT NULL    AUTO_INCREMENT COMMENT &#39;pk&#39;,
    `name`           VARCHAR(100)    NOT NULL    COMMENT &#39;유저이름&#39;,
    PRIMARY KEY (id)
);
INSERT INTO users (id, name) VALUES (1, &#39;user1&#39;);</code></pre>
<ul>
<li>테스트
중간중간 자신의 일이 잘진행되고 있는지 확인하는것은 시간을 매우 아껴준다.<pre><code class="language-mysql">#mysql 접속
&gt;&gt; source test.sql
&gt;&gt; desc users</code></pre>
<img src="https://images.velog.io/images/gyuseok-dev/post/8df71221-c11a-435e-b734-dacf16325397/image.png" alt=""></li>
</ul>
<h4 id="다섯-test-작성-및-실행">다섯. test 작성 및 실행</h4>
<p>이제 실제 UserView를 테스트하는 함수를 작성해보자.</p>
<pre><code class="language-python"># in test_main.py
...
def test_get_user(client):
    response = client.get(&#39;/user&#39;)
    assert response.status_code == 200
    assert response.json == { &#39;users&#39;: [{&#39;id&#39;: 1, &#39;name&#39;:&#39;user1&#39;}] } </code></pre>
<p>이렇게 하면 test.sql에서 만들어진 user1의 유저를 가져온다.</p>
<p>작성할 것은 다 작성했다. 실행해보자.</p>
<pre><code class="language-bash">pytest -s</code></pre>
<p>테스트가 잘 통과되었다.</p>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/cd09bb84-3916-406a-aa94-6888256ef565/image.png" alt=""></p>
<p>DB에 user1이 잘들어왔는지도 확인해보자.</p>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/6a99f6d1-646a-48d4-bf49-afffbb95b06a/image.png" alt=""></p>
<p>이런 방식으로 테스트DB를 원하는대로 세팅하고 테스트를 진행하면된다.</p>
<h4 id="기타">기타</h4>
<ul>
<li>pytest는 <code>test_*</code> 파일을 자동으로 읽어온다. 테스트파일은 반드시 <code>test_</code>로 시작하도록 한다. </li>
<li>fuction도 동일하게 <code>def test_</code>로 시작하도록 한다.</li>
</ul>
<h4 id="전체-파일">전체 파일</h4>
<p>main.py</p>
<pre><code class="language-python"># in main.py
from flask import Flask, jsonify, request
from flask.views import MethodView

from sqlalchemy    import create_engine, text

import config

def create_app(test_config=None):
    app = Flask(__name__)

    app.config.from_pyfile(&quot;config.py&quot;)

    if test_config:
        app.config.update(test_config)

    db = create_engine(app.config[&#39;DB_URL&#39;], encoding = &#39;utf-8&#39;, max_overflow = 0)

    # 뷰
    class UserListView(MethodView):
        def __init__(self, database):
            self.db = database

        def get(self):
            users = self.db.execute(&quot;SELECT * FROM users&quot;)
            return jsonify({&#39;users&#39;: [dict(row) for row in users] }), 200

    # 라우팅
    app.add_url_rule(&#39;/user&#39;, view_func=UserListView.as_view(&#39;user&#39;,db))
    return app

if __name__ == &#39;__main__&#39;:
    app = create_app()
    app.run(host=&#39;0.0.0.0&#39;, debug=True, port=8000)
</code></pre>
<p>tast_main.py</p>
<pre><code class="language-python"># in test_main.py
import pytest
from main import create_app
from sqlalchemy import create_engine, text
import config

TEST_CONFIG = {
    &#39;TESTING&#39;: True,
    &#39;DB_URL&#39;: config.TEST_DB_URL
}

def db_file(db, filename):
    sql_lines = []
    with open(filename, &#39;r&#39;) as file_data:
        # .sql를 주석을 제외하고 라인별로 분류
        sql_lines = [line.strip(&#39;\n&#39;) for line in file_data if not line.startswith(&#39;--&#39;) and line.strip(&#39;\n&#39;)]

        with db.connect() as conn:
            sql_command = &#39;&#39;
            for line in sql_lines:
                sql_command += line
                # ; 나오면 execute
                if sql_command.endswith(&#39;;&#39;):
                    try:
                        conn.execute(text(sql_command))
                    except Exception as e:
                        print(&#39;Fail DB Reset!!&#39;)
                        print(e)
                        return False
                    finally:
                        sql_command = &#39;&#39;
    return True

@pytest.fixture(scope=&#39;session&#39;) # 테스트 실행시 한번만 실행
def app():
    app = create_app(TEST_CONFIG)
    return app

@pytest.fixture(scope=&#39;session&#39;)
def db():
    db = create_engine(TEST_CONFIG[&#39;DB_URL&#39;], encoding = &#39;utf-8&#39;, max_overflow = 0)
    return db

@pytest.fixture # 매 테스트 실행 마다 실행
def client(app, db):
    db_file(db, &#39;test.sql&#39;)
    client = app.test_client()
    return client

def test_get_user(client):
    response = client.get(&#39;/user&#39;)
    assert response.status_code == 200
    assert response.json == { &#39;users&#39;: [{&#39;id&#39;: 1, &#39;name&#39;:&#39;user1&#39;}] } </code></pre>
<p>config.py</p>
<pre><code class="language-python">DB = {
    &#39;host&#39;: &#39;localhost&#39;,
    &#39;user&#39;: &#39;root&#39;,
    &#39;pass&#39;: &#39;&#39;,
    &#39;db&#39;: &#39;my_flask&#39;
}

DB_URL = f&#39;mysql://{DB[&quot;user&quot;]}:{DB[&quot;pass&quot;]}@{DB[&quot;host&quot;]}/{DB[&quot;db&quot;]}?charset=utf8&#39;

TEST_DB = &#39;test&#39;

TEST_DB_URL = f&#39;mysql://{DB[&quot;user&quot;]}:{DB[&quot;pass&quot;]}@{DB[&quot;host&quot;]}/{TEST_DB}?charset=utf8&#39;</code></pre>
<p>test.sql</p>
<pre><code class="language-sql">DROP TABLE users;
CREATE TABLE users
(
    `id`             INT             NOT NULL    AUTO_INCREMENT COMMENT &#39;pk&#39;,
    `name`           VARCHAR(100)    NOT NULL    COMMENT &#39;유저이름&#39;,
    PRIMARY KEY (id)
);
INSERT INTO users (id, name) VALUES (1, &#39;user1&#39;);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flask] Error handler]]></title>
            <link>https://velog.io/@gyuseok-dev/flask-error-handler</link>
            <guid>https://velog.io/@gyuseok-dev/flask-error-handler</guid>
            <pubDate>Wed, 23 Dec 2020 17:46:30 GMT</pubDate>
            <description><![CDATA[<h3 id="시작하며">시작하며</h3>
<p>이런 코드가 있다.</p>
<pre><code class="language-python">from flask import Flask, jsonify, request
app = Flask(__name__)

@app.route(&#39;/a&#39;, methods=[&#39;POST&#39;])
def a():
    try:
        data = request.get_json()[&#39;a&#39;]
        return jsonify({&#39;message&#39;: data}), 200
    except KeyError:
        return jsonify({&#39;massage&#39;: &#39;KEY_ERROR&#39;}), 400

@app.route(&#39;/b&#39;, methods=[&#39;POST&#39;])
def b():
    try:
        data = request.get_json()[&#39;b&#39;]
        return jsonify({&#39;message&#39;: data}), 200
    except KeyError:
        return jsonify({&#39;massage&#39;: &#39;KEY_ERROR&#39;}), 400

if __name__ == &#39;__main__&#39;:
    app.run(host=&#39;127.0.0.1&#39;, debug=True, port=8001)</code></pre>
<p>분명 잘동작하는 코드지만 <code>KeyError</code> 관련해서 중복의 냄새가 난다.</p>
<p>특히 POST의 경우 KeyError처리는 필수적이기 때문에 이러한 중복은 계속늘어날 수 있다.</p>
<p>flask에서는 친절하게도 이런 에러처리를 도와주는 decorator가 있다.
<a href="https://flask.palletsprojects.com/en/1.1.x/errorhandling/">도와줘요 공식문서</a></p>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/4d196020-b0b7-4db2-bd9b-4d62dbcd8d34/image.png" alt=""></p>
<p>대충 decorator에 처리하고 싶은 에러를 넣어주면 해당함수에서 처리해주고
decorator를 쓰기 싫으면 app에다 등록도 할 수 있다는 것 같다.</p>
<p>손가락이 근질근질하다. 바로 해보자.</p>
<h3 id="적용하기">적용하기</h3>
<pre><code class="language-python">from flask import Flask, jsonify, request
app = Flask(__name__)

# error handler
@app.errorhandler(KeyError)
def handler_key_error():
    return jsonify({&#39;massage&#39;: &#39;KEY_ERROR&#39;}), 400

@app.route(&#39;/a&#39;, methods=[&#39;POST&#39;])
def a():
#    try:
    data = request.get_json()[&#39;a&#39;]
    return jsonify({&#39;message&#39;: data}), 200
#    except KeyError:
#        return jsonify({&#39;massage&#39;: &#39;KEY_ERROR&#39;}), 400

@app.route(&#39;/b&#39;, methods=[&#39;POST&#39;])
def b():
#    try:
        data = request.get_json()[&#39;b&#39;]
        return jsonify({&#39;message&#39;: data}), 200
#    except KeyError:
#        return jsonify({&#39;massage&#39;: &#39;KEY_ERROR&#39;}), 400

if __name__ == &#39;__main__&#39;:
    app.run(host=&#39;127.0.0.1&#39;, debug=True, port=8001)</code></pre>
<h3 id="결과">결과</h3>
<p><img src="https://images.velog.io/images/gyuseok-dev/post/2c81afb8-c05b-44ef-a69f-3120fa2b2d46/image.png" alt=""></p>
<p>기능은 동일하지만 중복은 줄었다.</p>
<p>배웠으면 당장 refactoring하자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flask] mysql 연결 ]]></title>
            <link>https://velog.io/@gyuseok-dev/flask-mysql-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@gyuseok-dev/flask-mysql-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Sun, 20 Dec 2020 07:39:44 GMT</pubDate>
            <description><![CDATA[<p>pymysql 라이브러리를 이용해서 mysql에 접근해보자</p>
<ul>
<li>설치<pre><code class="language-bash"># in 가상환경
pip install pymysql</code></pre>
</li>
<li>작성<pre><code class="language-python"># in connection.py
import pymysql
</code></pre>
</li>
</ul>
<p>def get_connect():
    return pymysql.connect(
        host=&#39;127.0.0.1&#39;,
       password=&#39;&#39;,
          user=&#39;root&#39;,
       db=&#39;test&#39;,
        chaeset=&#39;utf8&#39;
    )</p>
<h1 id="user-생성까지-한번-해보자">user 생성까지 한번 해보자</h1>
<p> def create_user(name):
    conn = get_connection()
    curs = conn.cursor()
    ok = curs.execute(&quot;INSERT INTO users(name) VALUES (%s)&quot;, name)
    conn.commit()
    conn.close()</p>
<pre><code>return json.dumps({&#39;rows&#39;: ok})</code></pre><pre><code>- 결과 
python shell로 확인 해보자
```python
from connect import *

print(create_user(&#39;user&#39;))
{&quot;rows&quot;: 1}</code></pre><p><img src="https://images.velog.io/images/gyuseok-dev/post/1e0d7bd4-c38c-49fa-85e3-b42aba65e01d/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>