yuji0602’s blog

日々思っていることなど。

pipenvで作った環境をBitbucketのPipelinesでテストする

会社ではBitbucketを使ってgit管理しています。BitbucktにはPipelinesというCIツールがあります。

最近は主にpythonで開発していて、パッケージ管理をpipenvを使うようになったのですが、Bitbucketでテストする時にはbitbucket-pipelines.ymlに以下のような設定をすると良いようでした。

# This is a sample build configuration for Python.
# Check our guides at https://confluence.atlassian.com/x/x4UWN for more examples.
# Only use spaces to indent your .yml configuration.
# -----
# You can specify a custom docker image from Docker Hub as your build environment.
image: python:3.6.3

pipelines:
  default:
    - step:
        caches:
          - pip
        script: # Modify the commands below to build your repository.
          - pip install pipenv --upgrade
          - pipenv install --dev --deploy --system
          - python -m unittest

community.atlassian.com

とりあえずこれで気兼ねなくテストを書けるので捗りそうですね。

Node.jsでYahoo!ショッピングの注文APIを叩いてみる

概要

業務でYahoo!ショッピングAPIを利用しているのですが、注文APIは少し苦戦したのでメモを残します。 とりあえず立たけるようになったので良かった・・

ちなみに、マニュアルは以下のページにあります。

ショッピング:注文に関するAPI - Yahoo!デベロッパーネットワーク

なお、アクセストークンについては既に取れている前提で記載しています。

注文ステータス別件数参照

注文ステータス別の件数を取得するAPIです。こちらは簡単でした。

サンプル

const request = require('request')
const accessToken = '<ACCESS_TOKEN>'
const sellerId = 'snbx-xxxxx'
const url = 'https://test.circus.shopping.yahooapis.jp/ShoppingWebService/V1/orderCount'

const options = {
  method: 'GET',
  url: url,
  headers: {
    'Authorization': 'Bearer ' + accessToken
  },
  qs: {
    seller_id: sellerId,
  },
  ignore_errors: true
}

request.get(options, (err, res, body) => {
  if (!err && res.statusCode === 200) {
    console.log(body)
  } else {
    console.log('Error!')
    console.log(res)
  }
})

レスポンス

<?xml version="1.0" encoding="UTF-8" ?>
<ResultSet totalResultsAvailable="1" totalResultsReturned="1" firstResultPosition="1">
    <Result>
        <Status>OK</Status>
        <Count>
            <NewOrder>1</NewOrder>
            <NewReserve>0</NewReserve>
            <WaitPayment>0</WaitPayment>
            <WaitShipping>0</WaitShipping>
            <Shipping>0</Shipping>
            <Reserve>0</Reserve>
            <Holding>0</Holding>
            <WaitDone>0</WaitDone>
            <Suspect>0</Suspect>
            <SettleError>0</SettleError>
            <Refund>0</Refund>
            <AutoDone>1</AutoDone>
            <AutoWorking>0</AutoWorking>
            <Release>0</Release>
            <NoPayNumber>0</NoPayNumber>
            <StoreStatus1>0</StoreStatus1>
            <StoreStatus2>0</StoreStatus2>
            <StoreStatus3>0</StoreStatus3>
            <StoreStatus4>0</StoreStatus4>
            <StoreStatus5>0</StoreStatus5>
            <StoreStatus6>0</StoreStatus6>
            <StoreStatus7>0</StoreStatus7>
            <StoreStatus8>0</StoreStatus8>
            <StoreStatus9>0</StoreStatus9>
            <StoreStatus10>0</StoreStatus10>
        </Count>
    </Result>
</ResultSet>

注文検索API

注文検索APIの方はちょっと苦労したのですが、何とか値が取れるようになったので良かったです。

サンプル

セラーIDのsnbx-xxxxxxは任意の値を入れてください。

const request = require('request')
const accessToken = '<ACCESS_TOKEN>'
const sellerId = 'snbx-xxxxxx'
const url = 'https://test.circus.shopping.yahooapis.jp/ShoppingWebService/V1/orderList'

const xml = `
<Req>
<SellerId>${sellerId}</SellerId>
<Search>
<Condition>
<OrderTimeFrom>20180611000000</OrderTimeFrom>
</Condition>
<Field>OrderId,Version,OrderStatus</Field>
</Search>
</Req>`

const options = {
  method: 'POST',
  url: url,
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Authorization': 'Bearer ' + accessToken
  },
  body: xml,
  ignore_errors: true
}

request(options, (err, res, body) => {
  if (!err && res.statusCode === 200) {
    console.log(body)
  } else {
    console.log('Error!')
    console.log(err)
    console.log(res)
  }
})

レスポンス

<?xml version="1.0" encoding="UTF-8" ?>
<Result>
    <Status>OK</Status>
    <Search>
        <TotalCount>2</TotalCount>
        <OrderInfo>
            <OrderId>snbx-xxxxx-10000001</OrderId>
            <Version>1</Version>
            <OrderStatus>5</OrderStatus>
            <Index>1</Index>
        </OrderInfo>
        <OrderInfo>
            <OrderId>snbx-xxxxx-10000002</OrderId>
            <Version>1</Version>
            <OrderStatus>5</OrderStatus>
            <Index>2</Index>
        </OrderInfo>
    </Search>
</Result>

BigQueryで指数表記されてしまう数値を正常に表示する

概要

BigQueryで検索範囲を大きくした状態でSUMすると、1.0E7のような指数で表示されてしまうことがあります。ここではそれを回避する方法を書いていきます。

利用するクエリ

10000000.0くらいの数値になると1.0E7になるのですが、これを回避するためにCASTすると良いようでした。

WITH test_table AS (
  SELECT a FROM UNNEST(ARRAY[1000000.0, 10000000.0, 10000000.1]) a
)

SELECT
  a,
  CAST(a AS STRING) a_str
FROM test_table

実際に実行すると以下のような結果になります。a_strの方がCASTした値になります。

Row a a_str
1 1000000.0 1000000
2 1.0E7 10000000
3 1.00000001E7 10000000.1

まとめ

文字列としてしまうと具合良く表示されるようなので、今後同じような指数表記になった場合は慌てずにCASTしてみようかと思います。

BigQueryで月初と月末の日付を出す

概要

BigQueryで月初や月末日付を取りたい場合があると思うのですが、検索すると標準SQLの情報があまり見つからなかったので何とか自力で作成してみました。いつものように個人的なメモを残します。

月初

月初についてです。今は2018年5月なので2018/05/01が表示されるクエリとなります。

TIMESTAMPから表示するやり方は以下のようになりました。

SELECT FORMAT_DATE('%Y/%m/%d', DATE(TIMESTAMP_TRUNC(CURRENT_TIMESTAMP(), MONTH), "Asia/Tokyo"));

また、日付から表示するやり方は以下のようになりました。こちらの方がわかりやすいですかね。

SELECT FORMAT_DATE('%Y/%m/%d', DATE_TRUNC(CURRENT_DATE("Asia/Tokyo"), MONTH));

月末

次は月末についてです。先程の月初で取得した値に対してDATE_ADDで1ヶ月追加して、さらにDATE_ADDで1日引いた値となります。

こちらも先程の内容と同様にTIMESTAMPを利用したやり方です。

SELECT FORMAT_DATE('%Y/%m/%d',  DATE_ADD(DATE_ADD(DATE(TIMESTAMP_TRUNC(CURRENT_TIMESTAMP(), MONTH), "Asia/Tokyo") , INTERVAL 1 MONTH), INTERVAL -1 DAY))

こちらは日付を利用したやり方です。

SELECT FORMAT_DATE('%Y/%m/%d',  DATE_ADD(DATE_ADD(DATE_TRUNC(CURRENT_DATE("Asia/Tokyo"), MONTH), INTERVAL 1 MONTH), INTERVAL -1 DAY));

まとめ

何となく遠回りをしているような気がするのですが、こんな感じで表示することができたので、あとはFORMAT_DATEで値を整形して具合良く利用できるのではないかと思いました。

pythonでAWS シークレットマネージャーの値の取得と更新を試す

概要

最近AWS シークレットマネージャーを使い始めたのですが、値を取得するサンプルコードはシークレットマネージャー上で見れるものの、更新はどうやるのかなと思ったのでサンプルを書いてみました。

サンプルコード

とりあえずこんな感じで動きました。<SECRET_NAME>となっている部分は任意で書き換えてもらえればと思います。エンドポイントとリージョンもですね。

import boto3
import json
from botocore.exceptions import ClientError


class SecretsManager(object):
    def __init__(self):
        endpoint_url = "https://secretsmanager.us-west-2.amazonaws.com"
        region_name = "us-west-2"
        session = boto3.session.Session()
        self.client = session.client(
            service_name='secretsmanager',
            region_name=region_name,
            endpoint_url=endpoint_url
        )

    def get_secret(self, secret_name):
        get_secret_value_response = self.client.get_secret_value(
            SecretId=secret_name
        )

        if 'SecretString' in get_secret_value_response:
            return get_secret_value_response['SecretString']

        return get_secret_value_response['SecretBinary']

    def update_secret(self, secret_name, update_json):
        try:
            return self.client.update_secret(
                SecretId=secret_name,
                SecretString=update_json
            )
        except ClientError as e:
            print('Error!!!')
            print(e)


def main():
    secret_name = "<SECRET_NAME>"

    # シークレット値を取得
    sm = SecretsManager()
    secret = sm.get_secret(secret_name)
    print(secret)

    # シークレット値を更新
    secret_dict = json.loads(secret)
    secret_dict['xxxxx'] = 'piyo'
    sm.update_secret(secret_name, json.dumps(secret_dict))


if __name__ == '__main__':
    main()

まとめ

更新する方法も分かってしまえば難しいことではなかったようでした。辞書型で任意の値を設定した後にjson.loadsで変更すると意図した値で更新することが出来ました。 あとは用途を考えて、値を更新することがあればしていこうと思います。