Skip to content

Commit

Permalink
Merge pull request #72 from setminami/feature/inverse_schema
Browse files Browse the repository at this point in the history
 #24 reduced schema検討中
  • Loading branch information
setminami authored Feb 5, 2018
2 parents fdeaff9 + 146ae93 commit ce10c8a
Show file tree
Hide file tree
Showing 13 changed files with 401 additions and 95 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
language: python
python:
# - "3.4"
# - "3.5"
- "3.6"
# - "3.7-dev"
install:
Expand Down
10 changes: 5 additions & 5 deletions jsonica/jsonica.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from util import Hoare

# global settings.
VERSION = '0.0.9'
VERSION = '0.1.0'
PROGNAME = os.path.basename(__file__)

codec_help_url = 'https://docs.python.org/3.6/library/codecs.html#standard-encodings'
Expand Down Expand Up @@ -43,7 +43,7 @@ def test(self):
subcommand = self.sub_commands[args.subcmd_name]
subcommand.__run__(args=args)

def prepareArgParser(self):
def prepare_argparser(self):
argParser = argparse.ArgumentParser(prog=PROGNAME,
description='generate complex JSON structure with analyzing META descripted file like xlsx.')
# Version desctiprtion
Expand All @@ -54,7 +54,7 @@ def prepareArgParser(self):
subParsers = argParser.add_subparsers(dest='subcmd_name', metavar='', help='sub-commands')
for name, command in self.sub_commands.items():
if name == command.command_name:
command.makeArgparse(subParsers)
command.make_argparse(subParsers)
argParser.add_argument('-e', '--encoding',
type=str, default='utf-8', metavar='{python built-in codec}',
help='Set default charactor encode. When not set this, it is treated as "utf-8".\
Expand All @@ -72,7 +72,7 @@ def errorout(e, additonal=''):
print('%s : %s'%(errors[e], additonal), file=sys.stderr)
sys.exit(e)

def refactorCheck(validation): Hoare.P(validation, 'Have you made refactor??')
def refactor_check(validation): Hoare.P(validation, 'Have you made refactor??')

if __name__ == '__main__':
ins = Jsonica()
Expand All @@ -81,5 +81,5 @@ def refactorCheck(validation): Hoare.P(validation, 'Have you made refactor??')
# FIXME: とりあえず仮実装 Plugin実装する際に再考
for x in [Initialize(), Generate()]:
ins.regist_subcommand(x)
ins.prepareArgParser()
ins.prepare_argparser()
ins.test()
48 changes: 32 additions & 16 deletions jsonica/schema_helper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
import sys, enum
from util import Hoare
from util import Util, Hoare

class Validator(str, enum.Enum):
""" 対応validator """
Expand All @@ -22,14 +22,25 @@ class TypeSign(str, enum.Enum):
JSON_NULL = 'null'

class Schema:
""" abstract """
"""
抽象的な中継クラス
下流具象クラスへの中継とreduce以外の作業はさせないこと
"""
schema_url = '' # subclassで設定 mandatoryのため空宣言
DEBUG = False
class JsonSchema:
""" concrete 1 as jsonschema style """
def __init__(self):
self.__schemas = []
from jsonschema import Draft4Validator, ValidationError, SchemaError
# TEMP: jsonschema の対応状況により切り替える
self.schema_url = {'$schema': 'http://json-schema.org/draft-04/schema#'}
self.do_validate = Draft4Validator

def _makeSchema(self, type_desc):
def _make_schema(self, type_desc):
"""
最小粒度でのjsonschema構築
"""
schema = {'type':'object'}
if 'required' in type_desc[1].keys():
schema['required'] = [type_desc[0]]
Expand All @@ -38,33 +49,38 @@ def _makeSchema(self, type_desc):
return schema

def _validate(self, evl, schema):
from jsonschema import validate, ValidationError, SchemaError
# jsonschema による型チェック
# jsonschema による型チェック Draft-04
try:
validate(evl, schema)
self.do_validate(evl, schema)
except ValidationError as ve:
self.__print('Validation Error has found.\n%s'%ve)
Util.sprint('Validation Error has found.\n%s'%ve, self.DEBUG)
print('_validate {} with: {}'.format(evl, self.__schemas), self.DEBUG)
sys.exit(-1)
except SchemaError as se:
self.__print('Schema Error has found.\n%s'%se)
Util.sprint('Schema Error has found.\n%s'%se, self.DEBUG)
print('Error Schema : %s'%self.__schemas)
sys.exit(-2)

def __init__(self, validator):
self.schema_name = validator
self.schema_collection = []
# TEMP: type switch
if validator == Validator.jsonschema:
self.schema = Schema.JsonSchema()

def makeSchema(self, desc):
def make_schema(self, desc):
""" 一項目ずつの定義であることに留意 """
Hoare.P(isinstance(desc[0], str) and isinstance(desc[1], dict))
# HACK: failfastとして小粒度で都度Errorを上げるか、reduceしたあと最後にvalidationをかけるか
return self.schema._makeSchema(desc)
# TEMP: failfastとして小粒度で都度Errorを上げるか、reduceしたあと最後にvalidationをかけるか
self.schema_collection.append(self.schema._make_schema(desc))
return self.schema_collection[-1]

def validate(self, evl, desc):
sc = self.makeSchema(desc)
"""
_validateに流す
成功すればスルー、失敗したらその場でcommand error / 判定値は返さない
NOTE: 具体的なerrorハンドリングは_validate内で処理すること
"""
Hoare.P(isinstance(evl, list) or isinstance(evl, dict))
self.schema._validate(evl, sc)

def __print(self, _str, flag=False):
if flag: print(_str)
self.schema._validate(evl, self.make_schema(desc))
Util.sprint('i\'m {} \nNow I have -> {}'.format(self, self.schema_collection), self.DEBUG)
13 changes: 5 additions & 8 deletions jsonica/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ def __init__(self, fileloc, out, enc):
self.out = out
self.enc = enc

def checkSettingFile(self):
def check_settingfile(self):
with open(self.settings, 'r') as f:
self.setting_data = yaml.safe_load(f)
if self.setting_data[ATTACH[0]] == ATTACH[1][0]:
self.processor = XLSX(self.settings, self.enc)
Hoare.P(self.processor)

def createSheets(self, item=ROOT, name=None):
def create_sheets(self, item=ROOT, name=None):
root_item = self.setting_data[item]
if root_item:
sheet_name = name if (name and name != '') else root_item[SHEET_NAME]
if sheet_name:
sheet = self.processor.generateSheet(sheet_name)
sheet = self.processor.generate_sheet(sheet_name)
else:
raise SettingsError('Invalid sheet name', sheet_name)
cols = root_item[COLUMNS]
Expand All @@ -45,10 +45,10 @@ def createSheets(self, item=ROOT, name=None):
# care off-by-one
cell = sheet.cell(row=1, column=i + 1, value=c[COL_NAME])
schema = '{}'.format(c[SCHEMA]).lower()
self.processor.putCommentToCell(cell, '# {}\n{}'.format(c[NOTES], schema))
XLSX.put_cell_comment(cell, '# {}\n{}'.format(c[NOTES], schema))
if c[SCHEMA][SCHEMA_TYPE] == 'array':
for csheet in c[CHILD_SHEET]:
self.createSheets(c[REL_ITEM], csheet)
self.create_sheets(c[REL_ITEM], csheet)
else:
# 設定によってエラーとはいえないケースあり
print('%s : the item no columns.'%sheet_name)
Expand All @@ -61,9 +61,6 @@ def save(self):
print(r'generate template to {}'.format(output))
self.processor.book.save(output)

def __print(self, _str, flag=False):
if flag: print(_str)

class SettingsError(Exception):
""" ローカル設定 に関するエラー """
def __init__(self, message, item):
Expand Down
29 changes: 20 additions & 9 deletions jsonica/sub_command_core/generate.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
# -*- coding: utf-8 -*-
from . import SubCommands
from jsonica import errorout, refactorCheck
from jsonica import errorout, refactor_check
import os, sys, json, argparse, contextlib
from functools import reduce

from util import Util

output_formats = ['csv', 'tsv']
output_delimiters = [',', '\t']

SP_FILE = '-'

class Generate(SubCommands):
""" generate command """
VERSION = '0.0.9'
VERSION = '0.1.0'

__aliases = ['gen', 'g']
__help = 'generate analyzed files as TEXT from META descritor file. (e.g., Excel)'

DEBUG = not (os.getenv('TRAVIS', False))

def __init__(self):
super().__init__()

Expand All @@ -30,28 +34,29 @@ def __run__(self, **kwargs):
args = kwargs['args']
# default option 対策 seelaso AnalyzeXSeparatedOutPath
# https://github.com/setminami/Jsonica/issues/47
refactorCheck(args.__class__.__name__ == 'Namespace')
refactor_check(args.__class__.__name__ == 'Namespace')
if not args.output_format:
args.output_format = ('tsv', output_delimiters[1], './output/')
self.args = args
fileloc = os.path.abspath(os.path.expanduser(args.input))
workpath = Generate.__treatFileTypes(fileloc)
from xlsx import XLSX
self.__print('Analyzing... %s'%fileloc)
self._print('Analyzing... %s'%fileloc)
x = XLSX(workpath, args.encoding, args.output_format)
# sys.setrecursionlimit(1024 * 8)
j = x.generateJSON(sheet_name=args.root_sheet)
j = x.generate_json(sheet_name=args.root_sheet)
with wild_open(args.output, encoding=args.encoding) as f:
try:
print(json.dumps(j, sort_keys=True, indent=args.human_readable) \
if args.human_readable > 0 else json.dumps(j), file=f)
except:
errorout(6, args.output)
else:
self.__print('Output json Success ➡️ %s'%args.output)
self._print('Output json Success ➡️ %s'%args.output)
Util.sprint('XXX %s XXX'%x.piled_schema, self.DEBUG)

def makeArgparse(self, subparser):
myparser = super().makeArgparse(subparser)
def make_argparse(self, subparser):
myparser = super().make_argparse(subparser)
outs = reduce(lambda l, r: '{} | {}'.format(l, r), output_formats)
myparser.add_argument('-i', '--input',
nargs='?', type=str, default='./Samples/cheatsheet.xlsx',
Expand All @@ -64,6 +69,12 @@ def makeArgparse(self, subparser):
nargs='?', type=str, default='root', # Default root sheet name
metavar='sheetname',
help='set a sheetname in xlsx book have. \nconstruct json tree from the sheet as root item. "root" is Default root sheet name.')
# reserve
# myparser.add_argument('-s', '--schema',
# nargs='?', type=str,
# metavar='schema url',
# help='http://json-schema.org/draft-04/schema#')

myparser.add_argument('-o', '--output',
nargs='?', type=str, action=AnalyzeJSONOutPath,
metavar='path/to/outputfile(.json)',
Expand All @@ -82,7 +93,7 @@ def __treatFileTypes(cls, file):
else:
errorout(7, '%s format is not supported yet.'%file)

def __print(self, msg):
def _print(self, msg):
if not (self.args.output == SP_FILE): print(msg)

# argparse actions
Expand Down
10 changes: 5 additions & 5 deletions jsonica/sub_command_core/initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

class Initialize(SubCommands):
""" initialize command """
VERSION = '0.0.9'
VERSION = '0.1.0'

__aliases = ['init', 'i']
__help = 'create formated workbook template.'
Expand All @@ -30,17 +30,17 @@ def __run__(self, **kwargs):
else:
errorout(8, r'Please make sure [%s] location?'%setting_file)
settings = SettingProcessor(setting_file, args.template_xlsx, args.encoding)
settings.checkSettingFile()
settings.check_settingfile()
try:
settings.createSheets()
settings.create_sheets()
except SettingsError as se:
print(se)
else:
settings.save()
print('Construct xlsx file Success ➡️ %s'%args.template_xlsx)

def makeArgparse(self, subparser):
myparser = super().makeArgparse(subparser)
def make_argparse(self, subparser):
myparser = super().make_argparse(subparser)
myparser.add_argument('-tx', '--template_xlsx',
nargs='?', type=str, default='./template.xlsx', metavar='path/to/outputfile(.xlsx)',
help='This is an initialize helper option.\n\
Expand Down
6 changes: 3 additions & 3 deletions jsonica/sub_command_core/sub_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ def __run__(self, **kwargs):
# each process brabrabra
pass
def makeArgparse(self, subparser):
myparser = super().makeArgparse(subparser)
def make_argparse(self, subparser):
myparser = super().make_argparse(subparser)
# write each subcommand arg processor here
myparser.add_argument('-v', '--version',
action='version', version='{} {}'.format(self.command_name, VERSION))
Expand Down Expand Up @@ -82,7 +82,7 @@ def regist_command(cls, exchanger):

def __run__(self, **kwargs): Hoare.P(False)

def makeArgparse(self, subparser):
def make_argparse(self, subparser):
""" 個別optionを登録したargparseをsubparseにして返す """
parser = subparser.add_parser(self.command_name, aliases=self.aliases, help=self.help)
parser.add_argument('-v', '--version',
Expand Down
Loading

0 comments on commit ce10c8a

Please sign in to comment.