git 안쓰는 로컬 브랜치 한번에 지우기

 팀에서 협업을 하다보면 깃을 이용해서 매번 브랜치를 생성후 default 브랜치에 병합하게된다.


하지만 딱히 신경쓰지 않는 이상 로컬에는 작업했던 브랜치들이 늘어져있게된다.


한번에 지우고 싶어서 방법을 찾아봤다.


일단 로컬에서 깃헙에 없는 브랜치를 먼저 구분해 줘야한다.

```

git fetch --prune

```

이렇게 하면 로컬에서 리모트에 없는 브랜치에는 gone 이라고 표시되게 된다.

그다음 그것들을 필터링해 브랜치명만 가져와서 삭제 argument에 넣어주면 된다.


```

git branch -vv | grep ': gone]' | awk '{print $1}' | xargs -n 1 echo git branch -d

```

expo :app:configureCMakeRelWithDebInfo[arm64-v8a] 에러

 expo 프로젝트에서 안드로이드 빌드를 하다가

node_modules/@react-native-async-storage/async-storage/android/build/generated/source/codegen/jni/


이런 폴더를 찾을 수 없다는 에러를 뿜었다.


기존까지는 잘되던 빌드였는데,

node_modules 폴더를 삭제한 후 새롭게 패키지들을 다시 설치하고, (npm ci)

android 를 빌드하니 해당 에러가 생겨났다.


원래 빌드를 하면 해당 폴더가 자동으로 생성된다.

하지만 한번 빌드했던 프로젝트는 node_modules를 삭제하고 다시 패키지를 설치했더라도, 해당 프로세스를 건너뛰게 된다.

결론부터 말하자면 android-studio 캐시 문제였다.

해결하는 방법은 2가지이다.


1. android studio 캐시를 삭제하기

    Android Studio ▸ File ▸ Invalidate Caches / Restart… ▸ Invalidate & Restart

2. 수동으로 해당 cmake 파일 만들어주기

    ./gradlew generateCodegenArtifactsFromSchema    


둘중 하나만 하면 된다.

그다음부턴 빌드 잘됨.

node_modules안에서 확인해봐도 해당 폴더가 잘 생겨진걸 확인할 수 있다.

expo custom plugin 좌충우돌 typescript 에러

 expo 프로젝트에서 android manifest 파일을 수정해야할 일이 있었다.

그래서 custom plugin 을 만들어보기로 했다.


plugin.ts를 만들어 app.json plugins에 넣으면 에러가 난다. typescript를 제대로 인식하지 못하는 것이다.

expo 기본 프로젝트에서 생성되는 tsconfig.json 파일은 expo/tsconfig.base 이 파일을 상속하는데,

"compilerOptions" 에 "module": "preserve"라고 되어 있다.

이것때문에 루트에 존재하는 app.json에서 해당 ts 파일을 불러올수가 없다.

이것을 "commonjs"로 변경하면 잘 되긴 하는데, 영 찝찝하다.

그래서 prebuild만을 위한 tsconfig.plugin.json 파일을 따로 생성해주고

app.config.ts를 생성해주고 그 상단에서 해당 파일은 tsconfig.plugin.json을 사용하도록 처리해주면 된다.


tsconfig.plugin.json

{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"allowJs": true,
"customConditions": null
},
"include": ["app.config.ts", "plugins/**/*.ts"]
}


app.config.ts

import { register } from "ts-node";
register({ project: "./tsconfig.plugin.json" });

import { ExpoConfig } from "expo/config";

export default ({ config }: { config: ExpoConfig }) => {
config.plugins?.push("./path/to/plugin/ts");
return config;
};


이렇게 해주면 해결된다.



** 문제점 발견

이렇게 설정을 해주면 expo install을 할때 문제가 발생한다. 아마 expo install을 하는 과정에서 app.json을 건드리게 되는데 이게 app.config.ts까지 타게 되는데 라이브러리를 설치할때는 commonjs가 아니라 기존의 preserve 로 동작해야하나보다. 


그때는 귀찮지만, app.config.ts의 파일이름을 잠깐 다른걸로 변경해두면 된다.


좀더 엘레강스한 해결법은 못찾았다.

ax210 인식문제

집에서 맥미니와 윈도우 데스크탑을 사용하고 있고,

모니터 스위치를 이용해 각각 필요할때마다 전환해 사용하고 있다.

키보드 / 마우스는 멀티페어링이 가능한 무선제품으로 사용하고 있다.


윈도우 데스크탑은 직접 조립을 했는데, 예산을 맞추다보니 메인보드에서 자체적으로 블루투스 / 와이파이가 지원이 되지 않고 외부 모듈을 사용해야 했다.

그나마 타협한것이 외부 wifi/bluetooth 모듈을 m.2 슬롯에 설치할 수 있는 메인보드였는데, ax210 을 설치햇다.

몇달간 잘 사용하다가 갑자기 문득문득 키보드와 마우스의 블루투스 연결이 끊기기 시작했다.

리셋버튼을 이용해서 강제 재부팅을 하니 다시 연결이 잘 되어서,

모듈의 불량 혹은 윈도우 충돌로 생각하고 있었다.


하지만 이번에 블루투스 연결이 끊긴다음 재부팅을 해도 재연결이 되지 않았다.

여러 포럼들을 돌아다니며 해결책을 찾았지만 윈도우 재설치 / 메인보드 교체 / 모듈 교체 의 답변이 대부분이었다.


그러다가 문득 나와 똑같은 증상을 가진 게시글을 찾았다.

ax210를 사용중 블루투스가 장치관리자에서 사라지고, 와이파이는 잘 작동한다는 것이었다.

ax210을 해부해보진 않았지만, 장치관리자에서 따로 wifi, bluetooth가 잡히는걸 봤을때 내부 칩이 분리되어 있을거라는것은 쉽게 유추할 수 있었다.

그래서 wifi 모듈은 제대로 작동하지만 bluetooth 모듈은 제대로 부팅조차 되지 않는다는 것이다.

그 이유중 하나가 메인보드의 커패시터에 전기가 차있을때 ax210 내부 결함으로 인해 부팅이 되지 않는다는 것이다.

그 글에서 추천해준대로 본체 전원 플러그를 뽑고, 전원버튼을 충분히 눌러줘서 capacitor drain (방전)을 시켜줬다. 

그 다음 부팅했더니!! 두둥! 제대로 작동한다.


이전에 asrock 수입사 에즈윈에 문의했을때는 전혀 상관없는 문제라고, 모듈이 잘못된거라고 말씀하셨는데, ax210 의 내부 결함이 맞긴 하지만 돈/시간 전혀 안들이고 해결할 수 있어서 정말 다행이다.

dotlottie 다운로드 안되는 문제 (feat. AWS amplify)

 로컬서버에서 dotlottie 파일을 저장하고 사용해보면 잘 작동한다.

하지만 실제로 AWS amplify에 배포를 했을때, .lottie 파일이 다운로드 되지 않는다.

네트워크 탭을 살펴보면 index.html 을 리턴할 뿐이다.


찾아보니

AWS amplify 에서 rewrite / redirect 설정에 lottie라는 확장자를 예외로 처리해줘야 한다.

살펴보면, json, pdf, css 등등 이미 예외처리되어 있는 여러 확장자들이 보이는데

이 확장자 끝에 lottie를 추가해주자.

그다음 재배포 해주면 끝

Django - PostgresSQL connection 최적화 방안

 Django와 PostgreSQL 을 사용하고 있는 사람들 중,

DB connection 때문에 골머리를 쌓는 사람이 많을 것이다.


기본적으로 서버에서 핸들링 하지 못하는 에러가 있다면 / 비동기 작업내에서 db에 접근한다면 connection leak이 발생할 수 있고,

그렇다면 db connection 이 쌓이게되서 db 인스턴스가 죽어버리는 경우가 생긴다.


이를 방지하기 위해 db 단에서 idle_session_timeout과 idle_in_transaction_session_timeout 두가지 파라미터를 제공한다.


idle_session_timeout은 말그대로 하나의 세션안에서 긴 작업 (혹은 시스템 에러) 으로 인해 connection이 지정된 시간이상으로 유지되는 경우 자동으로 connection을 끊어버리는 옵션이다.

idle_in_transaction_session_timeout은 하나의 transaction (atomic) 안에서 idle 한 상태가 지속되면 자동으로 connection을 끊어버리는 옵션이다.


만약 django에서 Database atomic_request 옵션이 설정되어 있다면, idle_in_trasaction_session_timeout을 조절해야 할것이고, 그렇지 않다면 idle_session_timeout을 조절해야 할것이다.


실제로 django에서 request를 수신했을때, db 접근이 없더라도 middleware에서 커넥션을 생성한다. 그렇기때문에 connection이 끊길 수 있다는 생각을 항상 하자.


결론적으로 내가 생각하는 것은

api서버는 crud job 만을 위해 작동해야 한다. 

간단한 data 구조 변형이라면 괜찮지만, 길어질 수 있는 작업은 celery로 처리하도록 하자.

celery에서는 왠만하면 db 접근을 직접적으로 하지말고 api 서버에 webhook 방식으로 작동할 수 있게 하자.

Django Rest Framework (DRF) unique field serializer.is_valid() 자동 중복체크

Django rest framework에서 

아래와 같이 username 대신 email을 키로 갖는 유저테이블을 정의했다.


```

class User(AbstractUser):

    username = None

    email = Emailfield(unique=True)

```



그리고 serializer를 이용해서 validation 체크를 하려고 했다.


```

class SignupSerializer(ModelSerializer):

    class Meta:

        model = User

        field = ("email",)


    def validate_email(self, email):

        return email.lower()

```



위와같이 validation 함수에는 이메일을 소문자로 치환하는 로직만 넣어줬다.

view단에서 이메일 중복체크를 하려고 했다.


```

class SignupView(GenericAPIView):

    serializer_class = SignupSerializer


    def post(self, request):

        serializer = self.get_serializer(data = request.data)

        serializer.is_valid(raise_exception = True)

        ...

```


위와 같이 

이메일 중복체크를 아직 하지 않았음에도 불구하고,

serializer.is_valid()에서 중복체크를 해준다.

아무리 찾아봐도 관련 docs를 찾지 못했다. stackoverflow에서 비슷한 질문이 있었지만

원하는 답변이 없다. (https://stackoverflow.com/questions/64029496/validate-unique-email-django-rest-framework-serializer)


아마 내부적으로

unique field에 자동으로 rest_framework.validators.UniqueValidator

를 붙여주는게 아닐까 싶다. 


혹시 관련해서 아는 내용이 있으면 댓글 부탁드립니다.

루프문 최적화 (역행루프)

```
for (var i = 0; i < array.length; i++) {}
```

보다

```
var length = array.length
for (var i = length; i >=0; i--) {}
```

가 더 빠르다고 한다.


첫번째이유
매번 array.length를 계산하지 않는다. (지역변수에 할당)

두번째 이유
i를 0 (false)랑 비교하기 때문에 속성검색을 최소화 할 수 있다.

AWS Cloud watch "insufficient data" 문제

 AWS에서 cloud watch metric에는 크게 2종류의 데이터 수집 방법이 있다.

  • period-driven metric
  • event-driven metric

Period-driven metric


말 그대로 특정 interval 마다 데이터를 수집하는 방식이다. 
대표적으로는 cpu utilization같은것이 있다.

이런 겅우에는 매순간 데이터를 측정하기 때문에 현재상태가 정상인지 비정상인지 판단하는 기준이 간단하다.


Event-driven metirc


특정 이벤트가 일어날때만 데이터가 수집된다.
대표적으로 ELB_5XX_count 같은것이 있다.

실제로 500번대 에러가 나지 않는다면 데이터가 수집되지 않는다. 
그럼 이 값이 0이 될것 같지만 그렇지 않다.
이럴 경우 "insufficient data" 라는 상태가 되는데, 실제로 데이터가 수집되지 않았기 때문이다.


Missing Data


missing data라는 옵션이 있는데 이는 데이터가 수집되지 않을 경우를 의미한다.
ELB_5XX_Count Metric의 경우 데이터가 수집되지 않으면 실제로 정상이라고 해석할 수 있는 옵션을 제공한다. "not breaching" 옵션을 선택하자.

CPU Utilization metric 의 경우 데이터가 수집되지 않는다는것은 인스턴스가 죽어있을 화률이 높다. 그러므로 "Breaching" 옵션을 선택하자.

기본값은 "Missing". 그 전 상태 유지가 된다.


출처

PUT과 PATCH에 대한 고찰

최근에 팀안에 다른 개발자와

업데이트 API를 설계하면서 PUT을 사용하냐 PATCH를 사용하냐로 논쟁을 벌인적이 있다.


 CRUD라고 함은,

Create -> POST
Retrieve -> GET
Update -> PUT
Delete -> DELETE

위와 같이 각각 메소드에 해당한다.

보통 업데이트는 PUT을 사용해야한다고 배웠고, 사용을 해오고 있었다.


Django Rest Framework를 사용하다보면,

Viewset의 경우,

PUT method의 request가 들어오면 해당하는 serializer의 모든 field가 있어야하고,

PATCH method의 request가 들어오면 해당하는 serializer의 field중 일부분만 있어도 가능하다 (partial=True)

또한, 아래 stack overflow의 링크를 보면
https://stackoverflow.com/questions/28459418/use-of-put-vs-patch-methods-in-rest-api-real-life-scenarios

put은 좀더 replace에 가까운 느낌이고,

patch야 말로 진정한 update에 가까운 느낌으로 설명하고 있다.


REST API의 특성상 

해당 instance에서 업데이트가 일어난다고하면, 들어오는 request마다, 포함되는 body field마다 다른 endpoint와 serializer를 만드는 일은 매우 수고롭고 유지보수에도 상당한 리소스가 드는 일이다.

하나의 endpoint에서 어떤 필드가 들어오더라도 자연스래 update를 하기위해선 put 대신 patch를 사용하는게 훨씬 효과적이라고 생각한다.

물론, 프론트엔드를 설계할때 다양한 화면에서 instance를 수정하는 일은 드물겠지만, 나중에 확장성을 위해서라도 patch를 사용한다면 훨씬 편리한 확장이 가능할 것이다.

컨테이너, 도커, 도커 컴포즈, 쿠버네티스 기본 개념

구글링 해보면 정말 많은 블로그에서 다양하게 설명해놓았다.

하지만 정말 처음 도커를 접하는 사람들은 (나같이) 개념정리가 잘 안되는 상태에서

막상 명령어들과 도구들을 친절하게 설명해놓으 블로그를 봐도 개념잡기가 너무 힘들다.


그래서 이것저것 알아보며 공부하다가 감이 잡혀가는 것들을 정리해 보았다.


 컨테이너는 프로그램과 그 프로그램을 돌리기 위한 환경까지 모두 캡슐화 시킨것과 같다. 

컨테이너안에 여러가지 프로그램을 넣을순 있지만 보통 한 컨테이너 안에 하나의 프로그램만 넣고 캡슐화 시킨다. 

예를들면,

  • Nginx
  • Django
  • PostgreSQL

보통 위와 같은 프로그램들이 한데 얽혀 백엔드서버를 구성한다고 할지라도,한 컨테이너당 하나의 프로그램만 구성하여 총 3개의 컨테이너를 사용한다.

그에 따른 이점은 레고를 갈아끼듯이 필요한 버전업이 있는 경우 해당하는 컨테이너만 쉽게 교체할 수 있기 때문이라고 이해했다.


도커는 이러한 컨테이너 기법을 활용할 수 있는 툴 중 가장 유명한 것이다. 물론 다른 툴도 있다고 한다.

좀 전에 한 인스턴스에서 여러개의 컨테이너를 여러개 사용해서 하나의 서버를 구성할 수 있다고 했다. 그것을 위해서 백그라운드에서 프로세스당 각 컨테이너를 실행시켜 연결할 수 있다. 연결하는 방법은 보통 http 포트를 이용하는것 같다. 유닉스 소켓을 활용할 수 있을지 좀더 공부해 봐야겠다.

컨테이너들을 각각 실행시키는것은 괴로운 작업이 될것이다.

그래서 그 컨테이너들을 동시에 실행시켜줄 수 있는것이 도커 컴포즈이다.

도커컴포즈는 한 인스턴스 내에서 여러 컨테이너를 쉽게 관리 할 수 있게 도와주는 툴이다. 사실 도커 컨테이너를 실행할때 여러가지 옵션과 환경변수 설정하는것이 귀찮고 명령어가 길어지는데 도커 컴포즈를 이용하면 .yml파일에 모두 저장이 가능하여 단순한 명령어 하나로 컨테이너 구동이 가능하게 된다.


쿠버네티스는 여러 인스턴스에서 여러 컨테이너를 쉽게 관리 할 수 있게 해주는 툴이다.


개념정리가 될때마다 업데이트를 해야겠다.

[React-Query] useInfinteQuery 사용시 주의사항 (무한스크롤)

리액트 무한 스크롤 구현

  1. 무한 스크롤 구현방법에는 2가지가 있다.

    1. scroll 이벤트를 통해 페이지 마지막부분으로 scroll이 되었을경우 새로운 page를 fetching하는 방법.

    2. 페이지 마지막 부분에 div태그를 두고 Intersection Observer를 통해 해당 div가 화면에 보일때 새로운 page를 fetching 하는 방법

    두가지 방법의 차이점과 어느게 더 좋은지에 관한것은 다른 블로그에도 많으니 생략하겠다.



  2. 리액트에서 Intersection Observer를 편하게 사용하기 위한 라이브러리가 있다.

    react-intersection-observer를 사용해보자.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import React from 'react';
    import { useInView } from 'react-intersection-observer';
     
    const Component = () => {
      const { ref, inView, entry } = useInView({
        /* Optional options */
        // threshold: 0,
        // rootMargin:"50px"
      });
     
      return (
        <div>
            <ul>
                .......
            </ul>
            <div ref={ref}>
                <h2>{`Header inside viewport ${inView}.`}</h2>
            </div>
        </div>    
      );
    };
    cs

    ref를 페이지 마지막 div에 넣고 해당 div가 뷰포트 안에 들어오면 inView 값이 true가 되는 방식이다.



  3. useInfiniteQuery를 사용해보자.

    이제 inView값이 true 일때 useInfiniteQuery가 다음 page를 Fetching 하게 되면 된다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    ...
    import { useInfiniteQuery } from "@tanstack/react-query";
     
     
    const Component = () => {
        ....
     
        const { isFetchingNextPage, fetchNextPage } = useInfiniteQuery(['query-key'], fetchFc, {
            enabled: false// 자동으로 fetch가 되는것을 방지하고 오직 fetchNextPage함수에 의해서만 fetch 되도록 하기 위해
            getNextPageParams: (lastPage, allPages) => lastPage.page < lastPage.totalPage ? lastPage.page + 1 : undefined,
            // 마지막 페이지가 아닐경우 다음 페이지 넘버를 반환하고 마지막 페이지일 경우 undefined를 반환한다.
        });    
     
        useEffect(() => {
            if (inView && !isFetchingNextPage) {
                fetchNextPage()
            }
        }, [inView, isFetchingNextPage])
     
        return ...
    }
    cs

    fetchFn에 pageParam이 전달된다.

    1
    2
    3
    4
    const fetchFn = ({ pageParam = 1}) => {
        return fetch(`/api/getsomething?page=${pageParam}`)
            .then(response => response.json())
    }
    cs

    하지만 가장처음 호출될때는 undefined가 전달된다. 한번도 호출된적이 없기 때문에 getNextPageParam에서 사용될 lastPage가 없기 때문이다. 그래서 default값으로 1을 넣어준다. 참고

    이 방법에는 문제점이 있다.

    실제로 fetchNextPage가 실행되는 순간 isFetchingNextPage의 값이 true가 되기전에 바로 fetchNextPage가 또한번 더 실행된다. fetchNextPage가 비동기함수로써 실행되기까지 시간이 걸리기 때문이다. (isFetchingNextPage 값이 true가 되기까지 시간이 걸린다. 그전에 또다시 fetchNextPage가 실행된다)



  4. useState를 통해 상태를 관리해보자.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    ...
    import { useState} from 'react';
    import { useInfiniteQuery } from "@tanstack/react-query";
     
     
    const Component = () => {
        ....
        const [data, setData] = useState([]);
     
        const [isLoading, setIsLoading] = useState(false);
     
        const { fetchNextPage } = useInfiniteQuery(['query-key'], fetchFc, {
            ... 
            onSuccess: (newData) => {
                setData(newData.pages.map(pages.results).flat());
                setIsLoading(false);    // 다시 fetchNextPage가 가능할 수 있도록 해준다.
            }
        });    
     
        useEffect(() => {
            if (inView && !isLoading) {
                setIsLoading(true);
                fetchNextPage();
            }
        }, [inView, isLoading])
     
        return ...
    }
    cs

    위의 경우에는 fetchNextPage가 동시에 여러번 수행되지는 않는다. 하지만 fetch가 끝나고나서 setIsLoading(false)가 호출되는 순간 실제로 화면에 다시 렌더링되면서 화면에 새로운 li 엘레멘트들이 paint되기전에 inView가 트리거되어 또한번 fetchNextPage가 호출된다. 즉, 의도치 않은 fetching이 또 일어난다.



  5. useRef를 통해 상태관리를 해보자.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    ...
    import { useState} from 'react';
    import { useInfiniteQuery } from "@tanstack/react-query";
     
     
    const Component = () => {
        ....
     
        const isLoading = useRef(false);
     
        const { fetchNextPage } = useInfiniteQuery(['query-key'], fetchFc, {
            ... 
            onSuccess: (newData) => {
                ...
                isLoading.current = false;    // 다시 fetchNextPage가 가능할 수 있도록 해준다.
            }
        });    
     
        useEffect(() => {
            if (inView && !isLoading.current) {
                isLoading.current = true;
                fetchNextPage();
            }
        }, [inView, isLoading])
     
        return ...
    }
    cs

    이제 깔끔하다. 원하는대로 infinite scroll이 구현되었다.

[파이썬] circular import error

 django rest framework 를 사용하다가 

circular import error를 만나게 되었다. 


문제의 발단은 

Employee model과 Job model은 외래키로 연결이 되어 있다.

Employee serializer에서 Job serialzer를 참조해야하고

반대로 Job serializer에서 Employee serializer를 참조해야하는 상황이 발생했다.

사실 잘 발생할 일이 없는 일이지만 커스터마이즈를 최대한 하려다보니 마주친 에러였다.

django rest framework를 너무 깊게 들어가기엔 복잡하니

간단하게 파일 2개를 생성해서 당시상황을 재현해보자.


1
2
3
4
5
6
7
8
9
10
11
12
# first.py
 
from second import E
 
class A():
    pass
 
class B(E):
    pass
 
class C():
    pass
cs


1
2
3
4
5
6
7
8
9
10
11
12
# second.py
 
from first import C
 
class D():
    pass
 
class E():
    pass
 
class F(C):
    pass
cs


현재폴더에서 파이썬를 실행한 후

import first

를 입력하면 아래와 같은 Import Error가 난다.

1
ImportError: cannot import name 'C' from partially initialized module 'first' (most likely due to a circular import)
cs


1. 가장 기본적이고 확실한 방법은 파일을 분리하는 방법이다.

  • first.py의 C클래스를 다른 파일에 넣던지
  • first.py의 B(E) 클래스를 다른 파일에 넣던지
  • second.py의 E클래스를 다른 파일에 넣던지
  • second.py의 F(C)클래스를 다른 파일로 옮긴다.

하지만 파일분리가 애매할 경우가 발생할 수 있다.
2. 그럴때는 코드의 순서를 좀 바꿔준다.

아래 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
#first.py
 
class A():
    pass
 
 
class C():
    pass
    
    
from second import E
class B(E):
    pass
cs


1
2
3
4
5
6
7
8
9
10
11
# second.py
 
class D():
    pass
    
class E():
    pass
 
from first import C
class F(C):
    pass
cs


위와같이 first.py에서 import 당하게 되는 C 클래스가 

from second import E 위에 위치시켜주고 

second.py에서도 import 의 위치를 조금 변경시켜주면 circular import 에러를 피할 수 있다.

기본적으로 파이썬은 모든 import 문은 가장 위에 작성하는것이 권장되지만,

가끔 라이브러리의 코드를 보다보면 중간에 import 문이 작성되는경우를 보게 되는 경우도 있다.

필요한 시점에 import를 작성하면 circular import 에러를 피할 수 있다.

[React] 리액트 배포 방법 (ec2 / s3) 그리고 라우터

 리액트를 배포하는 방법은 2가지가 있다.


1. EC2에서 Nginx를 통해 배포하는 방법

2. S3에서 정적 사이트로 배포하는 방법

(각 방법에 해당하는 배포방법은 구글링하면 많이 나온다.)


리액트의 작동원리에 대해서 생각해보자.

리액트에서 링크를 클릭해서 페이지를 이동하는 경우

실제로 해당 path를 새로 request 하는것이 아닌

자바스크립트로 address를 변경된걸로 보이게하고 해당 리소스를 가져오는 방식이다.

즉 

https://example.com

에서 링크를 클릭해서 

https://example.com/path

로 이동하게 된다 하더라도 실제로 path에 해당하는 request를 보내는것이 아닌

주소창에 값을 바꾸고 해당 리소스를 가져오는 형식이다. 

일반적인 a태그의 동작방법이랑은 다르다.


그럼 만약 주소창에 

https://example.com/path를 입력하고 enter를 치면 어떻게 될까?

해당 path의 리소스로 바로 찾아가게 되는데 

리액트 파일구조를 보면

root 폴더안에 바로 해당하는 path의 폴더가 있는 구조가 아니다.

그래서 404 에러를 마주하게 된다.


이걸 해결하는 방법이 2가지가 있는데 

1. Nginx를 이용해서 배포한다.

Nginx에서 try_files라는 옵션이 있는데 여기 옵션에 나열된 순서대로 리소스를 가져오는 형식이다.

첫번째 리소스를 가져오는데 실패하면 다음 리소스를 가져오는 형식이다.

try_files 옵션에서 마지막에 /index.html을 추가하면 

https://example.com/path를 요청하더라도 결국 /index.html을 가져오게 된다.


2. 해쉬라우터를 사용한다.

react-router-dom에는 여러가지 router가 있는데 그중에 가장 많이 사용하는 것이

Browser Router와 Hash Router이다.

두개의 차이점은 Brower Router는 일반적인 웹사이트 주소처럼 

https://example.com/pathA/pathB 이렇게 주소값이 변경되는데

Hash Router는

https://example.com/#/pathA/pathB 이런식으로 주소값이 생성된다.

#은 브라우저에서 path로 인식되는것이 아니라 anchor로 사용된다. 

위와 같은 주소에서 브라우저는 https://example.com로 request를 보내고 

나머지 #/pathA/pathB는 리액트가 처리하게 된다는 뜻이다.

해쉬라우터를 사용하면 s3나 github page로 배포해도 괜찮다.


와이파이 비밀번호 찾기 (윈도우, 맥)

와이파이의 비밀번호를 잊어버렸을때 사용할 수 있다.


1. 윈도우

cmd창을 열고

netsh wlan show profile 을 입력하면 윈도우에 등록된 와이파이 목록이 뜬다.

netsh wlan show profile name="SSID" key=clear

을 입력하면 security settings 맨 아래에 보이는것이 비밀번호이다.

SSID를 원하는 와이파이 이름으로 바꾸는것을 잊지말자.


2. 맥

터미널에서 다음과 같이 입력

security find-generic-password -wa network_name

network_name을 원하는 와이파이 이름으로 바꾸는것을 잊지말자.



 출처: https://www.itworld.co.kr/news/109860

[파이썬] __new__, __init__, __call__, type, 메타클래스 (feat. singleton)

 파이썬의 메타클래스를 공부하다가 

좀 애매했던것들을 확실하게 정리하게 되었다.

일단 기본적인것을 먼저 짚고 좀더 자세히 보자.



1. __init__(self, *args, **kwargs)

이것을 생성자로 알고있는 사람이 많다.

하지만 엄밀히 따지면 초기화 함수이다.

여기서 self는 생성된 인스턴스를 의미한다.



2. __new__(cls, *args, **kwargs)

이것이 진정한 생성자 함수이다.

__init__보다 먼저 실행된다.

즉 인스턴스 생성시 __new__가 실행되고 __init__ 가 실행된다.

이건 클래스함수(@classmethod)이기 때문에 첫번째 인자가 self가 아니라 cls를 적어준다.

cls는 클래스 자체를 의미한다.



3. __call__(self, *args, **kwargs)

이것은 인스턴스가 호출될때 발생한다.



4. 메타클래스

메타클래스란 클래스 객체를 생성하는 클래스이다.

클래스는 메타클래스의 인스턴스인 셈이다.

반대로 메타클래스는 클래스의 클래스이다. (읭?! ㅎ)

클래스의 인스턴스 == 메타클래스의 인스턴스의 인스턴스  (읭?! ㅎㅎ)

파이썬에서 기본 메타클래스는 type이다.



5. type

type은 어떤 객체의 자료형을 리턴한다.

달리말하면 해당 객체가 어느 클래스로부터 비롯되었는지 (인스턴스화 하였는지) 나타낸다.

(파이썬에서 모든것은 객체이다)

아래 코드를 보자.

1
2
3
4
5
6
7
8
9
class Sample():
    pass
 
type(Sample) # type
 
 
= Sample()
 
type(s) # __main__.Sample
cs



5.1 type (메타클래스)

또한 type은 클래스를 만들 수 있다. 

syntax는 

1
type('클래스이름 string', (상속받는 클래스 tuple), {필드 및 메서드 dict})
cs


따라서 type을 상속하는 클래스는 메타클래스가 되는데

그 메타클래스를 이용해 위와 같은 문법으로 클래스를 만들 수 있다.

1
2
3
4
5
6
7
8
9
class CustomMeta(type):
    pass
 
Sample = CustomMeta()
 
 
# 위와 아래는 똑같이 Sample이라는 클래스를 생성한다.
 
Sample = CustomMeta('Sample', (), {})   #__main__.Sample

cs


6. Singleton

이제 다왔다.

메타클래스를 호출하면 클래스가 나온다.

클래스는를 호출하면 인스턴스를 생성할 수 있다.

클래스를 호출한다는것은 메타클래스의 __call__ 메서드를 호출하는것과 같다.

따라서 메타클래스의 __call__메서드에서 싱글톤 작업을 수행하면 된다.

1
2
3
4
5
6
7
8
9
10
class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]
 
 
class CustomClass(metaclass=Singleton):
    pass
cs

__call__ 메서드의 첫번째 파라미터가 self가 아니라 cls인 이유는,

메타클래스의 인스턴스가 클래스기 때문에 cls이라고 쓴것이다. 사실상 self와 똑같이 작동한다.



보통 클래스를 만들면 그 클래스는 object 객체(클래스)를 상속하고 type을 메타클래스로 사용하게 된다.

1
2
3
4
5
6
7
8
class MyClass():
    pass
 
 
class MyClass(object, metaclass=type):
    pass
 
# 위와 아래의 클래스는 같은 기능을 수행한다.
cs



참고1: https://stackoverflow.com/questions/6760685/creating-a-singleton-in-python

참고2: https://wikidocs.net/3693

[JS, TS] self-compare 자기자신비교(?)

리액트에서 불변성에 대해 알아보다가,

shallowEqual 코드를 살펴보고 있었다.

https://github.com/facebook/react/blob/v16.8.6/packages/shared/shallowEqual.js


그중 is라는 함수를 좀더 살펴봤는데

https://github.com/facebook/react/blob/v16.8.6/packages/shared/objectIs.js


그중에 눈에 띄는 코드가 있었다.

1
(x !== x && y !== y) // eslint-disable-line no-self-compare
cs


x !== x 라는 코드는 항상 false라고 생각했기 때문에 

if 조건절안에서 의미없는 코드라고 생각했다.

사실 거의 대부분 false이다.


https://eslint.org/docs/latest/rules/no-self-compare

위 docs를 살펴보니,

값이 NaN일 경우엔

1
2
3
let a = NaN
=== a // false
!== a // true
cs

이렇다고 한다.


하지만 가독성을 위해선 아래와 같이 더 좋은방법이라고 한다.

1
typeof x === 'number' && isNaN(x)
cs


[nestJS] DTO 클래스와 interface 인터페이스 차이

 nestJS를 하다보면 파라미터 타입지정을 DTO 클래스로 한다.

1
2
3
4
5
6
7
8
9
10
11
12
export class MoviesController {
 
  ...
 
  @Post()
  create(@Body() movieData: CreateMovieDto) {
      return this.moviesService.create(movieData);
  }
 
  ...
 
}
cs

여기서 CreateMovieDto 부분을 살펴보면 아래와 같다.

1
2
3
4
5
export class CreateMovieDto {
  readonly title: string;
  readonly year: number;
  readonly genres: string[];
}
cs


문득 드는 생각은 그냥 interface를 사용하면 안되나? 였다.

1
2
3
4
5
export interface ICreateMovie {
    title: string;
    year: number;
    genres: string[];
}
cs


결론부터 말하면 사용하는건 문제는 없다.

하지만 아래 두가지 이유로 DTO 클래스를 사용한다.

  1. Typescript는 실제로 컴파일될때 ES6 Javascript로 변환되는데 그때 interface는 사라진다. interface는 코딩하는 과정에서 도움을 줄 뿐 실제로 동작하는데는 영향을 미치지 않는다. 

  2. 파이프를 이용한 데이터 validation을 하기 위해서는 DTO 클래스를 써야한다.


실제로 github에서 이와 같은 논쟁이 있다.

https://github.com/nestjs/nest/issues/1228

공식홈페이지에서도 DTO 사용을 추천한다.

리액트 네이티브 코드푸시 적용하기

리액트 네이티브를 선택하는 이유중 가장 큰 2가지는

  • 멀티 플랫폼을 지원한다.
  • 코드푸시를 통해 앱스토어의 리뷰를 받지않고도 빠른 업데이트가 가능하다
구글에서 검색해보면 documentation을 번역해놓은게 많지만
처음 접하는 사람은 대략적인 개념정리가 필요한 경우가 많아
내가 공부한 것을 공유하고자 한다.

리액트 네이티브로 어플을 만들게 되면
안드로이드와 ios의 네이티브로 껍데기를 만든 후 그 안에 
자바스크립트/타입스크립트로 리액트를 사용해 로직과 껍데기를 만드는 것이다.

여기서 코드푸시란 
  1. 자바스크립트/타입스크립트 부분을 
  2. 특정 조건마다 (ex. 어플 실행마다 / 화면 전환 할때마다 등등)
  3. 새로 릴리즈된 버전으로 업데이트 하는것이다.

코드푸시는 Microsoft의 appcenter로 들어가게 되었다.
따라서 Microsoft appcenter (https://appcenter.ms/) 에서 서비스가 된다.

https://learn.microsoft.com/en-us/appcenter/distribution/codepush/rn-get-started
공식홈페이지를 보고 설정을 하는게 좋다.

그리고 마지막
multi deployment
이건 조금 많이 고민이 된다.

A/B testing을 위해서라도 유저들을 구분해서 Staging과 Production 릴리즈를 구분해서 설치할 수 있으면 좋을것 같다.

이걸 하는 방법은 2가지가 있는데
  • 유저가 Staging / Production 버전을 선택할 수 있게 한다.
  • Authenticated된 유저를 Staging 버전을 사용하게 한다.
리액트 코드부분에서 어떤 deployment를 선택할지 설정하는 방법은 좀더 고민해 봐야겠다.

리액트 scss 적용하기


이전까지 node-sass를 설치해서 사용했었다.

근데 node-sass가 deprecated 되었다고 한다.

뭐가 버전이 안맞는건지 node-sass가 설치시 에러가 난다.

대신 sass를 설치하니 잘된다.


npm i sass

그리고 css파일을 scss로 바꾸니 잘 된다.

import 문을 바꾸는것도 잊지 말자.