레이블이 flask인 게시물을 표시합니다. 모든 게시물 표시
레이블이 flask인 게시물을 표시합니다. 모든 게시물 표시

2022년 10월 30일 일요일

python flask jquery and cookie(jquery로 만드는 cookie 테스트)

 쿠키(cookie)란

인터넷 사용자가 어떠한 웹사이트를 방문할 경우 웹 브라우저를 통해 저장되는 정보를 쿠키, 웹 쿠키, 브라우저 쿠키라고 한다.

말이 어려운데 쇼핑몰의 장바구니를 생각해보면 좋을것 같습니다. 쇼핑몰에서 제품을 클릭할때 서버에 정보를 기록하는 것이 아닌 사용자의 PC에 임시로 저장하는 용도입니다.

JQuery plugin으로 쉽게 제어 가능하며 예제를 통해 알아 보도록 하겠습니다.


jquery-cookie 플러그인 설치

cdnjs(https://cdnjs.com/)사이트에서 'jquery-cookie'를 검색하여 url을 복사하면 아래와 같은 코드를 얻을 수 있습니다. 이것을 html script 부분에 추가해줍니다.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js" integrity="sha512-3j3VU6WC5rPQB4Ld1jnLV7Kd5xr+cq9avvhwqzbH/taCRNURoeEpoPBK9pDyeukwSxwRPJ8fDgvYXd6SkaZ2TA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>


쿠기 만들기

세션 쿠키 생성 - 브라우저를 닫으면 없으집니다.
$.cookie('name', 'value');

7일 뒤에 만료되는 쿠키 생성
$.cookie('name', 'value', { expires:7 });

전체 사이트에 대해 7일 뒤에 만료되는 쿠키 생성
$.cookie('name', 'value', { expires:7, path: '/' });


쿠키 읽기

키로 사용한 이름으로 값을 읽습니다.
키로 저장된 값이 없으면 undefined 가 반환됩니다.
$.cookie('name'); // => "value"
$.cookie('nothing'); // => undefined

모든 쿠키 읽기
모든 쿠키를 { "name": "value" } 형태의 객체로 반환합니다.
$.cookie(); // => { "name": "value" }


쿠키 삭제하기

성공적으로 삭제되면 true 를 반환하고, 삭제 못했을때는 false를 반환합니다.
$.removeCookie('name'); // => true
$.removeCookie('nothing'); // => false

쿠키 삭제시 생성할때 와 같은 path와 domain 을 사용해야 합니다.
$.cookie('name', 'value', { path: '/' });
$.removeCookie('name', { path: '/' }); 


flask 예제

base가 되는 예제는 flask를 이용하여 간단한 계산기를 만든 예제입니다. 여기에서 계산 하는 부분은 python으로 구현이 되어 있는데 cookie를 이용하여 마지막 계산했던 문장을 저장해서 다음번 접속할때 그대로 보여주도록 제작하였습니다.


base 예제 설명

https://swlock.blogspot.com/2021/12/python-flask-processing.html


python flask 소스

import time
from flask import Flask
from flask import request
from flask import render_template
from flask import jsonify

app = Flask(__name__)

@app.route("/")
def index():
return render_template('input_form_loading_wc.html')

@app.route("/ajax_page", methods=['POST'])
def ajax_page():
data = request.get_json()
print(data)
calc_result = calc(data['inputdata'])
rdata = {}
rdata['inputdata']=data['inputdata']
rdata['rlt']=calc_result
print(rdata)
return jsonify(rdata)

def calc(inputdata):
print("calc")
time.sleep(10)
return str(eval(inputdata))

if __name__ == "__main__":
app.run(port=8080, debug=True)


templates/imput_form_loadin_wc.html 소스

<!--https://api.jquery.com/click/-->
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>calc flask demo</title>
  <script src="https://code.jquery.com/jquery-3.5.0.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js" integrity="sha512-3j3VU6WC5rPQB4Ld1jnLV7Kd5xr+cq9avvhwqzbH/taCRNURoeEpoPBK9pDyeukwSxwRPJ8fDgvYXd6SkaZ2TA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>

<input type="text" id="txt1" name="inputdata"></input>
<button id="btn1">submit</button>

<div id="result1">
</div>

</p>

<script>
$( document ).ready(function() {
    <!--for debugging $.removeCookie('save_val1', { path: '/' });-->
    var cookie_val1 = $.cookie('save_val1')
    console.log( "ready!" );
    console.log( cookie_val1 );
    <!--undefined-->
    if (cookie_val1==undefined) {
        $('#txt1').val( "0" )
    }else{
        $('#txt1').val( cookie_val1 )
    }
});

$( "#btn1" ).click(function() {
	var txt1_v = $('#txt1').val();
	var postdata = {
		'inputdata':txt1_v
	}
	$.cookie('save_val1',txt1_v,{expires:7,path:'/'})
	$("#result1").text("processing...")
	<!--alert( "Handler for .click() called." );-->
	$.ajax({
		type: 'POST',
		url: '{{url_for("ajax_page")}}',
		data: JSON.stringify(postdata),
		dataType : 'JSON',
		contentType: "application/json",
		success: function(data){
			$("#result1").text("result:"+data['inputdata'] + '=' + data['rlt']);
			<!--alert('success :' + data['inputdata'] + '=' + data['rlt'])-->
		},
		error: function(request, status, error){
			alert('ajax error:['+status+']['+error+']')
		}
	})
});

</script>
 
</body>
</html>


소스 설명

이번에 추가된된 핵심 코드 부분만 설명 하도록 하겠습니다.

python 쪽은 수정 사항이 없다고 보면 되고 html의 script section만 변경되었습니다.

header에 cookie 를 사용하기 위한 script 부분이 추가되었습니다.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"

그리고 script 안쪽에 쿠키 값을 읽어오는 부분이 추가했습니다.

var cookie_val1 = $.cookie('save_val1')

만약 읽은 값이 없는 경우 undefined 값이 들어오는데 이때 0 이 입력되도록 처리했습니다.

if (cookie_val1==undefined) {
$('#txt1').val( "0" )
}else{
$('#txt1').val( cookie_val1 )
}

submit 버튼을 누르게 되면 cookie에 값을 저장하는 코드를 넣었습니다.

$.cookie('save_val1',txt1_v,{expires:7,path:'/'})

해당 쿠키는 7일의 유효 기간을 가지도록 처리했습니다.

또한 쿠키값을 삭제할 필요가 있어서 필요할 때 삭제 하기 위해서 주석 형태로 추가 하였습니다.

<!--for debugging $.removeCookie('save_val1', { path: '/' });-->




2022년 2월 27일 일요일

flask에 favicon 추가하기

favicon이란 탭이나 북마크를 구별하기 위해 사용하는 브라우저에서 사용되는 아이콘이다.

기본적으로는 16*16의 icon 파일을 기본으로 하고 있지만 일부 브라우저 파일에서 png 파일도 지원하고 있습니다.


favicon 만들기

windows에서 ico파일을 편집할 수 없기때문에 검색해서 만들수 있는 site에서 만들면 됩니다. 

https://favicon.io/ 에서 텍스트를 이용해서 간단하게 만들었습니다.


flask에서 넣기 참고 링크

https://flask-docs-kr.readthedocs.io/ko/latest/patterns/favicon.html


html에 설정하기

<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">


소스파일에 추가하기

방법1

app.add_url_rule('/favicon.ico',
                 redirect_to=url_for('static', filename='favicon.ico'))

어플리케이션에 루트에 있어야 한다고 하는데... 막상 해보면 방법1은 잘안됩니다. 


방법2

import os
from flask import send_from_directory

@app.route('/favicon.ico')
def favicon():
    return send_from_directory(os.path.join(app.root_path, 'static'),
                               'favicon.ico', mimetype='image/vnd.microsoft.icon')


 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
import os
from flask import send_from_directory
from flask import Flask
from flask import request
from flask import render_template
from flask import jsonify

app = Flask(__name__)

@app.route("/")
def index():
	return render_template('input_form.html')

@app.route('/favicon.ico')
def favicon():
	return send_from_directory(os.path.join(app.root_path, 'static'),'favicon.ico', mimetype='image/vnd.microsoft.icon')

@app.route("/ajax_page", methods=['POST'])
def ajax_page():
	data = request.get_json()
	print(data)
	calc_result = calc(data['inputdata'])
	rdata = {}
	rdata['inputdata']=data['inputdata']
	rdata['rlt']=calc_result
	print(rdata)
	return jsonify(rdata)

def calc(inputdata):
	return str(eval(inputdata))

if __name__ == "__main__":
	app.run(port=8080, debug=True)




static 폴더를 만들고 거기에 favicon.ico 파일을 넣어야 하는데 위치는 전체 소스를 참고하세요.

전체 소스는 github에 있습니다.

https://github.com/donarts/sourcecode/tree/main/python/example/_36_flask_favicon



2022년 1월 30일 일요일

Ajax로 받은 html tag를 rendering 하기

ajax로 리턴되는 값은 단순히 text일 수도 있고 아닐 수 도 있습니다. 만약 html 코드가 온다면 어떻게 처리하면 되는지에 대해서 정리하였습니다. 즉 서버에서 <b>Hello</b> tag 형태의 ajax리턴이 온다면 html 로 rendering 하는 방법입니다.

결론부터 말하자면 html 이라는 jquery 를 사용하면 됩니다.

flask로 예제를 만들어 보도록 하겠습니다.


python 예제 코드

flask 로 만들었고 예전 계산기 간단 코드에서 가져왔습니다. 

무조건 리턴값은 json format으로 {"return":"<b>Hello</b>"} 가 리턴되도록 작업하였습니다. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask import Flask
from flask import request
from flask import render_template
from flask import jsonify

app = Flask(__name__)

@app.route("/")
def index():
	return render_template('input_form.html')

@app.route("/ajax_page", methods=['POST'])
def ajax_page():
	data = request.get_json()
	print(data)
	rdata = {}
	rdata['return']="<b>Hello</b>"
	print(rdata)
	return jsonify(rdata)

if __name__ == "__main__":
	app.run(port=8080, debug=True)


html 코드

13 라인에 <div id="result1"></div> 결과 값을 보여주기위한 내용을 준비시켜 두고,

31 라인에 $("#result1").html(data['return']); 코드에 의해서 html tag가 렌더링 됩니다.

 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
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>flask demo</title>
  <script src="https://code.jquery.com/jquery-3.5.0.js"></script>
</head>
<body>
 
<input type="text" id="txt1" name="inputdata"></input>
<button id="btn1">submit</button>

<div id="result1"></div>

</p>

<script>
$( "#btn1" ).click(function() {
	var txt1_v = $('#txt1').val();
	var postdata = {
		'inputdata':txt1_v
	}
	<!--alert( "Handler for .click() called." );-->
	$.ajax({
		type: 'POST',
		url: '{{url_for("ajax_page")}}',
		data: JSON.stringify(postdata),
		dataType : 'JSON',
		contentType: "application/json",
		success: function(data){
			$("#result1").html(data['return']);
		},
		error: function(request, status, error){
			alert('ajax error:['+status+']['+error+']')
		}
	})
});

</script>
 
</body>
</html>


동작 화면

submit 버튼만 누르면 Hello라는 글씨가 굵게 출력됩니다.










2021년 12월 12일 일요일

python flask 응답이 느릴때 처리, processing중임을 표시하기


앞에서 간단한 계산기를 Flask로 구현해보았습니다. 계산기는 간단한 예일 뿐이고 실제는 좀더 복잡한 처리를 하게될겁니다. 

만약 복잡한 처리에 시간이 오래 걸린다면 어떻게 될까요?

계산기에 sleep 코드를 넣고 시험을 해보았습니다.


기존 코드

def calc(inputdata):

return str(eval(inputdata))


작업된 코드 10초 기다림

def calc(inputdata):

print("calc")

time.sleep(10)

return str(eval(inputdata))


예상은 브라우저가 계속 로딩중이 뜰줄 알았는데, 실제는 그렇지 않습니다.


위 화면이 10초 머무르다가

10초 후에 결과가 나오는 형태가 됩니다.


좀더 자연스러운 결과가 나오도록 수정해보겠습니다.

submit 버튼을 누르면 화면에 처리중이라는 표시가 나오도록 만들고 처리가 끝나면 결과가 나오도록 하면 됩니다.


기존 코드

<script>

$( "#btn1" ).click(function() {

var txt1_v = $('#txt1').val();

var postdata = {

'inputdata':txt1_v

}

<!--alert( "Handler for .click() called." );-->

$.ajax({

type: 'POST',

url: '{{url_for("ajax_page")}}',

data: JSON.stringify(postdata),

dataType : 'JSON',

contentType: "application/json",

success: function(data){

$("#result1").text("result:"+data['inputdata'] + '=' + data['rlt']);

<!--alert('success :' + data['inputdata'] + '=' + data['rlt'])-->

},

error: function(request, status, error){

alert('ajax error:['+status+']['+error+']')

}

})

});


</script>


변경 코드

<script>

$( "#btn1" ).click(function() {

var txt1_v = $('#txt1').val();

var postdata = {

'inputdata':txt1_v

}

$("#result1").text("processing...")

<!--alert( "Handler for .click() called." );-->

$.ajax({

type: 'POST',

url: '{{url_for("ajax_page")}}',

data: JSON.stringify(postdata),

dataType : 'JSON',

contentType: "application/json",

success: function(data){

$("#result1").text("result:"+data['inputdata'] + '=' + data['rlt']);

<!--alert('success :' + data['inputdata'] + '=' + data['rlt'])-->

},

error: function(request, status, error){

alert('ajax error:['+status+']['+error+']')

}

})

});


</script>


submit 버튼을 누르면 아래와 같은 화면이 표시되고 여기에서 10초 간 머무르게 됩니다.



그리고 결과는 기존처럼 나옵니다.


전체 코드

flask 코드

import time
from flask import Flask
from flask import request
from flask import render_template
from flask import jsonify

app = Flask(__name__)

@app.route("/")
def index():
	return render_template('input_form_loading.html')

@app.route("/ajax_page", methods=['POST'])
def ajax_page():
	data = request.get_json()
	print(data)
	calc_result = calc(data['inputdata'])
	rdata = {}
	rdata['inputdata']=data['inputdata']
	rdata['rlt']=calc_result
	print(rdata)
	return jsonify(rdata)

def calc(inputdata):
	print("calc")
	time.sleep(10)
	return str(eval(inputdata))

if __name__ == "__main__":
	app.run(port=8080, debug=True)


html 코드 templates/input_form_loading.html

<!--https://api.jquery.com/click/-->
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>calc flask demo</title>
  <script src="https://code.jquery.com/jquery-3.5.0.js"></script>
</head>
<body>
 
<input type="text" id="txt1" name="inputdata"></input>
<button id="btn1">submit</button>

<div id="result1">
</div>

</p>

<script>
$( "#btn1" ).click(function() {
	var txt1_v = $('#txt1').val();
	var postdata = {
		'inputdata':txt1_v
	}
	$("#result1").text("processing...")
	<!--alert( "Handler for .click() called." );-->
	$.ajax({
		type: 'POST',
		url: '{{url_for("ajax_page")}}',
		data: JSON.stringify(postdata),
		dataType : 'JSON',
		contentType: "application/json",
		success: function(data){
			$("#result1").text("result:"+data['inputdata'] + '=' + data['rlt']);
			<!--alert('success :' + data['inputdata'] + '=' + data['rlt'])-->
		},
		error: function(request, status, error){
			alert('ajax error:['+status+']['+error+']')
		}
	})
});

</script>
 
</body>
</html>


전반적으로 필요한 내용들은 모두 한것 같습니다.

당연한거지만 한가지 더 실험을 해보겠습니다. 여러명이 동시에 브라우저 접속이 일어난다면 어떻게 될까요?

당연한것이지만 flask가 thread를 여러개 생성시켜서 잘 처리해줍니다. 이 부분을 고려해서 여러프로세서 동기화 처리 작업을 하면 됩니다.




2021년 12월 4일 토요일

python에서 flask 로 web app 그리고 ajax calc jQuery 예제 맛보기


https://swlock.blogspot.com/2021/11/python-web-applications-with-flask.html

앞에서 flask를 이용해서 간단하게 웹앱을 제작해 보았습니다. 계산 결과를 받아서 전체 화면 페이지가 갱신이 되는 구조입니다. 그러나 어디까지나 간단한 예제일뿐 우리가 봐오던 웹앱과는 거리가 멀어 보입니다. 

단순 HTML의 한계를 뛰어 넘어야 하는데, Ajax라고 불리는 기술이 필요합니다. 처음 접했을때 뭔가 거창하게 들립니다만 Ajax는 자바스크립트에 기반하는 부분 데이터를 주고 받고 그 결과를 가지고 web 컨텐츠를 조작하는 기술이라고 보면됩니다.

(Ajax란 Asynchronous JavaScript and XML의 약자입니다. Ajax는 동적인 웹 페이지를 만들기 위한 개발 기법의 하나로 웹 페이지 전체를 다시 로딩하지 않고도, 웹 페이지의 일부분만을 갱신할 수 있습니다.)

예전에는 만들려면 좀 더 복잡했었는데, JavaScript 전문가가 아니다보니, jQuery라는 라이브러리를 사용해서 좀 더 쉽게 Ajax를 이용 할 수 있습니다.


이번 시간의 목표는 이전에 했던 계산기의 전체 페이지를 갱신하지 않고 일부분만 갱신하도록 구현해보겠습니다.


Ajax, Javascript, Flask, jQuery 에 대해서 조금씩 다루어지지만 깊게 들어가지는 않기 때문에 위 내용을 잘 몰라도 이해하는것은 큰 무리가 없을것 같습니다.


Flask의 render_template

Flask는 html파일을 만들어 놓고 비슷한 페이지를 조금씩 조작해서 처리 할 수 있는데 Jinja2 template engine을 사용합니다. 알아 두어야 하는 점이 하나 있는데 

@app.route("/")

def index():

return render_template('input_form.html')

이런 형태로 코드를 구현하면 'input_form.html' 파일을 현재 폴더에서 찾는게 아니라 templates 이란 하위 폴더 구조에서 찾게 됩니다.

https://flask.palletsprojects.com/en/2.0.x/quickstart/#rendering-templates

Case 1: a module:

/application.py
/templates
    /hello.html

Case 2: a package:

/application
    /__init__.py
    /templates
        /hello.html

즉 위와 같이 폴더를 구성해야합니다.

이번에는 Jinja2 문법에 대해서는 사용하지 않고 html 파일 그대로 브라워저로 보낼 예정이라 폴더 구조 부분만 이해하면 됩니다.


jQuery

Ajax 와 동적인 html처리를 위한 javascript 라이브러리 입니다.

html 코드 위치에 아래와 같은 코드를 삽입하고

<head>

  <script src="https://code.jquery.com/jquery-3.5.0.js"></script>

</head>

자신이 작성한 script는 body의 마지막 부분에 추가합니다.

스크립트 내에 아래와 같은 코드가 있습니다.

$(선택자).동작함수();

달러($) 기호는 jQuery를 의미하고, jQuery에 접근할 수 있게 해주는 식별자입니다.

$() 함수에 전달되는 인수는 반드시 따옴표("")를 사용한 문자열 형태로 전달되어야 합니다.

아이디(id)를 사용하여 특정 HTML 요소를 선택할 수도 있습니다. (#이용)

선택자를 이용하여 원하는 HTML 요소를 선택하고, 동작 함수를 정의하여 선택된 요소에 원하는 동작을 설정합니다.

여기에서 사용한 예제는 아래 링크를 참고 하였습니다.

https://api.jquery.com/click/


Javascript 기본

var 는 변수를 정의할때 사용합니다. javascript 언어는 type은 미리 정하지 않지만 변수명은 미리 정해야합니다.


입력 page 소스

<!--https://api.jquery.com/click/-->
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>calc flask demo</title>
  <script src="https://code.jquery.com/jquery-3.5.0.js"></script>
</head>
<body>
 
<input type="text" id="txt1" name="inputdata"></input>
<button id="btn1">submit</button>

<div id="result1">
</div>

</p>

<script>
$( "#btn1" ).click(function() {
	var txt1_v = $('#txt1').val();
	var postdata = {
		'inputdata':txt1_v
	}
	<!--alert( "Handler for .click() called." );-->
	$.ajax({
		type: 'POST',
		url: '{{url_for("ajax_page")}}',
		data: JSON.stringify(postdata),
		dataType : 'JSON',
		contentType: "application/json",
		success: function(data){
			$("#result1").text("result:"+data['inputdata'] + '=' + data['rlt']);
			<!--alert('success :' + data['inputdata'] + '=' + data['rlt'])-->
		},
		error: function(request, status, error){
			alert('ajax error:['+status+']['+error+']')
		}
	})
});

</script>
 
</body>
</html>


입력 html을 브라우저로 올린 화면

브라우저로 로딩을 하면 위와 같은 화면이 나온다.


스크립트 설명

주된 핵심은 3줄이 됩니다. 

<input type="text" id="txt1" name="inputdata"></input>

<button id="btn1">submit</button>

위 두줄은 입력을 받기위한 것입니다.

<div id="result1">

</div>

div는 결과를 출력하기 위한 용도입니다. 모두 id값이 들어있음을 주목 하세요. jQurey에서 #id를 하면 쉽게 접근이 가능합니다.

$( "#btn1" ).click(function() { 여기서 부터는 버튼을 클릭했을때 처리하는 부분입니다.

txt1_v 에 값을 읽은뒤 postdata 를 이용해서 json format 을 만들게 됩니다.

$.ajax({ 를 이용해서 ajax를 사용하게 되는데 이때 url: '{{url_for("ajax_page")}}', 여기에 기록된 page로 접속을 하게 되며 비로서 Flask에서 처리하게 됩니다. 

이번에는 Flask가 처리를 하고나면 success 쪽으로 넘어오게 되는데

$("#result1").text("result:"+data['inputdata'] + '=' + data['rlt']);

핵심 코드는 위 코드입니다. 위에 div로 설정해둔 아무런 text내용도 없는 곳에 위 코드로 인해서 결과가 기록되게 됩니다. 즉 전체 페이지가 갱신되는게 아니라 web page의 일부분만 값이 변경된다는 의미입니다.

다음으로는 Flask 코드입니다.


Flask 소스

from flask import Flask
from flask import request
from flask import render_template
from flask import jsonify

app = Flask(__name__)

@app.route("/")
def index():
	return render_template('input_form.html')

@app.route("/ajax_page", methods=['POST'])
def ajax_page():
	data = request.get_json()
	print(data)
	calc_result = calc(data['inputdata'])
	rdata = {}
	rdata['inputdata']=data['inputdata']
	rdata['rlt']=calc_result
	print(rdata)
	return jsonify(rdata)

def calc(inputdata):
	return str(eval(inputdata))

if __name__ == "__main__":
	app.run(port=8080, debug=True)

이전보다 코드가 깔끔해졌습니다. 

위에서 작성한 html은 input_form.html 이름으로 만들어 templates 폴더 아래에 위치하도록 합니다.  

/ 로 접속하되면 계산기 입력을 받을 수 있는 페이지 input_form.html이 나오도록 해줍니다.

그리고 /ajax_page 에 json format으로 접속이 되면 inputdata 항목에 있는 내용을 계산한뒤 다시 json 형태로 전달하면 됩니다.

아래 코드가 json을 받아서 출력해보는 코드입니다.

data = request.get_json()

print(data)

아래 코드는 계산된 결과를 rdata에 담아서 json으로 리턴하는 내용입니다.

rdata = {}

rdata['inputdata']=data['inputdata']

rdata['rlt']=calc_result

print(rdata)

return jsonify(rdata)


실행화면


소스코드



2021년 11월 28일 일요일

Python Web Applications (파이썬 웹 앱 만들기, 계산기) with Flask

 

시작전

Python 스크립트를 제작했다면 그것을 실행시킬 환경이 필요합니다. 이때 실행가능한 패키지를 만들어 배포하는 방법과 web application 형태로 만들어서 배포가 가능합니다.

그럼 Web Applications 이란 무엇일까요?

여기저기 검색해보니 모든 종류의 동적 웹 페이지와 "웹 앱" 간의 일반적인 구분은 명확하지 않습니다. "웹 응용 프로그램"이라고 하는 웹 사이트는 데스크톱 소프트웨어 응용 프로그램 또는 모바일 응용 프로그램과 유사한 기능을 가진 사이트입니다. 

웹 응용 프로그램의 장점은 플랫폼에 독립적이며 인터넷에 액세스할 수 있는 모든 사람이 실행할 수 있다는 것입니다. 코드는 백엔드 서버에서 구현되며, 이 서버에서는 프로그램이 들어오는 요청을 처리하고 모든 브라우저에서 이해할 수 있는 공유 프로토콜을 통해 응답합니다.

Python은 많은 대형 웹 애플리케이션을 지원하며 백엔드 언어로 일반적으로 선택됩니다


7 Top Python Frameworks 

조금만 검색해보면 유명한 framework이 많이 있습니다.

7 Top Python Frameworks You Should Consider for Your Next Project

Full Stack Framework

CubicWeb

Django

Web2Py

Giotto

Pylons Framework

Pyramid

TurboGears

Micro-framework

Bottle

CherryPy

Dash

Falcon

Flask

Hug

MorePath

Pycnic

출처 : https://www.monocubed.com/top-python-frameworks/

여기에서는 Flask 를 통해서 간단한 예제를 만들어 보도록 하겠습니다.



Python 웹 애플리케이션에 대해 알아보기

오래전 웹 사이트에는 해당 페이지에 액세스한 모든 사용자에게 동일한 고정 콘텐츠가 있었습니다. 이러한 웹 페이지는 상호 작용할 때 콘텐츠가 변경되지 않기 때문에 정적이라고 합니다. 정적 웹 페이지를 제공할 때 웹 서버는 어떤 사용자인지 관계없이 누구나 같은 해당 페이지의 콘텐츠를 다시 전송하여 요청에 응답합니다.

오늘날 대부분의 웹 사이트는 제공하는 콘텐츠를 변경할 수 있는 동적 웹 페이지를 제공하는 진정한 웹 앱입니다.

예를 들어 GMail 페이지를 접속해보면 다양한 방법으로 사용자와 응용 프로그램간 상호 작용할 수 있습니다. 

Python 기반 웹 애플리케이션은 Python 코드를 사용하여 수행할 작업과 표시할 콘텐츠를 결정합니다. 코드는 웹사이트를 호스팅하는 웹 서버에서 실행되므로 사용자는 아무것도 설치할 필요가 없습니다. 코드와 상호 작용하는 데 필요한 것은 브라우저와 인터넷 연결뿐입니다.

웹 사이트에서 Python을 실행하는 것은 복잡할 수 있지만 세부 사항을 자동으로 처리하는 다양한 웹 프레임워크가 있습니다. 위에서 언급했듯이 여기에서는 Flask 를 이용할 것입니다.

또한 실제 서버를 사용할것은 아니고 local pc에 간단하게 호스팅을 할 예정으로 따로 서버를 준비할 필요는 없습니다.


Flash 기본

flask간단한 기본 코드는 아래와 같습니다. 

파일명 : _31_flask_webapp.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
	return "This is a web app!"

if __name__ == "__main__":
	app.run(port=8080, debug=True)

cmd line 에서 위 내용의 py 파일을 실행 시켜줘야합니다.

C:\Users\USER\Documents\GitHub\sourcecode\python\example>python _31_flask_webapp.py
 * Serving Flask app "_31_flask_webapp" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 298-679-048
 * Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)

이번에는 브라우저로 접속 하면됩니다. 127.0.0.1 은 localhost와 같습니다.


간단한것 같은데 이걸로 뭘 만들 수 있을지 생각해봅시다.


Python 계산기 스크립트를 브라우저에서 실행 시키기

계산기를 웹 앱으로 만들기 전에 일단 계산기 기본 함수 코드는 아래와 같습니다.

def calc(stringdata):
	return str(eval(stringdata))

if __name__ == "__main__":
	print(calc("100+200"))

핵심은 eval 이란 내장 함수입니다. 너무 너무 간단합니다.

시험삼아 위와 같은 코드를 실행시키면 결과로 300 이 출력됩니다.


flask 기본앱에 계산기를 넣어보겠습니다.

전체 코드는 아래와 같습니다. 

from flask import Flask
from flask import request

app = Flask(__name__)

@app.route("/")
def index():
	inputdata = request.args.get("inputdata", "")
	if inputdata:
		result = calc(inputdata)
	else:
		result = ""
	return (
		"""<form action="" method="get">
				Calc : <input type="text" name="inputdata">
				<input type="submit" value="calc">
			</form>"""
		+ "result:" + result
	)

def calc(inputdata):
	return str(eval(inputdata))

if __name__ == "__main__":
	app.run(port=8080, debug=True)

위 코드를 이해하기 위해서는 html의 기초적인 문법을 이해해야하는데 아래 코드는 입력을 받기위한 FORM을 구성하는 화면입니다.

<form action="" method="get">
Calc : <input type="text" name="inputdata">
<input type="submit" value="calc">
</form>

이때 FORM의 get메소드를 이용해서 전송한다는 뜻입니다. 그리고 입력은 text로 inputdata란 이름을 가지고 있으며, 전송 버튼을 누르면 인자를 통해서 inputdata와 내용이 전송 되는데 request.args.get("inputdata", "") 이런 형태로 서버에서는 읽을 수 있습니다. 여기서 get은 form이 get으로 되어있기 때문에 그렇습니다.

inputdata 가 있으면 이전에 만들어둔 calc함수를 호출하여 결과를 받고 아니라면 빈결과를 출력 하면 됩니다.


서버를 실행시켜줍니다.

C:\Users\USER\Documents\GitHub\sourcecode\python\example>python _31_flask_webapp.py
 * Serving Flask app "_31_flask_webapp" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 298-679-048
 * Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)
127.0.0.1 - - [28/Nov/2021 16:55:16] "GET / HTTP/1.1" 200 -


아래와 같이 브라우저를 이용해서 접속을 합니다.



계산을 원하는 값을 넣고 


calc 버튼을 누르면 아래와 같이 동작 됩니다.

로컬 PC를 통해서 간단하게 Flask 로 Python 스크립트 실행시키는것에 대해서 알아보았습니다.
python으로 위와 같이 구현한 코드는 UI 적인 동작 부분을 뺀코드라고 보는게 맞을 것입니다.
웹앱을 만들려고 하면 브라우저에서 동작하는 코드를 만들어야 하니 당연히 HTML에 대해서 잘 알아야 합니다.

2021년 10월 4일 월요일

python flask 의 자동 리로드 기능 구현(How to make flask's automatic reload function)


이번에는 자동 리로드(자신의 소스를 수정하면 자동으로 재시작하는)기능을 직접 만들어 보겠습니다. 시작하기전에 몇가지 알아둬야 하는 상식이 있는데 이건 따로 아래 링크를 참고하세요

0. python flask 의 자동 리로드 기능 구현 원리(How to implement flask's automatic reload function) : https://swlock.blogspot.com/2021/10/python-flask-how-to-implement-flasks.html

1. subprocess call,run,Popen 차이 및 간단 사용법 : https://swlock.blogspot.com/2021/10/python-subprocess-callpopenrun.html

2. 파일이 변화 하였는지 감시하기 : https://swlock.blogspot.com/2021/10/python-monitor-if-file-has-changed.html


이제 본격적인 시작입니다.

구현을 위해서는 먼저 자신이 자신을 실행시키는 구조에 대해 이해해야합니다.

그래서 이해하기 쉽게 도식도를 그려봤습니다.



위 그림은 cmd line shell에서 python 스크립트 실행하는것이 (1) 이 됩니다. 해당 스크립트가 실행중이다가 재시작을 해야 한다고 판단될때 (2)를 실행하게 됩니다. 그러면서 기존에 있는 프로세서는 wait 상태가 됨을 의미합니다.

이걸 구현하는 방법은 subprocess의 blocking 방식의 run/call을 사용하면 됩니다.

즉 subprocess.run() -> subprocess.run() -> subprocess.run() 이런 형태가 됩니다. 여기에서 큰 문제가 하나가 있는데 중간에 process가 종료되지 않고 쌓이게 됨으로서 memory/process leak이 발생하게 됩니다.

네모 상자는 Process를 의미합니다. 화살표는 Process를 실행하는 subprocss를 의미하거나 종료됨을 의미합니다.

그럼 leak이 발생하지 않도록 이번에는 Popen을 이용해보도록 합시다.


형태는 위와 같이 됩니다. (2)가 호출되면서 process는 종료되면서 (3),(5)처럼 됩니다. leak이 발생하는 부분은 해결되었습니다. 그러나 여기에 문제가 있는데 shell에서 볼때 shell이 실행시킨 프로세서가 종료됨으로서 cmd line으로 돌아온 상태로 process와 통신을 할 수 없는 상태가 되어버립니다. 이를 해결하는 방법은 두가지 방법을 적당히 섞는 것입니다.


딱 봐도 구성이 복잡합니다. (2)(5)번이 reload로 자기자신을 실행시키는 구간이며 처음에는 (2) 가 호출되며 (2)에 의해서 process가 호출할때 blocking 방식으로 호출합니다. 그리고 reload구간에 진입하면 reload를 하는게 아니라 (3)에 의해서 process를 종료시킵니다. 이때 이전 process가 blocking에 의해서 집입되어 있으므로 blocking이 해제됩니다. 이 부분을 무한 루프로 구현하여 (4)가 되면서 새로운 process (5)를 생성시킵니다. 그리고 reload될때 (6)에 의해서 종료되는 방식입니다.

코드를 보면 좀 더 자세히 이해가 될겁니다.

import time
import os
import subprocess
import sys

import _19_check_file_mod

def get_args_for_reloading():
	py_exe = [sys.executable]
	py_script = sys.argv[0]
	args = sys.argv[1:]
	print(py_exe,py_script,args)
	
	ret=[]
	ret.extend(py_exe)
	ret.append(py_script)
	ret.extend(args)
	return ret

def reload():
	print("reload")
	args = get_args_for_reloading()
	new_environ = os.environ.copy()
	if os.environ.get("RE_LOAD")=="true":
		sys.exit(0)
		print("never")
	else:
		while True:
			sys.stdout.flush()
			new_environ["RE_LOAD"] = "true"
			exit_code = subprocess.call(args, env=new_environ)
			print("exit_code:",exit_code)
			time.sleep(1)
			
	print("never")

if __name__ == '__main__':
	check_file_mod = _19_check_file_mod.check_file_mod(sys.argv[0])
	print("start !")
	if os.environ.get("RE_LOAD")=="true":
		print("reloaded")
		
	while True:
		if check_file_mod.is_change():
			print("change")
			reload()
		time.sleep(2)

파일 변화 검사는 

check_file_mod = _19_check_file_mod.check_file_mod(sys.argv[0])

위 코드이며 다음 링크에서 참고 하기 바랍니다. 2. 파일이 변화 하였는지 감시하기 : https://swlock.blogspot.com/2021/10/python-monitor-if-file-has-changed.html


shell에 의해서 처음 실행된 프로세서 그림에서 (1)은 os.environ.get("RE_LOAD") 값이 없게 됩니다. 그래서 while 구간으로 진입해서 아래와 같은 코드에 머무르게 됩니다.

		while True:
			sys.stdout.flush()
			new_environ["RE_LOAD"] = "true"
			exit_code = subprocess.call(args, env=new_environ)
			print("exit_code:",exit_code)
			time.sleep(1)

이 부분이 (4)(7) 번 위치의 코드입니다.

	if os.environ.get("RE_LOAD")=="true":
		sys.exit(0)
		print("never")

그리고, 위 구간은 (3)(6) 번 처럼 돌아오기 위한 구간이 되겠습니다.


테스트 방법

테스트는 위 예제를 실행 후 실행한 파일을 수정하면 됩니다.

실행시 import 에러가 발생한다면 2. 링크의 게시물을 보시고 파일을 하나 더 추가해주면 됩니다.



2021년 10월 3일 일요일

python flask 의 자동 리로드 기능 구현 원리(How to implement flask's automatic reload function)

 

Flask 처음 실행해보고 느낌이 팍 온것은 flask 기능이 아니라 실행중인 auto reload하는 기능이었다. 이 얼마나 아름다운 기능인가!

실행 소스가 아닌 config/parameter 라면 쉽게 reload하는것은 구현이 간단하다. 그렇지만 flask테스트시 그건 단순 paramer reload가 아닌 소스코드 재시작임을 알 수 있었다.

일반적인 언어라면 구현이 까다롭다. 특히 컴파일 언어의 경우 컴파일 하는것도 힘들고 자기자신이 자기자신 process를 죽이고 다시 살아나는건 좀 더 어렵다고 볼 수 있다.

그렇다면 스크립트 언어라면 쉬울까? 딱히 그렇지도 않다. 하지만 python언어라면 python 언어 내부에서 다른 python 스크립트를 실행하는것도 가능하니 불가능할것은 없다고 생각된다.

Flask 소스코드를 탐험해보고 구현원리를 파헤치는 모험이 지금 시작 되고 있다.

소스 코드는  https://github.com/pallets/flask github에 친절히 있다. 실행하는곳을 따라가 보기로 했다.


기본 소스의 위치는 아래 링크에 있다.

https://github.com/pallets/flask/blob/main/src/flask/app.py


여기 class에서 부터 출발 하면 되는데

class Flask(Scaffold): 

당연히 기본함수는 run이 된다. flask실행시 run() 함수를 실행 시키므로

    def run(
        self,
        host: t.Optional[str] = None,
        port: t.Optional[int] = None,
        debug: t.Optional[bool] = None,
        load_dotenv: bool = True,
        **options: t.Any,
    ) -> None:

run함수 내부에 이런부분이 있다. 

        try:
            run_simple(t.cast(str, host), port, self, **options)

run_simple로 실행시키는가본데... 

        from werkzeug.serving import run_simple


이런... Flask가 본체가 아니었던 것이다. 

werkzeug 얘가 본체인것이다.

그럼 werkzeug 이걸 찾아들어가보자.

소스는 역시 github 형님이 가지고 있고, https://github.com/pallets/werkzeug


소스 여기에서 run_simple 본체를 찾을 수 있었다.

https://github.com/pallets/werkzeug/blob/main/src/werkzeug/serving.py


def run_simple(

더 깊은 곳으로

def run_with_reloader(*args: t.Any, **kwargs: t.Any) -> None:
    """Run a process with the reloader. This is not a public API, do
    not use this function.
    .. deprecated:: 2.0
        Will be removed in Werkzeug 2.1.
    """
    from ._reloader import run_with_reloader as _rwr


    warnings.warn(
        (
            "'run_with_reloader' is a private API, it will no longer be"
            " accessible in Werkzeug 2.1. Use 'run_simple' instead."
        ),
        DeprecationWarning,
        stacklevel=2,
    )
    _rwr(*args, **kwargs)


https://github.com/pallets/werkzeug/blob/main/src/werkzeug/_reloader.py


그렇다. ReloaderLoop 클래스를 찾을 수 있었다. 얘기 대마왕인것이다.

class ReloaderLoop:

여기가 그중에서도 핵심이 되겠다.

    def restart_with_reloader(self) -> int:
        """Spawn a new Python interpreter with the same arguments as the
        current one, but running the reloader thread.
        """
        while True:
            _log("info", f" * Restarting with {self.name}")
            args = _get_args_for_reloading()
            new_environ = os.environ.copy()
            new_environ["WERKZEUG_RUN_MAIN"] = "true"
            exit_code = subprocess.call(args, env=new_environ, close_fds=False)

            if exit_code != 3:
                return exit_code

def _get_args_for_reloading() -> t.List[str]: =>실행키켰던 exe 파일이나 인자들을 구해와서 subprocess 로 똑같이 재시작 하고 있던 것이었다.


그리고 아래 부분도 주의해서 봐야할 부분이다. 직접 실행했을때와 reload로 실행했을때 차이를 환경변수 environ["WERKZEUG_RUN_MAIN"] = "true" 에 값을 넣어서 그 조건일때만 다른 처리를 하도록 구현하였다.

def run_with_reloader(
    main_func: t.Callable[[], None],
    extra_files: t.Optional[t.Iterable[str]] = None,
    exclude_patterns: t.Optional[t.Iterable[str]] = None,
    interval: t.Union[int, float] = 1,
    reloader_type: str = "auto",
) -> None:
    """Run the given function in an independent Python interpreter."""
    import signal

    signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
    reloader = reloader_loops[reloader_type](
        extra_files=extra_files, exclude_patterns=exclude_patterns, interval=interval
    )

    try:
        if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
            ensure_echo_on()
            t = threading.Thread(target=main_func, args=())
            t.daemon = True

            # Enter the reloader to set up initial state, then start
            # the app thread and reloader update loop.
            with reloader:
                t.start()
                reloader.run()
        else:
            sys.exit(reloader.restart_with_reloader())
    except KeyboardInterrupt:
        pass


소스 따라가기는 여기서 마치도록 하고 실제 간단한 샘플로 구현은 나중으로 미루기로 한다.

정리하자면 재시작 하는 방법은 

1. 실행을 어떻게 했는지 정보를 파악하고

2. subprocess.call 로 동일하게 재시작한다.

3. 자신은 종료

4. 필요시 환경 변수를 이용해 처리