みおもん倶楽部 技術雑記

世界の片隅で、諜報活動と称して本を読んだりゲームをしたり

Sphinxを使ってpythonスクリプトからドキュメントを生成してみる

いつも恒例のn番煎じシリーズいきます。

やること

pythonスクリプト(docstring付き)をhtmlファイルのドキュメントに変換します。 docstring自体の記法はここでは触れません。

事前準備

pipでパッケージを入れておきます。

pip install sphinx

あとで使うので、テーマファイルも入れます。

pip install sphinx_rtd_theme

ソースコードを用意

.
└── src
    ├── entry.py
    ├── mod1.py
    └── mod2.py

上記のような感じでpythonファイルをいくつか用意します。とりあえずdocstringがちょっと書かれていれば中身はなんでもいいです。 ここでは以下のような感じでざっくり書いてみました。

entry.py

from mod1 import split_basename, compute_awesome_value, check_list_has_even_length
from mod2 import DownCounter


def main():
    """
    エントリーポイント

    以下の関数を順に実行する

    - split_basename
    - compute_awesome_value
    - check_list_has_even_length
    - DownCounterのインスタンス作成
    - DownCounterのtickメソッドを5回実行
    """
    rev1 = split_basename("hello/world.txt")
    rev2 = compute_awesome_value(2, 3.4, True)
    rev3 = check_list_has_even_length([1, 2, 3])

    print(rev1)
    print(rev2)
    print(rev3)

    counter = DownCounter(5, 1)
    for _ in range(5):
        counter.tick()


if __name__ == "__main__":
    main()

mod1.py

def split_basename(filepath: str) -> str:
    """ファイルパスからベース名を取得する

    Args:
        filepath (str): ベース名を取得するファイルパス

    Returns:
        str: ファイルパスのベース名
    """
    return filepath.split('/')[-1]


def compute_awesome_value(i: int, f: float, multiple: bool) -> float:
    """条件に基づいて素晴らしい値を計算する

    Args:
        i (int): 整数値
        f (float): 浮動小数点数
        multiple (bool): 掛け算または足し算を行うかを示すブール値

    Returns:
        float: 算術演算の結果
    """
    if multiple:
        return i * f
    else:
        return i + f


def check_list_has_even_length(numlist: list[int]) -> bool:
    """リストの長さが偶数かどうかを確認する

    Args:
        numlist (list[int]): 整数のリスト

    Returns:
        bool: リストの長さが偶数の場合はTrue、そうでない場合はFalse
    """
    if type(numlist) is not list:
        return False

    return len(numlist) % 2 == 0

mod2.py

class DownCounter:
    """
    カウンタ機能を持ったクラス

    Attributes:
        count: カウンタの値

    Methods:

        - increment: カウンタを1増やす
        - decrement: カウンタを1減らす
        - set: カウンタの値を設定する
        - get: カウンタの値を取得する
    """

    initial_count: int
    count: int
    step: int

    def __init__(self, count: int = 100, step: int = 1):
        """
        カウンタ値とstepを初期化する

        Args:
            count: カウンタの初期値、デフォルトは100
            step: カウンタを増減させる値、デフォルトは1

        """
        if type(count) is not int:
            raise ValueError("count must be an integer")

        if type(step) is not int:
            raise ValueError("step must be an integer")

        if count < 0:
            raise ValueError("count must be a non-negative integer")

        if step < 0:
            raise ValueError("step must be a non-negative integer")

        self.initial_count = count
        self.count = count
        self.step = step

    def tick(self):
        """
        カウンタ値を1減らし、0未満になった場合は警告を出力する
        """
        self.count -= self.step

        if self.count <= 0:
            print("beep beep beep!!")

    def reset(self):
        """
        カウンタ値を初期値にリセットする
        """
        self.count = self.initial_count

ドキュメントの雛形生成

sphinx-apidocで作っていきます。

sphinx-apidoc ./src -a -F -o docs

いろいろファイルが生成されますが、ビルドに関わる設定はconf.pyに対して行います。

説明

  • -a: conf.py./srcを追加、ただし絶対パスなので、複数人で持ち回る場合は相対パスに書き換えた方がよいと思います
  • -F: ビルドに必要なあれこれ(Makefileとか)も同時に作成します
  • -o docs: 出力先を指定します

-a についてはこんな感じです。

import os
import sys
sys.path.insert(0, '../src')

他にもオプションがありますが、詳しく以下からご確認ください。

https://www.sphinx-doc.org/ja/master/man/sphinx-apidoc.html

ファイル構成は以下のようになります。

.
├── docs
│   ├── Makefile
│   ├── _build
│   ├── _static
│   ├── _templates
│   ├── conf.py
│   ├── entry.rst
│   ├── index.rst
│   ├── make.bat
│   ├── mod1.rst
│   └── mod2.rst
└── src
    ├── entry.py
    ├── mod1.py
    └── mod2.py

ドキュメントのビルド

docsに移動してビルドしていきます。

Makefileの確認

以下のような内容になっています。

# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS    ?=
SPHINXBUILD   ?= sphinx-build
SOURCEDIR     = .
BUILDDIR      = _build

# Put it first so that "make" without argument is like "make help".
help:
    @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
    @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

sphinx-buildのラッパとして動作するようです。

単にmakeするとhelpが表示されるようなので、試してみます。

Sphinx v7.2.6
Please use `make target' where target is one of
  html        to make standalone HTML files
  dirhtml     to make HTML files named index.html in directories
  singlehtml  to make a single large HTML file
  pickle      to make pickle files
  json        to make JSON files
  htmlhelp    to make HTML files and an HTML help project
  qthelp      to make HTML files and a qthelp project
  devhelp     to make HTML files and a Devhelp project
  epub        to make an epub
  latex       to make LaTeX files, you can set PAPER=a4 or PAPER=letter
  latexpdf    to make LaTeX and PDF files (default pdflatex)
  latexpdfja  to make LaTeX files and run them through platex/dvipdfmx
  text        to make text files
  man         to make manual pages
  texinfo     to make Texinfo files
  info        to make Texinfo files and run them through makeinfo
  gettext     to make PO message catalogs
  changes     to make an overview of all changed/added/deprecated items
  xml         to make Docutils-native XML files
  pseudoxml   to make pseudoxml-XML files for display purposes
  linkcheck   to check all external links for integrity
  doctest     to run all doctests embedded in the documentation (if enabled)
  coverage    to run coverage check of the documentation (if enabled)
  clean       to remove everything in the build directory

いざビルド

htmlターゲットがあるので、それを実行してみましょう。

make html

ビルドできたようです。

.
├── docs
│   ├── Makefile
│   ├── _build
│   │   ├── doctrees
│   │   │   ├── entry.doctree
│   │   │   ├── environment.pickle
│   │   │   ├── index.doctree
│   │   │   ├── mod1.doctree
│   │   │   └── mod2.doctree
│   │   └── html
│   │       ├── _modules
│   │       │   ├── entry.html
│   │       │   ├── index.html
│   │       │   └── mod1.html
│   │       ├── _sources
│   │       │   ├── entry.rst.txt
│   │       │   ├── index.rst.txt
│   │       │   ├── mod1.rst.txt
│   │       │   └── mod2.rst.txt
│   │       ├── _static
│   │       │   ├── alabaster.css
│   │       │   ├── basic.css
│   │       │   ├── custom.css
│   │       │   ├── doctools.js
│   │       │   ├── documentation_options.js
│   │       │   ├── file.png
│   │       │   ├── language_data.js
│   │       │   ├── minus.png
│   │       │   ├── plus.png
│   │       │   ├── pygments.css
│   │       │   ├── searchtools.js
│   │       │   └── sphinx_highlight.js
│   │       ├── entry.html
│   │       ├── genindex.html
│   │       ├── index.html
│   │       ├── mod1.html
│   │       ├── mod2.html
│   │       ├── objects.inv
│   │       ├── py-modindex.html
│   │       ├── search.html
│   │       └── searchindex.js
│   ├── _static
│   ├── _templates
│   ├── conf.py
│   ├── entry.rst
│   ├── index.rst
│   ├── make.bat
│   ├── mod1.rst
│   └── mod2.rst
└── src
    ├── __pycache__
    │   ├── entry.cpython-312.pyc
    │   ├── mod1.cpython-312.pyc
    │   └── mod2.cpython-312.pyc
    ├── entry.py
    ├── mod1.py
    └── mod2.py

ビルド結果

alabaster entry
alabaster entry

alabaster mod1
alabaster mod1

alabaster mod2
alabaster mod2

とりあえず情報は出揃っているようです。

テーマを変える

少しテーマが味気ないように感じるので、よく見る感じのテーマに変えてみます。

conf.pyを少しいじります。

- html_theme = 'alabaster'
+ html_theme = 'sphinx_rtd_theme'

Makefileのあるディレクトリに移動してもう一度ビルドします。

make html

rtd entry
rtd entry

rtd mod1
rtd mod1

rtd mod2
rtd mod2

いい感じです。

ソースを更新したら

./src/配下のpythonスクリプトを編集した場合は、もう一度 make htmlを実行すればhtmlファイルに変更が反映されます。

dockerでSSH接続可能なubuntuコンテナを立てる

dockerで立てたコンテナにsshでアクセスして開発を行いたいなと思いました。

N番煎じもいいところですが、ちょっとハマったので記録を残しておきます。

目指すもの

  • イメージはubuntu
  • 認証方式は公開鍵認証、パスワード認証はしない。
  • ~/.ssh/terminal~/.ssh/terminal.pubというキーペアを作成する
  • アクセス方法はssh localhost -p 2222 -i ~/.ssh/terminal
  • rootではなく作成したユーザでログインする(maclinuxでは$USERで現在のユーザを参照できますが、その値でユーザを作成します)
  • ポートは2222

ファイル構成

.
├── Dockerfile
├── docker-compose.yaml
└── setup.sh

Dockerfile

FROM ubuntu:latest

# ビルド時、new_userという引数を受け取る
ARG new_user

# ユーザを作成
RUN useradd -ms /bin/bash ${new_user}

# sshディレクトリを作成
RUN mkdir /home/${new_user}/.ssh

# 公開鍵をコピー(terminal.pubが存在している必要がある)
COPY terminal.pub /home/${new_user}/.ssh/authorized_keys

# sshサーバとsudoコマンドをインストール
RUN apt update && apt install -y openssh-server sudo && apt clean

# ひとまずパスワードを設定、あとで変えたほうがいい
RUN echo "${new_user}:password" | chpasswd

# sudoに追加
RUN gpasswd -a ${new_user} sudo

# ssh用のディレクトリを作成
RUN mkdir /var/run/sshd

# パスワードを使用したログインを許可しない
RUN sed -i -r 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config 

# 公開鍵認証を許可
RUN sed -i -r 's/#AuthorizedKeysFile/AuthorizedKeysFile/' /etc/ssh/sshd_config

# ポートを22から2222に変更
RUN sed -i -r 's/^#Port 22/Port 2222/' /etc/ssh/sshd_config 

RUN /usr/sbin/sshd

EXPOSE 2222

ENTRYPOINT [ "/usr/sbin/sshd", "-D" ]

docker-compose.yaml

version: "3"
services:
  terminal:
    build:
      args:
        - new_user=${USER}
      context: .
      dockerfile: Dockerfile
    container_name: terminal
    privileged: true
    tty: true
    ports:
      - "2222:2222"

new_user=${USER}の部分では、ホスト側の環境変数を使用しています。 別の名前を使用したい場合はここを変更します。

ボリュームをマウントしたい場合、dnsを設定したい場合、他のコンテナを付け足したい場合、適宜改造してください。

setup.sh

手元ではコンテナが起動していたら止めるとか、キーペアが存在してたら中止するとかを付け加えていますが、その部分は省略して、エッセンスだけ抜粋します。

# キーペアの作成
ssh-keygen -t ed25519 -C "" -f ~/.ssh/terminal -N "" 

# 手元に公開鍵をコピー
cp ~/.ssh/terminal.pub .

# docker compose upを実行
docker compose up -d --build

# sshで接続
ssh localhost -p 2222 -i ~/.ssh/terminal

実行手順

上記記載済みですが、terminal.pubを用意してdocker compose upを実行すればOKです。

xxxxxxxx@yyyyyyyy zzzzzzzz % ./setup.sh                              
[+] Building 0.0s (15/15) FINISHED                                                                                                      docker:desktop-linux
 => [terminal internal] load build definition from Dockerfile                                                                                           0.0s
 => => transferring dockerfile: 880B                                                                                                                    0.0s
 => [terminal internal] load metadata for docker.io/library/ubuntu:latest                                                                               0.0s
 => [terminal internal] load .dockerignore                                                                                                              0.0s
 => => transferring context: 2B                                                                                                                         0.0s
 => [terminal  1/10] FROM docker.io/library/ubuntu:latest                                                                                               0.0s
 => [terminal internal] load build context                                                                                                              0.0s
 => => transferring context: 595B                                                                                                                       0.0s
 => CACHED [terminal  2/10] RUN useradd -ms /bin/bash xxxxxxxx                                                                                          0.0s
 => CACHED [terminal  3/10] RUN mkdir /home/xxxxxxxx/.ssh                                                                                               0.0s
 => CACHED [terminal  4/10] COPY terminal.pub /home/xxxxxxxx/.ssh/authorized_keys                                                                       0.0s
 => CACHED [terminal  5/10] RUN apt update && apt install -y openssh-server && apt clean                                                                0.0s
 => CACHED [terminal  6/10] RUN mkdir /var/run/sshd                                                                                                     0.0s
 => CACHED [terminal  7/10] RUN sed -i -r 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config                              0.0s
 => CACHED [terminal  8/10] RUN sed -i -r 's/#AuthorizedKeysFile/AuthorizedKeysFile/' /etc/ssh/sshd_config                                              0.0s
 => CACHED [terminal  9/10] RUN sed -i -r 's/^#Port 22/Port 2222/' /etc/ssh/sshd_config                                                                 0.0s
 => CACHED [terminal 10/10] RUN /usr/sbin/sshd                                                                                                          0.0s
 => [terminal] exporting to image                                                                                                                       0.0s
 => => exporting layers                                                                                                                                 0.0s
 => => writing image sha256:fbe41ddbf46c8601b2709640c58c66f311c66628c5cc53b0429877270bf0bad2                                                            0.0s
 => => naming to docker.io/library/zzzzzzzz-terminal                                                                                                    0.0s
[+] Running 1/2
 ⠙ Network zzzzzzzz_default  Created                                                                                                                    0.1s 
 ✔ Container terminal       Started                                                                                                                     0.1s 

xxxxxxxx@yyyyyyyy zzzzzzzz % ssh localhost -p 2222 -i ~/.ssh/terminal
Welcome to Ubuntu 22.04.1 LTS (GNU/Linux 6.6.16-linuxkit aarch64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

xxxxxxxx@c264796d36c0:~$ 

以下の情報は置き換えています。

  • ログインユーザ名:xxxxxxxx
  • ホストPCのホスト名:yyyyyyyy
  • 作業ディレクトリ名:zzzzzzzz

Dockerfileとかdocker-compose.yamlは雰囲気で書いていますので、変な部分あるかもしれません。

Rustのserdeを使ってみる

serdeはserializeとdeserializeを行うクレートです。

関数を呼んで機能を実現する「わかりやすいライブラリ」ではなく、インターフェースというか、プロトコルというか、1段メタな機能を実現するツールであるように思います。attributeで目印をつけ、たとえばserde_jsonという別クレートで実際のシリアライズ・デシリアライズを行うと理解しています。

以下、jsonとstructの相互変換を行う例を雑にまとめてみます。

必要クレート

serde = { version = "1.0.130", features = ["derive"] }
serde_json = "1.0.68"

準備

use serde::{Deserialize, Serialize};
use std::fs;

#[derive(Serialize, Deserialize, Debug)]
struct Person {
    name: String,
    age: u8,
}

structからJSON

serde_json::to_writer(&mut file, &p)

JSONからstruct

serde_json::from_reader::<std::fs::File, Person>

もしくは

serde_json::from_str::<Person>(&content)

全体

use serde::{Deserialize, Serialize};
use std::fs;

const JSON_FILE_NAME: &str = "person.json";

#[derive(Serialize, Deserialize, Debug)]
struct Person {
    name: String,
    age: u8,
}

fn gen_json() {
    let json = r#"
        {
            "name": "John Doe",
            "age": 43
        }
    "#;

    let p: Person = serde_json::from_str(json).unwrap();
    // save to file
    let mut file = std::fs::File::create(JSON_FILE_NAME).unwrap();
    serde_json::to_writer(&mut file, &p).unwrap();
    println!("{:?}", p);
}

fn read_json_fs() {
    let file = std::fs::File::open(JSON_FILE_NAME).unwrap();

    match serde_json::from_reader::<std::fs::File, Person>(file) {
        Ok(p) => println!("{:?}", p),
        Err(e) => println!("error: {}", e),
    }
}

fn read_json_str() {
    let content = fs::read_to_string(JSON_FILE_NAME).unwrap();

    match serde_json::from_str::<Person>(&content) {
        Ok(p) => println!("{:?}", p),
        Err(e) => println!("error: {}", e),
    }
}

fn main() {
    // gen_json();
    read_json_fs();
    read_json_str();
}

結果のJSON

{"name":"John Doe","age":43}

JSONの値を不正にしてみる

{"name":"John Doe","age":1000}

注:Personのageはu8

fn main() {
    // gen_json();
    read_json_fs();
    read_json_str();
}

結果

error: invalid value: integer `1000`, expected u8 at line 1 column 30
error: invalid value: integer `1000`, expected u8 at line 1 column 29

上記、jsonとstructの例でしたが、例えばHashMapとstructではこうもいかないと思います。 なぜかというと、HashMapはkeyとvalueが静的に決まっている必要があり、nameを取得したらStringだけど、ageを取得したらu8、というような表現力がHashMapにはないからです。(というかそういうことをしたいならstruct)

あるいはHashMapに全部stringで入れて(u8も"20"などのようにStringで入れる)、structと相互変換する、も方法としてはアリかもしれませんが、まあだったらJSONでいいのでは、と思っています。

何を思ってこんなことを書いているかというと、なんらかのデータをデータベースから取得し、一時的にHashMapに入れ、structに変換するのはアリなのか?と思ったのですが、HashMapよりはjsonなどの方が取り回ししやすそうだね、と思った次第です。

無論、データベースから値を取るならそれ用のクレートを使うべきです。(serde_dynamodbなど)

githubにsshで接続する

注意:Linux or macユーザを想定しています。windowsユーザも、細部は異なると思いますが、適用可能なはずです。

キーペアの作成

キーペアのディレクトリへ移動します

cd ~/.ssh

キーペアを生成します。ED25519にしますが、RSAとかでもいいと思います。

ssh-keygen -t ed25519

以下のような画面になるはずです。

username@hostname .ssh % ssh-keygen -t ed25519
Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/username/.ssh/id_ed25519): github⭐️1
Enter passphrase (empty for no passphrase):⭐️2
Enter same passphrase again:⭐️2
Your identification has been saved in github
Your public key has been saved in github.pub
The key fingerprint is:
SHA256:xa/hogehogehogehogehogehogehoge/w username@hostname.local

⭐️1 キー名を入力します。github、gitlab、codecommitなど使い分けるならそれがわかるように、もしくはデフォルトのままにして使い回してもOKです。ここではgithubと入力することにします。

⭐️2 パスフレーズを入力しますが私は普段空欄です。現場のルールに従ってください。

キーの登録

以下のURLを開いてください。

https://github.com/settings/keys

以下のような画面になるはずです。

sshkeyの登録画面

ここでNew SSH Keyをクリックします。以下のような画面になるはずです。

キーペアの登録画面

  • Titleは識別しやすい名前ならなんでもいいですが、私はmacにつけたホスト名をつけてます。他の端末からもアクセスすることがあるのですが、それが識別できればOKかと思います。

  • Keyにはキーペアのうち公開鍵の情報を登録します。秘密鍵は登録しないように。

なお、公開鍵は以下のコマンドで取得できます。

cat ~/.ssh/github.pub

キーを登録

github側の作業はこれでOKですが、もうちょっと続きます。

~/.ssh/configの設定

~/.ssh/configファイルを設定します。

編集コマンドはviでもcatでもなんでもいいですが、macではopenコマンドを使えば、多少不慣れな人でも操作可能かと思います。

open ~/.ssh/config

以下の内容を追加します。キー名は適宜置き換えてください。

Host github github.com
  Hostname github.com
  IdentityFile ~/.ssh/github
  User git

接続確認のために、以下を実行します。

ssh -T git@github.com

以下のように表示されれば成功です。

username@hostname .ssh % ssh -T git@github.com
Hi Username! You've successfully authenticated, but GitHub does not provide shell access.

追記:sshとは

そもそもsshとはSecure SHellの略で、ネットワーク経由で他のPCにアクセス・操作するための、クライアントとサーバ構成のプロトコルです。ごく簡単に言えば、sshクライアントがLinuxコマンドをネットワーク経由で送り付け、sshサーバがそれを解釈して実行し、その結果をネットワーク経由で送り返す、という挙動になります。(さらにいうと、Linuxコマンドを送るだけがsshではない)

種々のコマンドの実行を許可するので認証が当然必要になるのですが、大雑把にIDパスワードを使う方法とキーペアを使う方法があり、特に後者は公開鍵認証と呼ばれます。キーペアのうち公開して良い方(公開鍵)をsshサーバにあらかじめ登録しておき、認証時にはsshクライアントのみが持っているはずの秘密鍵を使って認証をするというものですが、詳しい挙動は適宜ググってください。

さて本題ですが、githubではローカル環境と接続する際に、httpとsshの2種類の方法で認証をすることが可能です。httpではIDとパスワード、sshではキーペアを用いた認証をするようです。ただ、githubへのアクセスにあたりsshを使ってLinuxコマンドを送りつけるのかというと、少なくともユーザがsshで接続し、Linuxコマンドを送りつけるということはありません。内部で何らかの形で使っていると思われますが、詳細を知らなくてもgithubは使えるので、気にしないことにします。

ViteでSvelteを始めてみる

ViteでSvelteをざっくり動かしてみる

create-viteパッケージをインストール

npm install -g create-vite

svelteプロジェクトを作成する

create-vite my-svelte-app --template svelte

ディレクトリに移動

cd my-svelte-app

依存をインストール

npm install

開発サーバを起動

npm run dev

以下のアドレスとポートでWebサーバプログラムが起動するので、アクセスしてみる。 ポートは環境によって変わるかもしれないので、ターミナルのメッセージを確認。

npm run devの結果

http://localhost:5173/

表示されたアドレスにアクセス。"Count is XXX"をクリックすると値が増える。

コンポーネントを作成

./my-svelte-app/src/lib/Hello.svelte を以下のように作成。

Hello.svelteを追加してみる

5行目と23〜25行目を追加

もう一度以下にアクセスすると、画面が少し変わる

http://localhost:5173/

Click meが増えている

Click meをクリックしてみると

予想通り、alert関数が実行される

PostgresサーバをMacにDockerでたてる

今回はdockerを使って、postgresサーバの環境を準備する方法について書いてみます。

dockerの環境が構築済みであることが前提です。 未構築の方はこちらもどうぞ。

Postgresサーバ

dockerを使ってサーバをたてます。

オフィシャルイメージはこちらから確認できます。

起動方法はシンプルで、以下のコマンドでpostgresサーバの起動を行います。

docker run -p 5432:5432 -e POSTGRES_PASSWORD=mysecretpassword -d postgres

ちょっとだけ補足を入れておくと

  • -p 5432:5432を指定することで、localhost:5432に対するパケットを受けると、POSTGRESコンテナの5432へポートフォワードされます。よくわからない場合は、dockerでTCPとかUDPをしたい時のおまじないであると考えてください。
  • -e POSTGRES_PASSWORD=mysecretpasswordを指定することで、環境変数POSTGRES_PASSWORDに値mysecretpasswordをセットします。POSTGRESコンテナは、これをユーザpostgresのパスワードであると解釈します。
  • -dを指定することで、バックグラウンドで起動させておきます。

Postgresクライアント

次に、このサーバに接続して、動作確認をするために、クライアントソフトpsqlをローカル環境にインストールします。 いつものごとく、homebrewを使ってインストールを行いますが、入れるパッケージはlibpqです。 libpqの詳細はこちらを参照していただければと思いますが、大事な箇所だけ抜き出すと

libpq is the C application programmer's interface to PostgreSQL. libpq is a set of library functions that allow client programs to pass queries to the PostgreSQL backend server and to receive the results of these queries.

あとは

libpq is also the underlying engine for several other PostgreSQL application interfaces, including those written for C++, Perl, Python, Tcl and ECPG.

あたりでしょうか。要するに、Cで書かれたクライアントソフトであり、他の言語からも使用されることがある、くらいの理解を私はしています。

ということで、インストールを以下のコマンドで行います。

brew install libpq

バイナリの置かれるパスを、環境変数PATHに追加しろ、と指示がありますので、これも実行します。 環境によってはzshrcではなくzprofileが良いかもしれませんので、適宜置き換えてください。

echo 'export PATH="/opt/homebrew/opt/libpq/bin:$PATH"' >> ~/.zshrc

注意:homebrewでインストールしたバイナリがどこに置かれるかは環境によって異なりそうです。画面に表示されたコマンドを実行してください。

その後シェルの再起動、もしくは設定ファイルの再読み込みを行って、ようやくpsqlが実行できるようになるはずです。

なお、設定ファイルの再読み込みは以下です。

source ~/.zshrc

以下のコマンドでインストールされていることを確認します。

psql --version

以下のようなメッセージが出れば成功です。

psql (PostgreSQL) 15.2

接続確認

では、満を持してサーバへの接続確認を行いましょう

まずは、以下のコマンドで接続をします。

psql -h localhost -p 5432 -U postgres

続いて、パスワードを尋ねられるので、環境変数にセットしたmysecretpasswordを入力します。

以下の画面が表示されたら成功です。あとはお好きなSQLを書いて楽しみましょう。

postgres=# 

GUIをインストールする

おまけで、GUIのインストール方法も書いておきます。 (実のところ、私はあまり使ったことがありません)

homebrewで以下のツールをインストールしましょう

brew install --cask pgadmin4

参考までに、homebrewのformulaeはこちら、本家のサイトはこちら

起動するとこんな感じになります。

なお、マスタパスワードの作成を求められると思いますので、適当に作成しておきましょう。

Serversを右クリック -> Register -> Serverを選んで

サーバを識別するための任意の名前と

ホスト名もしくはアドレス、そしてパスワード(上の例ではmysecretpassword)をいれます。

接続成功したのち、右クリックメニューなどでテーブルを作成できます。

その後、Create ScriptでSQLスクリプトを作成し、お好きなSQLを書いて楽しみましょう。


Pythonスクリプトを実行してみる

おまけその2として、PythonからPostgresに接続してみます。

Pythonはインストールされている前提です。

psycopg2のインストール

以下を実行して、Pythonから接続するためのツールをインストールします。

pip3 install psycopg2-binary

なお、以下のコマンドでもソースのダウンロード -> clangでビルド -> インストールできるようです。

pip3 install psycopg2

が、私の環境ではssl関連のライブラリが参照できない?などの理由でビルドに失敗しているようでした。 調べれば解決できそうでしたが、面倒だったのでビルド済みのバイナリだけを取ってくることにしました。

データを書き込む

ポケモン csv」などで検索し、ヒットしたデータを使用させていただき、データベースに格納してみたいと思います。

import psycopg2
import csv

connection = psycopg2.connect(host='localhost',
                              user='postgres',
                              password='mysecretpassword',
                              database='pokemon')

with connection:
    with connection.cursor() as cursor:

        # ポケモンテーブルを初期化
        sql = "DROP TABLE IF EXISTS monsters;"
        cursor.execute(sql)

        sql = "CREATE TABLE monsters (\
                id serial PRIMARY KEY, \
                pokedex_number smallint, \
                name varchar(255), \
                base_total smallint, \
                attack smallint, \
                defense smallint, \
                sp_attack smallint, \
                sp_defense smallint, \
                speed smallint, \
                hp smallint \
                );"

        cursor.execute(sql)

        # タイプテーブルを初期化
        sql = "DROP TABLE IF EXISTS types;"
        cursor.execute(sql)

        sql = "CREATE TABLE types (\
                id serial PRIMARY KEY, \
                pokedex_number smallint, \
                type varchar(255) \
                );"

        cursor.execute(sql)

        # ポケモンテーブルにデータを挿入
        with open('pokemon.csv') as f:
            for row in csv.reader(f):
                monsterinfo = row[:9]
                typeinfo = row[-2:]

                if monsterinfo[0] == 'pokedex_number':
                    continue

                sql = "INSERT INTO monsters (pokedex_number, name, base_total, attack, defense, sp_attack, sp_defense, speed, hp) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)"
                cursor.execute(sql, monsterinfo)

                for t in typeinfo:
                    if t == '':
                        continue

                    sql = "INSERT INTO types (pokedex_number, type) VALUES (%s, %s)"
                    cursor.execute(sql, (monsterinfo[0], t))

        # 相性テーブルを初期化
        sql = "DROP TABLE IF EXISTS affinities;"
        cursor.execute(sql)

        sql = "CREATE TABLE affinities (\
                id serial PRIMARY KEY, \
                attacker varchar(255), \
                defender varchar(255), \
                magnifier real \
                );"

        cursor.execute(sql)

        # 相性テーブルにデータ挿入
        with open('affinity.csv') as f:
            header = []
            for i, row in enumerate(csv.reader(f)):
                if i == 0:
                    header = [r.strip() for r in row]
                    continue

                t0 = row[0].strip()
                for t1 in header[1:]:
                    t1 = t1.strip()
                    mag = float(row[header.index(t1)])

                    sql = "INSERT INTO affinities (attacker, defender, magnifier) VALUES (%s, %s, %s)"

                    cursor.execute(sql, (t0, t1, mag))

    connection.commit()

pokemon.csvはこんな感じ。

pokedex_number,name,base_total,attack,defense,sp_attack,sp_defense,speed,hp,type1,type2
1,フシギダネ,318,49,49,65,65,45,45,grass,poison
2,フシギソウ,405,62,63,80,80,60,60,grass,poison
3,フシギバナ,625,100,123,122,120,80,80,grass,poison
4,ヒトカゲ,309,52,43,60,50,65,39,fire,
5,リザード,405,64,58,80,65,80,58,fire,
6,リザードン,634,104,78,159,115,100,78,fire,flying

以下を参考にさせていたきました。ありがとうございます。

https://gist.github.com/leoyuholo/b12f882a92a25d43cf90e29812639eb3

affinity.csvはこんな感じ。

"   ",normal, fire, water, electric, grass,ice, fighting, poison, ground, flying,psychic, bug, rock,ghost, dragon,dark, steel, fairy
normal,1,1,1,1,1,1,1,1,1,1,1,1,0.5,0,1,1,0.5,1
fire,1,0.5,0.5,1,2,2,1,1,1,1,1,2,0.5,1,0.5,1,2,1
water,1,2,0.5,1,0.5,1,1,1,2,1,1,1,2,1,0.5,1,1,1
electric,1,1,2,0.5,0.5,1,1,1,0,2,1,1,0.5,1,0.5,1,1,1
grass,1,0.5,2,1,0.5,1,1,0.5,2,0.5,1,0.5,2,1,0.5,1,0.5,1
ice,1,0.5,0.5,1,2,0.5,1,1,2,2,1,1,1,1,2,1,0.5,1
fighting,2,1,1,1,1,2,1,0.5,1,0.5,0.5,1,2,1,1,2,2,0.5
poison,1,1,1,1,2,1,1,0.5,0.5,1,1,1,0.5,0.5,1,1,0,2
ground,1,2,1,2,0.5,1,1,2,1,1,1,0.5,2,1,1,1,2,1
flying,1,1,1,0.5,2,1,2,1,1,1,1,2,0.5,1,1,1,0.5,1
psychic,1,1,1,1,1,1,2,2,1,1,0.5,1,1,1,1,1,0.5,1
bug,1,0.5,1,1,2,1,0.5,0.5,1,0.5,2,1,1,0.5,1,2,0.5,0.5
rock,1,2,1,1,1,2,0.5,1,0.5,2,1,2,1,1,1,1,0.5,1
ghost,0,1,1,1,1,1,1,1,1,1,2,1,1,2,1,0.5,1,1
dragon,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,0.5,0
dark,1,1,1,1,1,1,0.5,1,1,1,2,1,1,2,1,0.5,1,0.5
steel,1,0.5,0.5,0.5,1,2,1,1,1,1,1,1,2,1,1,1,0.5,2
fairy,1,0.5,1,1,1,1,2,0.5,1,1,1,1,1,1,2,2,0.5,1

こちらを使用させていただきました、ありがとうございます。

https://sironekolab.com/archives/6012

ちょっと遊んでみる

ポケモンのリストをとりあえず表示してみます。

種族値の合計が600を超えるポケモンで、ほのおタイプを持つものを表示してみたいと思います。SQL的に正しいかはよくわかりません。

ポケモンのタイプを横持ちに変換

DockerをMacにインストールする

dockerをMacに入れる方法について書いてみたいと思います。

方法は色々あるかと思いますが、私はもっぱらhomebrewしか使っていません。 これはdockerに限らず、何かソフトウェアを入れたいときはhomebrewにパッケージがあるかどうかをまずは確認し、なければ渋々dmgを探してインストールしています。

まず、homebrewが入っているかの確認をするには、ターミナルから以下を実行してください。

brew --version

入っていれば、以下のような結果が返ってくるはずです。

Homebrew 4.0.15
Homebrew/homebrew-core (git revision 830070c393f; last commit 2023-02-18)
Homebrew/homebrew-cask (git revision 1a8cad145c; last commit 2023-02-18)

もし入っていなかったら以下を実行してください。

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

なお、こちらが公式サイトです。気になる方はこちらも参考にしてください。

ちなみに、パッケージの検索はこちらからできます。

Dockerのインストール

以下のコマンドをターミナルから実行してください。

brew install --cask docker 

Dockerのエンジン管理にGUIを使うので、--caskオプションをつけています。

Dockerアカウントの作成

こちらからアカウントの作成をしてください。支払い情報などの入力は不要です。

Dockerエンジンの起動

dockerではエンジンとなるプロセスがまずは起動します。これを起動しておかないとdockerコマンドを実行しても成功しません。

spotlightなどからdockerを起動すると良いでしょう。

docker-spolight
docker spolight

正常に起動していれば、以下のようなアイコンがメニューバーに出ているはずです。

docker icon in menubar
docker icon in menubar

最後に動作確認をしてみます。dockerコマンドの詳細な使い方は置いておくとして、以下のコマンドで動作確認ができます。

docker run hello-world

以下の結果が出力されていれば、正常に動作しています。 また、最初の5行は手元にコンテナイメージがない場合、つまり初回などに出力されるメッセージです。

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
7050e35b49f5: Pull complete
Digest: sha256:4e83453afed1b4fa1a3500525091dbfca6ce1e66903fd4c01ff015dbcb1ba33e
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (arm64v8)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

ひとまず、ここまで完了すれば、dockerのインストールは完了です。