Flutter 初めてのアプリ作成 ストップウォッチ

今回はFlutterで簡単なストップウォッチを作成してみました。

環境

  • OS : Windous10
  • Flutter : v2.2.3

完成品

0秒からではなく、どの時間からでも開始できるストップウォッチにしてみました。
ストップウォッチを使っていて、途中から始めたい事がよくあったので、、、
機能は3つです。

1. 開始時間設定

f:id:wood__stock:20210812105432g:plain

2. ストップウォッチ開始、停止

f:id:wood__stock:20210812105456g:plain

3. リセット

f:id:wood__stock:20210812105533g:plain

Flutter習得

習得にはKBOYさんが運営しているFlutter大学の動画を見させていただきました。
環境構築から、簡単なYoutube風アプリ(側だけ)の作成まで、動画を見ながら2日で実施出来ました。
※2020年の動画で少し古いですが動作しました。

ストップウォッチ作成

1日でサクッと作成しました。
FlutterPicker というプラグインを使用すると、時間設定が楽に作成できます。
タイマー部分は、Timerで1秒の定期実行でカウントアップの関数を呼び出しています。
画面の更新は、SetStateではなくProviderパッケージを使い、notifyListeners()で変更を通知しています。

Widgetのツリー

f:id:wood__stock:20210812111532p:plain

ソースコード

/*
main.dart
メイン画面
*/

import 'package:flutter/material.dart';
import 'package:flutter_picker/Picker.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';

import 'main_model.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ストップウォッチ',
      home: ChangeNotifierProvider<MainModel>(
        create: (context) => MainModel(),
        child: Scaffold(
          appBar: AppBar(
            title: Text('ストップウォッチ'),
          ),
          body: Consumer<MainModel>(
            builder: (context, model, child) {
              return Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    TextButton( // タイマー時間表示 + Editボタン
                      onPressed: () {
                        if (!model.isEditTimeEnabled) return;
                        // 時間設定ウインド表示
                        Picker(
                          adapter: DateTimePickerAdapter(
                            type: PickerDateTimeType.kHMS,
                            value: model.timerTime,
                            customColumnType: [3, 4, 5],
                          ),
                          title: Text("Select Time"),
                          onConfirm: (Picker, List values) {
                            model.changeTimerTime(DateTime.utc(
                                0, 0, 0, values[0], values[1], values[2]));
                          },
                        ).showModal(context);
                      },
                      child: Text(DateFormat.Hms().format(model.timerTime),
                          style: TextStyle(
                            fontSize: 80,
                          )),
                    ),
                    Row( // START,STOP,RESETボタン
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Padding( // STARTボタン
                          padding: const EdgeInsets.all(8.0),
                          child: ElevatedButton.icon(
                              onPressed: !model.isStartEnabled
                                  ? null
                                  : () {
                                      model.startTimer();
                                    },
                              icon: Icon(Icons.play_arrow),
                              label: Text('START')),
                        ),
                        Padding( // STOPボタン
                          padding: const EdgeInsets.all(8.0),
                          child: ElevatedButton.icon(
                              onPressed: !model.isStopEnabled
                                  ? null
                                  : () {
                                      model.stopTimer();
                                    },
                              icon: Icon(Icons.stop),
                              label: Text('STOP')),
                        ),
                        Padding( // RESETボタン
                          padding: const EdgeInsets.all(8.0),
                          child: ElevatedButton.icon(
                              onPressed: !model.isResetEnabled
                                  ? null
                                  : () {
                                      model.resetTimerTime();
                                    },
                              icon: Icon(Icons.clear),
                              label: Text('RESET')),
                        )
                      ],
                    ),
                  ],
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}
/*
main_model.dart
メイン画面のデータモデル
*/

import 'dart:async';

import 'package:flutter/material.dart';

class MainModel extends ChangeNotifier {
  Timer? timer;
  DateTime timerTime = DateTime(0);
  bool isStartEnabled = true;
  bool isStopEnabled = false;
  bool isResetEnabled = true;
  bool isEditTimeEnabled = true;

  // 開始時間変更
  void changeTimerTime(DateTime time) {
    timerTime = time;
    notifyListeners();
  }

  // 開始時間リセット
  void resetTimerTime() {
    timerTime = new DateTime(0);
    notifyListeners();
  }

  // ストップウォッチ開始
  void startTimer() {
    if (timer == null) {
      timer = Timer.periodic(Duration(seconds: 1), (timer) {
        countUpTime(1);
      });
    }

    isStartEnabled = false;
    isStopEnabled = true;
    isResetEnabled = false;
    isEditTimeEnabled = false;
    notifyListeners();
  }

  // ストップウォッチ停止
  void stopTimer() {
    timer!.cancel();
    timer = null;
    notifyListeners();

    isStartEnabled = true;
    isStopEnabled = false;
    isResetEnabled = true;
    isEditTimeEnabled = true;
    notifyListeners();
  }

  // 1s カウントアップ
  void countUpTime(int second) {
    timerTime = timerTime.add(Duration(seconds: second));
    notifyListeners();
  }
}

参考

React公式チュートリアル 三目並べ 応用課題

Reactの公式チュートリアルにある三目並べゲーム (tic-tac-toe)を作ってみました。 ゲーム自体は記事の通りに進めれば完成できるので、最後の応用にあった6つの課題を追加実装しています。

React公式チュートリアル

応用課題

  1. 履歴内のそれぞれの着手の位置を (col, row) というフォーマットで表示する。
  2. 着手履歴のリスト中で現在選択されているアイテムを太字にする。
  3. Board でマス目を並べる部分を、ハードコーディングではなく 2 つのループを使用するように書き換える。
  4. 着手履歴のリストを昇順・降順いずれでも並べかえられるよう、トグルボタンを追加する。
  5. どちらかが勝利した際に、勝利につながった 3 つのマス目をハイライトする。
  6. どちらも勝利しなかった場合、結果が引き分けになったというメッセージを表示する。

出来たもの

全ての応用課題を実装したものになります。 f:id:wood__stock:20210810131029g:plain

ソースコードGitHubに上げました。 React自体初めてなので、正しい実装なのか不安ですが参考になれば幸いです。

GitHub - WoodStockn/react-tutorial

pythonでLoggingを使ってみる

実行環境

  • OS : Windows10
  • Python : v3.9.6

Loggingについて

Pythonを入れると標準で入ってくるモジュールです。
logging --- Python 用ロギング機能 — Python 3.9.4 ドキュメント

import logging

セットアップ

loggingインスタンスの生成

直接インスタンス化するのではなく、getLogger(name)を介してインスタンス化する事が推奨されています。

logger = logging.getLogger("hogehoge")
logger = logging.getLogger(__name__)

インスタンスの親子

インスタンスから子を作成。

# 親インスタンス
parentLogger = logging.getLogger(__name__)
# 子インスタンス
childLogger = logging.getLogger(__name__).getChild("hogehoge")

ログレベルの設定

ログを出力する閾値を設定する。
閾値を設定することで、それ以上深刻なログのみ出力される。
ロガーインスタンス初期値 : NOTSET
ルートロガー初期値 : WARNING

レベル 数値
CRITICAL 50
ERROR 40
WARNING 30
INFO 20
DEBUG 10
NOTSET 0
# レベル定数
CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0

# Debugレベルに設定
logger.setLevel(logging.DEBUG)
logger.setLevel(10)

# ルートロガー初期値確認
logger = logging.getLogger()
logger.getEffectiveLevel()  # ->  30

ログ出力

レベルごとにメソッドを使い分けて出力する。

# 各レベルで出力
logger.debug("debug")
logger.info("info")
logger.warnig("warning")
logger.error("error")
logger.critical("critical")

# 変数を出力文字内に入れる
name = "hogehoge"
logger.debug("Name:%s", name)  # ->  Name:hogehoge 

NOTSETについて

初期値のNOTSETレベルは、

ロガーがルートロガーであれば処理される、そうでなくてロガーが非ルートロガーの場合には親ロガーに委譲させる という設定で、NOTSET以外の設定を見つけるまで祖先をたどる。
ルートロガーの初期はWARNINGで設定されているため、レベル設定を行わない場合、WARNING以上しか出力されない。 引用元: Logging HOWTO — Python 3.9.4 ドキュメント

# warning以上しか出力されない
logger = logging.getLogger(__name__)
logger.info("info")  -> 
logger.warning("warning")  # -> warning

# ルートロガーのレベル設定
logging.basicConfig(level=logging.DEBUG)

basicConfigでは、ほかにフォーマットや、ハンドラなどで基本的なロギングの設定を行える

Handler

ログの送り先をハンドラとして設定する

StreamHandler

標準出力(stdout)、標準エラー出力(stderror)へ送信する

# ロガーインスタンス作成
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# Handler作成
sh = logging.StreamHandler()
sh.setLevel(logging.INFO)
# loggerにHandler追加
logger.addHandler(ch)

logger.info("hogehoge") # -> hogehoge

FileHandler

ログ出力をファイルに送信する

# ロガーインスタンス作成
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# # Handler作成 出力ファイル名_LOGFILE.log
fh = logging.FileHandler("./LOGFILE.log")
fh.setLevel(logging.INFO)
logger.addHandler(fh)

logger.info("hogehoge")

# ==LOGFILE.log==
hogehoge

Formatter

表示するメッセージの書式を設定する
フォーマット文字列内に特別な文字列を配置することで、日付やログレベルなどをメッセージに含められる。 フォーマッタの初期値は%(message)sのみで、メッセージだけが出力される

出力される内容
%(asctime)s 日時
%(name)s ロガーインスタンス
%(levelname)s ログレベル
%(message)s メッセージ
# ロガーインスタンス作成
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# Handler作成
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
# Formatter作成・追加
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
# loggerにHandler追加
logger.addHandler(ch)

logger.info("hogehoge") # -> 2021-07-18 00:00:00,000 - __main__ - INFO - hogehoge
logger.warning("hogehoge") # -> 2021-07-18 00:00:00,000 - __main__ - WARNING - hogehoge
logger.error("hogehoge") # -> 2021-07-18 00:00:00,000 - __main__ - ERROR - hogehoge
logger.critical("hogehoge") # -> 2021-07-18 00:00:00,000 - __main__ - CRITICAL - hogehoge

設定ファイルよりセットアップ

いままでの設定はコード内で行っていたが、
設定ファイル(.conf)を事前に定義して読み込みを行う事もできる。

# ==logging.conf==

# logger keys "root" 
[loggers]
keys=root 
# handler keys "streamHandler","fileHandler" 
[handlers]
keys=streamHandler,fileHandler
# formatter keys "formatter"
[formatters]
keys=formatter
# "root" logger setup
[logger_root]
level=DEBUG
handlers=streamHandler,fileHandler
# "streamHandler" handler setup
[handler_streamHandler]
class=StreamHandler
level=DEBUG
formatter=formatter
# "fileHandler" handler setup
[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=formatter
args=('LOGFILE.log', 'w')
# "formatter" formatter setup
[formatter_formatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
# conf読み込み
logging.config.fileConfig("./logging.conf")
# loggerインスタンス生成
logger = logging.getLogger(__name__)

辞書型で定義してセットアップ

コード内で辞書型で定義することもできる。
設定内容は設定ファイルでの設定と同じ。

# 辞書型で設定内容を作成
logging_conf = {
    'version': 1,
    'formatters': {
        'formatter': {
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        },
    },
    'handlers': {
        'streamHandler': {
            'level':'DEBUG',
            'class':'logging.StreamHandler',
            'formatter': 'formatter'
        },
        'fileHandler':{
            'level':'DEBUG',
            'class':'logging.FileHandler',
            'formatter': 'formatter',
            'filename': 'LOGFILE.log',
            'mode': 'w'
        },
    },
    'loggers': {
        'root': {
            'handlers': ['streamHandler', 'fileHandler'],
            'level': 'DEBUG',
        }
    }
}

# 辞書型で読み込み
logging.config.dictConfig(logging_conf)
# インスタンス作成
logger = logging.getLogger(__name__)

Pythonで flake8 (Linter,Formatter) を使ってみる

今回は、Linter Formatterについてそろそろ知っときたいなと思い調べたのでメモ
実際の動作に関係するものではないので、後回ししてました、、

f:id:wood__stock:20210711141412p:plain

Lintter, Formatter

Linter ってなに

lintとは、コンピュータプログラムなどのソースコードを読み込んで内容を分析し、問題点を指摘してくれる静的解析ツール。また、そのようなツールで解析を行うこと。ツールを指す場合は “linter” (リンター)と呼ぶこともある。
引用元 : IT用語辞典 e-Words

  • プログラムを実行する前に、問題が発生しそうな箇所を指摘してくれるもの。(変数を宣言してるのに使ってないなど)
  • 解析するプログラムを"リント"、解析ツールを"リンター"と呼ぶ。

Formatter ってなに

定めたルールに従ってコードをチェック・成形するツール。
動作に影響しないコードスタイルについてチームで統一することで、コードを読みやすくするもの。
よくあるルールとしては、空白の入れ方や改行位置などがあるそうです。

flake8

flake8ってなに

Flake8 is a wrapper around these tools: ・ PyFlakes
・ pycodestyle
・ Ned Batchelder’s McCabe script
引用元: flake8 · PyPI

  • PyFlakes : コード規約PEP8に準じ、コードのエラーチェックをするもの。 Lintter
  • pycodestyle : コード規約PEP8に準じ、コードスタイルをチェックするもの。 Formatter
  • Ned Batchelder’s McCabe script : 複雑度チェッカー

個別で存在するのLinter , Formatterをまとめて使えるものまとめて使用できるものだそうです。

インストール

pip install flake8

実行コマンド

$ flake8 [ファイル or ディレクトリ]
$ flake8 ./main.py (ファイル)
$ flake8 ./MyProject (ディレクトリ)

使用例

参考に簡単なコードにflake8を実行してみます。

# == main.py ==

def add(a,b):
    return a+b

if __name__ == "__main__":
    a = 1
    b = 2
    sum = add(a,b)
    print(sum)
# 実行結果

$ python main.py
3
flake8の実行
$ flake8 main.py

main.py:3:10: E231 missing whitespace after ','
main.py:6:1: E305 expected 2 blank lines after class or function definition, found 1
main.py:9:16: E231 missing whitespace after ','
main.py:10:15: W292 no newline at end of file

4つエラーが出ました。
E○○ W○○ とコードが出るのが、チェックしている項目ごとのコードになります。
ErrorCodes

今回出ているエラーとしては以下のようです。

  • E231 : [ , ; : ]の後に空白がありません
  • E305 : 関数またはクラスの終了後に2行の空白行が必要です
  • W292 : ファイルの終わりに改行がありません

特定のエラーのみチェックしたい・省略したい場合はオプションで設定できます。

# 特定のエラーのみチェック
$ flake8 --select E231,E305 main.py

# 特定のエラーを省く
$ flake8--ignore E231,E305 main.pu

構成ファイルの作成

実行時に毎回オプションを設定して、エラー項目を設定するのは大変なので、、、
flake8の構成ファイルを使って読み込めるようにしてみます。

構成ファイルの書き方

flake8コマンドのオプションで設定できる内容を並べていくしていくようです。
参考に、公式ドキュメントで使用されていたものです configuration

# == .flake8 ==

[flake8]
ignore = D203
exclude = .git,__pycache__,docs/source/conf.py,old,build,dist
max-complexity = 10

設定されている内容

  • ignore : チェックしないエラーコード
  • exclude : 除外するファイルまたはディレクト
  • max-complexity : McCabeでチェックする複雑度の閾値

自動生成でチェックの必要がないファイルなどは、事前に設定していてもよいかもしれません。

ユーザごとの設定

構成ファイルを保存しておけば自動的に読み込まれるようです。
OS保存先はOSごとに設定ファイルの保存場所ファイル名が違っています。

Windows : ~\.flake8
Linux : ~/.config/flake8

チーム内で共有

各々が作った構成ファイルを使用すると、コードが統一されず読みづらいです。 なので、事前に構成ファイルをgitプロジェクト内に含め共有し、それを使ってflake8を実行すると良いと思います。

# --config オプションを使って、構成ファイルを指定
$ flake8 --config flake8 main.go

最後に

導入が意外と簡単に出来るので、面倒ですがプロジェクトと開始時にやっておくとコードが統一されて良さそう。

SwaggerEditorでAIPを設計してみる

f:id:wood__stock:20210704213328p:plain

SwaggerEditorを使ってAPIの設計を行ったのでメモ

この記事で行っていること

  • SwaggerEditorを使ってAPI設計
  • SwaggerCodegenでPython-Flaskコードを生成
  • docker-composeで起動、SwaggerUIよりお試しリクエス

Swaggerとは

API開発をする際に使えるツールが詰まったもの。 以下のツールがオープンソースで誰でも使える

ツール
Swagger Codegen OpenAPIの定義からサーバ、クライアントのコードを生成する
Swagger Editor OpenAPIの仕様で、APIを設計するエディタ
Swagger UI 設計したAPIを表示するビュワー。実際にリクエストを送り、応答を確認できる。

OpenAPIとは

  • OpenAPI Initiative(OAI)というコミュニティが定めた、API仕様を記述する標準フォーマット
  • JSONYAMLの二つの形式で表すことが可能
  • ファイル名デフォルトはopenapi.jsonopenapi.yaml

用語の整理

  • OAI = OpenAPI Initiative というコミュニティ、団体
  • OAS = OpenAPI Specification OpenAPIの仕様
  • swagger2.0 = v2.0のOASフォーマット
  • OAS3.0 = v3.0のOASフォーマット

Swager Editorを使ってみる

書き方はこちらを参考に、 簡単なAPIを設計
OpenAPIで定義できる項目が多くて大変💦💦

今回設計するAPI
- BaseURL : http://localhsot:5000/api/v1 - Version : 0.0.1

エンドポイント一覧

メソッド パス
GET /users すべてのユーザ情報を取得
GET /users/{id} 指定idのユーザ情報を取得
POST /users 新規のユーザを登録

openapi.yaml

openapi: 3.0.0
info:
  title: "初めてのSwaggerEditor"
  description: "初めてSwaggerEditorを使って、API設計してみる"
  termsOfService: ""
  version: "0.0.1"
servers:
  - url: "http://localhost:8080/api/v1"
    description: "ユーザ情報を取得するAPIサーバ"
paths:
  /users:
    summary: "Get Users"
    description: "Usersに関する操作"
    get:
      summary: "Get Users"
      description: "全てのユーザを取得します"
      responses:
        200:
          description: "全てのユーザを返す"
          content:
            application/json:
              schema:
                type: string
              examples:
                users:
                  value: [{ "id":1, "name":"Jon", "age":19 },{ "id":2, "name":"Mike", "age":21 }]          
    post:
      summary: "Add User"
      description: "新しいユーザを追加します"
      operationId: "addUser"
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              properties:
                name:
                  description: "ユーザの名前"
                  type: string
                age:
                  description: "ユーザの年齢"
                  type: integer
              required:
                - "name"
                - "age"
      responses:
        201:
          description: "正常にユーザを追加しました"
        400:
          description: "不正なリクエストです"
  /users/{user_id}:
    parameters:
    - name: "user_id"
      in: path
      required: true
      description: "userId"
      schema:
        type: integer
    get:
      summary: "Get One User"
      description: "1人のユーザを取得します"
      responses:
        200:
          description: "1人のユーザを返す"
          content:
            application/json:
              schema:
                type: string
              examples:
                user:
                  value: {"id":1,"name":"Jon","age":19}                  

Swagger Codegenでコード生成

今回は、Python Flask でコードを生成してみる
タブからの[Generate Server]⇒[python-flask] を選択
f:id:wood__stock:20210704165623p:plain

生成されたファイル
選択していないが、Python3.6で作成された
コードはAPIサーバの基本部分のみなので、実際に使用する場合はcontroller.py などに追加する必要がある

swagger_server
│  .dockerignore
│  .gitignore
│  .swagger-codegen-ignore
│  .travis.yml
│  Dockerfile
│  git_push.sh
│  README.md
│  requirements.txt
│  setup.py
│  test-requirements.txt
│  tox.ini
│
├─.swagger-codegen
│      VERSION
│
└─swagger_server
    │  encoder.py
    │  type_util.py
    │  util.py
    │  __init__.py
    │  __main__.py
    │
    ├─controllers
    │      authorization_controller.py
    │      default_controller.py
    │      __init__.py
    │
    ├─models
    │      base_model_.py
    │      users_body.py
    │      __init__.py
    │
    ├─swagger
    │      swagger.yaml
    │
    └─test
            test_default_controller.py
            __init__.py

FlaskAPIServer 起動

Dockerfileも生成されたので、それで起動してみる

SwaggerUIよりリクエストを送信してみたら、CORSエラーが返ってきた
CORSについてか解決できなかったので、docker-composeでSwaggerEditorFlaskAPIServerを同時に起動。Nginxをプロキシサーバとして動かすことで、ドメインを統一してエラー回避

## ファイル構成

{Project}
|- docker-compose.yaml
|- nginx.conf
└─ swagger_server # SwaggerEditor より生成したコード
## docker-compose.yaml

version: "3.8"
services: 
    nginx:
        image: nginx
        ports: 
            - 8080:8080
        volumes:
           - ./nginx.conf:/etc/nginx/conf.d/nginx.conf
    swagger_editor:
        image: swaggerapi/swagger-editor
    flask_api_server:
        build:
            context: ./python-flask-server-generated
            dockerfile: Dockerfile
## nginx.conf

server {
    listen  8080;

    # SwaggerEfitor
    location / {
        proxy_pass http://swagger_editor:8080; 
    }

    # Flask API Server
    location ^~ /api/v1 {
        proxy_pass http://flask_api_server:8080/api/v1; 
    }
}

SwaggerUIでリクエストお試し

起動したAPIサーバに対して、SwaggerUIからリクエストを送ってみると、do some magic!とレスポンスが返ってくることを確認 f:id:wood__stock:20210704210356p:plain

SwaggerEditorを使ってみて

  • API設計とドキュメント作成(Swagger UI)が同時に出来て楽
  • SwaggerEditorでは、SwaggerUIがホットリロードしてくれるので書き易い
  • OASで定義できる項目が多いので、前提としてAPI設計について知っておく必要がある
  • 生成されたコードから編集するのみでAPIサーバを構築できるので楽

今回は以上です。

GitLab Privateリポジトリより go get してみる

自分が作成したGitLabリポジトリより go get する方法をメモ

実行環境

GitLabリポジトリ作成

  • https://gitlab.com/へログインし、プロジェクトを作成
  • Visiblity Level を [Private]に設定
  • 今回は、ProjectName を "go-package"とする gitlab プロジェクト作成

今回go get する自作パッケージを配置

  • リポジトリよりクローン
  • go mod init XXXX でgo.mod を生成
  • mathディレクトリ内に、自作"math"パッケージを作成
    • mathパッケージは平均を返す関数"Average"のみ実装
$ git clone https://gitlab.com/xxxxxx/go-package.git
$ cd go-package.git
$ go mod init gitlab.com/xxxxx/go-package # 自分のリポジトリに合わせて編集する
# /go-package/go.mod

module gitlab.com/xxxxx/go-package

go 1.15
# /go-package/math/math.go

package math

// Average 平均値を返す
func Average(s []int) int {
    total := 0
    for _, v := range s {
        total += v
    }
    return int(total / len(s))
}
# ディレクトリ構成
go-package
│  go.mod
└─math
  └─math.go

AccessToken作成

go get でインストール

確認のため適当なプロジェクト作成

$ mkdir goproject
$ cd goproject
$ go mod init main

AccessToken登録

  • ~/.netrcを作成
    • GITLAB_USERNAME: GitLabにログインしているユーザ名
    • ACCESS_TOKEN: 先ほど作成したAccessToken
# ~/.netrc

machine gitlab.com
  login GITLAB_USERNAME@gitlab.com
  password ACCESS_TOKEN

環境変数 設定

$ set GOPRIATE=gitlab.com/xxxxxx/go-package

いざ、go get

  • イントールされたファイルは、$GOPATH\pkg\mod\gitlab.com\xxxxxx\go-package@v0.0.0 に配置される
$ go get gitlab.com/xxxxx/go-package
# /goproject/go.mod

module main

go 1.15

require gitlab.com/xxxxxx/go-package v0.0.0 // indirect

プロジェクトへインポート

  • 適当なgoファイルmain.goを作成し、自作パッケージをインポート
  • math/Average関数を実行し、平均値計算結果が出力される
# main.go

package main

import (
    "fmt"

        // インポート
    "gitlab.com/xxxxxx/go-package/math"
)

func main() {
    fmt.Println(math.Average([]int{1, 2, 3}))
}
# 結果
2

GOPRIVATEを設定しなかった場合

  • 503 Service Unavailable エラーが返ってくる
go get gitlab.com/xxxxxx/go-package: unrecognized import path "gitlab.com/xxxxxx": reading https://gitlab.com/xxxxxx?go-get=1: 503 Service Unavailable

pipenv + Flask 簡単なwebアプリケーション作成

f:id:wood__stock:20210629220219p:plain
最近初めてpipenvを使い仮想環境とパッケージ管理を行えることを知ったので、メモ。

pipenvとは

仮想環境作成(venv)とパッケージ管理(pip)が合わさったもの

実行環境

準備

pipenvインストール

pipコマンドでインストール

pip install --upgrade pip # pipアップグレード(必要に応じて)
pip install pipenv

プロジェクトファイル作成

mkdir project
cd project

仮想環境作成

使用するpythonバージョンに応じて選択(選択したバージョンのpythonがインストールされていること)
実行すると、~/.virtualenvs/project-XXXXXXに仮想環境が作成され、プロジェクト内にPipfileが作成される

# 仮想環境作成
pipenv --three # python3で作成
pipenv --two # python2で作成
pipenv --python 3.8 # バージョンを指定して作成
# project\Pipfile
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]

[dev-packages]

[requires]
python_version = "3.9"

作成する場所を変更する場合

たとえば以下のように変更したい場合、それぞれ環境変数を設定

仮想環境を作成するディレクトリを変えたい

# .\venv\project-XXXXXX に作成
# WORKON_HOME
set WORKON_HOME=%USERPROFILE%\venv 

プロジェクト内に作成したい

# project\.venv\project-XXXXXX に作成
# PIPENV_VENV_IN_PROJECT
set PIPENV_VENV_IN_PROJECT=true

Flaskプロジェクト作成

インストール

仮想環境にFlaskをインストールすると、Pipfile.lockが作成されインストールしたときのバージョンや依存関係のパッケージが記載される。Pipfile.lockは他のPCで同じ環境を再現する時などに使用。

# Flaskインストール
pipenv install flask
# Pipfile
[packages]
flask = "*"

アプリ作成

簡単に"HelloWorld"を返すアプリを作成

# project\app.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "HelloWorld!"

仮想環境で実行

仮想環境に入り、アプリを実行

# 仮想環境へ入る
pipenv shell

# Flask起動
(.venv)flask run
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

# ブラウザで'http://localhost:5000'にアクセス
# 'HelloWorld'

# 仮想環境を抜ける
(.venv)exit