セッターゲッターについて
まずアクセス修飾子について
アクセス修飾子はクラスを基準としてアクセスできるかどうかを定めるものと考えると良い。
class Person{ public name: string; constructor(name: string){ this.name = name; }; }
let mario = Person("mario"); mario.name = 'maria'; console.log(mario.name); // maria
クラス外でもnameプロパティにアクセスできるのがpublicで、これをクラス外からアクセスできないようにするにはprivateを使う。
class Person{ private name: string; constructor(name: string){ this.name = name; }; }
let mario = Person("mario"); console.log(mario.name); // エラー クラス外からプロパティ値を参照できない mario.name = 'maria'; // エラー クラス外からプロパティを変えられない
privateを使うことで疎結合できるメリットが有るってことらしい。ここはおいておく。 疎結合できることをメリットとすると、クラス外からアクセスする事ができない。
じゃあ特別に参照できるようにするわけで、その時にゲッターを使う。
class Person{ private name: string; constructor(name: string){ this.name = name; }; get name(){ return this.name }; }
let mario = Person("mario"); console.log(mario.name); // mario mario.name = 'maria'; // エラー クラス外からプロパティを変えられない
プロパティの値を変えたいって思うならセッターを使うことになる。ここで疑問。セッターを使えるようにしたら、それはpublicと同じではないか、と。
セッターを使う意義について考えてみたけど、例えばクラス外から参照はできないけどプロパティ属性を変える事ができる場合につかったり、クラス外から属性値をセットする際に不適切値だったらエラーを出すってことしか考えられない。このへんはなにかわかったら書き加えたいところだ。
class Person{ private _name: string; constructor(name: string){ this._name = name; }; get name(){ return this._name }; set name(value:string){ if (!value || value === '') { throw new Error('値が不適切です'); } this._name = value; } }
let mario = Person("mario"); console.log(mario.name); // mario mario.name = 'maria';
あとgetだけでsetがない場合はreadonlyに自動的になるみたい。
typescriptコンパイラの設定について
udemyの超TypeScript入門 完全パック(2021)の内容をメモしている。とてもわかり易いのでおすすめ。
コンパイラの設定方法
- watchモードを使って、保存時に自動的にTSからJSにコンパイルする方法
- tsc --initコマンドでtsconfig.jsonを作り、すべてのファイルを一気にコンパイルする方法
- include,exclude,filesを使ってコンパイルするファイルを選ぶ方法
- targetを指定して、特定のバージョンのjavascriptに変換する方法
- libを指定して、Typescriptが用意している型の定義を追加する
- allowJs, checkJs, jsx, declaration, declarationMapの設定方法
- SourceMapを使用して、ブラウザでTypescriptを操作する方法
- outDirとrootDir,, removeDir, removeComments, noEmit, downlevelLeterationの使い方
- noEmitIbErorオプションを使って、エラーが出た時にコンパイルしない方法
- noImplicitAnyやstrictnullChecksなどのstrictの設定方法
- きれいなコードを書くための設定方法
コンパイラの設定のドキュメント
TypeScript: Documentation - tsc CLI Options
memo
1. watch モードを使って、保存時に自動的に TS から JS にコンパイルする方法
watch モードを使わないでコンパイルする方法は、以下のコマンド使ってコンパイルする。
// tscコマンドを使う tsc index.js // tsc {コンパイルしたいファイル}
watch モードを使う
// -w または --watchオプションを使う tsc -w tsc --watch // 止める際は ctl + C
2. tsc --init コマンドで tsconfig.json を作り、すべてのファイルを一気にコンパイルする方法
tsc コマンドでは、コンパイル対象のファイルを指定する必要があるが、それを予め設定することでファイル指定する手間を省く事ができる。その手間を省くには、tsconfig.json を作っておく必要がある。tsconfig.json は手動で作成するのではなく、以下のコマンドで作成する。
// tsconfig.jsonファイルを作成する tsc --init
djangoのインテリセンスを有効化する
参考: https://kic-yuuki.hatenablog.com/entry/vscode-python-autocomplete
この方の記事が分かりやすかった。 djangoをvscodeで書く際にインテリセンスを働かせる。
visual studio code のルートディレクトリ(プロジェクト)に.vscode/settings.jsonを作成する。そしてsettings.json内にインテリセンスを働かせたいライブラリのパスを追加するとインテリセンスがきくようになる。
.vscode/settings.jsonを作成
mkdir project cd project mkdir .vscode vi settings.json
settings.jsonで書く内容
{ "python.autoComplete.extraPaths": [ "ライブラリが格納されるパス", ] }
ライブラリが格納されるパスは以下で確認することができる。
>> python >>import django >>django.path ['/Users/chiaki/.local/share/virtualenvs/django_test_sample-9pz0Y4Xg/lib/python3.9/site-packages/django'] # ライブラリが格納されるパスはsite-packagesまでなので # '/Users/chiaki/.local/share/virtualenvs/django_test_sample-9pz0Y4Xg/lib/python3.9/site-packages/'
これをsettings.jsonに書くと以下のようになる。
{ "python.autoComplete.extraPaths": [ "/Users/chiaki/.local/share/virtualenvs/django_test_sample-9pz0Y4Xg/lib/python3.9/site-packages/" ] }
typescriptのメモ
typescriptの特徴
Detecting errors in code without running it is referred to as static checking. Determining what’s an error and what’s not based on the kinds of values being operated on is known as static type checking. TypeScript checks a program for errors before execution, and does so based on the kinds of values, it’s a static type checker. For example, the last example above has an error because of the type of obj.
TypeScript: Documentation - TypeScript for the New Programmer
typescriptの特徴はコードを走らせる前からエラーを発見する事ができる。エラーは値の種類に基づいてエラーを発見する。
Roughly speaking, once TypeScript’s compiler is done with checking your code, it erases the types to produce the resulting “compiled” code. This means that once your code is compiled, the resulting plain JS code has no type information.
TypeScript: Documentation - TypeScript for the New Programmer
typescriptはコンパイルされると型を消失し、型のないjavascriptに変化する。
We frequently see the question “Should I learn JavaScript or TypeScript?“. The answer is that you can’t learn TypeScript without learning JavaScript! TypeScript shares syntax and runtime behavior with JavaScript, so anything you learn about JavaScript is helping you learn TypeScript at the same time.
TypeScript: Documentation - TypeScript for the New Programmer
javascriptを学べば、typescriptに直結する。
typescriptのインストール方法
npm でインストールすれば良い
>> npm install -g typescript
typescriptをjavascriptに変換する方法
typescriptをインストールすると、tscコマンドが使えるようになる。tscはtypescriptcompileってことでjavascriptに変換するコマンドである。 このコマンドを使うとコンパイル対象のファイルが新たにjsバージョンで作成される。
>> tsc コンパイル対象の.tsファイル
typescript
型推論ができないときっていつなのか知りたい。 一例:
let test; // この場合testに値が入ってないので型推論しようとしてもできない。
typescriptの型
// typescriptの型とjavascriptの型は同じ名前の型であっても全く別物と考えること let hasValue: boolean = true; let count: number = 10; let float: number = 5.21; let negative: number = -3.522; let single: string = 'hello'; let double: string = "hello"; let back: string = `hello`; // オブジェクトに型をつける場合 const person: { name: string; age: number; } = { name: 'Jack', age: 21 } // 配列に型をつける const fruits: string[] = ['Apple', 'Banana', 'Grape'] // tuple型を使って決まった内容の配列を作る const book: [string, number, boolean] = ['business', 1500, false]; //このように書くと第一要素はstring,第2要素はnumber...と型を指定した配列を定義する事ができる。 //列挙型をENUMの使って定義する enum CoffeeSize { SHORT = 'SHORT', TALL = 'TALL', GRANDE = "GRANDE", VENTI = 'VENTI' } //こういう書き方もある enum CoffeeSize1 { SHORT, TALL, GRANDE, VENTI } // union -> 数字も文字列も入れたいときに使う let unionType: number | string = 10; unionType.toUpperCase(); // エラー unionType = `hello`; unionType.toUpperCase(); // エラーが出ない // unionの配列を作りたい場合 let unionTypes: (number | string)[] = [10, 'hello']; // Literal型(特定の決まった値のみを扱う) let apple: 'apple' = 'apple'; // type'apple'で、'apple'しか入れることができない apple = 'hello'; // エラー 'apple'のみ受け入れないからエラー // typeエイリアス type ClothSize = 'small' | 'medium' | 'large'; let clothSize: ClothSize = "large"; // これは以下のことと同じ let clothSize1: 'small' | 'medium' | 'large' = 'large'; // 関数 function add(num1: number, num2: number): number { return num1 + num2 } function sayHello(): void { console.log('hello'); } // callback関数を使う場合 function doubleAndhandle(num: number, cb: (num: number) => number): void { const doubleNum = cb(num * 2); console.log(num * 2); } doubleAndhandle(21, doubleNum => { return doubleNum });
コンパイラの設定方法について
typescriptコンパイラの設定について - diadia
ゲッター、セッターについて
インターフェースについて
インターフェース = オブジェクトのみのタイプエイリアス
インターフェイスの定義
interface Human { name: string; age: number; } const developer: Human = { name: 'Tom', age: 25 }
implements を使用するとクラスに対してインターフェイスを利用する事ができる
class Developer implements Human { constructor(public name: string, public age: number); greeting(message: string): void { return `${message}`; } }
カスタムディレクティブを使う
ドキュメント
作り方概要
以下2つのステップで実装する。
カスタムディレクティブを定義し、カスタムディレクティブを適用したい要素に設定する。
カスタムディレクティブを定義する
カスタムディレクティブを定義する方法はグローバルとローカルの2つの方法がある。 グローバルでカスタムディレクトリを使いたい場合はmain.jsで定義する。ローカルでカスタムディレクティブを使いたい場合、すなわちそのコンポーネントのみでカスタムディレクティブを使いたい場合は、当該コンポーネントで定義する。
グローバルで定義する
//main.js import Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false // カスタムディレクティブをグローバルで使うときはVue.directiveを使う // 引数は2つ取る 名前とオブジェクトである Vue.directive('border', { bind(el, binding, vnode){}, update(el, binding, vnode){} }); new Vue({ render: h => h(App), }).$mount('#app')
ローカルで定義する
// border という名前のカスタムディレクティブを登録 new Vue({ el: 'app', directives: { border: { bind(el, binding, vnode){}, update(el, binding, vnode){} } } })
フック関数
カスタムディレクティブはVueのようなライフサイクルを持っていて、カスタムディレクティブの挙動を定める=フック関数の挙動を編集する、と置き換えられる。このフック関数はパラメータを取ることができる。それはel, binding, vnode, oldnodeである。
elはDOM操作をしたいときに使い要素だと考えれば良い。
bindingはカスタムディレクティブにセットされた属性値を取り出すときに使われる。
フック関数の種類
- bind()...ディレクティブが初めて対象の要素に紐付いたとき
- inserted()... 親Noneに挿入されたとき
- update()... コンポーネントが更新されるたび。子コンポーネントが更新されるたび
- componentUpdated()...コンポーネントが更新されるたび。子コンポーネントが更新された後
- unbind()... ディレクティブが紐付いてる要素から取り除かれたとき
elを使ったサンプル例
// border という名前のカスタムディレクティブを登録 new Vue({ el: 'app', directives: { border: { bind(el, binding, vnode){ el.textContent = 'カスタムディレクティブが働いていればこの文章が見れる'; }, update(el, binding, vnode){} } } })
bindingを使ったサンプル例
bindingはカスタムディレクティブにセットされた属性値を取り出すときに使われる。てことで、カスタムディレクティブを定義するだけでなく、カスタムディレクティブを使う場面で属性値を定める必要もある
// border という名前のカスタムディレクティブを登録 new Vue({ el: 'app', directives: { border: { bind(el, binding, vnode){ // bindingを使ってカスタムディレクティブの属性値を取り出す // value変数は'カスタムディレクティブが働いていればこの文章が見れる'が入っている const value = binding.value; el.textContent = value; }, update(el, binding, vnode){} } } })
// HelloWorld.vue <template> <p v-border="'カスタムディレクティブが働いていればこの文章が見れる'">Home</p> </template> <script> export default { name: 'HelloWorld', props: { msg: String } } </script>
その他
基本的にbindとupdateを使い、bindとupdateは同じ動作を書くのが基本らしい。
カスタムディレクティブを使うどころ。 -> コードを抽象化したいときに使う!!
カスタムディレクティブを適用したい要素に設定
すべてのカスタムディレクティブはvから始まる。
カスタムディレクティブはv-hogehogeと名付けられる。定義する際はそのv-を除いて定義する。
以下はカスタムディレクティブの呼び出し(定義したものを使用)を指している。 カスタムディレクティブを定義するのは、main.jsかコンポーネントである。
// HelloWorld.vue <template> <p v-border>Home</p> # コレ </template> <script> export default { name: 'HelloWorld', props: { msg: String } } </script>
vueのプロジェクト始め方(vue-cliを使って)
1. Vue cliをインストールする
npm install -g @vue/cli vue --version
2. 新規プロジェクトの作成
#vue create プロジェクト名 vue create myproject
プロジェクトを作る際に出てくるbabel, vuexについて - babelはトランスパイラ。 - Vuexは状態管理を行うためのもの - Routerルート管理するためのもの
3. 作成したプロジェクトをrun server する
cd myproject npm run serve
SQLITE3のINSERT文を動的に作成する。
sqlite3のインサート文は文字列である必要がある。
文字列であるがゆえにダイナミックなインサートは実行しづらいが、
辞書型のデータをもとにインサート文を作成する事ができる。また同一の辞書型データからインサートの値であるタプルを作成できる。
だから動的なものを作るには辞書型のデータを作成すると作れるって覚えておくと便利。
import sqlite3 class JobOffer(object): id = None # int company_name = None # str age = None # str category = None # str type = None # str position = None # str license = None # str period = None # str sector = None # str def __init__(self, data): # dataはプロパティ名とプロパティの値が格納されているものとする for key, value in data.items(): setattr(self, key, value) print('attrチェック', self.__dict__) def generate_insert_data(self): # insertに使うデータはインスタンスのプロパティなのでデータを渡す必要はない data = {} data['table'] = 'my_table' values = {} for key, value in self.__dict__.items(): values[key] = value data['values'] = values return data def create(self, insert_data): """ insert 例:cur.execute("INSERT INTO mydb VALUES (?, ?)", (a,b)) """ table = insert_data['table'] columns = ', '.join(insert_data["values"].keys()) hatena = ', '.join(['?' for n in insert_data["values"].values()]) #print(f"INSERT INTO {table} ({columns}) VALUES ({hatena})" + "\n") insert_sql = f"INSERT INTO {table} ({columns}) VALUES ({hatena})" data = tuple(insert_data['values'].values()) # print(data) conn = sqlite3.connect('db.sqlite3') cursor = conn.cursor() try: cursor.execute(insert_sql, data) conn.commit() except sqlite3.IntegrityError as e: print('UNIQUE constraint failed') cursor.close() conn.close() def update(self): pass @staticmethod def create_table(): conn = sqlite3.connect('db.sqlite3') cursor = conn.cursor() create_table_sql = """CREATE TABLE my_table ( id integer primary key autoincrement, company TEXT, age TEXT, category TEXT, type TEXT, position TEXT, license TEXT, period TEXT, sector TEXT )""" cursor.execute(create_table_sql)
Seleniumを使う際のメモ(改訂版)
困ったときの参照ページ
Python + Selenium で Chrome の自動操作を一通り Selenium の API (Official) Selenium with Python
使用準備
まずseleniumを扱う環境を作成した。anacondaを利用して環境を設定した。
conda create -n selenium python=3.6.4 pip install selenium
これだけではseleniumを利用することはできず、実際にブラウザを制御するドライバーも必要になる。windowsでの環境設定を試みたが、うまく行かなかったのでmacで行った。windowsについては最後まで環境構築できたら記録する。 ドライバーのダウンロード先:https://www.seleniumhq.org/download/ ダウンロードページの少しスクロールした先にThird Party Browser Drivers NOT DEVELOPED by seleniumhqがあり、Google Chrome Driverがある。これをクリックしてバージョンを指定するとmac用のソースがあるのでダウンロードする。
簡単なスクレイプコード
今回はwebdriverを呼び出して、特定のページを表示させ、そのページの特定のタグをスクレイピングするコードを書く。
python from selenium import webdriver driver = webdriver.Chrome("User/myusername/desktop/chromedriver") #Chrorme内にはダウンロードしたwebdriverのフルパスを記述すること driver.get("https://yahoo.co.jp/") for h2 in driver.find_elements_tag_name("h2") print(h2)
classで取得する
# class属性値であり.classNameと書く必要はない driver.find_element_by_tag_name("className")
入力フォームに入力する
入力フォーム(inputタグ)を要素として取得する。そして取得した要素に対し、入力したいキーワードをsend_keys()を使って入力する。
from selenium import webdriver driver = webdriver.Chrome("/users/myusername/desktop/chromedriver") driver.get("https://google.co.jp") input = driver.find_element_by_id("lst-ib") #googleの検索フォームid名はlst-idである。 input.send_keys("ヤフーニュース") seach_button = driver.find_element_by_name("btnK") #検索のボタンはinputタグを取得する search.click() #click()を使うことでボタンを押すことができる。
これでヤフーニュースを検索する事ができた。取得したフォーム要素にclear()メソッドを使うと、フォームに入力した文字列を消去することができる。
現在表示されているウインドウ(ブラウザ)のURLを取得
seleniumはhtmlソースから要素を取得するだけでなく、htmlソース外からデータを取得することができる。具体的にはブラウザのurlである。 【Python】current_url・・・URLを取得する
current_urlメソッドを使うことでカレントページのURLを取得することができる。
current_url = driver.current_url
Chromewebdriverのヘッドレス化
通常運転するとseleniumはchrome等のブラウザを立ち上げて、操作される。これではメモリを消費してしまう理由などからブラウザが見えない状態で操作される状態にすることができる。これは昔PhantomJSというwebdriverがその役割を担ってきたそうだが、メンテナンスをやめるということでPhantomJSからFirefoxやChromeのヘッドレス化が一般になった模様だ。 ヘッドレス化はChromeのバージョンが59以降のものなら利用できる。バージョン確認方法はChrome://versionをブラウザに送ってあげると表示させることができる。
from selenium import webdriver from selenium.webdriver.chrome.options import Options CHROME_PASS = 'C:\\Users\\user\Desktop\chromedriver' options = Options() options.add_argument('--headless') driver_headless = webdriver.Chrome(executable_path = CHROME_PASS, chrome_options=options) #あとは普段どおりにseleniumのコードを書く
ポイントはwebdriverの定義でChromeに2種類の引数を渡すことだ。1つ目はChromedriverの実行ファイルのパスをkwargsとして渡す。2つ目はヘッドレスのオプションをkwargsとして渡すこと。
frame間の移動
iframeタグの中に存在する別のhtmlタグ以下の要素に対しては、そのまま要素を取得できない。そのため取得したい要素を含むiframeタグに移動し、そこから取得する流れに持ち込む。そのとき必要なのはswitch_to_frameやswitch_to_defaul_content()である。
3.4. Moving between windows and frames
driver.switch_to_frame("frameName") #frameNameは要素の取得(find_element...)で定める。 driver.switch_to_default_content() #親のフレームに戻る
別windowへ移動
aタグを押すことで別ウィンドウが開きそこの情報をスクレイピングしたいとする。webdriverはウィンドウが新たに開かれた場合にどのように挙動するのか?新たなウィンドウを開いたとて、現在のwebdriverのウィンドウを継続する。そのため新たに開いたウィンドウに移動する場合にはwebdriverのメソッド switch_to_window()を用いる必要がある。switch_to_window()の引数はwindowIDである。このwindowIDはwindow_handlesでリスト型のデータとしてwindowIDを取得することができる。
driver.get(URL) window_ids = driver.window_handles driver.swich_to_window(window_ids[-1]) https://stackoverflow.com/questions/13113954/selenium-webdriver-using-switch-to-windows-and-printing-the-title-doesnt-prin
python 3.7.1 selenium 3.14.1のバージョンだとswitch_to_window()が使えなかった。switch_to.window()として記述する必要がある。
classの名前が複数ある要素の取得について(有用)
例えばタグが以下のような状態の時、
input class=" first-part copy-target"
このような場合には、いつもfind_element_by_class_nameを試みて、以下のようなエラーが返されていた。
selenium.common.exceptions.InvalidSelectorException: Message: invalid selector: Compound class names not permitted
Compound class names not permittedとは複数のクラスネームの場合は対応できませんよってことだと思う。これに対する解決方法がわかった。それはfind_element_by_css_selectorを使うことだ。上記の場合には、
driver.find_elements_by_css_selector(".first-part.copy-target")
VisualStudio Codeでエディタとターミナル間の移動をショートカットキーに登録する
参考
統合ターミナルウィンドウへフォーカスするショートカットキーを設定する - Qiita
Ctrl+;でエディタ-ターミナル間を移動する(VSCode)
前提
前提としてショートカットキーを登録するには、ショートカットキーを登録するファイルが存在するので、そのファイルに登録したいショートカットキーとその挙動を記述する、ということを知っておかなければならない。
ファイルの探し方とショートカットキーを登録するファイルの名前
ファイルの探し方
ctrl(command) + shift + F または ctrl(command) + shift + P のあとに deleteキーを押して>マークを消す
ショートカットキーを登録するファイル名
ファイル名はkeybindings.jsonである。したがって
コマンドパレットが開いたら、
#以下を打ち込む keybindings.json
するとショートカットキーを登録するファイルが表示される。。
そのファイルに、
{ "key": "ctrl+;", "command": "workbench.action.terminal.focus", "when": "editorTextFocus" }, { "key": "ctrl+;", "command": "workbench.action.focusFirstEditorGroup", "when": "terminalFocus" }
を[ ]内に貼り付ければいい。するとこんな感じになる。
// Place your key bindings in this file to override the defaults [ { "key": "ctrl+;", "command": "workbench.action.terminal.focus", "when": "editorTextFocus" }, { "key": "ctrl+;", "command": "workbench.action.focusFirstEditorGroup", "when": "terminalFocus" } ]
それでctrl+Sを押せばオッケイ。
ちなみにctrl + Shift + P からもkeyboardとかうってるとkeybindings.jsonにたどり着ける。
Djangoのpermissionを付与するサンプルコード
基本的なmodelに基づくpermissionは自動的に作成される。ということで、permisson自体は作らず、作成されているpermissonを付与するサンプルコードを書く。
permissionを付与するにはUserに直接付与するケースとGroupに付与し、そのGroupに属するUserが間接的にパーミッションをもたせるケースがある。
概要;
user1.user_permissions.set([permission]) group.permissions.set([permission]) user2.groups.set([group])
userにはuser_permissions関連オブジェクトが存在する。manyToMayなのでsetとかaddメソッドを使って、user_permissonsにpermissionをセットする事になる。
# Permissonオブジェクトを作成(migrate時に自動作成されていのでパス-> getで取得) blog_view_permission = Permission.objects.get(name="""Can view blog""") # perisson_userにBlogの読み取りアクセス権を与える perisson_user.user_permissions.set([blog_view_permission])
Groupオブジェクトはpermissions関連オブジェクトが存在する。これはmanyToManyの関係なのでsetやaddメソッドでpermissonオブジェクトをセットする。で、Group自体はUser.groupsが関連オブジェクトなのでこれにもsetやaddでセットする。
https://docs.djangoproject.com/ja/3.1/ref/contrib/auth/#django.contrib.auth.models.User.groups
https://docs.djangoproject.com/ja/3.1/ref/contrib/auth/#group-model
# Groupオブジェクトを作成 blog_view_group = Group.objects.create(name='blog_view_group') # GroupオブジェクトにUserオブジェクトを紐づける group_user = User.objects.get(username=group_user_data['username']) group_user.groups.set([blog_view_group]) # 作成したGroupにPermissonオブジェクトを紐づける blog_view_group.permissions.set([blog_view_permission])
gunicornについて
テーマ アプリケーション・サーバー間やりとり
PythonではアプリケーションとWebサーバのインタフェースとして PEP3333 で定義された WSGI(Web Server Gateway Interface)という仕様が広く利用されているらしくgunicornについて全く分かっていないので少し理解しておきたい。
gunicornやuWSGIはWSGIサーバと呼ばれる
WSGIサーバの種類 - gunicorn - uWSGI
gunicornはpythonで動いているものなのか??? 答えはyes リポジトリを見ればpythonで書かれていることを確認できる。
gunicornのリポジトリ https://github.com/benoitc/gunicorn/tree/5d0c7783008d4df87d579a576d19182c4a95caf7/gunicorn
ドキュメント:
https://docs.gunicorn.org/en/stable/run.html
$ gunicorn [OPTIONS] APP_MODULE
wsgiの仕様にあったpythonのコードであれば、WSGIサーバを動かすことができることがわかった。
- WSGIのアプリケーションは、2つの引数を持った呼び出し可能なオブジェクトである。
- 第2引数として渡されたオブジェクトを呼び出し、HTTPステータスコードとヘッダ情報を渡す。
- レスポンスボディとしてバイト文字列をyieldするiterableなオブジェクトを返す。
参考: https://c-bata.link/webframework-in-python/wsgi.html
wsgiの基準では第一引数は辞書型オブジェクトという決まりがあるらしい。 このオブジェクトにurlやHTTPメソッドを格納して利用することができる.
WSGIの説明で分かりやすいもの
では、WSGIとは何でしょうか?WSGI(ウィズギー)とは、PythonでWebアプリケーションとWebサーバーを接続する際に考案されたインターフェース定義です。 その昔、PythonのWebフレームワークが急増したことにより、Webアプリ開発者にある不都合が生じました。 フレームワークごとに、サーバーと接続するためのインターフェースが異なっていたため、同一のアプリケーションであっても、接続できるサーバーが制限される(あるサーバーには接続できるのに、別のサーバーには接続できない)という事態が生じてしまったのです。 そのため、インターフェースを統一して、どのフレームワークでも同じように各種サーバーに接続できるようにしよう、ということで生まれたのがWSGIでした。 WSGIアプリケーションの定義として、 WSGIアプリケーションは、呼び出し可能なオブジェクトとして定義する。このオブジェクトが呼び出される際、第一引数に環境変数が渡され、第二引数にステータスコードとレスポンスヘッダを受け取る呼び出し可能なオブジェクトが渡される。 第二引数に渡されたオブジェクトを呼び出して、ステータスコードとレスポンスヘッダ情報を渡す。 戻り値として、バイト文字列をyieldするiterableなオブジェクトを返す。 といったものがあります。
https://qiita.com/sti320a/items/828d7bceabea5f363ad1
# ファイル名はhello.py def application(env, start_response): start_response('200 OK', [('Content-type', 'text/plain; charset=utf-8')]) return [b'Hello World']
gunicornを起動(gunicorn -w 1 hello:application)してlocalhost:8000にアクセスすると、ブラウザにHello Worldが表示される。
以下の場合も試してみた。
def application(change1, change2): print('change1 :,'change1) print('change2',change2) change2('200 OK', [('Content-type', 'text/plain; charset=utf-8')]) return [b'Hello World']
すると同じように動いた。gunicornではひいてはwsgi仕様では、第一引数と第二引数があり、それらを使って関数さえ書いていればブラウザに表示できることがわかった。
change1, change2を標準出力した結果を残しておく。
change1: {'wsgi.errors': <gunicorn.http.wsgi.WSGIErrorsWrapper object at 0x10ff16460>, 'wsgi.version': (1, 0), 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'wsgi.file_wrapper': <class 'gunicorn.http.wsgi.FileWrapper'>, 'wsgi.input_terminated': True, 'SERVER_SOFTWARE': 'gunicorn/20.0.4', 'wsgi.input': <gunicorn.http.body.Body object at 0x10ff165b0>, 'gunicorn.socket': <socket.socket fd=9, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8000), raddr=('127.0.0.1', 50644)>, 'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'RAW_URI': '/', 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_HOST': 'localhost:8000', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_CACHE_CONTROL': 'max-age=0', 'HTTP_DNT': '1', 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'HTTP_SEC_FETCH_SITE': 'none', 'HTTP_SEC_FETCH_MODE': 'navigate', 'HTTP_SEC_FETCH_USER': '?1', 'HTTP_SEC_FETCH_DEST': 'document', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'ja-JP,ja;q=0.9,es-ES;q=0.8,es;q=0.7,en-US;q=0.6,en;q=0.5', 'HTTP_COOKIE': 'csrftoken=LD89SVbaQvSe6eBpG8yp7zseGHi1yJpaFephs4Bi4SuAgkNFutL027VJmgasezv6; sessionid=n77unsdykcmss7i86rjb7rpziegzro79', 'wsgi.url_scheme': 'http', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '50644', 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '8000', 'PATH_INFO': '/', 'SCRIPT_NAME': ''} start_response <bound method Response.start_response of <gunicorn.http.wsgi.Response object at 0x10ff16490>> change1 : {'wsgi.errors': <gunicorn.http.wsgi.WSGIErrorsWrapper object at 0x10ff16550>, 'wsgi.version': (1, 0), 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'wsgi.file_wrapper': <class 'gunicorn.http.wsgi.FileWrapper'>, 'wsgi.input_terminated': True, 'SERVER_SOFTWARE': 'gunicorn/20.0.4', 'wsgi.input': <gunicorn.http.body.Body object at 0x10ff16490>, 'gunicorn.socket': <socket.socket fd=9, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8000), raddr=('127.0.0.1', 50645)>, 'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'RAW_URI': '/favicon.ico', 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_HOST': 'localhost:8000', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', 'HTTP_DNT': '1', 'HTTP_ACCEPT': 'image/avif,image/webp,image/apng,image/*,*/*;q=0.8', 'HTTP_SEC_FETCH_SITE': 'same-origin', 'HTTP_SEC_FETCH_MODE': 'no-cors', 'HTTP_SEC_FETCH_DEST': 'image', 'HTTP_REFERER': 'http://localhost:8000/', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'ja-JP,ja;q=0.9,es-ES;q=0.8,es;q=0.7,en-US;q=0.6,en;q=0.5', 'HTTP_COOKIE': 'csrftoken=LD89SVbaQvSe6eBpG8yp7zseGHi1yJpaFephs4Bi4SuAgkNFutL027VJmgasezv6; sessionid=n77unsdykcmss7i86rjb7rpziegzro79', 'wsgi.url_scheme': 'http', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '50645', 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '8000', 'PATH_INFO': '/favicon.ico', 'SCRIPT_NAME': ''} change2 <bound method Response.start_response of <gunicorn.http.wsgi.Response object at 0x10ff165e0>>
よく分からないけど二回出力されていることを確認した。
第一引数には何が格納されているか分かりづらいので以下のようにして出力をみた。
def application(env, start_response): print('env : ', env) print('env.keys() : ', env.keys()) print('start_response', start_response) start_response('200 OK', [('Content-type', 'text/plain; charset=utf-8')]) return [b'Hello World']
env.keys() : dict_keys(['wsgi.errors', 'wsgi.version', 'wsgi.multithread', 'wsgi.multiprocess', 'wsgi.run_once', 'wsgi.file_wrapper', 'wsgi.input_terminated', 'SERVER_SOFTWARE', 'wsgi.input', 'gunicorn.socket', 'REQUEST_METHOD', 'QUERY_STRING', 'RAW_URI', 'SERVER_PROTOCOL', 'HTTP_HOST', 'HTTP_CONNECTION', 'HTTP_USER_AGENT', 'HTTP_DNT', 'HTTP_ACCEPT', 'HTTP_SEC_FETCH_SITE', 'HTTP_SEC_FETCH_MODE', 'HTTP_SEC_FETCH_DEST', 'HTTP_REFERER', 'HTTP_ACCEPT_ENCODING', 'HTTP_ACCEPT_LANGUAGE', 'HTTP_COOKIE', 'wsgi.url_scheme', 'REMOTE_ADDR', 'REMOTE_PORT', 'SERVER_NAME', 'SERVER_PORT', 'PATH_INFO', 'SCRIPT_NAME'])
次にこれで実行してみた。なお、'http://localhost:8000/sjfof'をブラウザに入力している。
def application(env, start_response): #print('env : ', env) #print('env.keys() : ', env.keys()) #print('start_response', start_response) print('================start_response前 始まり========================') print('env["REQUEST_METHOD"] : ', env["REQUEST_METHOD"]) print('env["QUERY_STRING"] : ', env["QUERY_STRING"]) print('env["RAW_URI"] : ', env["RAW_URI"]) print('env["HTTP_HOST"] : ', env["HTTP_HOST"]) print('================start_response前 終わり========================') start_response('200 OK', [('Content-type', 'text/plain; charset=utf-8')]) print('================start_response後 始まり========================') print('env["REQUEST_METHOD"] : ', env["REQUEST_METHOD"]) print('env["QUERY_STRING"] : ', env["QUERY_STRING"]) print('env["RAW_URI"] : ', env["RAW_URI"]) print('env["HTTP_HOST"] : ', env["HTTP_HOST"]) print('================start_response後 終わり========================') return [b'Hello World']
================start_response前 始まり======================== env["REQUEST_METHOD"] : GET env["QUERY_STRING"] : env["RAW_URI"] : /sjfof env["HTTP_HOST"] : localhost:8000 ================start_response前 終わり======================== ================start_response後 始まり======================== env["REQUEST_METHOD"] : GET env["QUERY_STRING"] : env["RAW_URI"] : /sjfof env["HTTP_HOST"] : localhost:8000 ================start_response後 終わり======================== ================start_response前 始まり======================== env["REQUEST_METHOD"] : GET env["QUERY_STRING"] : env["RAW_URI"] : /favicon.ico env["HTTP_HOST"] : localhost:8000 ================start_response前 終わり======================== ================start_response後 始まり======================== env["REQUEST_METHOD"] : GET env["QUERY_STRING"] : env["RAW_URI"] : /favicon.ico env["HTTP_HOST"] : localhost:8000 ================start_response後 終わり========================
'http://localhost:8000/sjfof?mykey=myvalue&second=value2'をブラウザに入力している。
================start_response前 始まり======================== env["REQUEST_METHOD"] : GET env["QUERY_STRING"] : mykey=myvalue&second=value2 env["RAW_URI"] : /sjfof?mykey=myvalue&second=value2 env["HTTP_HOST"] : localhost:8000 ================start_response前 終わり======================== ================start_response後 始まり======================== env["REQUEST_METHOD"] : GET env["QUERY_STRING"] : mykey=myvalue&second=value2 env["RAW_URI"] : /sjfof?mykey=myvalue&second=value2 env["HTTP_HOST"] : localhost:8000 ================start_response後 終わり======================== ================start_response前 始まり======================== env["REQUEST_METHOD"] : GET env["QUERY_STRING"] : env["RAW_URI"] : /favicon.ico env["HTTP_HOST"] : localhost:8000 ================start_response前 終わり======================== ================start_response後 始まり======================== env["REQUEST_METHOD"] : GET env["QUERY_STRING"] : env["RAW_URI"] : /favicon.ico env["HTTP_HOST"] : localhost:8000 ================start_response後 終わり========================
この結果からどうやら二回標準出力は2回繰り返される。そして第一引数この場合はenvは1回目と2回目で内容が変化することだけわかった. それかfaviconのuriにアクセスするために二種類のリクエスト情報が作られたと考える方が正しいかもしれない。これは進めていけばいずれわかることだろう。
ここまでわかったこと(後日分かったことも追加している)
第一引数キー | 値 |
---|---|
REQUEST_METHOD | GET、POST等のリクエストメソッド |
QUERY_STRING | ?以降の内容 |
RAW_URI | ブラウザに入力したURIもしくはkeep aliveで追加でアクセスするURIな気がする |
HTTP_HOST | 接続先のホスト |
PATH_INFO | RAW_URIからQUERY_STRING部分を削除した、いわゆるPATH値が格納される |
また、ブラウザに異なるURIを入力してもenv['RAW_URI']にURIが格納されるだけで、実際に起動している 関数(この場合はhello.py application関数)が起動するだけである。したがってapplication関数内でRAW_URIの内容によって表示する条件分岐を作ればリソースのアドレス可能性を担保できるようになるのではないか??
APIViewのpermission_classesはどんな仕組みで動いているのか
APIViewのプロパティは以下の通り。
# APIViewのプロパティ class APIView(View): # The following policies may be set at either globally, or per-view. renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES #コイツ content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS metadata_class = api_settings.DEFAULT_METADATA_CLASS versioning_class = api_settings.DEFAULT_VERSIONING_CLASS # Allow dependency injection of other settings to make testing easier. settings = api_settings schema = DefaultSchema()
次にpermission_classesを使っているメソッドは何があるか??
それはget_permissionsだった。このメソッドはpermisson_classをインスタンス化したリストを作成する役割である。
# https://github.com/encode/django-rest-framework/blob/3db88778893579e1d7609b584ef35409c8aa5a22/rest_framework/views.py#L274 def get_permissions(self): """ Instantiates and returns the list of permissions that this view requires. """ return [permission() for permission in self.permission_classes]
じゃあ、このget_permissionsメソッドはどの文脈で使われているかということが次の疑問になる。
それは以下の通りcheck_permissionsとcheck_object_permissionsメソッドで使われていることが判明した。
def check_permissions(self, request): """ Check if the request should be permitted. Raises an appropriate exception if the request is not permitted. """ for permission in self.get_permissions(): if not permission.has_permission(request, self): self.permission_denied( request, message=getattr(permission, 'message', None), code=getattr(permission, 'code', None) ) def check_object_permissions(self, request, obj): """ Check if the request should be permitted for a given object. Raises an appropriate exception if the request is not permitted. """ for permission in self.get_permissions(): if not permission.has_object_permission(request, self, obj): self.permission_denied( request, message=getattr(permission, 'message', None), code=getattr(permission, 'code', None) )
こレラのメソッドはパーミッションがない場合にパーミッション拒否のメソッドが実行される。そういう意味で、permission.has_permission()メソッドがパーミッションのチェックとなっている。 このメソッドはpermissons.pyの各クラスメソッドに規定されている。