ほぼ老人のプログラミング日記

定年後の平凡なサラリーマンの趣味の日記

iPad Pro を注文しました

妻が還暦のお祝いにiPadを買ってくれるというので、速攻で (気が変わらないうちに) 発売したばかりの iPad Air (第5世代) を注文しました。
iPad Air はストレージのサイズが 64GB と256GB の 2 種類しかないので必然的に 256GB にしました。あと、WiFi+Cellular モデルにしました。これは、バッテリーが劣化して最近極端に使用頻度が落ちた Xperia X Compact に付けている IIJmio のsim を流用しようと思ったからです。
これで、価格は ¥110,800 です。念の為、妻に確認後、注文してから、落ち着いてレビューサイトなどで iPad Air のレビューを見ていたところ、iPad AiriPad Pro を比較している記事を沢山みかけました。

そもそも、iPad Air を選んだ理由は「新しいものはいいものだ」、「iPad Pro は高いし、俺にはオーバースペック」という思い込みだけで選んだのでスペックなどを吟味したわけではありませんでした。

そういうわけで、実は私にとってはいつものことなのですが、買ってから詳細を調べるという、ある意味残念なことをしていたわけです。そこでわかった衝撃の事実 iPad Pro (128GB, WiFi+Cellular) との価格差はなんと ¥2,000。う〜ん、ちょっと悩んで、まずは iPhoneApple Store を開いて注文をキャンセル出来るか確認。よし、キャンセルはできそうだ。

さらにレビューサイトで Air と Pro 比較記事を色々と読む。読めば読むほど、Air を買う理由がなくなくっていく。まぁこれは私の個人的な理由なので、Air の方がいい人は沢山いらっしゃると思います。わたしが、Pro の方が良いと思ったところは、

  • 256GBのストレージは私には必要ない(たぶん)
  • ディスプレイは Pro の方が良さそう
  • ProはUSB-C/Thunderbolt搭載

と、いったところです。あと、あまり写真は撮らないのてカメラの違いは気になりません。

結局、2 時間くらい悩んで、Air をキャンセルして、Pro の方をあらためて注文しました。納期は 3 週間位かかりそうです。

新型コロナワクチンを接種してきました

1時間ほど前に新型コロナワクチンを接種してきました。前回 2 回は、ファイザー製で今回はあえてモデルナ製にしました。前回の副反応は少し発熱があったのと、一週間位倦怠感があった程度で生活や仕事に支障はありませんでしたが、今回はどうでしょう。モデルナ製は副反応がきついって言うし、ちょっとびびってます。

平熱が低いせいか、37 度位の熱でも相当きついのに、さて今回はどうなるでしょうか。

 

 

2022/3/13 8:13 追記

全体的に体がだるい。発熱も少しある。

 

2022/3/13 11:5 追記

解熱剤飲んで、寝てたせいかだいぶ楽になった

 

2022/3/13 14:13 追記

やっぱり、きついので 2 回目の解熱剤を飲んで、少し寝た。でもやっぱりだるい

 

2022/3/13 17:45 追記

微熱が続いている感じ、こりゃ噂通り2回目よりきつい

 

2022/3/13 19:1 追記

熱、上がってきた、38.4℃ ある。ロキソニン飲んだ

 

2022/03/13 20:56 追記

ロキソニン、恐るべし、熱も37℃位まで下がったし、ずいぶん楽になった

 

2022/03/14 17:30 追記

午前中はまぁまぁ大丈夫だっのだけれど、午後からまた熱が出てきた。37.4℃

 

2022/03/14 21:07 追記

19時ごろアセトアミノフェンの解熱剤飲んだけど、熱は下がらない。けど、なんか知らんけど少し楽になった気がする。

 

2022/03/15 12:33 追記

やっと、元に戻りました。

 

「北九州市 新型コロナウイルス感染症 陽性患者数」日毎集計プログラム

私が住んでいる北九州市では、市のホームページで市内の新型コロナウイルスの感染状況などが発信されています。

www.city.kitakyushu.lg.jp

やはり、新型コロナウィルスの感染状況など気になるものですから、時々は見ていました。そんな中、上のページから「市内の最新感染動向」というページにたどり着き、そこから感染状況のデータがオープンデータとしてダウンロードできることを知りました。

stopcovid19-kitakyushu.jp

GROWI へのデータ移行も終わり、せっかく Python に取り組み始めたので、また何か作ってみたいと思っていたこともあり、「これは良いデータを見つけた」と思い、このデータからカレンダー形式で日毎の感染者数を表示する HTML ファイルを出力するプログラムを作ってみることにしました。
先に完成形のスクリーンショットを載せておきます。

f:id:tiger62shin:20220307170106p:plain

このように日毎の感染者数を集計して出力しています。ピンク色のセルは前週の同一曜日より感染者数が増えているところ、黄色のセルは減っているところです。
感染者数がいない (ゼロ) の日は白色にしています。
なんとなく、感染者数が減ってきているのがわかる気がします (2022/3/7 福岡県で実施中の「まん延防止等重点措置」が解除されました)

いきなり、スクリーンショットを載せましたが、実際にはこの段階では頭の中に完成形のイメージがあるだけですから、実現方法を考えていきます。

入力データ

まずは、どのような形式のデータがダウンロードできるのか確認します。対象とするデータは次のページからダウンロードできる「北九州市 新型コロナウイルス感染症 陽性患者属性」とします。

ckan.open-governmentdata.org

実際にダウンロードして中身を確認してみます。

f:id:tiger62shin:20220307171521p:plain

1 行で陽性患者 1 名分となっています。[公表_年月日] があるので、これをキーにしてデータ件数を数えればよさそうです。

入力データの取得

GROWI データ移行で使用した requests モジュールが使えそうです。

入力データの読み込みと集計

入力データの形式が CSV なので、CSV パーサー探してきて日付をキーにしたディクショナリに集計すればよいと考えていていたところ pandas の入力に CSV データが指定できることがわかり、「読み込み→集計」が pandas で完結するんじゃないかと思い調べてみました。
まずは、簡単なテストプログラムを作ってみます。

import requests
import io
import pandas as pd

url = 'https://ckan.open-governmentdata.org/dataset/aad66771-0e86-4d38-b08e-7b74d31f442e/resource/111b9476-bc80-4700-9551-3ba8a4ffcebc/download/401005_kitakyushu_covid19_patients.csv'
res = requests.get(url)
df = pd.read_csv(io.BytesIO(res.content), encoding='shift-jis', encoding_errors='ignore')
print(df.head())

問題なさそうです。
encoding_errors='ignore' があるのは元データにデコードできないデータがあってエラーになってしまうので指定しています。
日毎の集計は DataFrame の size() メソッドでできそうなところめまでは、わりとすぐに突き止めたのですが、size() メソッドでちょっとハマりました。そもそも pandas は存在は知っていましたが、使うのは初めてなので DataFrame や Series などの特性もわかっていませんでした。
DataFrame に対して size() メソッド実行すると Series になるところまでは分かったのですが、Series から DataFrame に戻す方法がわからなかったりで、結構時間がかかりました。最終的にこのようなコードになっています。

number_of_patients = df.groupby('Date').size() \
    .reset_index(name='Count') \
    .set_index('Date')
  • 'Date' をキーにして件数を数える
  • reset_index() で Series から DataFrame に変換。この時、件数に 'Count' という名前をつける
  • 'Date' (日付) をインデックスにする

Series から DataFrame に戻しているのは、この後各日のステータス (セルの色を変えるための「増えた」、「減った」の状態) の列を加えるためです。

HTML の出力

Python で使えるテンプレート エンジンを探したところ jinja2 を見つけましたので、これを使うことにします。



pandas 以外にも、いろいろなところでハマっていたので (pandas の使い方は、もっとしっかり勉強する必要がありそうです)、結構時間がかかりましたが、とりあえず完成しましたので、一日一回定時起動して生成した内容を公開しています。

www.calcium.mydns.jp

データはあまり頻繁には更新されないみたいなので、今現在の状況はわかりませんが、おおよその傾向はわかるのではないかと思います。

GROWI の時はソースコードもブログに掲載しましたが、これからは GitHub の方で公開します。

github.com

たまには、ジャンクPCでも - ThinkPad X61

以前は、出張でよく東京に行っていたので、帰りに秋葉原に寄ってジャンクPCを買って帰ったりしていたのですが、ここ数年は出張に行かなくなり、ジャンクPCもしばらく買っていませんでした。

昨年の夏あたりだったと思うのですが、たまたまネットでジャンクのMacBookを再生してしている人のことを知り「あー、Macのジャンクいじっている人もいるんだなー」、「俺もやってみるかなー」と思ったのですが、地方に住んでいるので、ジャンクPCを手に入れるにはオークションかハードオフくらいしかありません。ハードオフも近所にはなく、以前もよく利用していたオークションでチェックするようになりました。

結局、MacBook Air のジャンクは 2, 3 台入手して楽しんで、仕掛かりの MacBook Air の部品を探しているうちに、手頃な値段の ThinkPad X61 を見つけました。もちろんジャンクです。以前から X61 は欲しかったので少し競りましたが、落札しました。
以前は、ThinkPad X60X61 はオークションに多く出品されていましたが、最近はめっきり少なくなりました。

送られてきた X61 の外観をざっとチェックしたところ大きなクラックはありませんでしたが、天板が傷だらけです。ただ、ThinkPad 特有のピーチスキンのベタつきがないのはよかったです。

f:id:tiger62shin:20220226214657p:plain

f:id:tiger62shin:20220226215355p:plain

次に電源入れて起動して、BIOS を確認します。メモリがどの程度載っているか気になっていましたので、確認したところ 4GB 載っていました。これは、嬉しい。仕様上の MAX 載っています。実際には 8GB まで載るらしいですが。

f:id:tiger62shin:20220226215128p:plain

HDD には Windows10 がインストールされていたので、そのまま起動させたところ問題なく起動しました。ネットワークにも接続できたので H/W としては問題なさそうです。この Windows10 はなんか、問題ありそうなので、消して別の何かをインストールしましょう。

さて、この ThinkPad X61 の整備計画をたてます。

  • まずはディスプレイから、右側 1/3 程度が暗くてちょっとつらい感じです。上の写真の右側が暗いのは影ではなく実際にこのような感じで暗いです。文字が読めないほどではないので、このままでもよいのですが、やはり気になるので、直したいと思っています。今のところバックライトを交換する方向で考えています。
  • 天板はつや消しのクリア吹けばきれいになると思います。
  • HDD は余ってる SSD に交換して、軽めの Linux でも入れるつもりです。
  • バッテリーは完全にダメになっているのですが、このままにしておくことにします。

だいたいこんな感じでしょうか、完成がいつになるかはわかりませんが、ちょっとずつやっていこうと思います。

Visual Studio 2015 の UnitTest で Oracle.DataAccess が読み込めない

Visual Studio 2015 で Oracle.DataAccess を使った UnitTest を実行すると

System.BadImageFormatException: System.BadImageFormatException: ファイルまたはアセンブリ 'Oracle.DataAccess, Version=4.112.4.0, Culture=neutral, PublicKeyToken=89b483f429c47342'、またはその依存関係の 1 つが読み込めませんでした。間違ったフォーマットのプログラムを読み込もうとしました。

といった、エラーが発生してテストが実行できなかったが、意外なところに設定があった。

f:id:tiger62shin:20220214170947p:plain

「既定のプロセッサ アーキテクチャ」を「X64」に変更すると動作するようになる。

OS も 64bit、Oracle.DataAccess.dll も 64bit なので当たり前といえば当たり前なのですが、設定箇所がわからずにずいぶん探しました。

Knowledge→GROWI 移行 (7)

移行実施

いよいよ、knowledge から GROWI にデータを移行します。移行に際して、下記のような移行用のプログラムを作成しました。使い捨てです。

import psycopg2
from psycopg2.extras import DictCursor

from growiclient import GrowiClient

# DB接続情報
DB_HOST = 'postgres'
DB_PORT = '5432'
DB_NAME = 'knowledgedb'
DB_USER = 'kbadmin'
DB_PASS = '**********'

# GROWI 接続情報
GROWI_HOST = 'growi'
GROWI_PORT = '3000'
GROWI_SSL = False
GROWI_API_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
GROWI_USER = 'tiger'


# DB接続関数
def get_connection():
    return psycopg2.connect('postgresql://{user}:{password}@{host}:{port}/{dbname}'
                            .format(user=DB_USER,
                                    password=DB_PASS,
                                    host=DB_HOST,
                                    port=DB_PORT,
                                    dbname=DB_NAME))


# 添付ファイルの移行
def migrate_attachments(conn, growi_client, page, id):
    with conn.cursor(name='files_cursor', cursor_factory=DictCursor) as file_cur:
        id_col = 'knowledge_id'
        if growi_client.is_draft():
            id_col = 'draft_id'
        file_cur.execute('select file_no, knowledge_id, comment_no, draft_id, file_name, file_binary '
                         'from knowledge_files '
                         'where {} = %s'.format(id_col), (id, ))
        for file_row in file_cur:
            print(file_row['file_name'])
            file_path = './knowledge_to_growi/attachments/' + file_row['file_name']
            with open(file_path, 'wb') as f:
                f.write(file_row['file_binary'])
            file_url = growi_client.set_attachment(page, file_path)
            page.replace_attachment(file_row['file_name'], file_url)


# Knowledge -> GROWI ページ移行
def migrage_knowledge(conn, growi_client):
    with conn.cursor(name='knowledges_cursor', cursor_factory=DictCursor) as kb_cur:
        if growi_client.is_draft():
            kb_cur.execute('select draft_id, tag_names, title, content from draft_knowledges '
                           'where delete_flag = 0 '
                           '  and (knowledge_id is null or knowledge_id not in (select knowledge_id from knowledges)) '
                           'order by draft_id')
        else:
            kb_cur.execute('select knowledge_id, tag_names, title, content '
                           'from knowledges '
                           'where delete_flag = 0 '
                           'order by knowledge_id')
        titles = {}
        for kb_row in kb_cur:
            if growi_client.is_draft():
                id = kb_row['draft_id']
            else:
                id = kb_row['knowledge_id']
            print(str(id) + ' : ' + kb_row['title'])
            page_tags = []
            if kb_row['tag_names']:
                page_tags = kb_row['tag_names'].replace(chr(0xa0), '').split(',')
                if len(page_tags) == 1:
                    page_tags.append('')
            title = kb_row['title']
            if title in titles:
                titles[title] += 1
                title += '({})'.format(str(titles[title]))
            else:
                titles[title] = 1
            content = kb_row['content']
            if len(content) == 0:
                content = "## {}".format(title)
            page = growi_client.create_page(title, page_tags, content)
            migrate_attachments(conn, growi_client, page, id)
            growi_client.update_page(page)


with get_connection() as conn:
    # 並び順を後ろの方にするためにドラフトページを先に移行
    growi_client = GrowiClient(
        GROWI_HOST, GROWI_PORT, GROWI_API_KEY, GROWI_USER, GROWI_SSL, True)
    migrage_knowledge(conn, growi_client)

    # 公開ページの移行
    growi_client = GrowiClient(
        GROWI_HOST, GROWI_PORT, GROWI_API_KEY, GROWI_USER, GROWI_SSL)
    migrage_knowledge(conn, growi_client)

実行して、簡単に内容を確認してみましたが大丈夫そうです。
移行元のデータベースは Docker コンテナの状態で保管しておくので、問題が見つかれば、その時に対応することにします。


実はもっと簡単に済むんじゃないかと思っていたのですが、意外に時間がかかりました。しかし、Python から使える GROWI のクライアントを手に入れたので、今後何かに使えるんじゃないかと思っています。

Knowledge→GROWI 移行 (6)

移行用の GROWI アクセス クラスを作る

GROWI API の使い方もだいたいわかってきたので、Knowledge→GROWI データ移行のプログラムは書けると思います。データの移行さえしてしまえばいいので、ダラダラと書いてもいいと思いましたが、Python の学習も兼ねて GROWI にアクセスするためのクラスを作ることにしました。

で、作ったのがこれです。

import os
import requests
import json
import mimetypes
import re


class GrowiClient:
    """
    GROWI クライアント
    """
    def __init__(self, growihost, port, apitoken, username, ssl=False,
                 draft=False):
        """
        Parameters
        ----------
        growihost : str
            GROWI ホスト名
        apitoken : str
            GROWI API Token
        username : str
            GROWI ユーザ名
        ssl : bool
            true : Yes
            false : No
        draft : bool
            true  ドラフト
            false 公開
        """
        self.base_url = 'http{}://{}'.format('s' if ssl else '', growihost)
        if port:
            self.base_url += ':{}'.format(port)
        self.base_url += '/_api'
        self.base_path = '/{}'.format(username)
        self.params = {"access_token": apitoken, "user": username}
        self.draft = draft

        self.cur_pages = {}
        growi_res = self.__get('pages.list', {"limit": -1})
        for page in growi_res['pages']:
            self.cur_pages[page['path']] = GrowiPage(page['_id'], page['path'],
                                                     page['revision'],
                                                     None, None, None)

    def create_page(self, title, tags, content):
        """
        GROWI のベージを作製する

        Parameters
        ----------
        title : str
            ページ・タイトル
        tags : array
            タグ
        content : str
            ページ本文

        Retruns
        -------
        page : GrowiPage
            GROWI のページを表すオブジェクト
        """
        path = self.__to_path(title)
        if path in self.cur_pages:
            page = self.cur_pages[path]
            page.title = title
            page.tags = tags
            page.content = content
            self.__initialize_attachments_info(page)
            self.update_page(page)
            return page

        payload = {"body": content, "path": path}
        res = self.__post('v3/pages', payload)
        self.cur_pages[path] = GrowiPage(res['page']['id'], res['page']['path'],
                                         res['page']['revision'], title, tags, content)
        return self.cur_pages[path]

    def set_attachment(self, page, file_path):
        """
        指定されたファイルを指定された GROWI ページの添付ファイルとして設定する

        Parameters
        ----------
        page : GrowiPage
            移行対象の GROWI ページを表すオブジェクト
        file_path : str
            移行するファイルのパス
        Retruns
        -------
        file_url : 追加した添付ファイルの参照 url
        """
        file_name = os.path.basename(file_path)
        attachment_info = page.get_attachment_info(file_name)
        if attachment_info:
            self.__remove_attachment(page, attachment_info)
        mime_type = mimetypes.guess_type(file_name)[0]
        file = {'file': (file_name, open(file_path, 'rb'), mime_type)}
        payload = {"page_id": page.id, "path": page.path}
        res = self.__post('attachments.add', payload, file)

        page.add_attachment_info(res['attachment']['id'],
                                 res['attachment']['originalName'],
                                 res['attachment']['filePathProxied'])

        return res['attachment']['filePathProxied']

    def update_page(self, page):
        """
        GROWI ページを更新する

        Parameters
        ----------
        page : GrowiPage
            GROWI ページを表すオブジェクト
        """
        payload = {"body": page.content,
                   "pageTags": page.tags,
                   "page_id": page.id,
                   "revision_id": page.revision}
        res = self.__post('pages.update', payload)
        page.revision = res['page']['revision']

    def is_draft(self):
        """
        DRAFT ページに対する処理かどうかを返す

        Retruns
        -------
        draft : bool
            True  Yes
            False No
        """
        return self.draft

    def __initialize_attachments_info(self, page):
        """
        指定された GROWI ページ情報の添付ファイル情報を初期化する

        Parameters
        ----------
        page : GrowiPage
            GROWI ページを表すオブジェクト
        """
        page_no = 1
        while True:
            growi_res = self.__get('v3/attachment/list',
                                   {"pageId": page.id, "page": page_no})
            if len(growi_res['paginateResult']['docs']) == 0:
                break
            page.initialize_attachments_info(growi_res)
            page_no += 1

    def __remove_attachment(self, page, attachment_info):
        """
        添付ファイルを削除する

        Parameters
        ----------
        page : GrowiPage
            GROWI ページを表すオブジェクト
        attachment_info : GrowiAttachment
            削除する添付ファイル情報
        """
        print("Remove attachment : {}".format(attachment_info.original_name))
        payload = {"attachment_id": attachment_info.id}
        self.__post('attachments.remove', payload)

        page.remove_attachment_info(attachment_info.id)

    def __post(self, verb, payload, file=None):
        """
        GROWI サーバーに POST リクエストを行う

        Parameters
        ----------
        verb : str
            GROWI API
        payload : dict
            リクエストボディ
        file : dict
            アップロードファイル情報
            {'name': ('filename', fileobj)}
        Retruns
        -------
        growi_res : json
            リクエストのレスポンス
        """
        url = self.base_url + '/{}'.format(verb)
        res = requests.post(url, data=payload, files=file, params=self.params)
        res.raise_for_status

        growi_res = res.json()
        # print(json.dumps(growi_res, indent=4))
        if 'errors' in growi_res:
            print(json.dumps(growi_res, indent=4))

        return growi_res

    def __get(self, verb, params=None):
        """
        GROWI サーバーに GET リクエストを行う

        Parameters
        ----------
        verb : str
            GROWI API
        params : dict
            GET のパラメタ
        Retruns
        -------
        growi_res : json
            リクエストのレスポンス
        """
        url = self.base_url + '/{}'.format(verb)
        req_params = self.params.copy()
        if params:
            req_params.update(params)
        res = requests.get(url, params=req_params)
        res.raise_for_status

        growi_res = res.json()
        # print(json.dumps(growi_res, indent=4))
        if 'errors' in growi_res:
            print(json.dumps(growi_res, indent=4))

        return growi_res

    def __to_path(self, title):
        """
        GROWI ページのバスを返す

        Parameters
        ----------
        title : str
            ページ・タイトル

        Retruns
        -------
        path : str
            GROWI のページのバス
        """
        path = '{}/'.format(self.base_path)
        if self.draft:
            path += 'draft/'
        path += title.replace('^', '^') \
                     .replace('$', '$') \
                     .replace('*', '*') \
                     .replace('%', '%') \
                     .replace('?', '?') \
                     .replace('/', '/')
        return path


class GrowiPage:
    """
    GROWI ページを表す

    Attributes
    ----------
    id : str
        ページ ID
    path : str
        パス
    revision : str
        リビジョン
    title : str
        タイトル
    tags : array
        タグ
    content : str
        本文
    """
    def __init__(self, id, path, revision, title, tags, content):
        """
        Parameters
        ----------
        id : str
            ページ ID
        path : str
            パス
        revision : str
            リビジョン
        title : str
            タイトル
        tags : array
            タグ
        content : str
            本文
        """
        self.id = id
        self.path = path
        self.revision = revision
        self.title = title
        self.tags = tags
        self.content = content
        self.attachments = {}

    def replace_attachment(self, file_name, file_path_proxied):
        """
        本文の指定されたファイルの参照 (リンク) を指定された参照 (リンク) に置き換える

        Parameters
        ----------
        file_name : str
            置き換えるフアイル名
        file_path_proxied : str
            置き換える参照 (リンク)
        """
        self.content = re.sub(r'(!\[' + file_name + r'\])\(.+\)',
                              r'\1(' + file_path_proxied + r')',
                              self.content)

    def initialize_attachments_info(self, attachments_list_res):
        """
        GROWI ページの添付ファイル情報を設定する

        Parameters
        ----------
        attachments_list_res : json
            _api/v3/attachment/list の返却データ
        """
        for attachment in attachments_list_res['paginateResult']['docs']:
            self.attachments[attachment['id']] \
                        = GrowiAttachment(attachment['id'],
                                          attachment['originalName'],
                                          attachment['filePathProxied'])

    def add_attachment_info(self, attachment_id, original_name,
                            file_path_proxied):
        """
        添付ファイル情報を追加する
        Parameters
        ----------
        id : str
            添付ファイル ID
        original_name : str
            オリジナルのファイル名
        file_path_proxied : str
            添付ファイルの参照パス (リンク)
        """
        self.attachments[attachment_id] = GrowiAttachment(attachment_id,
                                                          original_name,
                                                          file_path_proxied)

    def get_attachment_info(self, file_name):
        """
        この GROWI ページが指定されたファイル名の添付ファイル情報を返す

        Parameters
        ----------
        file_name : str
            ファイル名
        Retruns
        -------
        attachment : GrowiAttachment
            添付ファイル情報
            添付ファイルが存在しない場合は None
        """
        attachment = [self.attachments[id] for id in self.attachments
                      if self.attachments[id].original_name == file_name]
        if attachment:
            return attachment[0]
        return None

    def remove_attachment_info(self, attachment_id):
        """
        この GROWI ページから指定された添付ファイル ID の添付ファイル情報を削除する

        Parameters
        ----------
        attachment_id : str
            添付ファイル ID
        """
        del self.attachments[attachment_id]


class GrowiAttachment:
    """
    GROWI 添付ファイルを表す

    Attributes
    ----------
    id : str
        添付ファイル ID
    original_name : str
        オリジナルのファイル名
    file_path_proxied : str
        添付ファイルの参照パス (リンク)
    """
    def __init__(self, id, original_name, file_path_proxied):
        """
        Parameters
        ----------
        id : str
            添付ファイル ID
        original_name : str
            オリジナルのファイル名
        file_path_proxied : str
            添付ファイルの参照パス (リンク)
        """
        self.id = id
        self.original_name = original_name
        self.file_path_proxied = file_path_proxied

使い方は

  1. GrowiClient のインスタンスを作る
    • draft を true にすると、ユーザディレクトリの下に "draft" という名前のディレクトリを作ってそこに記事を作成します。
  2. create_page で新しい記事を作成する
  3. 添付ファイルがあれば set_attachment で追加する
    • 必要なら記事内の添付ファイルの参照 url を書き換える
    • 添付ファイルの数分実施する
  4. 添付ファイルの url を書き換えたり、タグがあるなら update_page で記事を更新する
  5. 記事の数分、2〜4 を繰り返す

次のコードは、新しく記事を作って、ファイルを添付して、さらにそれを更新するものです。

from growiclient import GrowiClient

growi_client = GrowiClient('growi',
                           '3000',
                           'rMVCHsrPDuN7wlZfVOn9lWgqC5flSd2yjtqffO4T4aw=',
                           'tiger')

title = 'GlowiClientで作成したページ'
content = """\
このページは Python で作成した GLOWI Client で作成しました。

![tora.png](/tora.png)
"""
page = growi_client.create_page(title, ['GlowiClient', 'TEST'], content)
file_url = growi_client.set_attachment(page, './knowledge_to_growi/attachments/tora.png')
page.replace_attachment('tora.png', file_url)
growi_client.update_page(page)

content = page.content + \
          """ \
\n

ページを更新します。

![ojiisan.png](/ojiisan.png)
"""

page = growi_client.create_page(page.title, page.tags, content)
file_url = growi_client.set_attachment(page, './knowledge_to_growi/attachments/ojiisan.png')
page.replace_attachment('ojiisan.png', file_url)
growi_client.update_page(page)

こんな、記事が作成されます。 f:id:tiger62shin:20220207140051p:plain