7839

雑草魂エンジニアブログ

【Python】Boto3を使ってPython でAWS S3を操作する

今回は、AWS のS3 から、必要なデータを(リネイムして)再アップロードしたいという要望があり、Python を用いて自動化したので備忘録として残しておく。

Boto3 とは

以前の記事で、今回同様に PythonAWSを操作する際に、AWS CLI を用いて行った記事を紹介した。

serip39.hatenablog.com

この時、今回紹介する、Boto3 の存在を知らなかったので、AWS CLI を力技で Python で動かしていた。しかしながら、Boto3 というステキなライブラリが存在していた。(教えてくれた師匠に感謝。)

aws.amazon.com

Boto3 とは、PythonAWS SDK であり、Boto3 を使用することで、Python のアプリケーション、ライブラリ、スクリプトAWS の各種サービス(Amazon S3Amazon EC2Amazon DynamoDB など)と容易に統合することができる。めちゃくちゃ便利なライブラリである。

boto3.amazonaws.com

pip で簡単にインストールすることができ、すぐに利用することができる。

$ pip install boto3

認証情報の設定方法(profile での設定)

AWS の認証情報に関して、私は、configとcredentialsに情報を書き込み、profileで指定している。 profile 機能に関しては、以前の記事で書いたので参考にして欲しい。

serip39.hatenablog.com

Boto3 で profile を使う場合は、以下のように設定することで使うことができる。

from boto3.session import Session

PROFILE_NAME="profile1"
BUCKET_NAME="test"

session = Session(profile_name=PROFILE_NAME)
s3 = session.resource('s3')
s3bucket = s3.Bucket(BUCKET_NAME)
s3client = s3.meta.client

# clientのみ必要の場合
s3client = session.client('s3')

Session を用いて、認証情報を格納し、サービスクライアント及びリソースを作成できるようにする。

S3からファイル一覧を取得する

S3.Client.list_objects_v2 を使用する。このメソッドは、1回のリクエストで、1000件までしか取得することができない。そのため、response の IsTruncated で途中で中断されているかを確認することができる。'IsTruncated': True の場合には、続きから取得する必要があるので、request のパラメータ StartAfter に前回取得した最後の key を割り当ててあげることで、続きから取得することができる。

from boto3.session import Session

def getFilteredFilenames(file_names=[]):
    if len(file_names) == 0:
        start = ''
    else:
        print(file_names[-1])
        start = file_names[-1]
    
    response = s3client.list_objects_v2(
        Bucket=BUCKET_NAME,
        Prefix=PREFIX,
        StartAfter=start
    )

    if 'Contents' in response:
        file_names = [content['Key'] for content in response['Contents']]
        if 'IsTruncated' in response:
            return getFilteredFilenames(file_names)
    return file_names

if __name__ == '__main__':

    PROFILE_NAME='profile1'
    BUCKET_NAME = 'test'
    PREFIX = 'test'

    session = Session(profile_name=PROFILE_NAME)
    s3client = session.client('s3')

    print(getFilteredFilenames())

S3から任意のファイルをダウンロードする

S3.Bucket.download_fileまたは、S3.Client.download_fileを使用する。

import os, json 
from boto3.session import Session
import time

def getFilteredFilenames(file_names=[]):
     # 省略

def downloadS3(file_name):
    file_path = FILE_PATH + file_name
    s3bucket.upload_file(file_name, file_path)

if __name__ == '__main__':

    PROFILE_NAME='profile1'
    BUCKET_NAME = 'test'
    FILE_PATH='./download/'

    session = Session(profile_name=PROFILE_NAME)
 s3 = session.resource('s3')
 s3bucket = s3.Bucket(BUCKET_NAME)
 s3client = s3.meta.client

    file_names = getFilteredFilenames()

    for file_name in file_names:
        downloadS3(file_name)

S3にファイルをアップロードする

S3.Bucket.upload_file または、S3.Client.upload_fileを使用する。

以下の例では、所定のフォルダにあるファイルを全てS3にアップロードする場合を想定している。(1秒間隔でアップロードをするようにしている。)

import os, json 
from boto3.session import Session
import time

def uploadS3(file_name):
    file_path = FILE_PATH + file_name
    s3bucket.upload_file(file_path, file_name)

def getAllFileNamesInFolder():
    json_files = [pos_json for pos_json in os.listdir(FILE_PATH)]
    return json_files

if __name__ == '__main__':

    PROFILE_NAME='profile1'
    BUCKET_NAME = 'test'
    FILE_PATH='./download/'

    session = Session(profile_name=PROFILE_NAME)
    s3 = session.resource('s3')
    s3bucket = s3.Bucket(BUCKET_NAME)

    file_names = getAllFileNamesInFolder()

    for file_name in file_names:
        uploadS3(file_name)
        time.sleep(1)

まとめ

今回は、Boto3 を使って、PythonAWS S3 の操作をまとめた。Boto3 はとても便利なので、今後様々なアプリケーションで使っていきたい。

それでは、ステキな開発ライフを。

ぼくの husky で設定した pre-commit が動かない。。。

今回は、エラー解決にかなり時間を要したので、備忘録として残しておく。

タイトルのとおり、新規プロジェクトを開始する際に、なぜか私の pre-commit が動かないという事態に遭遇した。pre-commit は複数人で開発を行っている際に、とても便利なのでその機能も含め、紹介する。

pre-commit とは

Gitにも、特定のアクションが発生した時にカスタムスクリプトを叩く方法がある。(Gitフックという。)pre-commit もその一つで、コミットの実行前に実行される。また、エラーを返すことでコミットを中断させることができる。

設定に関しては、.git/hooks のフォルダの中に、pre-commit というファイルが存在すれば自動的に実行されることになっている。(git init でリポジトリを作成した時に、サンプルのファイル pre-commit.sample が存在しているので、pre-commit にリネームして中身のシェルスクリプトを書き換えることで任意の処理を実行することができる。)

git-scm.com

pre-commit 前に、実行したいこととしては、Lint の確認や Prettier での自動整形、テストの自動実行などが挙げられる。

これらを簡単に設定できるライブラリとして、husky がある。husky は、Gitフックの設定を package.json (.huskyrc 等)からできるツールである。.git/hooks/pre-commit にスクリプトを書いた場合、プロジェクト内で共有がしにくいが、husky を使って設定を package.json に書けば簡単に共有できるメリットが大きい。

github.com

さらに、すべてのファイルに Lintのチェックや整形をするのではなく、今回 commit する変更箇所(すなわち、GItのステージに上がっているファイルのみを対象)にスクリプトを適応させれば十分なので、lint-staged というライブラリも追加する。変更箇所のみに適応させることができるので、効率的である。

github.com

husky と lint-staged の設定方法

インストール

npm または yarn でインストールすることができる。

$ npm install --save-dev husky lint-staged

$ yarn add -D husky lint-staged

設定

処理の流れとしては、husky で pre-commit フックに lint-staged を設定し、lint-staged から ESLint や Prettier を実行する。 事前に、eslintprettiertypescript などをインストールしておく必要があるが、pre-commit 時に、ESLint のチェックとPrettier による自動整形 を行い、pre-push 時に、 TypeScript の型チェックを行う設定ファイルは以下のようになる。

{
  "scripts": {
    "type-check": "tsc --pretty --noEmit",
    "format": "prettier --write **/*.{js,ts,tsx}",
    "lint": "eslint . --ext ts --ext tsx --ext js",
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "pre-push": "yarn type-check"
    }
  },
  "lint-staged": {
    "*.@(ts|tsx)": [
      "yarn lint",
      "yarn format"
    ]
  },
}

husky で設定した pre-commit が動かない問題

いよいよ、今回の本題である。

上記の設定を行い、いざ commit や push をしても、Gitフックか動作せず、スルー状態で、ハマりました。。。

そもそも pre-commit が設定されているか確認する

通常、husky をインストールした時点で、.git/hooks/pre-commit が自動的に生成される。

しかしながら、私の場合、husky をインストールしても、.git/hooksには、.sample ファイルがあるだけで、新規にファイルが作成されていない事態。

WEBで調べると、対応方法は以下のように記載されていた。

$ rm -rf .git/hooks
$ yarn add -D husky --force

一度、.git/hooks を削除して、再インストールするが、変化なし。

Git のバージョンを確認する

husky に以下の記述がある。

Verify that your version of Git is >=2.13.0. ENOENT error 'node_modules/husky/.git/hooks'

GitHub のバージョンは、git version 2.27.0 で最新であり、問題なし。

参照先が間違っていないか確認する

今回のフォルダ構成は、以下のようになっている。

app
  |-- .git
    |-- hooks
  |-- server
  |-- client
    |-- package.json

package.jsonと同じ階層に、.git がないので、hooks を参照できていないのかと疑ったが、以下の方法で確認すると、問題なかった。

$ git rev-parse --git-path hooks
$ ../.git/hooks

Yarnのバージョンを確認する

インストール時に、きちんと処理ができていない可能性が高いと考え、パッケージマネージャーとして使っている Yarn を疑うことにした。

$ yarn --version
1.3.2

version 1.3.2??? めちゃくちゃ古い。。。 現在のmacOSの安定版は、version 1.22.4 である。

https://classic.yarnpkg.com/ja/docs/install/#mac-stable

Yarnを最新版にアップグレードする。

$ brew upgrade yarn

$ yarn --version
1.22.4

yarn install --force で再インストールを実行、無事に pre-commit ファイルが作成された。

まとめ

今回は、まさかのパッケージマネジャーのバージョン違いで、正常に動作していませんでした。インストールで不具合が生じた場合は、Yarn のバージョンも確認お忘れなく。

それでは、ステキな開発ライフを。

Vue.jsエンジニアのReact入門 - Vue.js ライクな ReactComponent の書き方

Vue.js エンジニアの React 入門。
React の実装方法を Vue.js と対比しながら理解を深めていくシリーズ。

これまでに、以下の内容についてまとめてきたので、参考にご覧ください。

今回は、「Componentの書き方」について紹介する。

ただし、今回は対比するというよりは、ReactComponent をどのように書くのがいいかを考えた末に、Vue ライクな構成が一番可読性がよく、リファクタリングしやすいのではないかという考えになり、Vue.js ライクな ReactComponent の書き方を紹介する。Vue.js の書き方に慣れていることも大きな理由ではあるが、Vue.js エンジニアにとって導入しやすいと思われるので、参考になれば嬉しい。

Vue.js

Vus.js のコンポーネントにおいては、<script><template><style> の3層構造で、コンポーネントを書く。

f:id:serip39:20200726015115p:plain

詳細は、公式のスタイルガイドを参照してみてください。

jp.vuejs.org

React

今回の構成では、以下を想定している。

  • TypeScript
  • styled-components

ReactComponent の書き方は、以下のようにすることで、Vue.jsライクな構成で書くことができる。

f:id:serip39:20200726015842p:plain

本構成に関しては、以下の記事を参考にした。(本当にわかりやすい構成をありがとうございました。)

qiita.com

こちらの記事で述べられている、以下のSFC(Stateless Functional Component)の5層の順番を入れ替えて、よりVue.js ライクな構成とした。

// (1) import層
import React from 'react'
import styled from 'styled-components'
// (2) Types層
type ContainerProps = {...}
type Props = {...} & ContainerProps
// (3) DOM層
const Component: React.FC<Props> = props => (...)
// (4) Style層
const StyledComponent = styled(Component)`...`
// (5) Container層
const Container: React.FC<ContainerProps> = props => {
  return <StyledComponent {...props} />
}

この構成で書くことで、個人的には、上から下に流れるように読むことができるので、可読性が向上したように思っている。

// (1) Script( import層/Types層/Container層)
import React from 'react'
import styled from 'styled-components'
type ContainerProps = {...}
const Container: React.FC<ContainerProps> = props => {
  return <StyledComponent {...props} />
}

// (2) HTML(Types層/DOM層)
type Props = {...} & ContainerProps
const Component: React.FC<Props> = props => (...)

// (3) CSS(Style層)
const StyledComponent = styled(Component)`...`

まとめ

今回、Vue.js ライクな ReactComponent の書き方を紹介した。React の場合は、Vueのような型がないので、自由に書ける分、最適な書き方はどうすべきなのか、まだ暗中模索中である。是非、オススメがあれば、教えていただきたいです。よろしくお願いします。

それでは、ステキな開発ライフを。

【TS】TypeScriptのすすめ

遅ればせながら、最近やっと Babel から卒業して TypeScript に入門した私ですが、便利だなぁーと思っている。というか、TypeScript を理解しておくことで、めっちゃ効率が上がっている気がする。まだまだ初学者の私であるが、私のように TypeScript に手を出したいと思っていても、まだ手を出せていない方に向けて、TypeScript のすすめを参考に書いてみる。

TypeScriptとは

www.typescriptlang.org

TypeScript は、「JavaScriptコンパイルされる、静的型システムのあるJavaScriptの上位集合(上位互換)」である。すなわち、TypeScript は JavaScriptコンパイルされてからコードが実行される。そのため、JavaScript が動く環境であれば、TypeScript のコードは実行することができると言える。TypeScript は、JavaScript を用いている様々なプロジェクトに導入することが可能である。

大きな特徴として、以下の 2点が挙げられる

  1. 静的型システムがある

    • コードの品質向上
    • 可読性の向上
  2. JavaScriptの上位集合

    • 段階的な導入が可能

1. 静的型システムがある

TypeScriptは「型をつけられるJavaScript」である。「静的型付け言語」という種別にあたる。同じ種別の言語としては、C、C++JavaScala、Swiftなどが挙げられる。変数や定数、関数の引数や戻り値などが「どの型なのか」を定義する必要がある。静的型付けが必要な理由は、実行する前にネイティブにコンパイルするには必須であるということが挙げられる。(反対に、動的型付け言語には、JavaScriptPHPRubyPythonなどがあり、実行時に型が自動的に決定する。コードを書く際に、型を指定する必要がない分、手軽に書くことができるメリットがある。ただし、実行時にしかエラーが判明しないため、考え無しに実装すると実行時に思った通り動作しなくなり、バグの究明にかなり時間がかかることがある。)

TypeScriptは、コーディングの生産性に対する影響をなるべく小さく抑えながら型の安全性を提供するために、可能な限り、型推論を行う。型推論とは、TypeScriptが、ソースコードを解析し、そのコードの流れから、変数や関数などの型を推測してくれる仕組みのことである。 TypeScript Deep Dive 日本語版

2. JavaScriptの上位集合

f:id:serip39:20200722072613p:plain

図にすると、上記のようになる。すなわち、TypeScript は、JavaScript のすべての機能を使うことができ、JavaScript に新しい機能(静的型システムや JavaScript の将来のバージョンで計画されている機能)を追加したものであるといえる。そのため、実際には、型を利用するかどうかは、完全に任意(オプション)である、と言える。既存の .jsファイルを TypeScriptの拡張子である .ts に変更してコンパイルしても、TypeScriptのコンパイラは、元の JavaScript ファイルと同じ有効な .js を出力する。(コンパイル時に、型システムでエラーを検知することはあるが、有効なJavaScriptファイルを生成することができる。)ゆえに、既存の JavaScript のプロジェクトにおいては、TypeScript を途中からでも段階的に導入していくことが可能である。そのため、あまり気負わずに導入ができると言える。

TypeScriptのメリット

TypeScript を導入してみて感じたメリットとして、以下の 3点が挙げられる。

  1. コードの品質・可読性の向上
  2. Linterとしてのエラー検知
  3. ES5へのコンパイラ

1. コードの品質・可読性の向上

型システムのお陰で、コードの品質・可読性の向上した。チーム開発である場合には特に、この型定義がすごく有用である。コードを書く量が多くなってしまうというデメリットはあるが、それ以上に得られるこの安心感と可読性の良さは比べ物にならないと感じている。確かに個人開発や JSDoc などに従い、コメントでしっかりと書いているから問題ないと思われるかもしれないが、ヒューマンエラーを自動では検出することができない。TypeScript の型推論でヒューマンエラーさえも検出してくれるので、今は理解しているコードも、数年後に見返した時にすぐに理解できるとは限らない。

型は、それ自体が、完璧なドキュメントです。関数のシグネチャは定理であり、関数の本体は証明です。 TypeScript Deep Dive 日本語版

「型は完璧なドキュメントです。」このようにハッキリと言い切っている感じがステキだと感じる。ただ、実際にその通りであると実感することができる。TypeScript の型定義をみることで、公式ドキュメントを読んでいるかのように使い方がわかる。

ここで、ひとつ例をあげて、型でどこまで確認できるのか、見てみる。
非同期通信でよく使っている、「axios」というライブラリがある。

axios.post(url: "http://api.test.com", data: { ... })

上記のように、axiosで何らかのデータをPOSTしようとした場合に、header などの設定をどのように書けばいいか忘れたとする。もちろん公式ドキュメントをみてもいいが、Visual Studio Code でコーディングをしている方であれば、importしたモジュール名の名前の部分で右クリック→「Go to Definition」を選択することで、index.d.ts という型定義のファイルを確認することができる。

f:id:serip39:20200722044640p:plain

以下は、一部抜粋であるが、axiosでPOSTメソッドがどのような型定義がされているか、確認することができる。headerの設定に関しては、引数のconfigの中に定義すればいいことがわかる。詳細な型定義の方法の説明は今回は省略するが、このように型定義をみるだけで、関数の中身の処理まではわからないにしても、使い方を読み解くことができる。処理内容に関しては、メソッド名で明示的に示す必要がある。

~~~~
post<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
~~~~
export interface AxiosRequestConfig {
  url?: string;
  method?: Method;
  baseURL?: string;
  transformRequest?: AxiosTransformer | AxiosTransformer[];
  transformResponse?: AxiosTransformer | AxiosTransformer[];
  headers?: any;
  params?: any;
  ~~~
}

2. Linterとしてのエラー検知

こちらも、型推論がもたらしてくれる恩恵に他ならない。1のメリットとほぼ同義であるが、型推論が自動的にエラーを検知してくれるのである。Linterとは、「コードを実行する前に、コードが正しいかを検証するツール」を指し、TypeScript では実行前のコンパイル時に必ずエラー検知をしてくれる。また、エディタのプラグインESLint と組み合わせることで、コンパイル前のコードを書いている途中でエラーを検出してくれる。この機能のお陰で、開発スピードを格段に早めることができる。(正直、TypeScript に慣れるまでは、このエラー検知のせいで、型推論のエラー解消をするためにかなりの時間を取られてしまう。本末転倒な気もするが、コードの品質担保のために、必要な学習コストと考えるべきであると考えている。)もちろんESLintだけでも、エラー検出だけであれば可能であるが、TypeScript の型推論と組み合わせることでより強固なエラー検出ができるようになり、コードの品質担保が可能となる。

エディタのTypeScriptサポート情報は以下の通りである。 TypeScript Editor Support · microsoft/TypeScript Wiki · GitHub

Visual Studio Code, a lightweight cross-platform editor, comes with TypeScript support built in.

3. ES5へのコンパイラ

JavaScriptは毎年バージョンアップが行われている。現在、ほとんどのブラウザで対応しているバージョンがECMAScript2015(ES6)である。しかし、古いブラウザではECMAScript2015(ES6)の構文で書いたJavaScriptが正常に動作しません。そこで、TypeScriptのコンパイル機能が役立つのである。もちろん Babel を用いることでトランスパイルすることもできるが、TypeScript にも同様の機能が搭載されている。JavaScriptコンパイルするので当たり前と思うかもしれないが、この機能はまさに欠かせない機能であり、メリットと言える気がしている。

ES6の対応表は以下を参照してみてください。

kangax.github.io

TypeScript 初学者にオススメのドキュメント

TypeScript は意外に学習コストがかかる気がしている。。。段階的に導入が可能とはいえ、慣れるまでにかなり時間が必要が気がしている。私自身もその一人である。。。これも、JavaScript の書きやすいコードに慣れていた弊害ともいうべきなのであろうか。。。

また、TypeScript に関して、あまり日本語の文献がまだ多くないように感じる。そこで、オススメのリンクを紹介しておく。

typescript-jp.gitbook.io

book.yyts.org

www.udemy.com

まとめ

今回は、TypeScript の概要およびメリットをまとめてみた。TypeScript を少しでもやってみようかなぁと思ってくれる人が一人でも増えたら嬉しい限りです。

それでは、ステキな開発ライフを。

【React】Higher-Order Component(HOC)

React の様々なコードを読む中で、頻繁にみる HOC に関して、整理するためにまとめを行った。

Higher-Order Component(HOC) とは

ja.reactjs.org

高階関数は「関数を引数または戻り値として扱う関数」のことである。関数を引数として扱うのは、コールバック関数がよく例に挙げられる。HOC は、「コンポーネントを引数にとってコンポーネントを返す関数」であり、一見高階関数ではないように思える。しかしながら、コンポーネント自体が関数であり、Props を引数として受け取り、React DOM を返している。そのため、HOC は高階関数といえる。

const EnhancedComponent = higherOrderComponent(WrappedComponent)

HOC を利用することのメリットは、1 つの場所にロジックを定義し、多数のコンポーネントを横断してロジックを共有可能にするような抽象化ができることが挙げられる。

雛形

実際に、HOC を定義する場合は、以下のような雛形となる。

import React, { Component } from 'react'

export const HOC = (WrappedComponent) => {
  return class HigherOrderComponent extends Component {

    // 共通の処理を記述する

    render() {
      return <WrappedComponent ... />
    }
  }
}

利用例

参考ライブラリ

  • material-ui / muiThemeable

github.com

  • react-router / withRouter

github.com

Private Route

ログインしているアカウントしか閲覧できないページなどを制御したい場合のラッパーコンポーネントを実装する場合にも利用できる。

import React from 'react'
import Router from 'next/router'

const login = '/login?redirected=true'

const checkUserAuthentication = () => {
  return { auth: null }; // change null to { isAdmin: true } for test it.
}

export default WrappedComponent => {
  const hocComponent = ({ ...props }) => <WrappedComponent {...props} />

  hocComponent.getInitialProps = async ({ res }) => {
    const userAuth = await checkUserAuthentication()

    if (!userAuth?.auth) {
      // Handle server-side and client-side rendering.
      if (res) {
        res?.writeHead(302, { Location: login })
        res?.end()
      } else {
        Router.replace(login)
      }
    } else if (WrappedComponent.getInitialProps) {
      const wrappedProps = await WrappedComponent.getInitialProps(userAuth)
      return { ...wrappedProps, userAuth }
    }
    return { userAuth }
  }

  return hocComponent
}
import React from 'react'
import withPrivateRoute from '../components/withPrivateRoute'

const Dashboard = () => {
  return <div>This is a Dashboard page which is private.</div>
}

Dashboard.getInitialProps = async props => {
  console.info('##### Congratulations! You are authorized! ######', props)
  return {}
}

export default withPrivateRoute(Dashboard)

引用元:How to simply create a Private Route in Next.js | by Ali Eslamifard | Medium

注意事項

  1. HOC は副作用のない純関数でなければいけない
    HOC は入力のコンポーネントを改変したり、振る舞いをコピーするのに継承を利用したりしない。HOC は元のコンポーネントをコンテナコンポーネント内にラップすることで組み合わせるのみである。

  2. renderメソッド内部でHOCを使用しないこと
    必ずコンポーネント定義の外で HOC を適用してください。render内に HOC を作った場合、毎回新しい参照のComponentができ、そのコンポーネント以下のツリーは全て毎回再描画されることになってしまう。

  3. 静的メソッドは必ずコピーする、またはエクスポートする
    HOC をコンポーネントに適用すると、新しいコンポーネントは元のコンポーネントの静的メソッドを 1 つも持っていないということになってしまう。そのため、コンテナコンポーネントを返す前にメソッドをコピーするか、hoist-non-react-statics を使用することで、全ての非 React の静的メソッドを自動的にコピーする必要がある。または、静的メソッドをコンポーネントと分離して、静的メソッドでエクスポートする必要がある。

  4. ref 属性は渡されない
    HOC から出力されたコンポーネントの要素に ref 属性を追加する場合、ref 属性はラップされた内側のコンポーネントではなく、最も外側のコンテナコンポーネントを参照する。

まとめ

React のコンポーネント設計で重要な HOC について整理をしてみた。アプリケーション内で共通に利用する機能や、パーツをまとめるのに、非常に便利であると思えた。

それでは、ステキな開発ライフを。