概要
すでにリモート側にも push している状態から復元する方法を紹介します
環境
- git 2.47.0
ファイルとコミットハッシュの確認
-
git log -- src/utils/logger.js
復元
- git checkout fe3c7b64cd81ae68b797a7804960accaae2fbfd4 – src/utils/logger.js
最後に
過去の実装を戻したい場合に使えます
すでにリモート側にも push している状態から復元する方法を紹介します
過去の実装を戻したい場合に使えます
タイトルの通りです
手元のコードを学習させたりコンテキストとして持たせたりするベストプラクティスは何がいいのでしょうか
まずはコンテキストを与えないで質問します
* *[11:06:32]* You :you:
Please provide a sample python code that uses the Message class.
** *[11:06:34]* Copilot(gpt-4o) :copilot:
*** Sample Python Code Using the Message Class
Here is a sample Python code that demonstrates the use of the `Message` class from the `email.message` module. This example creates an email message, sets its headers, and adds content.
#+BEGIN_SRC python
from email.message import EmailMessage
def create_email():
# Create an instance of the EmailMessage class
msg = EmailMessage()
# Set the email headers
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg['Subject'] = 'Sample Email Subject'
# Add the email content
msg.set_content('This is the plain text content of the email.')
# Add an HTML version of the content
msg.add_alternative("""
<html>
<body>
<h1>This is the HTML content of the email.</h1>
<p>It includes <b>bold</b> and <i>italic</i> text.</p>
</body>
</html>
""", subtype='html')
return msg
# Example usage
if __name__ == "__main__":
email_message = create_email()
print(email_message)
#+END_SRC
*** Key Points
+ The `EmailMessage` class is part of Python's built-in `email` module.
+ The `set_content` method is used to add plain text content.
+ The `add_alternative` method is used to add an HTML version of the email content.
+ Headers like ~From~, ~To~, and ~Subject~ are set using dictionary-like syntax.
次に以下のファイルを開いて copilot-chat-add-current-buffer で開いたファイルをコンテキストに追加します
import time
class Message:
def say(self, **kwargs):
print("Start say")
print(kwargs)
time.sleep(10)
raise ValueError()
# print("End say")
そして更に質問します
するとコンテキストとして追加したクラスをちゃんと使って修正してくれます
* *[11:07:47]* You :you:
Use the Message class in my_libs/utils.py
** *[11:07:48]* Copilot(gpt-4o) :copilot:
*** Sample Code Using the ~Message~ Class in ~my_libs/utils.py~
Below is an example of how to use the ~Message~ class from your provided code in ~my_libs/utils.py~.
#+BEGIN_SRC python
from my_libs.utils import Message
def main():
# Create an instance of the Message class
msg = Message()
# Call the say method with keyword arguments
try:
msg.say(text="Hello, World!", sender="user@example.com", recipient="recipient@example.com")
except ValueError:
print("An error occurred while executing the say method.")
if __name__ == "__main__":
main()
#+END_SRC
*** Explanation
+ The ~Message~ class is imported from ~my_libs/utils.py~.
+ An instance of the ~Message~ class is created using ~msg = Message()~.
+ The ~say~ method is called with keyword arguments like ~text~, ~sender~, and ~recipient~.
+ A ~try-except~ block is used to handle the ~ValueError~ raised by the ~say~ method.
更に Message クラスに機能を追加する依頼をしてみます
ちゃんと元の Message クラスの構成をキープしつつ追加してくれました
* *[11:11:04]* You :you:
Add a field to the Message class to manage the message body.
** *[11:11:06]* Copilot(gpt-4o) :copilot:
*** Updated ~Message~ Class with a Field for the Message Body
Below is the updated ~Message~ class with a new field to manage the message body.
#+BEGIN_SRC python
import time
class Message:
def __init__(self, body=""):
"""
Initialize the Message class with an optional body field.
"""
self.body = body
def say(self, **kwargs):
"""
Print the message body and additional keyword arguments.
"""
print("Start say")
print("Message Body:", self.body)
print("Additional Arguments:", kwargs)
time.sleep(10)
raise ValueError()
# print("End say")
#+END_SRC
*** Explanation of Changes
+ A new field ~body~ is added to the ~Message~ class, initialized in the constructor (~__init__~).
+ The ~say~ method now prints the value of the ~body~ field along with the additional keyword arguments.
*** Example Usage
Here is an example of how to use the updated ~Message~ class:
#+BEGIN_SRC python
from my_libs.utils import Message
def main():
# Create an instance of the Message class with a custom body
msg = Message(body="This is the message body.")
# Call the say method with keyword arguments
try:
msg.say(sender="user@example.com", recipient="recipient@example.com")
except ValueError:
print("An error occurred while executing the say method.")
if __name__ == "__main__":
main()
#+END_SRC
*** Key Points
+ The ~body~ field allows you to store and manage the message content directly within the ~Message~ class.
+ The ~say~ method now includes the ~body~ field in its output for better context.
* ╭──── Chat Input ────╮ :copilotChatInput:
emacs の copilot-chat でコンテキストを追加する方法を紹介しました
こんな感じで公開されていないコードや手元にあるライブラリ情報をコンテキストとして追加すればそれを考慮したコードを書いてくれるようです
以下懸念点です
前回 Claude を Mac にインストールしてみました
今回は Claude Desktop の MCP クライアントとしての機能を使ってみます
サーバ側は https://github.com/github/github-mcp-server を使います
この MCP サーバは Github にある自身のリポジトリや Issue などを検索するのに役立ちます
Claude desktop に MCP サーバを追加します
設定ファイルがあるのでそれに MCP サーバの定義を追加します
claude_desktop_config.json 自体は初回にはないので作成すれば OK です
{
"mcpServers": {
"github": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
}
}
}
}
YOUR_TOKEN の部分には Github から取得したトークンを入力してください
MCP サーバはほぼすべての Github API を使用するので権限は多めに設定するのがいいかなと思います
MCP サーバに呼び出させたい API だけ権限を付与するのもありです
このように記載すると Claude 起動時に docker を使って自動で MCP サーバを起動してくれます
初回起動時は Claude から docker へのアクセス権限がなく失敗するので許可してから再起動しましょう
MCP サーバが docker で起動していれば OK です
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3b725ec0e9a8 ghcr.io/github/github-mcp-server "./github-mcp-server…" 1 second ago Up 1 second amazing_lumiere
これで Github に関する質問をすると MCP サーバを介して検索してくれます
最初に get_me という命令を使って自分の Github の情報を Claude が取得してもいいか聞かれるので許可します
こんな感じで Claude が MCP サーバを介して Github から必要な情報を取得してくれます
他にも公開されている MCP サーバはたくさんあるので助けてほしい項目のサーバがあれば追加してみるといいかなと思います
基本的には RAG に近い機能ではありますが MCP サーバを構築/追加するだけであるコンテキストに特化した情報が簡単に検索できるようになるので便利です
また MCP サーバというかアプリケーションなので例えば Github であれば Issue を作成したり Pull-Request の操作なども Claude desktop からできるようにもなります
起動した MCP サーバは Claude desktop を閉じれば自動的に停止します
git ls-files を使って取得する方法を紹介します
.el
.gitignore
.lock
.md
.rb
.ruby-version
.sh
.yasnippet
.yml
15 .yasnippet
13 .el
8 .rb
2 .yml
2 .md
1 .sh
1 .ruby-version
1 .lock
1 .gitignore
拡張子のないファイルは取得されません
過去に紹介した copilot.el だと補完はしてくれますがチャットはできないので別途拡張をインストールする必要があります
チャット用のバッファが起動したら質者を入力し送信します
送信する場合はエンターではなく C-c C-c で送信できます
するとワンタイムコードが発行されるので copilot を使うユーザでブラウザの https://github.com/login/device にアクセスし表示されているワンタイムコードを入力します
これで copilot-chat が使えるようになります
C-c C-c でチャットを送信すると以下のように結果が返ってきます
結果を別のバッファに反映させるには copilot-chat-yank を実行します
キーバインドで行いたい場合は以下の設定が良いようです
(use-package copilot-chat
:bind (:map global-map
("C-c C-y" . copilot-chat-yank)
("C-c M-y" . copilot-chat-yank-pop)
("C-c C-M-y" . (lambda () (interactive) (copilot-chat-yank-pop -1))))
)
emacs から copilot にチャットを送信してみました
Symbol's function definition is void: json-parse-buffer
になる場合はそもそも copilot のチャット機能が無効化されている可能性があります
もしくは emacs のバージョンが古いと出るので 30 台にバージョンアップしてください
コマンドの説明とコマンドの推論だけできるようです
(type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) \
&& sudo mkdir -p -m 755 /etc/apt/keyrings \
&& out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \
&& cat $out | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
&& sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
&& sudo apt update \
&& sudo apt install gh -y
https://github.com/login/device にアクセスしてコードを入力する方法でログインします
? What account do you want to log into? GitHub.com
? What is your preferred protocol for Git operations on this host? HTTPS
? Authenticate Git with your GitHub credentials? Yes
? How would you like to authenticate GitHub CLI? Login with a web browser
! First copy your one-time code: xxxx-xxxx
Press Enter to open github.com in your browser...
! Failed opening a web browser at https://github.com/login/device
exec: "xdg-open,x-www-browser,www-browser,wslview": executable file not found in $PATH
Please try entering the URL in your browser manually
? Authentication complete.
- gh config set -h github.com git_protocol https
? Configured git protocol
! Authentication credentials saved in plain text
? Logged in as username
あとは suggest コマンドなどが使えます
忘れるのでメモ
https://hawksnowlog.blogspot.com/2024/11/self-update-nvm.html
基本的には同系列のバージョンアップであれば自動的にデフォルトで新しいバージョンを使うようになります
明示的に指定したいのであれば use を使います
プロジェクトごとに指定する場合は
gitlab のリポジトリに対して renovate を実行してみました
CLI で直接実行する方法と CI を使う方法を紹介します
renovate/renovate-runnerを自身の Gitlab にミラーして使います
fork なりミラーしたリポジトリ名を yaml に記載します
Gitlab.com であれば以下のままで OK です
スキャン対象のリポジトリを RENOVATE_EXTRA_FLAGS に追加することで管理するようです
stages:
- test
include:
- project: 'renovate/renovate-runner'
file: '/templates/renovate.gitlab-ci.yml'
renovate:
stage: test
variables:
RENOVATE_EXTRA_FLAGS: "--platform=gitlab --endpoint=https://your-gitlab-endpoint/api/v4 --username=oauth2 user/repo"
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
- if: '$CI_PIPELINE_SOURCE == "push"'
RENOVATE_TOKEN は必須なので CI の変数に設定しましょう
設定する値は Gitlab の Personal Access Token で OK です
GITHUB_COM_TOKEN はあったほうがいいです
Github アカウントがある場合は Github の Personal Access Token を取得して設定しましょう
基本は CI を使って定期的に MR を作る感じになるかなと思います
ただ更新対象が自動で決まるのとデフォルトだと2MRしか作成してくれないのでそこは変数による調整が必要かもです
また当然ですがバージョンを固定しているケースは renovate でもバージョンを上げられないのでそこは手動によるバージョン管理になります
前回ローカルで renovate を動かしてみました
今回は bitbucket と連携して使ってみます
https://bitbucket.org/account/settings/app-passwords/ から作成できます
作成するパスワードに必要な権限は以下のとおりです
手動で実行するので schedule の部分はいつでも OK にします
特定の時間帯で renovate が PR を作成可能にする場合は時間を指定しましょう
{
"extends": [
"config:recommended"
],
"enabledManagers": [
"pipenv"
],
"packageRules": [
{
"description": "Schedule all packages update on Sunday nights (9 PM - 12 AM)",
"matchManagers": [
"pipenv"
],
"groupName": "pip dependencies",
"schedule": ["* * * * *"],
"automerge": true,
"automergeType": "branch"
}
],
"pipenv": {
"lockFileMaintenance": {
"enabled": true,
"schedule": ["* * * * *"]
},
"fileMatch": [
"(^|/)Pipfile$"
]
}
}
bitbucket のユーザ名と先ほど発行したアプリパスワードを使って認証するように修正します
ユーザ名/パスワードは環境変数で渡せるようにしましょう
{
"dependencies": {
"renovate": "^39.222.1"
},
"scripts": {
"renovate": "renovate --platform=bitbucket --username=$BITBUCKET_USERNAME --password=$BITBUCKET_TOKEN team-name/repo-name",
"renovate_help": "renovate --help"
}
}
team-name/repo-name の部分は自身の bitbucket 上で管理しているチーム名とリポジトリ名に変更してください
リポジトリ名を指定せずに --autodiscover=true
にしても OK ですがリポジトリが大量にある場合は精査に時間がかかるのでリポジトリ名を指定したほうが速いです
Renovate 用のアカウントを作成する必要があるので作成しましょう
そしてそのアカウントからアプリパスワードを取得し設定します
とりあえずローカルで動くか試しましょう
認証情報の部分は適宜変更してください
もしこれで master ブランチ上で更新するべきパッケージがあった場合には MR を作成してくれます
あとは renovate コマンドを定期的に実行する CI を設定するだけです
bitbucket の場合 Mend という専用のアプリがあるのでこれを使うのが一番簡単です
renovate は nodejs で書かれた自動パッケージ更新ツールです
python の pipenv でも使えるのでローカルで試してみました
なおローカルで試す場合には実際にブランチやPRの作成はされません
本来は CI と組み合わせて使います
{
"extends": [
"config:recommended"
],
"enabledManagers": [
"pipenv"
],
"packageRules": [
{
"description": "Schedule all packages update on Sunday nights (9 PM - 12 AM)",
"matchManagers": [
"pipenv"
],
"groupName": "pip dependencies",
"schedule": [
"* 21-23 * * 0"
],
"automerge": true,
"automergeType": "branch"
}
],
"pipenv": {
"lockFileMaintenance": {
"enabled": true,
"schedule": [
"monthly"
]
},
"fileMatch": [
"(^|/)Pipfile$"
]
}
}
{
"dependencies": {
"renovate": "^39.222.1"
},
"scripts": {
"renovate": "renovate --platform=local --dry-run=full",
"renovate_help": "renovate --help"
}
}
ローカルで実行する場合にはいろいろとオプションが必要です
コマンドの実体は以下です
これで Pipfile に書かれたパッケージが更新されるはずです
npx で直接実行もできます
local 動作では --dry-run=lookup
になっており Pipfile.lock が作成されないので上書きします
LOG_LEVEL=debug
を指定すると詳細なログが確認できます
またまだ renovate.json がない場合には --require-config=ignore
を設定してください
エラーが出なければ OK です
通常 renovate は更新があった場合にブランチからのPRが作成されます
ただ platform=local 実行の場合にはブランチの作成が許可されていないので platform=bitbucket などを指定して実際の挙動を確認しましょう
当然ですがバージョンが固定されているので更新されません
isort = "==6.0.0"
で renovate を実行しても 6.0.1 には更新されない
この場合は 6.0.1 に更新されます
isort = "~=6.0.0"
で Pipfile.lock が更新され 6.0.1 がインストールされます
ローカルで renovate を試してみました
ローカルでは設定ファイルの確認くらいしかできないので実際はリモートリポジトリと組み合わせて使います
次回は bitbucket と組み合わせてみたいと思います
前回 Openhermes をローカルに構築しました
今回は emacs の gptel を使って Openhermes と通信してみます
なお今回の記事はすでにローカルで Openhermes で起動している前提で話を進めます
;; OPTIONAL configuration
(setq
gptel-model 'openhermes:latest
gptel-backend
(gptel-make-ollama
"Ollama"
:host "localhost:11434"
:stream t
:models '(openhermes:latest)))
M-x gptel を実行すると Openhermes とやりとりするバッファを選択するので *Ollama*
バッファを選択します
すると ###
というプロンプトになるのでここに質問を入力します
質問を入力したら C-c RET で質問を送信できます
gptel-send は現在のカーソル位置より上部のテキストを送信します
また返答も現在のバッファにそのまま書き込まれます
gptel-rewrite はリファクタなどに使えます
現在選択している部分を上書きしてくれる機能です
リファクタする際に一行ずつやリファクタする方法などを追加で送信することもできます
他にもいろいろ機能はありそうです
Tools モードを使うと特定のパスに設定ファイルを作成してくれるタスクをインタラクティブに生成することもできるようです
copilot-mode と混同しているとキーバインドが同じケースがあるので別の割当てをする必要がありそうです
copilot-mode はデフォルトで auto-complete のように自動で補完してくれます
gptel はデフォルトではそうでなく修正したい箇所や新規でコードを追加してい場合で質問して LLM 側に書いてもらう感じになります
おそらくどちらも同じ機能があるかなと思うので使い分けというよりかはどちらか一方を使い倒すほうがいいかなと思います
copilot は無料だと制限があるので無制限にかつ無料で使いたい場合には必然的に gptel になるかなと思います
ただ gptel は当然ですがローカルに LLM を動かす環境が必要になるのでそれなりのマシンスペックが要求される点に注意です
Ollama を使ってローカルで動かします
この時点でインタラクティブモードになり質問することもできます
localhost:3000 にアクセスすると UI を経由して Openhermes を使うことができます
精度はそこそこと言った感じです
軽量で日本語にも対応してるのでローカルで動かす LLM としては十分かもしれません
ただそこまで回答に期待はしないほうがいいかもです
設定ファイルのマイグレーションが必要です
.eslintrc.js を eslint.config.mjs にマイグレーションします
devDependencies の部分を手動で書き換えます
{
"devDependencies": {
"eslint": "^9.22.0"
}
}
ちなみに最新バージョンの確認は npm outdated で確認します
あとはインストールするだけです
ちなみに "eslint": "^8.41.0"
という感じで書いている場合に npm update すると 8 系の最新までは自動でアップデートしてくれます
npm だけを使っている場合には手動でバージョンを書き換えて update する必要があります
複数のワークスペースを手動で追加したい場合には直接設定ファイルを編集しちゃうのが簡単です
~/.emacs.d/.cache/treemacs-persist
先に emacs を停止しましょう
treemacs-persist とバックアップファイルを直接編集します
例えば以下のように特定のディレクトリ配下のプロジェクトを一括で追加できます
echo "* Default" > ~/.emacs.d/.cache/treemacs-persist && for i in `ls | grep -v update_libs.sh`; do echo "** ${i}\n - path :: ~/data/repo/${i}"; done >> ~/.emacs.d/.cache/treemacs-persist
echo "* Default" > ~/.emacs.d/.cache/treemacs-persist\~ && for i in `ls | grep -v update_libs.sh`; do echo "** ${i}\n - path :: ~/data/repo/${i}"; done >> ~/.emacs.d/.cache/treemacs-persist\~
これで再度 emacs を開くと treemacs にプロジェクトが追加されています
なぜか .emacs.d/.cache/treemacs-persist
が改行されるというなぞの現象になるので修正します
この現象は before-save-hook などで自動フォーマットが設定されている場合に自動的に treemacs-persist もフォーマットしてしまい発生してる可能性が高いです
# State when last error occurred on 2025-03-20 10:36:48
# Error was 'First item must be a workspace name' in line '*'
*
Default
**
python-try
-
path
::
~/data/repo/python-try
* Default
** python-try
- path :: ~/data/repo/python-try
面倒な場合は直接キャッシュファイルを編集しちゃいましょう
フォーマットがあるのでそこだけ気をつけてください
elisp-autofmt を使ってフォーマットした結果 git diff で差分があった場合はエラーにすることで lint チェックします
フォーマットするためのシェルスクリプトです
内部で emacs を使って elisp-autofmt を実行しフォーマットします
ls-files で取得するファイル名は適宜変更してください
for file_name in `git ls-files files | egrep -e 'el|emacs'`
do
emacs --batch $file_name -l ~/.emacs.d/elpa/elisp-autofmt-*/elisp-autofmt.el --eval "
(progn
(let ((use-stdout nil))
(dolist (buf (buffer-list))
(with-current-buffer buf
(when (buffer-file-name)
(setq buffer-undo-list t) ;; Disable undo.
(if use-stdout
(progn
(elisp-autofmt-buffer)
(princ (buffer-substring-no-properties (point-min) (point-max))))
(princ buffer-file-name)
(princ \"\n\")
(elisp-autofmt-buffer-to-file)))))))"
done
ame: Emacs Lisp Format Check
on:
push:
pull_request:
jobs:
format-check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Emacs
run: sudo apt-get install -y emacs-nox
- name: Install pyenv
uses: actions/setup-python@v5
with:
python-version: "3.12.9"
- name: Install elisp-autofmt into emacs
run: |
mkdir -p ~/.emacs.d/elpa/elisp-autofmt-latest/
wget -O ~/.emacs.d/elpa/elisp-autofmt-latest/elisp-autofmt.el https://codeberg.org/ideasman42/emacs-elisp-autofmt/raw/branch/main/elisp-autofmt.el
- name: Run format_elisp_files.sh
run: sh ./format_elisp_files.sh
- name: Check diff
run: git diff --exit-code
これで CI 上で elisp の lint ができます
emacs の設定ファイルである el ファイルをフォーマットする方法を紹介します
emacs を実行しているパスで python コマンドが使えないとダメなので注意しましょう
実行時に
elisp-autofmt: error output
Note, built-in variable ‘image-scaling-factor’ not bound
となる場合は再度 elisp-autofmt-buffer
を実行すればエラーが解消されます
(add-hook 'emacs-lisp-mode-hook 'elisp-autofmt-mode)
(add-hook 'lisp-mode-hook 'elisp-autofmt-mode)
これで el ファイル保存時に自動でフォーマットしてくれます
うまく保存時にフォーマットしてくれない場合は以下でも OK です
(add-hook 'before-save-hook 'elisp-autofmt-buffer)
emacs が必要ですが CLI からも実行できます
https://codeberg.org/ideasman42/emacs-elisp-autofmt/src/branch/main/elisp-autofmt-cmd.py を使っても OK です
for file_name in `git ls-files files | egrep -e 'el|emacs'`
do
emacs --batch $file_name -l ~/.emacs.d/elpa/elisp-autofmt-*/elisp-autofmt.el --eval "
(progn
(let ((use-stdout nil))
(dolist (buf (buffer-list))
(with-current-buffer buf
(when (buffer-file-name)
(setq buffer-undo-list t) ;; Disable undo.
(if use-stdout
(progn
(elisp-autofmt-buffer)
(princ (buffer-substring-no-properties (point-min) (point-max))))
(princ buffer-file-name)
(princ \"\n\")
(elisp-autofmt-buffer-to-file)))))))"
done
こちらも実行するパスに python が必要になるので注意しましょう
elisp-autofmt をインストールして elisp ファイルの自動フォーマットをしてみました
次回は CI を組み込んで lint してみたいと思います
いくつかあるので紹介します
(add-hook 'python-mode-hook
(lambda ()
(shell-command "echo 'Hello, Python!' >> ~/start_python_mode_msg.txt")))
(add-hook 'c-mode-hook
(lambda ()
(start-process "make-process" nil "make")))
(add-hook 'go-mode-hook
(lambda ()
(async-shell-command "go build")))
(add-hook 'js-mode-hook
(lambda ()
(shell-command "bash ~/scripts/setup.sh")))
必要に応じて使い分けましょう
flask-sqlalchemy + pyright を使っているとデータベースのモデルからインスタンスを生成する際に pyright のエラーになります
回避方法はいろいろありますが今回は pyright 用のスタブ情報を手動で作成することで対応してみました
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
# インスタンス作成
user = User(id=1, name="Alice")
これで pyright を実行すると以下のえらーになります
これをスタブを作成することで回避します
/path/to/my_libs/schema.py
/path/to/my_libs/schema.py:12:13 - error: No parameter named "id" (reportCallIssue)
/path/to/my_libs/schema.py:12:19 - error: No parameter named "name" (reportCallIssue)
2 errors, 0 warnings, 0 informations
これで typings 配下に以下のファイルが自動で生成されます
ls -ltR typings
total 0
drwxr-xr-x 12 user01 staff 384 3 18 10:22 flask_sqlalchemy
typings/flask_sqlalchemy:
total 120
-rw-r--r-- 1 user01 staff 171 3 18 10:22 track_modifications.pyi
-rw-r--r-- 1 user01 staff 710 3 18 10:22 table.pyi
-rw-r--r-- 1 user01 staff 1428 3 18 10:22 session.pyi
-rw-r--r-- 1 user01 staff 1870 3 18 10:22 record_queries.pyi
-rw-r--r-- 1 user01 staff 3024 3 18 10:22 query.pyi
-rw-r--r-- 1 user01 staff 5279 3 18 10:22 pagination.pyi
-rw-r--r-- 1 user01 staff 5297 3 18 10:22 model.pyi
-rw-r--r-- 1 user01 staff 14197 3 18 10:22 extension.pyi
-rw-r--r-- 1 user01 staff 321 3 18 10:22 cli.pyi
-rw-r--r-- 1 user01 staff 180 3 18 10:22 __init__.pyi
スタブファイルを作成した状態で再度 pyright を実行しましょう
先のほどまで出ていた No parameter named "name"
などのエラーが出ないことが確認できると思います
__init__
メソッドを User モデルクラスに実装するこの方法でも pyright のエラー自体は出なくなりますがコンストラクタを生成することで初期化時の処理が変わる可能性があるのであまりおすすめはしないです
User 生成時にキーワード引数ではなく辞書の展開を使ってもエラーを回避できます
params = {
"id": 1,
"name": "hawk"
}
user = User(**params)
flask-sqlalchemy の pyright エラーに対応してみました
typings/ ディレクトリは今回は自動生成しているので git には含めないほうがいいです
手動で作成している場合は含めたほうがいいです
CI などで typings が必要な場合は毎回生成してあげるようにしましょう
以下試したけどダメだったパターンです
スタブファイルはプロジェクトルートの typings というディレクトリ配下に作成します
またパッケージの階層も同じにする必要がありかつファイル名は pyi ファイルにします
from flask_sqlalchemy import SQLAlchemy
db: SQLAlchemy
class User():
id: int
name: str
def __init__(self, id: int, name: str) -> None: ...
6.0 だとエラーになるので 6.0.2 をインストールしましょう
FROM python:3.12.9-slim-bookworm
RUN apt -y update && apt -y install libyaml-dev gcc
RUN pip install pyyaml=="6.0.2"
最後の行が pyyaml=="6.0"
だとエラーになります
理由は不明です
おそらく何かしらのパッケージが足りていないんだと思います
同じように uwsgi も 2.0.22 だと slim-bookworm ではエラーになりました
Issue および Pull request を一覧ですべて表示します
your-github-account-name に自分のアカウント名を入力すればすべて表示されます
これまでの活動内容をすべて表示したいときに便利です
Xcode を起動しないでとりあえずビルドできるかチェックしたい場合に便利です
あとは CI などに使えます
以下のパスに app ファイルが作成されます
~/Library/Developer/Xcode/DerivedData/project_name-bpovlgogrctpyxcxsxxxxxxxcxon/Build/Products/Debug-iphonesimulator/project_name.app
destination オプションで generic/platform=iOS
だけを指定します
成果物のパスは以下に変わります
~/Library/Developer/Xcode/DerivedData/project_name-bpovlgogrctpyxcxsxxxxxxxcxon/Build/Products/Debug-iphoneos/project_name.app
Cocoapods を使ってサードパーティのライブラリを管理している場合はこちらの方法でサードパーティ側のバージョンも固定しなければいけない場合があります
固定する設定がなく Pods 側のビルドバージョンの変更を毎回 Xcode でやっていた場合には固定化する設定が必要になります
わざわざ Xcode を開いてビルドする必要がなくなるので Pods をアップデートして問題なくビルドできるかなどの確認作業を CLI のみで行うことができるようになります
Flask アプリから celery の非同期タスクを呼び出した際にもトレースできるようにします
Flask、Celery で一貫して OLTP にトレース情報を送信するサンプルコードを紹介します
前回のに追加で以下をインストールします
from celery import Celery
from flask import Flask
from opentelemetry import trace
from opentelemetry.propagate import inject
from lib.tasks import roll_dice_async
# Flask アプリの作成
app = Flask(__name__)
# Celery 設定
app.config["CELERY_BROKER_URL"] = "redis://localhost:6379/0"
app.config["CELERY_RESULT_BACKEND"] = "redis://localhost:6379/0"
celery = Celery(app.name, broker=app.config["CELERY_BROKER_URL"])
celery.conf.update(app.config)
# OpenTelemetry の設定
tracer = trace.get_tracer("diceroller.tracer")
@app.route("/rolldice")
def roll_dice():
username = get_username()
# トレースコンテキストを取得して Celery に渡す
headers = {}
inject(headers)
roll_dice_async.apply_async(kwargs={"username": username, "headers": headers})
return "Rolling dice asynchronously!"
def get_username():
with tracer.start_as_current_span("get_username") as span:
username = "hawksnowlog"
span.set_attribute("username", username)
return username
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
from random import randint
from opentelemetry import trace
from opentelemetry.propagate import extract
from lib.worker import celery
tracer = trace.get_tracer("diceroller.tracer")
@celery.task
def roll_dice_async(username, headers):
return roll(username, headers)
def roll(username: str, headers):
# OpenTelemetry のコンテキストを受け取り、トレースを継続
with tracer.start_as_current_span("roll", context=extract(headers)) as span:
res = randint(1, 6)
span.set_attribute("roll.value", res)
return res
from celery import Celery
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.celery import CeleryInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
# Celery インスタンスを作成
celery = Celery("tasks", broker="redis://localhost:6379/0")
celery.conf.update(broker_connection_retry_on_startup=True)
# OpenTelemetry の設定
tracer_provider = TracerProvider()
tracer_provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4317"))
)
# Celery の OpenTelemetry を有効化
CeleryInstrumentor().instrument()
Flask
Celery
Jaeger
localhost:8080/rolldice にアクセスして Jaeger にアクセスすると一つのトレース内に Flask と Celery のタスクが処理が含まれていることが確認できると思います
Flask + Celery に OLTP のトレースを設定する方法を紹介しました
トレースに間があるのは非同期特有なのかもしれません
もしくは BatchSpanProcessor を使っているのでリアルタイムでなくバッチ処理として送信している影響かもしれません
これまではアプリ -> コレクタ -> exporter -> Jaeger という経路でトレース情報を送信していました
推奨ではないですがコレクタと exporter をすっ飛ばして直接トレース情報を Jaeger に送信することもできるので紹介します
4317 で OTLP のトレース情報を直接受けます
上記の設定では otlp は localhost:4317 に送信されます
前回と変わりませんが記載しておきます
from random import randint
from flask import Flask
from opentelemetry import trace
# Acquire a tracer
tracer = trace.get_tracer("diceroller.tracer")
app = Flask(__name__)
@app.route("/rolldice")
def roll_dice():
username = get_username()
return str(roll(username))
def get_username():
with tracer.start_as_current_span("get_username") as rollspan:
username = "hawksnowlog"
rollspan.set_attribute("username", username)
return username
def roll(username: str):
# This creates a new span that's the child of the current one
with tracer.start_as_current_span("roll") as rollspan:
res = randint(1, 6)
rollspan.set_attribute("roll.value", res)
return res
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
Jaeger を確認するとトレース情報が確認できます
おそらく OpenTelemetry が推奨する方法ではないですがこの方法でもトレース情報を送信できます
アプリのメトリックスを取得しない(トレースだけ)かつ OTLP の情報を Jaeger 以外に送らないのであればこの構成でもいいのかもしれません