もうちょっと複雑なpythonでgoのコードを生成する例(わりとよい)
もうちょっと複雑なpythonでgoのコードを生成する例を紹介して見ることにした。具体的にはswagger specを見てgoのstructを生成する処理。 go-swaggerなどswagger specからgoのコードを生成するツールは既にあったりはするのだけれど。これと似たようなことを自分でもやってみる(正確にはswagger specに愚直に従ったコードを生成するとほとんどがpointerになってしまうという問題があり。それを避けたコードを生成したいという気持ちがあった)。
swagger2go
swagger2goというコマンドを作ってみた。
これは、例えば、以下の様なswagger specから
definitions: person: description: ヒト type: object properties: name: type: string age: type: integer
swagger2go src/00*.yaml --package=github.com/podhmo/person --position=./dst --file=person.go --ref "#/definitions/person" goimports -w dst/person/person.go
以下の様なgoのコードが生成される。
package person // Person : ヒト type Person struct { Name string `json:"name"` Age int64 `json:"age"` }
もう少し複雑な構造
もう少し複雑な構造としてarrayの定義とstructの参照をしてみる。例えば以下のようなyamlから。
definitions: people: type: array items: $ref: "#/definitions/person" person: description: ヒト type: object properties: name: type: string age: type: integer group: type: object properties: name: type: string members: $ref: "#/definitions/people"
(--ref
を外すと全てが出力される)
swagger2go src/01*.yaml --package=github.com/podhmo/person --position=./dst/01 --file=person.go
以下のようなgoのコードが生成される。
package person // People : type People []Person // Person : ヒト type Person struct { Name string `json:"name"` Age int64 `json:"age"` } // Group : type Group struct { Name string `json:"name"` Members People `json:"members"` }
出力されるfieldにpointerを含めてみる
pointerの扱いは明示的に行うようにした。ただ自身への参照はdefaultでpointer扱いになっている。おせっかいかも知れない。defaultは値でx-go-pointer
をつけるとpointer扱いになる(このあたりはswaggerへの完全な追随を止めている)。
definitions: info: type: object properties: description: type: string person: description: ヒト type: object properties: name: type: string age: type: integer father: $ref: "#/definitions/person" mother: $ref: "#/definitions/person" info: $ref: "#/definitions/info" info2: x-go-pointer: true $ref: "#/definitions/info"
こういう感じ(面倒になったので、もはや生成用のコマンドは併記しない)。
package person // Info : type Info struct { Description string `json:"description"` } // Person : ヒト type Person struct { Name string `json:"name"` Age int64 `json:"age"` Father *Person `json:"father"` Mother *Person `json:"mother"` Info Info `json:"info"` Info2 Info `json:"info2"` }
別の型を参照するように変える
既に定義されている別の型を参照したい場合があるかもしれない。例えば上の例のInfoが既に定義されているというような状態。これは x-go-type
を指定してあげれば良いことにした。
diff -u 02* 03* --- 02person.yaml 2017-05-10 05:43:42.000000000 +0900 +++ 03person.yaml 2017-05-10 05:49:02.000000000 +0900 @@ -1,5 +1,6 @@ definitions: info: + x-go-type: "github.com/podhmo/person.Info" type: object properties: description:
Infoは既に手動で定義されている場合など。
package person // Person : ヒト type Person struct { Name string `json:"name"` Age int64 `json:"age"` Father *Person `json:"father"` Mother *Person `json:"mother"` Info Info `json:"info"` Info2 Info `json:"info2"` }
ところで、yaml自身の定義を省略して書いちゃっても良い(swagger specとしてはinvalid)。
definitions: person: description: ヒト type: object properties: name: type: string age: type: integer father: $ref: "#/definitions/person" mother: $ref: "#/definitions/person" info: x-go-type: "github.com/podhmo/person.Info" type: object info2: x-go-type: "github.com/podhmo/person.Info" x-go-pointer: true type: object
異なるpackageのものを参照する場合
異なるpackageのものを参照するようにしても上手くやってくれる。このあたりの対応もほぼほぼ自動なのでgoawayを作ってよかったという感じ。例えば、time.Timeを追加してみたり。上の例のInfoを別のpackageにしてみる。
definitions: person: description: ヒト type: object properties: name: type: string age: type: integer birth: x-go-type: "time.Time" father: $ref: "#/definitions/person" mother: $ref: "#/definitions/person" info: x-go-type: "github.com/podhmo/message.Info" type: object info2: x-go-type: "github.com/podhmo/message.Info" x-go-pointer: true
BirthとInfoの型にはprefixがついている。やりましたね。import部分も自動で挿入されてはいるのだけれど。このあたりはフォーマットにgoimportsを使ってしまえばすむ話しではあるかもしれない。
package person import ( "time" "github.com/podhmo/message" ) // Person : ヒト type Person struct { Name string `json:"name"` Age int64 `json:"age"` Birth time.Time `json:"birth"` Father *Person `json:"father"` Mother *Person `json:"mother"` Info message.Info `json:"info"` Info2 message.Info `json:"info2"` }
ついでにenumも出力可能に
ついでにenumも出力可能に。genderというenumを追加。
definitions: gender: type: string enum: - notKnown - male - female - notApplicable people: type: array items: $ref: "#/definitions/person" person: type: object properties: name: type: string age: type: integer gender: $ref: "#/definitions/gender" father: $ref: "#/definitions/person" mother: $ref: "#/definitions/person"
はい。
package person import ( "fmt" ) // People : type People []Person // Gender : type Gender string const ( // GenderNotKnown : GenderNotKnown = Gender("notKnown") // GenderMale : GenderMale = Gender("male") // GenderFemale : GenderFemale = Gender("female") // GenderNotApplicable : GenderNotApplicable = Gender("notApplicable") ) // String : stringer implementation func (g Gender) String() string { switch g { case GenderNotKnown: return "notKnown" case GenderMale: return "male" case GenderFemale: return "female" case GenderNotApplicable: return "notApplicable" default: panic(fmt.Sprintf("unexpected Gender %s, in string()", string(g))) } } // ParseGender : parse func ParseGender(g string) Gender { switch g { case "notKnown": return GenderNotKnown case "male": return GenderMale case "female": return GenderFemale case "notApplicable": return GenderNotApplicable default: panic(fmt.Sprintf("unexpected Gender %v, in parse()", g)) } } // Person : type Person struct { Name string `json:"name"` Age int64 `json:"age"` Gender Gender `json:"gender"` Father *Person `json:"father"` Mother *Person `json:"mother"` }
enumの出力結果を変えてみる
ちょっとここまで気合のは言ったコードは不要かもしれない。enumの出力結果を変えてみる。 ここはちょっとトリッキーなのだけれど。利用するwriterのクラスを変えてあげることができる。以下の様なコードを書く。元のコードの一部を使わないようなクラスを使うように変える。
# writer.py from goaway.writer import Writer, EnumWriter class TinyEnumWriter(EnumWriter): def write(self, enum, file, m): self.write_definition(enum, file, m) # self.write_string_method(enum, file, m) # self.write_parse_method(enum, file, m) return m class MyWriter(Writer): enum_writer_factory = TinyEnumWriter
--writer
オプションで定義したクラスを渡すようにする。相対パスでも渡せる
swagger2go --writer=./writer.py:MyWriter src/07*.yaml --package=github.com/podhmo/person --position=./dst/07 --file=person.go
出力が短くなった。
package person // People : type People []Person // Gender : type Gender string const ( // GenderNotKnown : GenderNotKnown = Gender("notKnown") // GenderMale : GenderMale = Gender("male") // GenderFemale : GenderFemale = Gender("female") // GenderNotApplicable : GenderNotApplicable = Gender("notApplicable") ) // Person : type Person struct { Name string `json:"name"` Age int64 `json:"age"` Gender Gender `json:"gender"` Father *Person `json:"father"` Mother *Person `json:"mother"` }
一部のものだけ出力先のファイルを変えてみる
先のenumの例のように長ったらしいコードを生成する場合には出力先のファイルを変えたい事があるかもしれない。x-go-filename
を付加すると出力先のファイル名を変えられる(同一パッケージに展開はされる)。
--- src/07person.yaml 2017-05-10 06:04:51.000000000 +0900 +++ src/08person.yaml 2017-05-10 06:08:52.000000000 +0900 @@ -1,5 +1,6 @@ definitions: gender: + x-go-filename: "gender.go" type: string enum: - notKnown
person.goとgender.goが生成される。
swagger2go --writer=./writer.py:MyWriter src/08*.yaml --package=github.com/podhmo/person --position=./dst/08 --file=person.go INFO:goaway.emitter:write: ./dst/08/person/person.go INFO:goaway.emitter:write: ./dst/08/person/gender.go
こういう感じに分かれる。
person.go
package person // People : type People []Person // Person : type Person struct { Name string `json:"name"` Age int64 `json:"age"` Gender Gender `json:"gender"` Father *Person `json:"father"` Mother *Person `json:"mother"` }
gender.go
package person // Gender : type Gender string const ( // GenderNotKnown : GenderNotKnown = Gender("notKnown") // GenderMale : GenderMale = Gender("male") // GenderFemale : GenderFemale = Gender("female") // GenderNotApplicable : GenderNotApplicable = Gender("notApplicable") )
タグの形を変えたい
bsonタグも追加したくなったりする。
writerのときと同様の理屈でwalkerを変える。
walker.py
from goaway.commands.swagger2go import Walker class MyWalker(Walker): def resolve_tag(self, name): # return ' `json:"{name}"`'.format(name=name) return ' `json:"{name}" bson:"{name}"`'.format(name=name)
--walker=./walker.py:MyWalker
を一緒につけるとbsonタグも蒸される。
package person // People : type People []Person // Person : type Person struct { Name string `json:"name" bson:"name"` Age int64 `json:"age" bson:"age"` Gender Gender `json:"gender" bson:"gender"` Father *Person `json:"father" bson:"father"` Mother *Person `json:"mother" bson:"mother"` }
まとめ
swagger spec(もどき)からgoのコードを生成。なかなか柔軟に色々出来る感じになっている。
- 異なるpackageの型を参照できる
- 出力する型をpointerにするか決められる
- 出力先のファイルを分割できる
- 出力形式の変更が出来る(
--writer
) - 出力形式の変更が出来る(
--walker
)
出力先のファイルの分割に限っていうと、x-go-filename
を自動で付加するtransformerを書いたり、あるいはschema名からファイルを見るみたいなように書き換えたwalkerを作っても良いかもしれない。