[SK shieldus Rookies 19기] 인프라 활용을 위한 파이썬 6일차

기록하는짱구·2024년 3월 7일
0

SK Shieldus Rookies 19기

목록 보기
6/43
post-thumbnail

데이터베이스 접속

pymysql.connect(host="localhost", 		   # 데이터베이스 주소
  port=3306, 			    # 데이터베이스 서비스 포트 (MySQL 기본 포트: 3306)
  user="springboot", 		# 데이터베이스 접속 계정 
  passwd="p@ssw0rd", 		# 데이터베이스 사용자 패스워드
  db="sampledb"		        # 사용할 데이터베이스/스키마
  autocommit=True|False	# INSERT, UPDATE, DELETE 구문 실행 결과를
                          자동으로 반영할지의 여부 설정
  cursorclass=pymysql.cursors.DictCursor	# SELECT 결과를 컬럼 명과 값으로
                                              구성된 dict 타입으로 반환
) 

cursorclass를 지정하지 않으면 SELECT 결과를 값으로 구성된 튜플 타입으로 반환

(1, '홍길동', 23, 'hong@test.com')

with pymysql.connect(host="localhost", port=3306,
user="springboot", passwd="p@ssw0rd", db="sampledb") as conn:

cursorclass를 pymysql.cursors.DictCursor으로 설정하면 SELECT 결과를 컬럼 이름과 값으로 구성된 딕셔너리로 반환

{'member_id': 1, 'member_name': '홍길동', 'member_age': 23, 'member_email': 'hong@test.com'}

with pymysql.connect(host="localhost", port=3306, user="springboot",
passwd="p@ssw0rd", db="sampledb", cursorclass=pymysql.cursors.DictCursor) as conn:

데이터 추가 기능 구현

📢 여기서 잠깐! commit이란?
만들고 수정하는 작업 후 연결시켜주는 행위

import pymysql
from faker import Faker

try:
    with pymysql.connect(host="localhost", port=3306, user="springboot", passwd="p@ssw0rd", db="sampledb", cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        fake = Faker('ko-KR')
        for i in range(10):
            query = f"insert into members (member_name, member_age, member_email) values ('{fake.name()}', {fake.pyint(10, 30)}, '{fake.email()}')"
            print(query)
            curr.execute(query)

        conn.commit()			# 10개의 insert 구문의 실행 결과를 DB에 반영

except pymysql.MySQLError as e:
    print(e)

데이터베이스 연결 시 autocommit을 True로 설정

📢 여기서 잠깐! autocommit이란?
변경을 자동으로 반환

import pymysql
from faker import Faker

try:
    with pymysql.connect(host="localhost", port=3306, user="springboot", passwd="p@ssw0rd",
    db="sampledb", cursorclass=pymysql.cursors.DictCursor, autocommit=True) as conn:
                                                           ~~~~~~~^~~~~~~~~
        curr = conn.cursor()     트랜잭션 관리가 필요한 경우 False(기본값)으로 설정
                                          commit() 또는 rollback()을 처리 
        fake = Faker('ko-KR')
        for i in range(10):
            query = f"insert into members (member_name, member_age, member_email) 
            values ('{fake.name()}', {fake.pyint(10, 30)}, '{fake.email()}')"
            print(query)
            curr.execute(query)

        # conn.commit()	⇐ commit() 함수를 호출하지 않아도 변경사항이 DB에 반영되는 것을 확인할 수 있음

except pymysql.MySQLError as e:
    print(e) 

데이터 추가 후 조회하도록 수정

fetchone()

커서로부터 하나의 행(레코드) 반환

import pymysql
from faker import Faker

try:
    with pymysql.connect(host="localhost", port=3306, user="springboot",
    passwd="p@ssw0rd", db="sampledb", cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        fake = Faker('ko-KR')
        for i in range(10):
            query = f"insert into members (member_name, member_age,member_email)
            values ('{fake.name()}', {fake.pyint(10, 30)}, '{fake.email()}')"
            print(query)
            curr.execute(query)

        conn.commit()

        query = "select * from members"
        curr.execute(query)

        # 조회 결과를 하나씩 가져오기
        while True:
            result = curr.fetchone()  
            if result == None: break
            print(result)

except pymysql.MySQLError as e:
    print(e)

fetchall()

커서로부터 모든 행(레코드) 반환

import pymysql
from faker import Faker

try:
    with pymysql.connect(host="localhost", port=3306, user="springboot",
    passwd="p@ssw0rd", db="sampledb", cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        fake = Faker('ko-KR')
        for i in range(10):
            query = f"insert into members (member_name, member_age, member_email) values ('{fake.name()}', {fake.pyint(10, 30)}, '{fake.email()}')"
            print(query)
            curr.execute(query)

        conn.commit()

        query = "select * from members"
        curr.execute(query)

        # 모든 조회 결과를 반환
        results = curr.fetchall()  
        for result in results:
            print(result)

except pymysql.MySQLError as e:
    print(e)

fetchmany(n)

커서로부터 n개의 행(레코드)을 반환

import pymysql
from faker import Faker

try:
    with pymysql.connect(host="localhost", port=3306, user="springboot",
    passwd="p@ssw0rd", db="sampledb", cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        fake = Faker('ko-KR')
        for i in range(10):
            query = f"insert into members (member_name, member_age, member_email)
            values ('{fake.name()}', {fake.pyint(10, 30)}, '{fake.email()}')"
            print(query)
            curr.execute(query)

        conn.commit()

        query = "select * from members"
        curr.execute(query)

        # 조회 결과의 일부를 반환
        results = curr.fetchmany(10)  → 조회 결과의 첫 부분부터 10개를 가져와 반환
        for result in results:
            print(result)

        print("*" * 10)
        
        results = curr.fetchmany(10)  → 그 다음(11번째)부터 10개를 가져와서 반환
        for result in results:
            print(result)

except pymysql.MySQLError as e:
    print(e)

입력한 내용과 일치하는 데이터 조회

import pymysql

try:
    with pymysql.connect(host="localhost", port=3306, user="springboot",
    passwd="p@ssw0rd", db="sampledb", cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        name = input("검색할 이름을 입력하세요 : ")

        query = f"select * from members where member_name = '{name}'"
        curr.execute(query)

        results = curr.fetchall()
        for result in results:
        
            print(f"ID : {result['member_id']}")
            print(f"이름 : {result['member_name']}")
            print(f"나이 : {result['member_age']}")		   
            print(f"이메일 : {result['member_email']}")
            			  	→ 컬럼의 이름으로 값을 추출하는 것이 가능
            				  DictCursor를 사용하지 않는 경우 result[0]과 같이
            				  튜플 데이터의 인덱스를 사용해야 함
        
except pymysql.MySQLError as e:
    print(e) 

입력한 내용을 포함하고 있는 데이터 조회

import pymysql

try:
    with pymysql.connect(host="localhost", port=3306, user="springboot",
    passwd="p@ssw0rd", db="sampledb", cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        name = input("검색할 이름을 입력하세요 : ")

        query = f"select * from members where member_name like '%{name}%'"
        count = curr.execute(query)SELECT 구문인 경우 조회 결과 개수를 반환
                                       INSERT, UPDATE, DELETE 구문인 경우
                                       등록, 수정, 삭제된 행의 개수를 반환
        
        print(f"총 {count}건을 조회했습니다.")
        
        results = curr.fetchall()
        
        print(f"총 {len(results)}건을 조회했습니다.")
        
        for result in results:
            print(f"ID : {result['member_id']}")
            print(f"이름 : {result['member_name']}")
            print(f"나이 : {result['member_age']}")
            print(f"이메일 : {result['member_email']}")
        
except pymysql.MySQLError as e:
    print(e)

아래와 같은 입력을 전달하는 경우 모든 데이터가 조회되어 반환되는 것을 확인 가능

검색할 이름을 입력하세요 : a' or 'a' = 'a' or 'a
총 71건을 조회했습니다.
query = f"select * from members where member_name like '%{name}%'"

# 입력값이 결합된 쿼리의 형태
select * from members where member_name like '%a' or 'a' = 'a' or 'a%'
                            ~~~~~~~~~~~~~~~~~~~~~    ~~~~~~~~~    
                                                      항상 참이 되는 조건

                   쿼리를 조작할 수 있는 문자(' or =)를 포함한 입력
                   ~~~~~~~~~~~
원래 의도와 다르게 외부 입력값에 의해 쿼리의 구조와 의미가 변경되어서 실행 → SQL 인젝션
~~~~~~~~~                                           ~~~~
이름에 특정 글자가 포함된 회원을 조회           모든 회원 데이터를 조회하는 것으로 변경

구조화 된 쿼리를 실행하는 형태로 변경

import pymysql

try:
    with pymysql.connect(host="localhost", port=3306, user="springboot", passwd="p@ssw0rd", db="sampledb", cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        name = input("검색할 이름을 입력하세요 : ")

		# 쿼리문의 구조 정의
        query = "select * from members where member_name like %s" 
        
        # execute() 함수의 두번째 인자에 쿼리 실행에 필요한 값을
          튜플 또는 딕셔너리를 통해 전달	
        count = curr.execute(query, ('%'+name+'%',))	
        
        # execute() 함수가 값을 안전한 형태로 변경해서 쿼리를 생성, 실행
        print(f"총 {count}건을 조회했습니다.")		
        
        results = curr.fetchall()		 
        print(f"총 {len(results)}건을 조회했습니다.")
        for result in results:
            print(f"ID : {result['member_id']}")
            print(f"이름 : {result['member_name']}")
            print(f"나이 : {result['member_age']}")
            print(f"이메일 : {result['member_email']}")
        
except pymysql.MySQLError as e:
    print(e)


검색할 이름을 입력하세요 : a' or 'a' = 'a' or 'a
총 0건을 조회했습니다.

해당 입력을 통해서 생성되는 쿼리는   
→ ' ' 홑따움표가 이스케이프 처리되서 문자 그 자체로  해석
select * from members where member_name like 'a\' or \'a\' = \'a\' or \'a'                                   
                                                                  
                                             |                           |  
                                             +---------------------------+
                                                   %s = 문자열 데이터      

📌 정규표현식

정규표현식(正規表現式, Regular Expression)

https://docs.python.org/ko/3/library/re.html

문자열 데이터에 특정 패턴을 이용해 검색, 추출, 치환하는 기능
문자열을 처리하는 모든 곳에서 사용하는 일종의 형식 언어
정규표현식을 줄여 '정규식' 이라고 하기도 함

정규표현식의 기초

메타문자

특별한 의미를 가진 문자

. ^ $ * + ? { } [ ] \ | ( )

[] 문자

문자 클래스

문자 클래스로 만들어진 정규식

‘[’ 와 ‘]’ 사이의 문자들과 매치’라는 의미
[ ] 사이에는 어떤 문자도 들어가기 가능
[] 안의 두 문자 사이에 하이픈(-)을 사용하면 두 문자 사이의 범위를 의미

Ex.

  • 정규표현식 [abc]
    이 표현식의 의미는 ‘a, b, c 중 한 개의 문자와 매치’라는 뜻

  • 정규표현식 [0-5]
    이 표현식의 의미는 [012345]와 동일

숫자 하나

[0123456789]        # 대괄호 → 나열된 데이터 중 하나
[0-9]			    # 대괄호 내의 - → 가질 수 있는 값의 범위
\d
[^0-9]              # 0부터 9까지를 제외한 문자

알파벳 소문자 한 글자

[abcdefghijklmnopqrstuvwxyz]
[a-z]

알파벳 대문자 한 글자

[ABCDEFGHIJKLMNOPQRSTUVWXYZ]
[A-Z]

알파벳 대소문자 관계 없이 한 글자

[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]
[a-zA-Z]

📢 여기서 잠깐! 문자 클래스 안에 ^ 메타 문자를 사용한다면?
이는 반대(not)라는 의미
[^0-9] 라는 정규 표현식이 있다면 이는 숫자가 아닌 문자만 매치

16진수 한 글자

[0-9a-fA-F]

문자열의 시작 지정

^ 			# circumflex, carrot, hat, cap

문자열의 끝 지정

$

한글 한 글자

[ㄱ-힣]

반복횟수 지정

? * + {m} {m,n} 등으로 표현

\d?		    # 숫자 0회 또는 1회 
\d*		    # 숫자 0회 이상
\d+		    # 숫자 1회 이상
\d{m}	    # 숫자 m개 
\d{m, n} 	# 숫자 m개 ~ n개

.(dot) 문자

줄바꿈 문자인 \n을 제외한 모든 문자와 매치
단, re.DOTALL 옵션을 주면 .(dot) 문자와 \n 문자도 매치 가능

a.b
→ "a + 모든 문자 + b"
→ 즉, a와 b라는 문자 사이에 어떤 문자가 들어가도 모두 매치된다는 뜻

* 문자

반복을 의미하는 메타 문자

ca*t
→ * 앞에 있는 a가 아예 안 나오거나 여러 번 반복될 수 있다는 뜻
→ ct(a가 0번 반복), cat(a가 0번 이상 반복), caaat(a가 0번 이상 반복)
  등의 문자열은 모두 매치 가능

+ 문자

최소 1번 이상의 반복을 나타내는 문자
*와 차이점이라면 *은 반복 횟수 0번부터라면 +은 반복 횟수 1번부터 포함

ca+t
→ "c + a가 1번 이상 반복 + t"
→  cat(a가 1번 이상 반복), caaat(a가 3번 이상 반복) 등과는 매치가 가능하지만
   ct(a가 0번 반복)와는 매치 X

{} 문자와 ? 문자

{} 메타 문자를 사용하면 반복 횟수를 고정시켜줌

? 메타 문자는 문자가 아예 없거나 1번 있는
→ ? 메타 문자가 의미하는 것은 {0, 1}

{m, n} 정규식
→ 반복 횟수가 m부터 n까지인 문자와 매치
→ m 또는 n 생략 가능

ab?c
→ "a + b가 있어도 되고 없어도 됨 + c"

정규 표현식을 지원하는 re 모듈

파이썬은 정규 표현식의 지원을 위해 re(regular expression) 모듈을 제공
re 모듈은 파이썬을 설치할 때 자동으로 설치되는 표준 라이브러리

💡 re 모듈의 사용 방법
패턴이란 정규식을 컴파일한 결과

>> import re
>> p = re.compile('ab*')  # re.compile을 사용하여
                            정규 표현식(예제에선 ab*)을 컴파일
                          # re.compile의 리턴값을
                            객체 p(컴파일 된 패턴 객체)
                            에 할당해 그 이후의 작업 수행

정규식을 이용한 문자열 검색

컴파일 된 패턴 객체를 사용하여 문자열 검색 수행
총 4가지 메서드 제공

💡 Method와 목적
match()
→ 문자열이 정규식과 매치되는지 조사
search()
→ 문자열 전체를 검색해 매치되는지 조사
findall()
→ 매치되는 문자열(substring)을 리스트로 반환
finditer()
→ 매치되는 문자열을 반복 가능한 객체로 반환
※ match, search
→ 매치될 때는 match 객체 리턴, 매치되지 않을 때는 None 리턴
※ match 객체
→ 정규식의 검색 결과로 리턴된 객체를 의미

아래의 패턴 객체로 메서드 사용 예시

import re
p = re.compile('[a-z]+')

match

문자열의 처음부터 정규식과 맞는지 조사

"python" 문자열은 [a-z]+ 정규식에 부합되므로 match 객체 리턴

result = p.match('python')
print(result)           # <re.Match object; span=(0, 6), match='python'>

"3 python" 문자열은 첫 번째 문자인 3이 [a-z]+에 부합되지 않으므로 None 리턴

result = p.match('3 python')
print(result)           # None

"life is too short" 문자열은 [a-z]+ 정규식에 부합되므로 match 객체 리턴

result = p.match('life is too short')
print(result)           # <re.Match object; span=(0, 4), match='life'>

컴파일 된 객체 p로 search 메서드 수행

"python" 문자열에 search 메서드를 수행하면 match 때와 동일하게 매치

result = p.search('python')
print(result)           # <re.Match object; span=(0, 6), match='python'>

"3 python" 문자열의 첫 번째 문자는 3이지만 search는 문자열의 처음부터가 아닌 문자열의 전체를 검색하므로 "3" 이후의 "python" 문자열과 매치

result = p.search('3 python')
print(result)           # <re.Match object; span=(2, 8), match='python'>

"life is too short" 문자열에 search 메서드를 수행하면 match 때와 마찬가지로 정규식에 부합되므로 매치

result = p.search('life is too short')
print(result)           # <re.Match object; span=(2, 8), match='python'>

💡 match 메서드와 search 메서드
문자열의 처음부터 검색할지의 여부에 따라 다르게 사용

findall

패턴([a-z]+)과 매치되는 모든 값을 찾아 리스트로 리턴

results = p.findall('life is too short')
print(results)          # ['life', 'is', 'too', 'short']


results = p.findall('3 python')
print(results)          # ['python']

finditer

findall과 동일하지만 그 결과로 반복 가능한 객체(iterator object) 리턴
또한 반복 가능한 객체가 포함하는 각각의 요소는 match 객체

results = p.finditer('life is too short')
print(results)          # <callable_iterator object at 0x000002232E003310>
for result in results: print(result)
                        # <re.Match object; span=(0, 4), match='life'>
                        # <re.Match object; span=(5, 7), match='is'>
                        # <re.Match object; span=(8, 11), match='too'>
                        # <re.Match object; span=(12, 17), match='short'>
results = p.finditer('3 python')
print(results)          # <callable_iterator object at 0x000002232E0024A0>
for result in results: print(result)
                        # <re.Match object; span=(2, 8), match='python'>

match 객체의 메서드

match 객체

p.match, p.search 또는 p.finditer 메서드에 의해 리턴된 매치 객체(Match Object)를 의미

💡 Method와 목적
group
→ 매치된 문자열을 리턴
start
→ 매치된 문자열의 시작 위치 리턴
end
→ 매치된 문자열의 끝 위치 리턴
span
→ 매치된 문자열의 (시작, 끝)에 해당하는 튜플 리턴

import re

p = re.compile('[a-z]+')

result = p.search('3 python')
type(result)

print("매치된 문자열 : " + result.group())  # python
print("매치된 문자열의 시작 위치(인덱스) : " + str(result.start()))  # 2
print("매치된 문자열의 끝 위치(인덱스) : " + str(result.end()))  # 8
print("매치된 문자열의 (시작, 끝) 튜플 : " + str(result.span()))  # (2, 8)

💡 search 메서드
문자열의 처음부터 검색하는 게 아니라 문자열 전체를 검색하기 때문에 3 이후의 python 문자열과 매치되어 start 값은 2가 나옴

컴파일 옵션

DOTALL, S

. 메타 문자는 줄바꿈 문자(\n)를 제외한 모든 문자와 매치되는 규칙 존재
만약 \n 문자도 포함하여 매치하고 싶다면 re.DOTALL 또는 re.S 옵션을 사용해 정규식을 컴파일하면 됨

💡 re.DOTALL 옵션
여러 줄로 이루어진 문자열에서 줄바꿈 문자에 상관없이 검색할 때 많이 사용

import re

p = re.compile('a.b')
result = p.match('a\nb')    # \n은 매치되지 않기에 결과값으로 None 출력
print(result)               # None

p = re.compile('a.b', re.DOTALL)
result = p.match('a\nb')
print(result)               # <re.Match object; span=(0, 3), match='a\nb'>

IGNORECASE, I

re.IGNORECASE 또는 re.I 옵션은 대소문자 구별 없이 매치를 수행할 때 사용하는 옵션

💡 [a-z]+ 정규식
소문자만을 의미
→ 그러나 re.I 옵션으로 대소문자 구별 없이 매치

import re

p = re.compile('[a-z]+')
result = p.match('python')
print(result)               # <re.Match object; span=(0, 6), match='python'>

result = p.match('Python')
print(result)               # None

result = p.match('PYTHON')
print(result)               # None

p = re.compile('[a-z]+', re.IGNORECASE)
result = p.match('python')
print(result)               # <re.Match object; span=(0, 6), match='python'>

result = p.match('Python')
print(result)               # <re.Match object; span=(0, 6), match='Python'>

result = p.match('PYTHON')
print(result)               # <re.Match object; span=(0, 6), match='PYTHON'>

MULTILINE, M

^는 문자열의 처음, 는 문자열의 마지막을 의미
re.MULTILINE 또는 re.M 옵션은 이러한 메타 문자인 ^ , 와 연관된 옵션

Ex.

  • 정규식이 ^python이라면 문자열의 처음이 python으로 시작해야 매치

  • 정규식이 python$이라면 문자열의 마지막이 python으로 끝나야 매치

import re
p = re.compile(r'^python\s\w+')

result = p.findall('''python one\nlife...\npython
two\nyour nedd python\npython threee
life is too short
python two
your nedd python
python three''')
print(result)           # ['python one']

^python\s\w+

python이라는 문자열로 시작하고 그 뒤에 화이트 스페이스, 그 뒤에 단어가 와야 한다는 의미

결과로 'python one'만 출력

이는 ^ 메타 문자에 의해 python이라는 문자열을 사용한 첫 번째 줄만 매치된 것

💡 ^ 메타 문자
문자열 전체의 처음이 아니라 각 라인의 처음으로 인식시키고 싶은 경우에 사용할 수 있는 옵션이 바로 re.MULTILINE 또는 re.M

# r"^python\s\w+" 또는 "^python\\s\\w+" 형식으로 표기
p = re.compile(r"^python\s\w+", re.MULTILINE)

# raw string           \를 이용해서 이스케이프
result = p.findall('''python one			
life is too short
python two
your nedd python
python three''')        # ['python one', 'python two', 'python three']
print(result)    

re.MULTILINE 옵션의 뜻

^ 메타 문자가 문자열 전체가 아닌 각 줄의 처음이라는 의미를 가지게 됨

즉, re.MULTILINE 옵션은 ^ , $ 메타 문자를 문자열의 각 줄마다 적용해 주는 것

실습 #1

CLI 기반의 CRUD 프로그램 작성

==========================
메뉴
--------------------------
Q : 종료			⇒ 프로그램을 종료
I : 등록			⇒ 등록 화면으로 이동
S : 검색			⇒ 검색 화면으로 이동
==========================
메뉴를 선택하세요 >>>  


==========================
회원 등록
--------------------------
이름 : 홍길동
나이 : 23
이메일 : hong@test.com
==========================
Y : 등록 / N : 취소		⇒ Y → members 테이블에 입력한 내용을 저장 → 등록했습니다. 출력 후 메뉴 화면으로 이동
				   N → 메뉴 화면으로 이동


==========================
회원 검색
--------------------------
이름 : 길동
==========================
Y : 검색 / N : 취소		⇒ Y → members 테이블에 like 검색 → 검색 결과 화면으로 이동
				   N → 메뉴 화면으로 이동


==========================
회원 검색 결과 (OO건)
--------------------------
1 : 홍길동			⇐ 검색 결과를 회원아이디 : 회원이름 형식으로 나열
2 : 고길동			   검색 결과가 없는 경우 "일치하는 결과 없음" 출력
3 : 신길동 
==========================
번호 : 상세 조회 / N : 취소	⇒ 번호(회원 아이디) → 회원 상세 조회 화면으로 이동 
				   N → 메뉴 화면으로 이동


==========================
회원 상세 조회
--------------------------
아이디 : 1
이름 : 홍길동
나이 : 23
이메일 : hong@test.com
==========================
U : 수정 D : 삭제 / Y : 메뉴로 이동	
⇒ D → 해당 회원 삭제 후 "삭제했습니다" 메시지를 출력하고 메뉴 화면으로 이동
   U → 수정 화면으로 이동 
				   N → 메뉴 화면으로 이동

==========================
회원 수정
--------------------------
아이디 : 1			⇐ 회원 정보를 보여주고,
이름 : 홍길동
나이 : 23
이메일 : hong@test.com
==========================
나이 : 43			
이메일 : gildong@test.com
수정할까요? (Y : 수정 / N : 메뉴로 이동)
				   Y → 나이와 이메일을 입력 받아서 수정 후 "수정했습니다." 메시지를 출력하고 
        메뉴 화면으로 이동 
				   N → 메뉴 화면으로 이동

회원 등록 및 수정 시 이름, 나이, 이메일의 형식을 정규식을 이용해서 검증 후 등록/수정 처리

결과

import pymysql
import re


def print_double_line():
    print("======================================")

def print_single_line():
    print("--------------------------------------")

def show_menu_page():
    print_title("메뉴")
    print("I: 회원 정보를 등록합니다.")
    print("S: 회원을 LIKE 검색합니다.")
    print("Q: 프로그램을 종료합니다.")
    print_tail()

def print_title(title):
    print()
    print_double_line()
    print(title)
    print_single_line()

def print_tail():
    print_double_line()    

def check_input(message, valid_inputs, line_yn=False):
    if line_yn: print("--------------------------------------")

    while True:
        i = input(message + " >>> ").upper()
        if i in valid_inputs:
            return i
        else:
            print("잘못된 입력입니다. 확인 후 다시 입력해 주세요.")

def main():
    while True:
        show_menu_page()

        menu = check_input("메뉴를 선택하세요. (I: 등록 / S: 검색 / Q: 종료)", "ISQ")
        if menu == "Q":
            print("프로그램을 종료합니다.")
            break
        elif menu == "I":
            do_insert()
        elif menu == "S":
            do_search()


def do_insert():
    print_title("회원 등록")
   
    name = input("이름 : ")
    age = int(input("나이 : "))
    email = input("이메일 : ")


    # 형식 검증 후 메시지 출력


    yn = check_input("등록하시겠습니까? (Y: 등록 / N: 취소)", "YN", True)
    if yn == "N":
        print("회원 등록을 취소합니다.")
        return
   
    query = "insert into members (member_name, member_age, member_email) value (%s, %s, %s)"

    with pymysql.connect(host="localhost", port=3306, user="springboot", password="p@ssw0rd", db="sampledb", autocommit=True) as connection:
        cursor = connection.cursor()
        count = cursor.execute(query, (name, age, email))
        if count == 1:
            print("회원 정보를 정상적으로 등록했습니다.")
        else:
            print("회원 정보를 등록하는데 실패했습니다.")


def do_search():

    print_title("회원 검색")    
    name = input("이름 : ")
   
    yn = check_input("검색하시겠습니까? (Y: 검색 / N: 취소)", ("Y", "N"), True)
    if yn == "N":
        print("회원 검색을 취소합니다.")
        return    
   
    with pymysql.connect(host="localhost", port=3306, user="springboot", password="p@ssw0rd", db="sampledb", cursorclass=pymysql.cursors.DictCursor) as connection:
        query = "select * from members where member_name like %s"
        cursor = connection.cursor()
        count = cursor.execute(query, ("%"+name+"%",))
        members = cursor.fetchall()
       
        print_title(f"검색 결과 (총 {count}건)")

        if count == 0:
            print("일치하는 결과가 없습니다. ")
        else:
            ids = []
            for member in members:
                ids.append(str(member['member_id']))
                print(f"{member['member_id']} : {member['member_name']}")
               
            do_detail(ids)


def do_detail(ids: list):
    ids.append('N')
    mid = check_input("상세 정보를 조회하시겠습니까? (회원번호: 상세조회 / N: 취소)", ids, True)
    if mid == "N":
        print("상세 조회를 취소합니다.")
        return
   
    with pymysql.connect(host="localhost", port=3306, user="springboot", password="p@ssw0rd", db="sampledb", cursorclass=pymysql.cursors.DictCursor) as connection:
        query = "select * from members where member_id like %s"
        cursor = connection.cursor()
        cursor.execute(query, (mid,))
        member = cursor.fetchone()

        print_title("회원 상세 조회")
        print(f"ID: {member['member_id']}")
        print(f"이름: {member['member_name']}")
        print(f"나이: {member['member_age']}")
        print(f"이메일: {member['member_email']}")

        udn = check_input("회원 정보를 수정 또는 삭제하시겠습니까? (U: 수정 / D: 삭제 / N: 메인 화면으로 이동)", ("U", "D", "N"), True)
        if udn == "U":
            do_update(member['member_id'])
        elif udn == "D":
            do_delete(member['member_id'])
        elif udn == "N":
            return


def do_delete(id):
    yn = check_input("회원 정보를 삭제하시겠습니까? (Y: 삭제 / N: 취소)", "YN")
    if yn == "N":
        print("회원 정보 삭제를 취소합니다.")
        return
   
    with pymysql.connect(host="localhost", port=3306, user="springboot", password="p@ssw0rd", db="sampledb", autocommit=True) as connection:
        query = "delete from members where member_id = %s"
        cursor = connection.cursor()
        count = cursor.execute(query, (id,))
        if count == 1:
            print("회원 정보를 정상적으로 삭제했습니다.")
        else:
            print("회원 정보를 삭제하는데 실패했습니다.")


def do_update(id):
    with pymysql.connect(host="localhost", port=3306, user="springboot", password="p@ssw0rd", db="sampledb", cursorclass=pymysql.cursors.DictCursor) as connection:
        query = "select * from members where member_id like %s"
        cursor = connection.cursor()
        cursor.execute(query, (id,))
        member = cursor.fetchone()

        print_title("회원 정보 수정")
        print(f"ID: {member['member_id']}")
        print(f"이름: {member['member_name']}")
        print(f"나이: {member['member_age']}")
        print(f"이메일: {member['member_email']}")    
        print_tail()

        age = int(input("변경할 나이 : "))
        email = input("변경할 이메일 : ")


        # TODO 형식 검증 후 메시지 출력


        yn = check_input("회원 정보를 수정하시겠습니까? (Y: 수정 / N: 취소)", ("Y", "N"), True)
        if yn == "N":
            print("회원 정보 수정을 취소합니다.")
        else:
            query = "update members set member_age = %s, member_email = %s where member_id = %s"
            cursor = connection.cursor()
            count = cursor.execute(query, (age, email, id))
            if count == 1:
                print("회원 정보를 정상적으로 수정했습니다.")
                connection.commit()
            else:
                print("회원 정보를 수정하는데 실패했습니다.")
                connection.rollback()


if __name__ == "__main__":
    main()

0개의 댓글