추상 클래스 처리 형식

2024년 7월 9일 화요일

Today I Learned

날짜

2024년 7월 9일 화요일

내용

추상클래스 처리형식

여러 형태의 필요한 데이터들을 가져와 스프레드시트에 추가하는 기능을 만들었다. 대동소이하기 떄문에 추상클래스로 나름 깔끔하게 만들어봤다.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
class GoogleSheets(ABC):
    def __init__(
        self,
        values: list,
        service_id: str,
        naver_account_id: int,
        raw_data_format: RawDataFormat,
        db: Session,
    ):
        self.values = values
        self.service_id = service_id

        # 로우데이터 불러와 스프레드시트 ID, 시트 ID 가져오기
        stmt = select(models.NaverRawData).where(
            models.NaverRawData.naver_account_id == naver_account_id,
            models.NaverRawData.type == raw_data_format.value,
        )
        naver_raw_data = db.execute(stmt).scalar()
        self.raw_data_format = raw_data_format
        self.spreadsheet_id = naver_raw_data.spreadsheet_id
        self.sheet_id = naver_raw_data.sheetfile_id
        self.db = db

    @abstractmethod
    def get_credentials(self):
        """구글 API 인증을 위한 credentials를 가져옵니다."""
        return GoogleAuth(
            service_id=self.service_id, db=self.db
        ).make_google_credentials()

    @abstractmethod
    def format_date(self, date_str):
        """1열에 들어갈 날짜 형식을 변경합니다."""
        date_obj = datetime.strptime(date_str, "%Y%m%d")
        formatted_date = date_obj.strftime("%Y.%m.%d.")
        return formatted_date

    @abstractmethod
    def get_first_row(self):
        """1행에 들어갈 필터링 헤더를 가져옵니다."""
        self.first_row = []
        pass

    @abstractmethod
    def set_header(self):
        """1행에 필터링 헤더를 삽입합니다."""
        creds = self.get_credentials()
        service = build("sheets", "v4", credentials=creds)
        first_row = self.get_first_row()
        body = {"values": [first_row]}
        service.spreadsheets().values().update(
            spreadsheetId=self.spreadsheet_id,
            range="A1",
            valueInputOption="RAW",
            body=body,
        ).execute()

    @abstractmethod
    def set_data_type(self):
        """각 열의 데이터 타입을 변경합니다."""
        pass

    @abstractmethod
    def update_request(self):
        """각 데이터로 스프레드시트 업데이트하는 요청을 생성합니다."""
        data = self.set_data_type()
        update_request = {
            "updateCells": {
                "rows": [
                    {
                        "values": [
                            {"userEnteredValue": {"stringValue": str(cell)}}
                            for cell in self.first_row
                        ]
                    }
                ]
                + data,
                "fields": "userEnteredValue",
                "start": {"sheetId": self.sheet_id, "rowIndex": 0, "columnIndex": 0},
            }
        }
        return update_request

    @abstractmethod
    def basic_filter_request(self):
        """첫번쨰 열의 필터링 헤더를 필터로 설정합니다."""
        return {
            "setBasicFilter": {
                "filter": {
                    "range": {
                        "sheetId": self.sheet_id,
                        "startRowIndex": 0,
                        "endRowIndex": len(self.values) + 1,
                        "startColumnIndex": 0,
                        "endColumnIndex": len(self.first_row),
                    }
                }
            }
        }

    @abstractmethod
    def sort_range_request(self):
        """스프레드시트 데이터에 정렬 조건을 추가합니다."""
        pass

    @abstractmethod
    def hiding_columns_request(self):
        """스프레드시트 데이터에 데이터가 삽입되지 않는 열을 숨깁니다."""
        return {
            "updateDimensionProperties": {
                "range": {
                    "sheetId": self.sheet_id,
                    "dimension": "COLUMNS",
                    "startIndex": len(
                        self.first_row
                    ),  # 첫 번째 행 길이 이후의 열을 숨김
                    "endIndex": 26,  # Z열까지 숨긴다고 가정 (0부터 시작하는 인덱스)
                },
                "properties": {"hiddenByUser": True},
                "fields": "hiddenByUser",
            }
        }

    @abstractmethod
    def update_spreadsheet(self):
        """스프레드시트를 업데이트합니다."""
        self.get_first_row()
        update_requests = [
            self.update_request(),
            self.basic_filter_request(),
            self.sort_range_request(),
            self.hiding_columns_request(),
        ]

        body = {"requests": update_requests}
        creds = self.get_credentials()
        service = build("sheets", "v4", credentials=creds)
        result = (
            service.spreadsheets()
            .batchUpdate(spreadsheetId=self.spreadsheet_id, body=body)
            .execute()
        )

        return result

음청나게 길긴 하지만 결국 마지막 update_spreadsheet() 만 불러오면 된다. 이 클래스를 상속받아 데이터 타입별로 클래스를 생성했다. 그떄그떄 그 클래스를 불러오기 불편해서, 데이터타입에 맞게 알맞은 클래스를 불러오는 클래스를 추가했다.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class GoogleSheetProcessor:
    def __init__(
        self,
        values: list,
        service_id: str,
        naver_account_id: int,
        raw_data_format: RawDataFormat,
        db: Session,
    ):
        self.values = values
        self.service_id = service_id
        self.naver_account_id = naver_account_id
        self.raw_data_format = raw_data_format
        self.db = db

    def get_processor(self):
        if self.raw_data_format.value == 1:
            return GoogleSheetsNSAGTD(
                self.values,
                self.service_id,
                self.naver_account_id,
                self.raw_data_format,
                self.db,
            )
        elif self.raw_data_format.value == 2:
            return GoogleSheetsNSACTD(
                self.values,
                self.service_id,
                self.naver_account_id,
                self.raw_data_format,
                self.db,
            )
        elif self.raw_data_format.value == 3:
            return GoogleSheetsNSATD(
                self.values,
                self.service_id,
                self.naver_account_id,
                self.raw_data_format,
                self.db,
            )
        elif self.raw_data_format.value == 4:
            return GoogleSheetsNSAT(
                self.values,
                self.service_id,
                self.naver_account_id,
                self.raw_data_format,
                self.db,
            )

저 변수 넣는것만 보기좋게 다듬으면 좋겠는데..

어쩄든 클래스로 관리하니 요청 라우터는 깔끔해졌다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@router.get("/import")
def _import_naver_data(
    raw_data_format: RawDataFormat,
    naver_account_id: int,
    service_id: str = Header(...),
    db: Session = Depends(get_db),
):
	importer = NaverDataImporter(
	        raw_data_format=raw_data_format,
	        naver_account_id=naver_account_id,
	        db=db,
	    )
	result = importer.data_process()
	
	google_sheet_processor = GoogleSheetProcessor(
	    values=result,
	    service_id=service_id,
	    naver_account_id=naver_account_id,
	    raw_data_format=raw_data_format,
	    db=db,
	).get_processor()
	
	return google_sheet_processor.update_spreadsheet()

회고

생각보다 구현이 잘되서 당황스럽다…