Photoruction工事中!

Photoructionの開発ブログです!

モバイルリリースとテスト自動化について

こんにちは!株式会社フォトラクションでQAグループのリーダーをしている塩谷です。

Photoruction Advent Calendar 2022の15日目の記事になります。

本日は弊社で取り組んでいるモバイルテスト自動化の導入検討について、これまでに調査して分かったことを記事にしたいと思います。

モバイルテスト自動化導入の背景

モバイルテストの自動化を検討するきっかけとなったのは、リリース回数の増加になります。

弊社はandroidiOSそれぞれのアプリをサービス展開しており、以前はそれぞれ月1回程度のリリース回数でした。

しかし現在は、リリーストレインを導入し、毎週リリースをするという計画になっています。(リリース対象やリリースに問題が発生した場合はスキップ)

リリース回数の増加によって、リリース前に行なっているリグレッションテストの工数が増加し、

またテスターリソースが限られていることからリリースまでの時間的猶予がなくなってしまいました。

リグレッションテストは毎回同じ項目を実施するため、自動化することによってリグレッションテストの工数削減と時間短縮を行おうと導入の検討を開始しました。

モバイルテスト自動化ツールについて

モバイルテストの自動化を行う目的はリリース毎に実行するリグレッションテストの工数削減になります。

また多端末での検証やテストミス、エラーの見逃し防止など品質面の向上も見込めます。

これらの目的を達成できるようにモバイルテストの自動化ツール2つについて調査をしています。

1つはAutifyMobileでもう1つはMagicPodになります。

弊社で調査している2つのツールの比較表を記載します。

AutifyMobileのトライアル結果について

AutifyMobileのトライアルを実施しましたので、その調査結果と考察を記載いたします。

調査内容

トライアル期間で実施した内容は、リグレッションテストケースの一部自動化を行いました。

具体的にはアプリ起動からログイン、プロジェクト一覧(弊社サービスのTOP画面)の表示とプロジェクト新規作成になります。

弊社のサービスではプロジェクト一覧からプロジェクトを選択し、プロジェクト詳細で写真や図面を操作することが主な機能になりますが、こちらについてはAutifyMobileと弊社のアプリの間に問題があり、クラッシュが発生してしまうことから調査できませんでした。

考察

トライアルを通して分かったAutifyMobileの良かった点と悪かった点を記載いたします。

まとめ

今回はモバイルテスト自動化について執筆いたしました。

現在はAutifyMobileの調査しかしていないので、MagicPodの調査結果については別の機会にご紹介したいと思います。

AutifyMobileを調査した結果では、操作が簡単ですぐに導入可能なことが大きなメリットと感じましたが、オフラインや横画面・写真撮影など実施できない機能が意外と多いこと、iPadに対応していない、アプリがクラッシュする操作がある・実行時間が長い操作があるなど弊社アプリとの相性が良くないという点が見受けられました。

また、料金が割高で弊社で想定している使用方法では費用対効果が低いと感じました。

自動テストで全てのテストケースをまかなうことはできないため、手動テストと組み合わせて自動テストを有効活用することになると思います。

まだまだ調査段階ではありますので、エンジニアと協力しながら自動テストの有効な活用方法を検討し、自動テストの推進をしていきたいと思います!

株式会社フォトラクションでは一緒に働く仲間を募集しています

無料プランでも高機能なCloudflare Zero Trustで遊んでみた

こんにちは、PhotoructionでWebエンジニアをしている田村です。

Photoruction Advent Calendar 2022の14日目の記事になります。

はじめに

こないだ Cloudflare Zero Trust を使っていろいろ試してみたので、このサービスについて紹介したいと思います!

とある案件で「アクセス制御にVPN使うか?」という話になったものがあり(けっきょく使わなかった)、どんなサービスがあるか軽く調べてみたことがこのサービスを知ったきっかけです。

Cloudflare Zero Trustとは

Cloudflare Zero TrustはVPNファイアウォール、プロキシなど便利なツール盛りだくさんのサービスとなっています。 そしてこれがなんと無料(複数プランあり)で、家庭や小さな組織なら十分なクオリティがあります。

https://www.cloudflare.com/ja-jp/products/zero-trust

必要なもの

  • Cloudflareアカウント メールアドレスがあればかんたんにサインアップできます。 プランを選択する際は”Free”でOKです。
  • WARP client Zero Trustに接続するデバイスでインストールしておきます。 スマホアプリはあの有名な1.1.1.1 ってやつですね。

Zero Trustの初期設定など

ダッシュボードはCloudflareとZero Trustの2種類あります。 Cloudflareの中のZero Trustっていう位置づけです。

  • Team domain(Settings > General) 例えばTeam名がtest-2022の場合 Team domainはtest-2022.cloudflareaccess.com となります。

  • Device enrollment(Settings > WARP Client) 初期状態ではLogin methodsとしてOne-time PIN が利用可能となっていますが、これだけだとメールアドレスを持っていれば誰でもログイン可能となってしまいます。 このようなruleを追加することで、ログインできるメールアドレスを制限できます。

WARPクライアント設定

Preferenceから作成したチーム名(test-2022)を設定して認証するとZero Trustクライアントして使用可能となります。

やってみたこと① Googleアカウントで認証

デフォルトで設定されているOne-time PIN以外にもいろいろと認証方式を追加・使用することができます。 今回はGoogleアカウントで認証する方法を試してみました。

  • Google以外にもこれだけのidプロバイダを使用することができます。

Google Cloud ConsoleでOAuth 2.0 クライアント追加

説明は省きますが、 下記の設定画面を開くと詳しい説明が載っていて、親切すぎてもう何も言えません。

Login methods(Settings > Authentication)追加

登録したOAuth 2.0 クライアント情報を入力するだけ。 ページの右サイドはOAuth 2.0 クライアントを設定する手順です!

WARPクライアントでログイン

  • WARPクライアント起動(キャプチャはMac版)

  • Preferenceからチーム名を入力するとブラウザでログインフォーム表示 設定した「TEST Google」(Googleによる認証)が表示されていますね これをクリックするといつものGoogleログインのあのフォームへ進みます

  • Googleでログイン成功するとZero Trustクライアントへ表示が変わります (Device enrollmentで設定したメールアドレスだけがログインできます)

GoogleでZero TrustのTeamにログインすることができました!!

やってみたこと② 特定のDNSへのアクセスをブロックする

Zero Trustに接続している状態のときにみんな大好きGoogleサイトを閲覧できないように設定してみます。

設定はとてもかんたんで、何も考えなくても入力できてしまいますね!

Zero TrustクライアントをONにして Googleにアクセスすると、このような表示のページに切り替わります。

Googleサイトの閲覧をブロックすることができました!!

最後に

いかがでしょうか?

今回紹介したのは多数ある機能の中でもはじめの一歩程度で、

これ以外にも

  • 外部に公開していないネットワークにリモートから参加
  • 外部に公開していないサービスをリモートから利用
  • マルウェアや有害サイトから一括ブロック
  • ユーザーをグループ分けし、グループごとにポリシーの適用範囲や内容を設定可能
  • 操作ログ、アクセスログ

等々いろいろな要望に対応可能で、無料の範囲内でもかなりの高機能なサービスとなっています!

このサービスを使ってみて一番考えさせられたのは、UIがシンプルで使い勝手もとてもよく、ある程度のキーワードをしていれば直感で操作できる点です。 こう言ったところは自社サービス構築でも考慮していきたいところだなと思いました。

また今後も時間を見つけて試してみようと思っており、わかったことは機会をあらためて紹介したいと思います。

株式会社フォトラクションでは一緒に働く仲間を募集しています

「一つのツールは、すべてを統べ」

開発者がよく作業上で使っている言語のツールチェインを変えたりはします。

最新版のプロジェクトなら、ほとんどの場合はツールチェインの最新版で実装されるだろう。

けれど、X年前からずっとサポートしないといけないプロジェクトもあります。

結局プロジェクトそれぞれ別のツールチェインに依存するのが珍しくありません。

おそれるな!バージョン管理ツールのお出ましだ!

幸いに、もうほとんどの言語のツールチェインがバージョン管理ツールをサポートしています。

はい、問題解決。

記事終わりです。皆さんありがとうございました。

と言いたいが、言語ごとバージョン管理ツールを使わなければなりません。

Pythonなら、pyenvです。NodeJSなら、nvmがメインです。などなど。

大きなシステムの開発者は大体言語一つでコードをすべて書くというわけではありません。

 

レガシーアプリケーションを考えるだけで難しいし、言語はそれぞれ、得意点、不得意点があります。フロントエンドコードなら、Javascript・Typescriptから逃げられません。

Webの一般的なバックエンドなら、PHPPythonRubyなどを使ってもおかしくないです。

 

パフォーマンスが必要なWebアプリケーションなら、Goも考えられます。

最速が必要な特殊な機能なら、C系又はRustが良いかもしれません。

結果として、各言語のバージョン管理ツールが必要になってしまいます。

NodeJS が好きな開発者の声を聞こえます。

 

はい、Nodeで全部かけるかもしれません。ただそれが一番いいですか?「出来る」からと言って、「良い」に限りません。(個人的な意見ですが・・・)



各ツールに特殊なコマンドと使い方を覚えないといけません。

正直、頭は別のことで使いたいです・・・

では、どうすればいいですか?

asdf と言うツールを紹介しましょう。プロジェクトホームはこちらです。

asdf はまず意外と覚えやすくて、打ちやすいコマンドです。

asdf を使って、言語ツールチェインに限らず、様々なツールやソフトのバージョン管理ができます。

一つのツールですべてを管理するのが絶好です。

ツール毎にプラグインがあります。そのプラグインをインストールすれば、簡単にバージョン管理ができます。

asdf のインストール

必要なツール(前提条件): curlgitMacの場合は Homebrew )

参考リンク

Mac & bash

brew install asdf
echo -e "\\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ~/.bash_profile
echo -e "\\n. $(brew --prefix asdf)/etc/bash_completion.d/asdf.bash" >> ~/.bash_profile

Mac & zsh

brew install asdf
echo -e "\\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ${ZDOTDIR:-~}/.zshrc

Linux / WSL2 & bash

git clone <https://github.com/asdf-vm/asdf.git> ~/.asdf --branch v0.10.2
echo -e "\\n. $HOME/.asdf/asdf.sh" >> ~/.bashrc
echo -e "\\n. $HOME/.asdf/completions/asdf.bash" >> ~/.bashrc

Linux / WSL2 & zsh

git clone <https://github.com/asdf-vm/asdf.git> ~/.asdf --branch v0.10.2
echo '. $HOME/.asdf/asdf.sh' >> ~/.zshrc
echo 'fpath=(${ASDF_DIR}/completions $fpath)' >> ~/.zshrc
echo 'autoload -Uz compinit && compinit' >> ~/.zshrc

ツールのプラグインをインストールする

どんなプラグインはデフォルトに入っているのかを分かるために、下記のコマンドを実行できます。

asdf plugin-list-all

仮にみんなの大好きな nodejs をインストールしたい場合は

asdf plugin add nodejs

plugin-list-all に含まれていないプラグインなら、追加パラメータのGitURLをプラグインの後に追加してください。

asdf plugin add hoge-matsuri <https://github.com/hogehoge/hoge-matsuri.git>

そして、NodeJS をインストールします。

# 特定のNodeJSをインストールする
asdf install nodejs <バージョン>

インストール出来るバージョンは

# インストール出来るNodeJSバージョンを取得
asdf list-all nodejs

で取得できます。

インストールした後、正式インストールできたかどうか

# インストール済みのNodeJSバージョンを取得
asdf list nodejs

これでインストールできたがまだ使用されていません!

バージョンアクテイブ化する

やり方は複数があります。目的によって、適切な方法を選択してください。

  1. 現在のシェルだけに使う

    asdf shell nodejs <バージョン>
    



    戻すために、シェルを閉じる、又は前のバージョンに戻すがいいです。

  2. 現在のフォルダの配下に使う

    asdf local nodejs <バージョン>
    

    これを実行すると .tool-versions ファイルが作成されます。

    このフォルダに入ると自動的に設定したバージョンが有効になります。



    注意: asdf shell.tool-versions のより優先されます。

    こちらはおすすめのやり方です。

  3. システム全体のデフォルトとして使う

    asdf global nodejs <バージョン>
    

    全体的にバージョンを設定します(ログイン中のユーザーのみ)。

追記①:特別な system バージョンがあります。使用中のバージョンを system にするとシステムにインストールされているバージョンになります。

追記②:例は nodejs で書いたが、他にサポートされているツールは色々あります。

  • terraform みたいなツールもありますし
  • sqlitepostgresql のようなDBも
  • 他のCLIツールもサポートされています: ripgreppeconeovim などなど

注意点

  • ツールチェインがバイナリーをインストールするとパスに入れられるためにたまに

    asdf reshim <プラグイン> 
    

    を実行する必要があります。

ツールのバージョン管理地獄から脱出できて何という幸せ!

株式会社フォトラクションでは一緒に働く仲間を募集しています

Nuxt3 × Vuetify × Instagram APIで写真検索アプリ作ってみよう

こんにちは!株式会社フォトラクションでプロダクトマネージャーをしています南風原はえばる)です。

Photoruction Advent Calendar 2022の12日目の記事になります。

はじめに

ついに、待望のNuxt3が正式リリースされましたね👏

これからNuxt3を使ってみようと思っている初心者向けに、Nuxt3 × Vuetify × Instagram APIを用いた簡単な写真検索アプリをハンズオン形式で紹介していきたいと思います。

[前提]開発環境の各種バージョンは以下になります

os: macOS Monterey
node: v18.12.1
yarn: 1.22.11

Nuxt3をインストールしよう

npx nuxi init nuxt-instagram

cd nuxt-instagram

yarn install

yarn dev -o

Vuetify3をインストールしよう

yarn add vuetify@next mdi
yarn add -D sass

Nuxt3でVuetifyを利用できるようにしよう

// ./plugins/vueitfy.ts
import { createVuetify } from 'vuetify';
import * as components from 'vuetify/components';
import * as directives from 'vuetify/directives';

export default defineNuxtPlugin((nuxtApp) => {
  const vuetify = createVuetify({
    components,
    directives,
  });

  nuxtApp.vueApp.use(vuetify);
});
// ./nuxt.config.ts
export default defineNuxtConfig({
    css: ['vuetify/lib/styles/main.sass', 'mdi/css/materialdesignicons.min.css'],
    build: {
      transpile: ['vuetify'],
    },
})

Instagram Graph APIを利用できる準備をしよう

Instagramプロアカウントを作成

  1. Instagramアカウントを作成する
  2. 設定から「プロアカウントに切り替える」をクリックしプロアカウントに切り替える

Facebookページを作成

  1. Facebookにログインし、任意のFacebookページを作成する

    ※ページ名に「Instagram」など固有サービス名を含めるとページがBANされてAPI利用できないので注意!

  2. FacebookページとInstagramプロアカウントの紐づけをする

    1. 右上のアイコンクリック > 設定とプライバシー > 設定 > リンク済みのアカウント

    2. 「アカウントをリンク」よりFacebookページとInstagramアカウントを紐付けます

Facebookアプリを作成

  1. Facebook for Developersにアクセスし「利用を開始」するよりFacebook for Developersアカウントを作成する
  2. 「アプリを作成」> 「ビジネス」> 基本情報を入力する
  3. FacebookアプリにInstagram APIを追加する
    1. InstagramグラフAPI」を追加する
    2. 左サイドメニュー「設定」の中の「ベーシック」をクリック
    3. アプリIDとApp Secretキーをメモし、「変更を保存」をクリック

アクセストークンを取得

  1. 1番目のアクセストークンを取得する

    1. ヘッダーの「ツール」から「InstagramグラフAPIエクスプローラー」をクリック
    2. 作成したアプリを選択し、「ページアクセストークンを取得」をクリックし、作成したFacebookページを選択し「完了」をクリック
    3. ↓1番目のアクセストークンをコピーする

  2. 2番目のアクセストークンを取得する

// ブラウザで下記URLにアクセスする
https://graph.facebook.com/v15.0/oauth/access_token?grant_type=fb_exchange_token&client_id=${アプリID}&client_secret=${app secret}&fb_exchange_token=${1番目のトークン}
{
    "access_token": "2番目のトークン",
    "token_type": "bearer",
    "expires_in": 5183464
}
  1. 3番目のアクセストークンを取得する
// ブラウザで下記URLにアクセスする
https://graph.facebook.com/v15.0/me?access_token=${2番目のトークン}
{
    "name": "山田 太郎",
    "id": "12345678"
}

上で取得したIDを使って、ブラウザで下記URLにアクセスします。

https://graph.facebook.com/v15.0/${id}/accounts?access_token=${2番目のトークン}
// 3番目のアクセストークンが発行される
{
    "data": [
        {
            "access_token": "3番目のトークン",
            "category": "xxxx",
            "category_list": [
                {
                    "id": "123456",
                    "name": "xxxx"
                }
            ],
            "name": "Facebookページ名",
            "id": "FacebookページID"
        }
    ],
    "paging": {
        "cursors": {
            "before": "xxxx",
            "after": "xxxx"
        }
    }
}

InstagramビジネスアカウントIDの取得

// 上で取得したIDを使って、ブラウザで下記URLにアクセスする
https://graph.facebook.com/v15.0/${FacebookページID}?fields=id,name,instagram_business_account&access_token=${3番目のトークン}
// InstagramビジネスアカウントID取得結果
{
    "id": "FacebookページID",
    "name": "Facebookページ名",
    "instagram_business_account": {
        "id": "InstagramビジネスアカウントID"
    }
}

「3番目のアクセストークン」と「InstagramビジネスアカウントID」の取得ができたので、晴れてInstagram GraphAPIの利用が可能となります!

.envに環境変数を追加しよう

.envに環境変数を登録後、nuxtから呼び出せるようにnuxt.config.tsにも定義します。

// ./nuxt.config.ts
export default defineNuxtConfig({
    runtimeConfig: {
      public: {
        instagramApiUrl: process.env.INSTAGRAM_API_URL,
        userId: process.env.USER_ID,
        accessToken: process.env.ACCESS_TOKEN,
      }
    }
})

検索フォームを作成してみよう

// ./app.vue
<template>
  <SearchInput />
</template>
// ./components/SearchInput.vue
<script setup lang="ts">
interface State {
    loaded: boolean
    loading: boolean
    value: string
}

const state = reactive<State>({
    loaded: false,
    loading: false,
    value: ''
})

interface Emits {
  (e: 'search', value: string): void
}
const emit = defineEmits<Emits>()

const handleSearch = () => {
    state.loading = true
    setTimeout(() => {
        state.loading = false
        state.loaded = true
    }, 2000)
    emit('search', state.value)
}
</script>
<template>
    <v-text-field
        v-model="state.value"
        :loading="state.loading"
        density="compact"
        variant="solo"
        label="検索"
        append-inner-icon="mdi-magnify"
        single-line
        hide-details
        @click:append-inner="handleSearch"
     />
</template>

↓こういう感じで、表示されればOK👌

ハッシュタグ検索機能をつくろう

APIエンドポイントを定義

外部APIを直接呼び出そうとするとCORSエラーになってしまうので、server/apiAPIエンドポイントを定義して呼び出すようにします。

ハッシュタグのIDを取得

// server/api/hashtagId.ts
export default defineEventHandler ( async (event) => {
    const config = useRuntimeConfig()
    const query = getQuery(event)
    const result = await $fetch(`${config.public.instagramApiUrl}/ig_hashtag_search`, {
        params: query
    })
    return result
})

ハッシュタグの付いたメディアを取得

// server/api/recentMedia/[hashtagId].ts
export default defineEventHandler ( async (event) => {
    const config = useRuntimeConfig()
    const query = getQuery(event)
    const result = await $fetch(`${config.public.instagramApiUrl}/${event.context.params.hashtagId}/recent_media`, {
        params: query
    })
    return result
})

データ取得に関するロジックを書く

composablesディレクトリにデータ取得などのロジックを書くことで、Vue側で自動インポートされて利用できるようになります。

// ./composables/useInstagram.ts
import { Media } from '@/@types/instagram'

export const useInstagram = () => {
    const config = useRuntimeConfig()
    const medias = ref<Media[]>([]);

    const getHashtagId = async (hashtag: string) => {
        const response = await $fetch('/api/hashtagId', {
            params: {
                user_id: config.public.userId,
                access_token: config.public.accessToken,
                q: hashtag
            }
        })
        return response.data[0].id
    }

    const getRecentMedia = async (hashtag: string) => {
        const hashtagId = await getHashtagId(hashtag)
        const response = await $fetch(`/api/recentMedia/${hashtagId}`, {
            params: {
                user_id: config.public.userId,
                access_token: config.public.accessToken,
                fields: 'id,media_url,permalink,media_type,timestamp'
            }
        })
        medias.value = response.data.filter(i => {
            if(i.media_url && i.media_type === 'IMAGE') {
                return true
            }
            return false
        })

        return medias.value
    } 

    return {
        getHashtagId,
        getRecentMedia,
        medias
    }
}

Vue側でComposablesな関数を呼び出してみよう

<script setup lang="ts">
import { useInstagram } from '@/composables/useInstagram'
const { getRecentMedia, medias } = useInstagram()

const handleSearch = async (keyword: string) => {
  await getRecentMedia(keyword)
}
</script>

<template>
  <div class="wrapper">
    <SearchInput class="mt-8" @search="handleSearch" />
    <v-row class="my-8">
        <v-col
          v-for="media in medias"
          :key="media.id"
          class="d-flex child-flex p-2"
          cols="4"
        >
        <v-card min-width="100%">
          <v-img
            :src="media.media_url"
            aspect-ratio="1"
            cover
            class="align-end"
          >
            <template v-slot:placeholder>
              <v-row
                class="fill-height ma-0"
                align="center"
                justify="center"
              >
                <v-progress-circular
                  indeterminate
                  color="grey-lighten-5"
                ></v-progress-circular>
              </v-row>
            </template>
          </v-img>
        </v-card>
        </v-col>
    </v-row>
  </div>
</template>

<stype lang="scss" scoped>
  .wrapper {
    width: 65%;
    margin: 0 auto;
  }
</stype>

完成形

まとめ

実際作ってみて、Nuxt3を使う以前にInstagram Graph APIの事前準備のところで手こずってしまい意外と時間かかってしまいました。

久々に個人開発をして楽しかったので良しとします笑

Nuxt3になってからTypeScriptのサポートが強化されたのはもちろんのこと、ビルドのスピードがより速くなったり、各ディレクトリのルールに沿ってファイルを作成することで自動インポートをしてくれたり、書き方がシンプルになったり、などなどNuxt2よりも爆速で開発ができるようになった印象です。

まだまだ、情報が少ない部分もありますがぜひNuxt3を使っていきましょう〜♪

最後に、今回作成したものはGitHubにあげています。

少しでも参考になれば幸いです。

株式会社フォトラクションでは一緒に働く仲間を募集しています

PyMuPDFでPDFを操作する①

はじめに

こんにちは、株式会社フォトラクションでエンジニアをしています酒井です。

自分のチームではPDFを扱うことが多く、これらを PyMuPDF と呼ばれるPythonのライブラリを用いて操作しています。

Pythonには他にも PyPDF2PDFminer など様々なPDF操作用のライブラリがありますが、日本語への対応や実装されている関数の多さ、ドキュメントの読みやすさ等をふまえ、弊社では PyMuPDFを使用しています。(参考: PyMuPDFドキュメント

今回はPyMuPDFで可能となる操作をいくつか紹介したいと思います。

1. 画像変換(pdf ⇒ ndarray)

import io

import cv2
import fitz
import numpy as np
from PIL import Image

# PDFファイルパスの読み込み
pdf_path = input()

# 1ページ目の要素を取得
document = fitz.open(pdf_path)
page = document[0]

# 拡大度を指定
zoom = 3.0

# pixmapを取得
matrix = fitz.Matrix(zoom, zoom)
pixmap = page.get_pixmap(matrix=matrix)

# pillowでpng形式のバイトオブジェクトを取得
byte = pixmap.pil_tobytes("png")

# PIL画像を取得
binary = io.BytesIO(byte)
pil_img = Image.open(binary)

# PIL画像をNumPy配列の画像に変換
ndarray_img = np.array(pil_img)

# BGR -> RGB
ndarray_img = cv2.cvtColor(ndarray_img, cv2.COLOR_BGR2RGB)

2. 注釈取得

# PDFファイルパスの読み込み
pdf_path = input()

# 1ページ目の要素を取得
document = fitz.open(pdf_path)
page = document[0]

# 注釈情報を取得
annotations = page.annots()

3. PDFサイズの取得

# PDFファイルパスの読み込み
pdf_path = input()

# 1ページ目の要素を取得
document = fitz.open(pdf_path)
page = document[0]

pdf_width, pdf_height = page.rect.width, page.rect.height

最後に

PyMuPDFを使えば、他にも「ページの分割」や「注釈の設定」等様々な操作が可能となります。

よかったら使ってみてください。

参考

Introduction - PyMuPDF 1.21.0 documentation

株式会社フォトラクションでは一緒に働く仲間を募集しています

google driveで管理するファイルのIDを取得する方法

目次

  1. はじめに
  2. IDの取得方法
  3. 最後に

1. はじめに


時が過ぎるのは早いもので、AIエンジニアとしてキャリアをスタートしてから、早3年経とうとしています。

最近drive APIを使用して google driveのファイルを操作する機会があったのですが、その時に基本的にパスではなく、IDを使用して操作するような設計になっているようでしたので、ファイルの操作になかなか苦戦しました。

そこで、今回はパスからIDを取得するライブラリを発見したので、それを今回は紹介したいと思います。

2. IDの取得方法


手順として、まずkoraというライブラリをインストールします。

!pip install kora==0.9.20

そして、以下のように使用する事で、ファイルのパスからIDを取得する事ができます。

from kora.xattr import get_id
file_path = "./hogehoge/hogehoge.gsheet"
spreadsheet_id = get_id(file_path)

これで、ファイルのIDを取得する事ができました!

3. 最後に


これ以外にも、drive apiやsheets apiを使用する上で、例えばGCPのサービスアカウントに権限を渡す必要があるなど、ややこしい事が多々ありますが、IDの取得さえできれば他は結構簡単に解決していけると思うので、今回はdrive apiを使用する上で欠かせないIDの取得方法について解説しました。最後まで読んで頂きありがとうございました。

株式会社フォトラクションでは一緒に働く仲間を募集しています

Androidアプリのアーキテクチャの現状 ver.2022年

Androidアプリ開発者の久木田です。

この記事はPhotoruction Advent Calendar2022の日目です。

去年のアドベントカレンダーAndroidアプリのアーキテクチャの現状 という記事を書きました。

今回はここから1年でどういう進捗があったのかをお話しします。

TL;DR

目を見張るほどのものはないかもしれないが、着実に良くなっていっている。

新しいことも始めていきたいな〜。。。

計測と所感

去年と同じ内容でデータを取ってみました。

色々改善が進んでいるのがわかるんじゃないないかなと思います。

ファイル数からわかること

種類 条件 コマンド
Activity 62 ファイル名にActivityがついている find . -name "*Activity.java" -o -name "*Activity.kt" | wc -l
Fragment 80 ファイル名にFragmentがついている find . -name "*Fragment.java" -o -name "*Fragment.kt" | wc -l
ViewModel 87 ファイル名にViewModelがついている find . -name "*ViewModel.java" -o -name "*ViewModel.kt" | wc -l
69 ↑のうち、ViewModelもしくはAndroidViewModelを継承しているクラスのあるファイル find . -name "*ViewModel.java" -o -name "*ViewModel.kt" | xargs grep -l AndroidViewModel | wc -l
Repository 82 ファイル名にRepositoryがついている find . -name "*Repository.java" -o -name "*Repository.kt" | wc -l
Realm 49 RealmObjectを継承しているクラスのあるファイル rg -l "extends RealmObject" . | wc -l
Room 31 @Entityが付いているクラスのあるファイル rg -l "@Entity" . | wc -l
Test 36 ファイル名にTestがついている find . -name "*Test.java" -o -name "*Test.kt" | wc -l

去年と比べての改善点

  • Activityがあまり変わらないけどFragmentが大幅に増えた → FragmentでViewが作られている
  • ViewModelの数が大幅に増えた
  • Repositoryが大幅に増えた

→ ViewModel、Repository経由でのDBアクセスへの書き換えが進んでいる

  • Realmは変わらないがRoomが増えている → 新規のテーブルがRoomで作られている

逆に、改悪した点は特に見受けられないかなと思います。

ステップ数からわかること

種類 条件 コマンド
平均 202 拡張子が.javaのファイルの行数の平均 wc -l /*.java | sort | sed '$d' | awk '{n += $1}{i +=1 }; END{print int(n/i) }'
80 拡張子が.ktのファイルの行数の平均 wc -l /*.kt | sort | sed '$d' | awk '{n += $1}{i +=1 }; END{print int(n/i) }'
中央値 90 拡張子が.javaのファイルの行数の中央値
35 拡張子が.ktのファイルの行数の中央値
長大ファイル 20 拡張子が.javaのファイルの1000行を超えているファイル数
0 拡張子が.ktのファイルの1000行を超えているファイル数

去年と比べての改善点

  • Javaファイルの行数が減少傾向
  • Kotlinは平均が少し上がっている

  • Kotlinで書いているViewModelでロジックが詰め込まれているファイルがあるためかなと予想している

  • 1000行越えのファイルが増えた

  • Realmのマイグレーションファイルが1000行を超えてしまったので+1になった

  • これをやってて気づいたのでこれから分割する予定

この1年での改善点

  1. packageの移動

  2. 設計方針で合意していた場所へほとんどのファイルを移動した。

  3. モジュール分割を始めた

  4. 修正による副作用の低減などを求めてモジュール分割をし始めた。
    まだまだやり始めで、3モジュールを切り出した程度ですが1歩前進といった感じ。

  5. 業務委託の方にガンガンリファクタリングをしてもらう体制になった

  6. 設計方針に沿ったMVVMの形にするために、釘宮さんをはじめ業務委託の方にリファクタリングをお願いすることにした。

  7. テストコードが大幅に増えた

  8. 新しいロジックに関しては基本的にテストを書いているのでどんどん増えています。

来年のやっていきたいこと

  1. リファクタリングの大幅な進捗

できれば、ほぼ全てのActivity/FragmentがMVVMな形になってるといいなと思いますがそれはちょっと夢を見過ぎかな…

  1. Crashlyticsの改善

現状が改善する価値があるくらいの数値なので、ここもテコ入れしていきたい

  1. 新しいことを始めたい

Jetpack ComposeやCameraXなど新しい技術の導入を進められればなと思っている

PS.

今回の記事を書くのにgrepの代わりにripgrepを使ってみましたが、すごい速くて快適だったのでおすすめです!

株式会社フォトラクションでは一緒に働く仲間を募集しています