diadia

興味があることをやってみる。自分のメモを残しておきます。

デプロイ時にプロジェクトに必要なライブラリを漏れなくインストールする

結論:pip freezeを使う

サーバーに必要なライブラリをインストールするために開発環境でpip freezeまたはpip listを使い確認しては、サーバーでライブラリをインストールしてきたが一気にインストールしてしまったほうが良い。

pip freeze > requirements.txt

これでテキストファイルができるので、サーバにて以下を実行すれば一気にライブラリをインストールできる。

pip install -r requirements.txt

ドキュメント:https://pip.pypa.io/en/latest/cli/pip_freeze/#examples

django:PCとスマホで表示を変えたい

ユーザーのデバイスによって表示を変更したい

表示方法としてレスポンシブデザインが可能なBootstrapを利用してきた。しかしレスポンシブデザインでは納得いく表現ができなかった。
バイスによって異なるテンプレートを使い分けれればより良くなると思われる。実現できるか調査し実装できれば実装してみたい。

ユーザーのデバイスはuser-agentsを利用して捕捉することができるようだ。
https://pypi.org/project/user-agents/
その他にdjango-user-agentsなるものもあった。これも同じことが実現できそうか?
https://pypi.org/project/django-user-agents/
そもそも標準ライブラリのplatformを使えば対処できるのでは?という疑問も出てくる。何か分かったら更新する。

platformを使う案について

platformではwebアプリにアクセスするユーザーデータ(どんなデバイスを使っているか)を補足することはできない
"実行中プラットフォームの固有情報を参照する"機能を提供するのがplatformなので、これを使ってもdjangoが動くマシンの情報を得るだけになってしまう。だからユーザーのデバイス情報を取得できない。

バイスによってテンプレートを変える

django-user-agentsを使う案

django-user-agentsを使うと手軽にテンプレートをデバイスによって使い分けられるようになった。テンプレートの使い分けはviews.pyでユーザのデバイスの種類を特定し、デバイスの種類によってrenderでテンプレートを使い分ける。

その他に同一のテンプレートにスマホ、PC用の内容を記述する方法もある。この場合はdjango-user-agentsのテンプレートタグである
{% load user_agents %}や{% if request|is_pc %}等を使って実装する。これはデバイスごとに複数のテンプレートを準備するviews.pyのデメリットを解消できる。

下記はviews.pyにdjango-user-agentsを使う方法である。

from django_user_agents.utils import get_user_agent

class ProductListView(View):

    def get(self, request, *args, **kwargs):
        object_list = Product.objects.all()
        context["object_list"] = object_list
        
        user_agent = get_user_agent(request)
        if user_agent.is_mobile:
            return render(request, "products/list_mobile.html", context)
            
        elif user_agent.is_tablet:
            return render(request, "products/list_tablet.html", context)
            
        else:
            return render(request, "products/list_pc.html", context)

django-user-agentsを使うとミドルウェアを通してrequestに属性を付け加えられる。これを利用してユーザーのデバイスを特定することができる。

インストール方法

pip install pyyaml ua-parser user-agents
pip install django-user-agents

場合によっては以下もインストールが必要。

pip install python-memcached

参考:https://stackoverflow.com/questions/41575601/importerror-no-module-named-memcache-django-project

settings.pyの設定項目

INSTALLED_APPS = (
    # Other apps...
    'django_user_agents',
)

# キャッシュを使うとdjango-user-agentsを素早く働かせることができる
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
    }
}


# キャッシュを使わない場合はNoneと記述。キャッシュを使う場合は基本的には'default'を使う
USER_AGENTS_CACHE = 'default'

# ミドルウェアにも下記を加える
MIDDLEWARE_CLASSES = (
    # other middlewares...
    'django_user_agents.middleware.UserAgentMiddleware',
)

django signalの情報を集める

signalの仕組みを理解するには2つのキーワードがある。senderとreceiverである。名前の通り、senderはsignalを送り出す役割を担う。receiverはそのsignalを受取り、何か挙動を起こす役割を持つ。receiverは関数またはインスタンスメソッドで無くてはならない。
senderとreceiverの両者はsignal dispatcherによってつながる。このsignal dispatcherってのはSignalのインスタンスである。

postgresのパスワードが分からない場合

postgresのパスワードが分からない場合

いろんな方のブログを見ていると、postgresqlをインストールすると同時にpostgresユーザーが作成される。そしてpostgresのパスワードも設定することになる、と書いてあるが、自分の場合postgresのパスワードを定める機会がなかった。パスワードを定める機会がなかったり、パスワードを忘れてしまった場合にはデータベースに接続できなくなる。このときの対処法を記録しておく。

手順

まずデータベース接続に無条件で接続できるように認証設定ファイルpg_hba.confを編集してpostgresqlに接続する。接続後、postgresのパスワードを再設定する。

無条件で接続するには, local all all trustに変更し、postgresqlを再起動すればよい。postgresqlがあるローカル環境ならパスワードなしで接続できるようになる。

次にpostgresのパスワード再設定だけれど、これはpostgresに接続した状態で以下のコマンドを実行すれば良い。

ALTER ROLE postgres WITH PASSWORD '新しいパスワード';

結果にALTER ROLEと表示されれば再設定できた。
このあとは再びpg_hba.confでlocal all all md5に戻してpostgresql(postgresql-11)を再起動してあげれば良い。

Bootstrapを見る

ドキュメント

https://getbootstrap.jp/docs/4.2/layout/grid/

レスポンシブレイアウト

レスポンシブレイアウトを構築するための5つの階層が定義されている。(extra small,small,medium,large,exyra large)
バイスを問わず、同じグリッドの場合に使いたいとき、class="col", class="col-*"を使う。

知りたいこと

カード横の空白を無くす方法

django:sendgridでメール本文において&が&に変換されてしまう件について

状況

Checkクラスにはurlを格納する属性があり、一定時間ごとに各インスタンスurlを利用し、ウェブサイトにアクセスする。そして特定の情報が存在した場合にsendgridを使って自分のメールに送信する仕組みを設けた。メール本文にはアクセスしたurlを載せる。メール本文の作成は大まかに以下のようになっている。

from django.core.mail import send_mail
from django.template.loader import render_to_string

class CheckView(View):
    def get(self, request, *args, **kwargs):
    
        ・・・
        check_objs = Check.objects.all()
        for obj in check_objs:
            url = obj.url
            
        ・・・
        
            subject = "チェック結果"
            context = { "url" : url}
            message = render_to_string("mytemplate.txt", context)
        
            send_mail(
                subject,
                message,
                ["mynamesasasasa@gmail.com",],
                fail_silently=False,
                )
        ・・・

この状況でurlの&部分がすべて& amp;に変換されてしまった。

この問題の対処

この原因がどこによるモノなのか分からなかった。sendgridの諸設定によるものかもしれないし、djangoによるものなのかもしれない。
いろいろ試した結果、メール本文にはテンプレートと変数のレンダリングする方式ではなく、そのまま文字列(url)を送る方法を採用した。これにより&の&変換問題が解消された。
コードは以下のような感じになる。

from django.core.mail import send_mail
from django.template.loader import render_to_string

class CheckView(View):
    def get(self, request, *args, **kwargs):
    
        ・・・
        check_objs = Check.objects.all()
        for obj in check_objs:
            url = obj.url
            
        ・・・
        
            subject = "チェック結果"
            # context = { "url" : url} ←削除
            message = url
        
            send_mail(
                subject,
                message,
                ["mynamesasasasa@gmail.com",],
                fail_silently=False,
                )
        ・・・

この結果sendgridから送ったメールがそのまま送ることができたので、原因はdjango側にある可能性が高いと推定した。

今回の原因と周辺情報

https://docs.djangoproject.com/ja/2.2/ref/templates/language/#automatic-html-escaping

要点にまとめると、djangoにはクロスサイトスクリプティング対策がされており、テンプレートレンダリングする場合には変数タグの出力を自動的にエスケープする仕様になっている。つまり常時エスケープ機能がonになっているので、今回の件で別案の解決方法としてエスケープ機能をオフにするときだけ設定する方法もあった。
エスケープ機能については具体的にはエスケープのオフにはエスケープオフの範囲によってタグを使い分ける。
変数単位でエスケープする場合、テンプレートまたはテンプレートの部分にエスケープする場合の2パターンがある。 前者は{{ data | safe }}とすればエスケープ機能がオフになる。後者はテンプレート内で{% autoescape off %}と{% endautoescape %}を挟んだ範囲が機能オフになる。

クロスサイトスクリプティングやセキュリティについて理解を深めたいならば、『体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践』を読むと良いだろう。

python:自作モジュールのインポートについて

自作モジュール内でインポートをするのに困ったので解決方法をメモしておく。

そもそもimportするには?

ディレクトリ以下にあるファイルをインポートすることができる。

    MY_DIR----- myscript.py
           L--- myconfig.py
           L--- init.py

カレントディレクトリがMY_DIRにある時、myscript.py,myconfig.py,init.py内の変数や関数をfrom myscript import var みたいにインポートできる。カレントディレクトリ以下にないpyファイルをインポートする場合はインポートするパスを追加することで対応できる。

具体的には

パスに追加する方法はsys.path.append(パス)で追加できる。

     +-----B_DIR------ function.py
     |            L--- settings.py
    A_DIR
     |
     +--- MY_DIR----- myscript.py
           L--- myconfig.py
           L--- init.py

例えばfunction.pyの関数をmyscript.py内に呼び出す際には

myscript.pyにて
    
    import os, sys
    sys.sppend(os.path.join(os.path.dirname(os.path.dirname(__file__)),"B_DIR"))
    from function import func

こうすればB_DIRのパスを追加できるのでインポートすることができる。

python:画像の保存

どうすれば画像を保存できるか書いておく。

データの種類

画像の保存にはバイナリタイプのデータが求められる。urllib3でurlにアクセスするとデフォルトでバイナリタイプの戻り値なので、画像を保存するには向いている。

保存方法

保存方法はファイルをバイナリ形式でバイナリデータを書き込めばよい。したがって以下のような形をとる。

# requestsを使った場合
import requests

res = requests.get(url).content
with open(IMAGE_FILE, "wb") as file:
    file.write(res)

# urllib3を使った場合    
import urllib3 
res = urllib3.urlopen(url)
with open(IMAGE_FILE, "wb") as file:
    file.write(res)

seleniumでもバイナリデータで情報を取得できるので画像取得はできる。 https://kurozumi.github.io/selenium-python/api.html#selenium.webdriver.remote.webdriver.WebDriver.get_screenshot_as_png

python: windowsとmacから同一のスクリプトを利用する方法

前提

dropboxにデータやスクリプトを配置している。

問題点

windowsmacではcsvモジュールを使う場合以下の差異が存在する。
1.ファイルパスが異なる。
2.csvファイル書き込みの際にlineterminatorが必要か否か。
1.2も今まではwindows用、mac用のスクリプトを別々に準備し使うOSによってスクリプトを使い分けてきた。今回はこの問題を解決する。

import csv, platform

pl = platform.system()
if pl == 'Windows':
    # windowsファイルパス
    CSV_FILE = "C*************.csv"
elif pl == 'Darwin':
    # macファイルパス
    CSV_FILE = "Users/********.csv"



test_list = ["ロシア", "アメリカ", "中国", "ブラジル"]
with open("path/test.csv", "a", encoding="utf-8") as f:
    if pl == 'Windows':
        writer = csv.writer(f, lineterminator="\n")
    elif pl == 'Darwin':
        writer = csv.writer(f)
        
    writer.writerow(test_list)

解決方法

見ればわかる。上記のようにplatformモジュールを使えばよい。
https://docs.python.org/ja/3/library/platform.html#platform.system

Python:CSVモジュール使い方

関連記事

csvモジュールの使い方

リストデータをファイルとしてアウトプットしたい場合にcsvファイルにするのは便利。 csvモジュールの使い方をメモしておく。
csvモジュールはpythonの標準ライブラリなのでpip install csvのようなことはせずに利用することができる。
https://docs.python.org/ja/3/library/csv.html

import csv
使用例

import csv

test_list = ["ロシア", "アメリカ", "中国", "ブラジル"]
with open("path/test.csv", "a", encoding="utf-8") as f:
    writer = csv.writer(f, lineterminator="\n")
    writer.writerow(test_list)

csv.writer()関数を使うとwriterオブジェクトを返す。このwriterオブジェクトにはcsvファイルに書き込むためのメソッドがある。wirerow()メソッド又はwriterows()メソッドである。これらのメソッドに与えた引数がcsvファイルに書き込まれることになる。

writerオブジェクトを作成する

1行書くか複数行書くかのメソッドがある。そのメソッドを使うためのオブジェクトがwriterオブジェクト。 writerで返されるのがwriterオブジェクトである。

 csv.writer(ファイルオブジェクト) 

1行書く場合

writer で返されたオブジェクトにwriterowメソッドを使う。

 w.writerow(シーケンス)  

複数行書く場合

writer で返されたオブジェクトにwriterowsメソッドを使う。

 w.writerows(シーケンス)  

csvファイルの読み込み

csvファイルの一行目がカラムの説明になる場合がある。読み込みの際にこの一行目を省いて2行目以降のデータのみ読み込む方法を調べた。 https://docs.python.jp/3/library/csv.html#reader-objects http://www.bokupy.com/detail/67

 import csv 
with open("book1.csv", "r") as f:
    reader = csv.reader(f)
    for row in reader:
        if reader.line_num == 1:
            continue print(row) 

重要な点として、reader.line_numを使うことのほか、continueも重要である。continueはfor構文中にcontinueに出くわしたとき、今の現在のループを中断し、次の繰り替し処理を行う動作になる。

urllib3についてメモ

document

https://urllib3.readthedocs.io/en/latest/index.html

コードの流れを追う

import urllib3  #1

http = urllib3.PoolManager()  #2
r = http.request('GET', 'http://httpbin.org/robots.txt')   #3

#1

import urllib3

https://github.com/urllib3/urllib3/blob/master/src/urllib3/__init__.py#L11
urllib3をインポートしたときにディレクトリ内の__init__.pyでPoolManagerもインポートされる。

#2

http = urllib3.PoolManager()

urllib3.PoolManagerのインスタンスhttpを生成する。これでrequestメソッドの呼び出しの準備になる。

#3

r = http.request('GET', 'http://httpbin.org/robots.txt')

PoolManager.request()を実行しているが、正確にはPoolManagerの親クラスRequestMethod.request()メソッドを実行している。
https://github.com/urllib3/urllib3/blob/master/src/urllib3/request.py#L10

 

urllib3.request.RequestMethods(object).request()

    def request(self, method, url, fields=None, headers=None, **urlopen_kw):
        """
        Make a request using :meth:`urlopen` with the appropriate encoding of
        ``fields`` based on the ``method`` used.
        This is a convenience method that requires the least amount of manual
        effort. It can be used in most situations, while still having the
        option to drop down to more specific methods when necessary, such as
        :meth:`request_encode_url`, :meth:`request_encode_body`,
        or even the lowest level :meth:`urlopen`.
        """
        method = method.upper()

        urlopen_kw["request_url"] = url

        if method in self._encode_url_methods:
            return self.request_encode_url(
                method, url, fields=fields, headers=headers, **urlopen_kw
            )
        else:
            return self.request_encode_body(
                method, url, fields=fields, headers=headers, **urlopen_kw
            )

request()メソッドでは、methodが_encode_url_methodsに含まれているものならばurllib3.request.RequestMethods(object).request_encode_url()メソッドを実行する。具体的に言えばリクエストメソッドが"DELETE", "GET", "HEAD", "OPTIONS"のどれかであれば、RequestMethods(object).request_encode_url()メソッドを実行する。一方で_encode_url_methodsにmethodがない場合は、urllib3.request.RequestMethods(object).request_encode_body()メソッドを実行する。
_encode_url_methodsはセット型データでRequestMethods(object)の属性である。_encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"}
https://github.com/urllib3/urllib3/blob/master/src/urllib3/request.py#L39

 

urllib3.request.RequestMethods(object).request_encode_url()

    def request_encode_url(self, method, url, fields=None, headers=None, **urlopen_kw):
        """
        Make a request using :meth:`urlopen` with the ``fields`` encoded in
        the url. This is useful for request methods like GET, HEAD, DELETE, etc.
        """
        if headers is None:
            headers = self.headers

        extra_kw = {"headers": headers}
        extra_kw.update(urlopen_kw)

        if fields:
            url += "?" + urlencode(fields)

        return self.urlopen(method, url, **extra_kw)

 

urllib3.poolmanager.PoolManager(RequestMethods).urlopen()

    def urlopen(self, method, url, redirect=True, **kw):
        """
        Same as :meth:`urllib3.connectionpool.HTTPConnectionPool.urlopen`
        with custom cross-host redirect logic and only sends the request-uri
        portion of the ``url``.
        The given ``url`` parameter must be absolute, such that an appropriate
        :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it.
        """
        u = parse_url(url)
        conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme)

        kw["assert_same_host"] = False
        kw["redirect"] = False

        if "headers" not in kw:
            kw["headers"] = self.headers.copy()

        if self.proxy is not None and u.scheme == "http":
            response = conn.urlopen(method, url, **kw)
        else:
            response = conn.urlopen(method, u.request_uri, **kw)

        redirect_location = redirect and response.get_redirect_location()
        if not redirect_location:
            return response

        # Support relative URLs for redirecting.
        redirect_location = urljoin(url, redirect_location)

        # RFC 7231, Section 6.4.4
        if response.status == 303:
            method = "GET"

        retries = kw.get("retries")
        if not isinstance(retries, Retry):
            retries = Retry.from_int(retries, redirect=redirect)

        # Strip headers marked as unsafe to forward to the redirected location.
        # Check remove_headers_on_redirect to avoid a potential network call within
        # conn.is_same_host() which may use socket.gethostbyname() in the future.
        if retries.remove_headers_on_redirect and not conn.is_same_host(
            redirect_location
        ):
            headers = list(six.iterkeys(kw["headers"]))
            for header in headers:
                if header.lower() in retries.remove_headers_on_redirect:
                    kw["headers"].pop(header, None)

        try:
            retries = retries.increment(method, url, response=response, _pool=conn)
        except MaxRetryError:
            if retries.raise_on_redirect:
                raise
            return response

        kw["retries"] = retries
        kw["redirect"] = redirect

        log.info("Redirecting %s -> %s", url, redirect_location)
        return self.urlopen(method, redirect_location, **kw)

 conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme)のconnはHTTP(s)ConnectionPoolインスタンスである。
HTTP(s)ConnectionPool.urlopen()を実行するとresponseが得られる。このconnオブジェクトを得るには以下のような複数のメソッドを実行してreturn poolとして得る。

 

https://github.com/urllib3/urllib3/blob/master/src/urllib3/poolmanager.py#L213

    def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None):
        """
        Get a :class:`ConnectionPool` based on the host, port, and scheme.
        If ``port`` isn't given, it will be derived from the ``scheme`` using
        ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is
        provided, it is merged with the instance's ``connection_pool_kw``
        variable and used to create the new connection pool, if one is
        needed.
        """

        if not host:
            raise LocationValueError("No host specified.")

        request_context = self._merge_pool_kwargs(pool_kwargs)
        request_context["scheme"] = scheme or "http"
        if not port:
            port = port_by_scheme.get(request_context["scheme"].lower(), 80)
        request_context["port"] = port
        request_context["host"] = host

        return self.connection_from_context(request_context)

 

 

https://github.com/urllib3/urllib3/blob/master/src/urllib3/poolmanager.py#L236

    def connection_from_context(self, request_context):
        """
        Get a :class:`ConnectionPool` based on the request context.
        ``request_context`` must at least contain the ``scheme`` key and its
        value must be a key in ``key_fn_by_scheme`` instance variable.
        """
        scheme = request_context["scheme"].lower()
        pool_key_constructor = self.key_fn_by_scheme[scheme]
        pool_key = pool_key_constructor(request_context)

        return self.connection_from_pool_key(pool_key, request_context=request_context)

 

 

https://github.com/urllib3/urllib3/blob/master/src/urllib3/poolmanager.py#L249

    def connection_from_pool_key(self, pool_key, request_context=None):
        """
        Get a :class:`ConnectionPool` based on the provided pool key.
        ``pool_key`` should be a namedtuple that only contains immutable
        objects. At a minimum it must have the ``scheme``, ``host``, and
        ``port`` fields.
        """
        with self.pools.lock:
            # If the scheme, host, or port doesn't match existing open
            # connections, open a new ConnectionPool.
            pool = self.pools.get(pool_key)
            if pool:
                return pool

            # Make a fresh ConnectionPool of the desired type
            scheme = request_context["scheme"]
            host = request_context["host"]
            port = request_context["port"]
            pool = self._new_pool(scheme, host, port, request_context=request_context)
            self.pools[pool_key] = pool

        return pool

戻り値はpolであるがこれは、HTTPConnectionPool又はHTTPSConnectionPoolクラスのインスタンスである。poolを新しく生成する場合は# Make a fresh ConnectionPool of the desired type以下の部分で生成される。もっと言えば_new_poolメソッドである。

 

 

https://github.com/urllib3/urllib3/blob/master/src/urllib3/poolmanager.py#L177

    def _new_pool(self, scheme, host, port, request_context=None):
        """
        Create a new :class:`ConnectionPool` based on host, port, scheme, and
        any additional pool keyword arguments.
        If ``request_context`` is provided, it is provided as keyword arguments
        to the pool class used. This method is used to actually create the
        connection pools handed out by :meth:`connection_from_url` and
        companion methods. It is intended to be overridden for customization.
        """
        pool_cls = self.pool_classes_by_scheme[scheme]
        if request_context is None:
            request_context = self.connection_pool_kw.copy()

        # Although the context has everything necessary to create the pool,
        # this function has historically only used the scheme, host, and port
        # in the positional args. When an API change is acceptable these can
        # be removed.
        for key in ("scheme", "host", "port"):
            request_context.pop(key, None)

        if scheme == "http":
            for kw in SSL_KEYWORDS:
                request_context.pop(kw, None)

        return pool_cls(host, port, **request_context)

requestsを見てみる

そもそも

requestsはurllib3をもとに作られている。requests.__init__.pyを見ればわかる。それでHTTPリクエストを送る関係のライブラリがpythonには複数ある。
urllib2, urllib(.request), urllib3. requests。これらについて理解するのも後々役に立つだろう。

  • urllib2...python2で使われていたもの。python3では使えない。
  • urllib(.request)...python3でurllib2の関数を編成して新しい単位にした。
  • urllib3...上記とは違い非標準ライブラリ、つまり3rdパーティ製ライブラリ。
  • requests... urllib3をもとにさらに使いやすくしたもの。

requestsの中身はどうなっているのか

一つのライブラリを使えるようになるだけなくどのように動いているか調べてみる。

ソース

https://github.com/kennethreitz/requests/tree/master/requests

クラスの整理

requests/models.py

https://github.com/kennethreitz/requests/blob/master/requests/models.py
requests/models.pyでクラスが定義されている。

class RequestEncodingMixin(object)
class RequestHooksMixin(object)
class Request(RequestHooksMixin)
class PreparedRequest(RequestEncodingMixin, RequestHooksMixin)
class Response(object)

requests/session.py

class SessionRedirectMixin(object)
class Session(SessionRedirectMixin)

requests/adapters.py

class BaseAdapter(object)
class HTTPAdapter(BaseAdapter)

requests/cookies.py

class MockRequest(object)
class MockResponse(object)
class CookieConflictError(RuntimeError)
class RequestsCookieJar(cookielib.CookieJar, MutableMapping)

requests.get(URL)に限って言えば、各クラスは以下の関係がある。
再考中。

コードの流れの整理

 import requests
 requests.get(URL)
1. [ import requests ]  =======>> [ requests.__init__.pyにてapi.get関数をインポート ]
2. [ requests.get(URL) ]      ============ [ get関数を実行 ]
3. [ get関数を実行 ]          ===============>>   戻り値[ api.request関数 (の実行) ]
4. [ api.request関数 (の実行)  ]  =====>> 戻り値[ Sessionインスタンス生成+requestメソッド実行 ]
5. [ Sessionインスタンス生成+requestメソッド実行]  ======>> 戻り値[ Responseインスタンス作成 ]
1. [ import requests ] =========>> [ requests.__init__.pyにてapi.get関数をインポート ]

https://github.com/kennethreitz/requests/blob/master/requests/__init__.py#L115

from .api import request, get, head, post, patch, put, delete, options
2. [ requests.get(URL) ] ============ [ get関数を実行 ]
 
3.[ get関数を実行 ] =========>> 戻り値[ api.request関数 (の実行) ]
def get(url, params=None, **kwargs):

    kwargs.setdefault('allow_redirects', True)
    return request('get', url, params=params, **kwargs)
4.[ api.request関数 (の実行) ] ====>> 戻り値[Sessionインスタンス生成+requestメソッド実行]
def request(method, url, **kwargs):

    with sessions.Session() as session:
        return session.request(method=method, url=url, **kwargs)
5.[ Sessionインスタンス生成+requestメソッド実行] ======>> 戻り値[ Responseインスタンス作成 ]
(( requestメソッド実行 ))
    
    def request(self, method, url,
            params=None, data=None, headers=None, cookies=None, files=None,
            auth=None, timeout=None, allow_redirects=True, proxies=None,
            hooks=None, stream=None, verify=None, cert=None, json=None):

        # Create the Request.
        req = Request(
            method=method.upper(),
            url=url,
            headers=headers,
            files=files,
            data=data or {},
            json=json,
            params=params or {},
            auth=auth,
            cookies=cookies,
            hooks=hooks,
        )
        prep = self.prepare_request(req)

        proxies = proxies or {}

        settings = self.merge_environment_settings(
            prep.url, proxies, stream, verify, cert
        )

        # Send the request.
        send_kwargs = {
            'timeout': timeout,
            'allow_redirects': allow_redirects,
        }
        send_kwargs.update(settings)
        resp = self.send(prep, **send_kwargs)

        return resp

Constructs a :class:`Request <Request>`, prepares it and sends it.Returns :class:`Response <Response>` object.
まず最初にRequestインスタンスを作成し、RequestインスタンスからPreparedRequestインスタンスに変換し、それを送信する。その結果Responseインスタンスを得る。
Responseインスタンスを得るのはSession.send()メソッドの実行によってであり、このsend()メソッドはさらに複数のクラスメソッド、関数から構成されている。

requestメソッドに含まれるメソッド、関数を軽く説明

Session.send()

Session.send()

    
        def send(self, request, **kwargs):
        """Send a given PreparedRequest.
        :rtype: requests.Response
        """
        # Set defaults that the hooks can utilize to ensure they always have
        # the correct parameters to reproduce the previous request.
        kwargs.setdefault('stream', self.stream)
        kwargs.setdefault('verify', self.verify)
        kwargs.setdefault('cert', self.cert)
        kwargs.setdefault('proxies', self.proxies)

        # It's possible that users might accidentally send a Request object.
        # Guard against that specific failure case.
        if isinstance(request, Request):
            raise ValueError('You can only send PreparedRequests.')

        # Set up variables needed for resolve_redirects and dispatching of hooks
        allow_redirects = kwargs.pop('allow_redirects', True)
        stream = kwargs.get('stream')
        hooks = request.hooks

        # Get the appropriate adapter to use
        adapter = self.get_adapter(url=request.url)

        # Start time (approximately) of the request
        start = preferred_clock()

        # Send the request
        r = adapter.send(request, **kwargs)

        # Total elapsed time of the request (approximately)
        elapsed = preferred_clock() - start
        r.elapsed = timedelta(seconds=elapsed)

        # Response manipulation hooks
        r = dispatch_hook('response', hooks, r, **kwargs)

        # Persist cookies
        if r.history:

            # If the hooks create history then we want those cookies too
            for resp in r.history:
                extract_cookies_to_jar(self.cookies, resp.request, resp.raw)

        extract_cookies_to_jar(self.cookies, request, r.raw)

        # Redirect resolving generator.
        gen = self.resolve_redirects(r, request, **kwargs)

        # Resolve redirects if allowed.
        history = [resp for resp in gen] if allow_redirects else []

        # Shuffle things around if there's history.
        if history:
            # Insert the first (original) request at the start
            history.insert(0, r)
            # Get the last request made
            r = history.pop()
            r.history = history

        # If redirects aren't being followed, store the response on the Request for Response.next().
        if not allow_redirects:
            try:
                r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs))
            except StopIteration:
                pass

        if not stream:
            r.content

        return r

# Get the appropriate adapter to use adapter = self.get_adapter(url=request.url)の部分ではprefixがhttps://なのかhttp://一つに絞っている。

Session.send()メソッド

    def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):
        """Sends PreparedRequest object. Returns Response object.
        :param request: The :class:`PreparedRequest ` being sent.
        :param stream: (optional) Whether to stream the request content.
        :param timeout: (optional) How long to wait for the server to send
            data before giving up, as a float, or a :ref:`(connect timeout,
            read timeout) ` tuple.
        :type timeout: float or tuple or urllib3 Timeout object
        :param verify: (optional) Either a boolean, in which case it controls whether
            we verify the server's TLS certificate, or a string, in which case it
            must be a path to a CA bundle to use
        :param cert: (optional) Any user-provided SSL certificate to be trusted.
        :param proxies: (optional) The proxies dictionary to apply to the request.
        :rtype: requests.Response
        """

        try:
            conn = self.get_connection(request.url, proxies)
        except LocationValueError as e:
            raise InvalidURL(e, request=request)

        self.cert_verify(conn, request.url, verify, cert)
        url = self.request_url(request, proxies)
        self.add_headers(request, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies)

        chunked = not (request.body is None or 'Content-Length' in request.headers)

        if isinstance(timeout, tuple):
            try:
                connect, read = timeout
                timeout = TimeoutSauce(connect=connect, read=read)
            except ValueError as e:
                # this may raise a string formatting error.
                err = ("Invalid timeout {}. Pass a (connect, read) "
                       "timeout tuple, or a single float to set "
                       "both timeouts to the same value".format(timeout))
                raise ValueError(err)
        elif isinstance(timeout, TimeoutSauce):
            pass
        else:
            timeout = TimeoutSauce(connect=timeout, read=timeout)

        try:
            if not chunked:
                resp = conn.urlopen(
                    method=request.method,
                    url=url,
                    body=request.body,
                    headers=request.headers,
                    redirect=False,
                    assert_same_host=False,
                    preload_content=False,
                    decode_content=False,
                    retries=self.max_retries,
                    timeout=timeout
                )

            # Send the request.
            else:
                if hasattr(conn, 'proxy_pool'):
                    conn = conn.proxy_pool

                low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)

                try:
                    low_conn.putrequest(request.method,
                                        url,
                                        skip_accept_encoding=True)

                    for header, value in request.headers.items():
                        low_conn.putheader(header, value)

                    low_conn.endheaders()

                    for i in request.body:
                        low_conn.send(hex(len(i))[2:].encode('utf-8'))
                        low_conn.send(b'\r\n')
                        low_conn.send(i)
                        low_conn.send(b'\r\n')
                    low_conn.send(b'0\r\n\r\n')

                    # Receive the response from the server
                    try:
                        # For Python 2.7, use buffering of HTTP responses
                        r = low_conn.getresponse(buffering=True)
                    except TypeError:
                        # For compatibility with Python 3.3+
                        r = low_conn.getresponse()

                    resp = HTTPResponse.from_httplib(
                        r,
                        pool=conn,
                        connection=low_conn,
                        preload_content=False,
                        decode_content=False
                    )
                except:
                    # If we hit any problems here, clean up the connection.
                    # Then, reraise so that we can handle the actual exception.
                    low_conn.close()
                    raise

        except (ProtocolError, socket.error) as err:
            raise ConnectionError(err, request=request)

        except MaxRetryError as e:
            if isinstance(e.reason, ConnectTimeoutError):
                # TODO: Remove this in 3.0.0: see #2811
                if not isinstance(e.reason, NewConnectionError):
                    raise ConnectTimeout(e, request=request)

            if isinstance(e.reason, ResponseError):
                raise RetryError(e, request=request)

            if isinstance(e.reason, _ProxyError):
                raise ProxyError(e, request=request)

            if isinstance(e.reason, _SSLError):
                # This branch is for urllib3 v1.22 and later.
                raise SSLError(e, request=request)

            raise ConnectionError(e, request=request)

        except ClosedPoolError as e:
            raise ConnectionError(e, request=request)

        except _ProxyError as e:
            raise ProxyError(e)

        except (_SSLError, _HTTPError) as e:
            if isinstance(e, _SSLError):
                # This branch is for urllib3 versions earlier than v1.22
                raise SSLError(e, request=request)
            elif isinstance(e, ReadTimeoutError):
                raise ReadTimeout(e, request=request)
            else:
                raise

        return self.build_response(request, resp)

エラー:django.core.exceptions.ImproperlyConfigured: The included URLconf '***.urls' does not appear to have any patterns in it. If you see valid patterns in the file then the issue is probably caused by a circular import.

django.core.exceptions.ImproperlyConfigured: The included URLconf '***.urls' does not appear to have any patterns in it. If you see valid patterns in the file then the issue is probably caused by a circular import.

djangoのエラーでa circular import.と表示されることがよくある。このエラーパターンの原因をメモしておく。

大抵このエラーはurls.pyに不適切なコードが書かれていると表示される。

urlpatterns = [
    url('', TemplateView.as_view(template_name="diary/home.html"), name="home"),
    
    ]

上記のようにurlpatternsに含める要素はpath(***)である。しかしurl(***)と書いてしまうとエラーが出る。これはconfig.urls.pyよりもアプリケーション側で起こすことが多い。(なぜならconfig側ならadminの例があるのでpathと書く動機になるが、アプリ側はurls.pyをゼロから作り自分で書いてるとミスを起こす確率が高いから)

...apps/views.pyにて

    from django.core.pagenator import Paginator, Page, PageNotAnInteger
    

ずっとurls.pyにエラーが有るはずだと考えるとこのミスに気づけない。
上記の例はページネーション関係のクラスをimportする際に記述ミスしている。from django.core.paginator にしないとエラーが出てしまう。