Python サブディクトリ下をインポートする書き方

今更の基本的なことだけど、、
サブディクトリ下に配置したスクリプトをインポートする方法
./
+-- main.py
|
+-- util
   +--- sub.py
    +--- stdin.py
このように、main.py が置いてあるディレクトリに util というディレクトリがあって
その下に、メソッドしか持たない Python スクリプト、Class 構成のスクリプト
あったとする。
sub.py

def func():
    print('sub--func()')

util の下の sub.py をインポートしてメソッド実行

from util import sub
sub.func()

stdin.py

# -*- coding: UTF-8 -*-

class StdIn:
    def read(self):
        inlist = []
        try:
            while True:
                inp = input('')
                if inp == '': break
                inlist.append(inp)
        except EOFError:
            pass
        return inlist

util の下の stdin.py をインポートして
クラスを利用

from util.stdinput import StdIn
stdin = StdIn()
list = stdin.read()
print(list)

Python ログ出力 logging iniファイルを使用しない

Python でログ出力を行いログ管理することが本当に必要なのか甚だ疑問ではあるが、
開発でデバッグで使いたい場合もあるだろう。。
以前、ログ出力管理設定の ini ファイル形式を使用するサンプルを書いた。
→ Python ロギング日付ローテーションのスニペット - Oboe吹きプログラマの黙示録
設定ファイルを置くことがどうも、ナンセンスのような気がして、
iniファイルを使用しない方法で、
改めて、以下の logger.py を用意する。
注意:設定ファイルを用意しない代わりに、ログ出力先ファイル名は、
このログクラスインスタンス内で決定する。

logger.py

# -*- coding: UTF-8 -*-
from logging import Formatter, handlers, StreamHandler, getLogger, DEBUG, WARN
import inspect

class Logger:
    def __init__(self, name=__name__):
        if name=="logger":
            name = inspect.getmodule(inspect.stack()[1][0]).__name__
        self.logger = getLogger(name)
        self.logger.setLevel(DEBUG)
        formatter = Formatter(fmt="%(asctime)s.%(msecs)03d %(levelname)7s %(message)s [%(name)s  %(processName)s - %(threadName)s]", datefmt="%Y/%m/%d %H:%M:%S")
        ### WARN レベルまで標準出力する ###
        sthandler = StreamHandler()
        sthandler.setLevel(WARN)
        sthandler.setFormatter(formatter)
        self.logger.addHandler(sthandler)

        ### 時刻ローテーション ###
        handler = handlers.TimedRotatingFileHandler(filename='/var/log/test.log',
                                                    encoding='UTF-8',
                                                    when='D',
                                                    backupCount=7 )
        ### サイズローテーション ###
        ''' 
        handler = handlers.RotatingFileHandler(filename='/var/log/test.log',
                                               encoding='UTF-8',
                                               maxBytes=1048576,
                                               backupCount=3)
        '''
        ############################
        handler.setLevel(DEBUG)
        handler.setFormatter(formatter)
        self.logger.addHandler(handler)

    def debug(self, msg):
        self.logger.debug(msg)

    def info(self, msg):
        self.logger.info(msg)

    def warn(self, msg):
        self.logger.warning(msg)

    def error(self, msg):
        self.logger.error(msg)

    def critical(self, msg):
        self.logger.critical(msg)

ログの書式は、Formatter で指定している。
テスト実行は、よくコンソールで実行することが多いので
あえて、エラーや警告出力を、標準出力するようにハンドラ sthandler を
セットしている。
本番稼働時などは、この sthandler 設定など不要であろう。
また、ログ出力元の名称としてインスタンス生成で渡す name が省略された時、
 多くは、 Logger()  とそのまま書いてしまうコードが多いであろうとして、
inspect モジュールスタックトレースから
呼出し元を探してモジュール名を、ログ出力するようにしている、

使い方

from logger import Logger

logger = Logger("aaa")
logger.debug("■ %s" % "test_あ")

Logger インスタンス引数を省略すれば、

logger = Logger()

Formatter で指定した

[%(name)s  %(processName)s - %(threadName)s]

の %(name)s は、Logger インスタンス生成元のモジュール名、__main__ などになる。

設定ファイル ini で管理するにしても、設定ファイルを用意しない方法にしても、
どちらも開発製造テストから、本番稼働では、手入れが必要な筈で
どちらが良いか悩ましい。

以下で、書き直した!!!
Python ログ、標準の logging - Oboe吹きプログラマの黙示録

Oracle 結合演算子(+) は奨励しない書き方だったと思いだす。。

久しぶりに Oracle 使用のプロジェクトで、
SQLで、WHERE句で結合演算子(+) を書いてるのを見かけた。。。

あれ!、たしか Oracle 12c で奨励されなくなったのでは?!。。。

https://docs.oracle.com/database/121/SQLRF/queries006.htm#SQLRF30046

引用すると以下のように書いてあるし。。
Oracle recommends that you use the FROM clause OUTER JOIN syntax rather than the Oracle join operator.
Outer join queries that use the Oracle join operator (+) are subject to the following rules and restrictions,
which do not apply to the FROM clause OUTER JOIN

FROM 句で
LEFT OUTER JOIN ~ ON ~
あるいは、
LEFT JOIN ~ ON ~

の方が、読みやすいし、
複数 (+)= を書いて想定外の結果を得るよりも
こちらの JOINを記述する方が良いと思う。

Bootstrap Datepicker カレンダーに、日本の祝日をマークする

元々、10年程前に Java で日本の祝日を求めるものを作って公開したのだが、
数年前に JavaScript 版も作っていた。jQuery - UIdatepicker で祝日をマークする為だった。

JavaScript 版も、バージョン 1.4 では、JHoliday.descriptionDate(date) にバグがあったので、
1.5 で修正し公開した。

Java祝日計算 プロジェクト日本語トップページ - OSDN


これを機に、Bootstrap の datepicker も対応してみる。
1.5 として配布した jholiday.js はそのままで変更はない。
以下のように、日本の祝日の日付文字色を赤くして、ツールチップで祝日説明をする。
f:id:posturan:20190611223719j:plain

JavaScript 版 1.5 をダウンロードして、他のJS、CSSCDNサイトで書くと、、

<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"  rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.css"  rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://momentjs.com/downloads/moment-with-locales.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/locales/bootstrap-datepicker.ja.min.js"></script>
<script src="jholiday.js"></script>

Bootstrap datepicker にする input タグ

<div class="col-md-2">
   <input type="text" class="form-control" id="sample">
</div>

CSS

.datepicker-days th.dow:first-child, .datepicker-days td:first-child{
    color: #ff0000;
}
.datepicker-days th.dow:last-child, .datepicker-days td:last-child{
    color: #0000ff;
}
.datepicker-days thead{
   border-bottom: 1px solid #cccccc;
}
.datepicker-days .holiday{
    color: #ff0000;
}

Bootstrap datepicker の指定

$('#sample').datepicker({
    format: 'yyyy/mm/dd',
    language:'ja',
    todayHighlight: true,
    enableOnReadonly: true,
    templates: {
       leftArrow: '<',
       rightArrow: '>'
    },
    beforeShowDay: function(date){
        if (JHoliday.isHolidayDate(date)==1){
           return {
             classes: 'holiday', tooltip:JHoliday.descriptionDate(date)
          };
        }
    },
});

beforeShowDay オプションでカレンダー日付表示前の
カレンダー表示日すべてが、Date型オブジェクトで関数を実行で戻り値に、
日付表示の class属性などを指定できる。
詳細は、↓
https://bootstrap-datepicker.readthedocs.io/en/latest/options.html#beforeshowday
これを利用して、JavaScript 版祝日計算で、祝日判定と descriptionDate で
祝日名称を取得して、ツールチップ表示させるようにする。

後日、このHTMLサンプルを JavaScript 版 1.51 として配布しようと思う。
jholiday.js は、1.5 のまま変更なしで、サンプル配布として追加するだけなので、1.51 である。

フォルダ指定のアップロード

HTML5 input の webkitdirectory 付与は、Google chrome と Fire Fox しか今のところ使えないが、、
Wicket でこれを受信するのは、FileUploadField コンポーネントの getFileUploads() で
List<FileUpload> を受け取ることになる。

<input wicket:id="uploadfile" id="uploadfile" type="file" webkitdirectory>

Wikcet

final FileUploadField fileuploadField = new FileUploadField("uploadfile");
queue(fileuploadField);
queue(new Button("submit").add(AjaxFormSubmitBehavior.onSubmit("click", t->{
   fileuploadField.getFileUploads().stream().forEach(f->{
      // f.getClientFileName());
      // f.getSize()  byte
      // Bytes.bytes(f.getSize()).toString()  KByte
      // f.getInputStream()
   });
})));

フォルダを選択した時の change イベントで JavaScript 上では以下のように
情報を読み取る。

$(function(){
   $("#uploadfile").change(function(ev) {
      for(var i=0; i < ev.target.files.length; i++){
         var file = ev.target.files[i];
         console.log(file);
         // ディレクトリの相対パス
         var path = file.webkitRelativePath;
         console.log([" + path + "]");
      }
   });
});

twitter/Typeahead.js を Wicket の AJAX として Autocomplete の振るまいにする

WicketAutocomplete といえば、jQuery UI を利用したものが慣れ親しんでものであった。
最近よく使われる Bootstrap デザインを適用しても使えないわけではない、
CSSスタイルシートを合わせて書いていけば良いのだが、
Bootstrap と jQuery UI を併用が嫌で jQuery UI をやめたくなる。
datepicker も Bootstrap向けが存在するわけだし、
Draggable / Drop これさえ Bootstrap でできれば、
jQuery UI を完全にやめれるのではないか?

前置きはこのくらいで、本題!
typeahead.js/jquery_typeahead.md at master · twitter/typeahead.js · GitHub

twitter/typeahead.js が Bootstrap デザインと相性が良いようだ。

まず、twitter/Typeahead.js のリモートからマッチ候補を受信する書き方を把握しておく。。
JSソースとしては、
<input type="text" id="typeahead"> に対して

$('input#typeahead').typeahead({
   highlight: true,
   minLength: 1
}, {
   //name : 'states',
   displayKey: 'display',
   limit: 7,
   source : new Bloodhound({
      datumTokenizer: function(datum){
         return Bloodhound.tokenizers.whitespace(datum.display);
      },
      queryTokenizer: Bloodhound.tokenizers.whitespace,
      remote:{
         wildcard: '%QUERY',
         url: 'http://xxxxxxxxxxxxx?query=%QUERY',
         transform: function(response){
            return $.map(response, function(item){
               return { display:item.value, id:item.id };
            });
         },
      }
   })
}).on('typeahead:select', function(ev, item){
     console.log('選択された id = ' + item.id);
     console.log('選択された 表示文字列 = ' + item.display);
}).on('change', function(ev){
     console.log($(this).val());
});

このようなJSソースをリソースとして用意するのではなく、
Wicket TextField の振るまい Behavior として
AJAX通信→ マッチ候補リスト表示させるようにするのです。

CSSは、以下に適当なものがあったので、
https://github.com/bassjobsen/typeahead.js-bootstrap-css
これを参考に以下のように用意します。

span.twitter-typeahead .tt-menu,
span.twitter-typeahead .tt-dropdown-menu {
  cursor: pointer;
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 1000;
  display: none;
  float: left;
  min-width: 100%;

  padding: 5px 0;
  margin: 2px 0 0;
  list-style: none;
  font-size: 14px;
  text-align: left;
  background-color: #ffffff;
  border: 1px solid #cccccc;
  border: 1px solid rgba(0, 0, 0, 0.15);
  border-radius: 4px;
  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
  background-clip: padding-box;
}
span.twitter-typeahead .tt-suggestion {
  display: block;
  padding: 3px 20px;
  clear: both;
  font-weight: normal;
  line-height: 1.42857143;
  color: #333333;
  white-space: nowrap;
}
span.twitter-typeahead .tt-suggestion.tt-cursor,
span.twitter-typeahead .tt-suggestion:hover,
span.twitter-typeahead .tt-suggestion:focus {
  color: #ffffff;
  text-decoration: none;
  outline: 0;
  background-color: #337ab7;
}
.input-group.input-group-lg span.twitter-typeahead .form-control {
  height: 46px;
  padding: 10px 16px;
  font-size: 18px;
  line-height: 1.3333333;
  border-radius: 6px;
}
.input-group.input-group-sm span.twitter-typeahead .form-control {
  height: 30px;
  padding: 5px 10px;
  font-size: 12px;
  line-height: 1.5;
  border-radius: 3px;
}
span.twitter-typeahead {
  width: 100%;
}
.input-group span.twitter-typeahead {
  display: block !important;
  height: 34px;
}
.input-group span.twitter-typeahead .tt-menu,
.input-group span.twitter-typeahead .tt-dropdown-menu {
  top: 32px !important;
}
.input-group span.twitter-typeahead:not(:first-child):not(:last-child) .form-control {
  border-radius: 0;
}
.input-group span.twitter-typeahead:first-child .form-control {
  border-top-left-radius: 4px;
  border-bottom-left-radius: 4px;
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.input-group span.twitter-typeahead:last-child .form-control {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  border-top-right-radius: 4px;
  border-bottom-right-radius: 4px;
}
.input-group.input-group-sm span.twitter-typeahead {
  height: 30px;
}
.input-group.input-group-sm span.twitter-typeahead .tt-menu,
.input-group.input-group-sm span.twitter-typeahead .tt-dropdown-menu {
  top: 30px !important;
}
.input-group.input-group-lg span.twitter-typeahead {
  height: 46px;
}
.input-group.input-group-lg span.twitter-typeahead .tt-menu,
.input-group.input-group-lg span.twitter-typeahead .tt-dropdown-menu {
  top: 46px !important;
}

Wicket の TextField を継承
キー入力イベント後のAJAX通信で返す JSON は、Goole gson で生成しています。

import java.util.List;
import java.util.Optional;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.IRequestParameters;
import org.apache.wicket.request.handler.TextRequestHandler;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
 * Twitter Boostrap Typeahead TextField.
 */
public abstract class AjaxTypeahead<T extends Typeahead> extends TextField<String>{
   private AbstractDefaultAjaxBehavior queryAjaxBehavior;
   private List<Typeahead> choicelist;

   public AjaxTypeahead(String id, IModel<String> model){
      super(id, model);
      Gson gson = new GsonBuilder().serializeNulls().create();
      queryAjaxBehavior = new AbstractDefaultAjaxBehavior(){
         @Override
         protected void respond(AjaxRequestTarget target){
            IRequestParameters p = getRequestCycle().getRequest().getQueryParameters();
            Optional.ofNullable(p.getParameterValue("query").toString()).ifPresent(s->{
               choicelist = getChoices(s.trim());
               getComponent().getRequestCycle()
.replaceAllRequestHandlers(new TextRequestHandler("application/json", "UTF-8", gson.toJson(choicelist)));
            });
            Optional.ofNullable(p.getParameterValue("selected").toString()).ifPresent(s->{
               String id = p.getParameterValue("id").toString();
               for(Typeahead t:choicelist){
                  if (t.id.equals(id)){
                     onSelect(target, t);
                     break;
                  }
               }
            });
            Optional.ofNullable(p.getParameterValue("change").toString()).ifPresent(s->{
               String display = Optional.ofNullable(p.getParameterValue("display").toString())
                           .orElse("").trim();
               onChange(target, display);
            });
            getRequestCycle().getResponse().close();
         }
         @Override
         protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
            super.updateAjaxAttributes(attributes);
            attributes.setDataType("json");
            attributes.setWicketAjaxResponse(false);
         }
      };
      add(queryAjaxBehavior);
   }

   @Override
   protected void onAfterRender(){
      super.onAfterRender();
      getResponse().write("<script type=\"text/javascript\">");
      getResponse().write(" $(\"#" + this.getMarkupId(true) + "\").typeahead({highlight:true,minLength:1},{");
      getResponse().write("displayKey: 'display',");
      getResponse().write("limit: 7,");
      getResponse().write("source : new Bloodhound({");
      getResponse().write("datumTokenizer: function(datum){");
      getResponse().write("return Bloodhound.tokenizers.whitespace(datum.display);");
      getResponse().write("},");
      getResponse().write("queryTokenizer: Bloodhound.tokenizers.whitespace,");
      getResponse().write("remote:{");
      getResponse().write("wildcard: '%QUERY',");
      getResponse().write("url: '" + queryAjaxBehavior.getCallbackUrl() + "&query=%QUERY',");
      getResponse().write("transform: function(r){");
      getResponse().write("return $.map(r, function(t){");
      getResponse().write("return { display:t.value, id:t.id };");
      getResponse().write("});}}})");
      getResponse().write("}).on('typeahead:select', function(ev, item){");
      getResponse().write("var url = '" + queryAjaxBehavior.getCallbackUrl() + "' + '&selected=&id=' + item.id + '&display=' + item.display;");
      getResponse().write("Wicket.Ajax.get({ u: url });");
      getResponse().write("}).on('change', function(ev){");
      getResponse().write("var url = '" + queryAjaxBehavior.getCallbackUrl() + "' + '&change=&display=' + $(this).val();");
      getResponse().write("Wicket.Ajax.get({ u: url });");
      getResponse().write("});");
      getResponse().write("</script>");
   }

   /** 入力文字→候補リスト  */
   protected abstract List<Typeahead> getChoices(String input);

   /**
    * 選択イベント捕捉.
    * @param target AjaxRequestTarget
    * @param id
    * @param dislay
    */
   protected void onSelect(AjaxRequestTarget target, Typeahead typeahead){
   }
   /** 変更イベント捕捉    */
   protected void onChange(AjaxRequestTarget target, String dislay){
   }
}

表示用の素材、Typeahead は、id と プルダウン表示及び入力文字列をSerializableである。
この Typeahead を継承するかそのまま使用する。

import java.io.Serializable;

/**
 * twitter/Typeahead.js element.
 */
public class Typeahead implements Serializable{
   public String id;
   public String value;

   public Typeahead(){}

   public void setValue(String value){
      this.value = value;
   }
   public void setId(String id){
      this.id = id;
   }
}

使用する例: Example

AjaxTypeahead<Typeahead> simpleField = new AjaxTypeahead<>("name", new Model<>()){
   @Override
   protected List<Typeahead> getChoices(String input){
      // 入力が空 Enter では、空リストを送る。
      if (Strings.isEmpty(input)) return Collections.emptyList();
      // TODO 入力 input にマッチするリストを返す。
      return list;
   }
   @Override
   protected void onSelect(AjaxRequestTarget target, String id, String dislay){
      // TODO 選択時の処理、選択した要素の id と 表示文字列 display を受信
   }
   @Override
   protected void onChange(AjaxRequestTarget target,  String dislay){
      // TODO change イベントの処理、変更後の表示文字列 display を受信
   }
};
queue(simpleField);

あくまでも、TextField の継承であり、
フォーム送信で受け取る Wicket の Model オブジェクトは、String である。

Bootstrap の datepicker

メモ、
以下からダウンロード
https://github.com/uxsolutions/bootstrap-datepicker

HTML head 読込み指定
曜日を日本語表示の為には、配布されてる中の bootstrap-datepicker.ja.min.js を使う。

<link href="dist/css/bootstrap-datepicker.css"  rel="stylesheet" />
<script src="dist/js/bootstrap-datepicker.js"></script>
<script src="dist/locales/bootstrap-datepicker.ja.min.js"></script>

CSS・・・自由に。

.datepicker-days th.dow:first-child,
.datepicker-days td:first-child {
   color: #f00;
}
.datepicker-days th.dow:last-child,
.datepicker-days td:last-child {
    color: #00f;
}
.datepicker-days thead{
   border-bottom: 1px solid #cccccc;
}
.datepicker-title{
   background-color: #ffbb11;
}

datepicker 指定

$('#date_sample').datepicker({
    format: 'yyyy/mm/dd',
    language:'ja',
    todayHighlight: true,
    templates: {
        leftArrow: '<',
        rightArrow: '>'
    },
}).on('change', function(){
     // TODO $(this).val()
});

f:id:posturan:20190601154918j:plain

タイトルを付ける Option

$('#date_sample').datepicker({
    format: 'yyyy/mm/dd',
    language:'ja',
    todayHighlight: true,
    title: '誕生日',
    templates: {
        leftArrow: '<',
        rightArrow: '>'
    },
}).on('change', function(){
     // TODO $(this).val()
});

f:id:posturan:20190601155247j:plain

他にもオプション、
曜日の始まり指定

weekStart: 1, // 0=Sunday

曜日によるハイライト表示

daysOfWeekHighlighted: [0],

などがある。