anoy 0.1.2__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: anoy
3
+ Version: 0.2.0
4
+ Summary: This is a library that provides simple type checking for YAML.
5
+ Author-email: masaniki <masaniki.software@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/masaniki/annotation-yaml
8
+ Project-URL: Documentation, https://github.com/masaniki/annotation-yaml/tree/master/docs
9
+ Project-URL: Issues, https://github.com/masaniki/annotation-yaml/issues
10
+ Keywords: text,YAML,CLI,type,check
11
+ Classifier: Development Status :: 1 - Planning
12
+ Classifier: Environment :: Console
13
+ Classifier: Natural Language :: Japanese
14
+ Classifier: Natural Language :: English
15
+ Classifier: Topic :: Text Processing
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE.txt
19
+ Requires-Dist: PyYAML
20
+ Dynamic: license-file
21
+
22
+ # Index
23
+
24
+ - **English Document**
25
+
26
+ English Document <- Here
27
+
28
+ - **Japanese Document**
29
+
30
+ [Japanese Document](docs/README_JP.md)
31
+
32
+ - **Annotation YAML(ANOY)**
33
+
34
+ A YAML file that allows type checking for Map types.
35
+
36
+ More about [ANOY](about_anoy.md)
37
+
38
+ - **Configuration YAML(Config YAML)**
39
+
40
+ A YAML file that defines the data types usable in Annotation YAML.
41
+
42
+ More about [Config YAML](about_config.md)
43
+
44
+ - **ANOY CLI**
45
+
46
+ A CLI application that verifies whether Annotation YAML adheres to the data type defined in Configuration YAML.
47
+
48
+ More about [ANOY CLI](about_anoycli.md)
49
+
50
+ # Introduction
51
+
52
+ YAML maps often become nested and complex.
53
+
54
+ And these requests have come up.
55
+
56
+ - Detecting typos in map keys.
57
+ - Verifying the data type of map values.
58
+ - Ensuring the map's nested structure is appropriate.
59
+
60
+ This library satisfies these requests.
61
+
62
+ # Installing
63
+
64
+ `pip install anoy`
65
+
66
+ ## Package Dependencies
67
+
68
+ The following packages may not work properly if they are not installed:
69
+
70
+ - [PyYAML](https://pypi.org/project/PyYAML/): Most popular YAML parser for Python.
71
+
72
+ # Usage
73
+
74
+ library_config.yaml
75
+
76
+ ```
77
+ "@Books":
78
+ "@Summary": List the book titles.
79
+ "!ChildValue": FreeDict
80
+ "@Author":
81
+ "@Summary": Author of the book.
82
+ "!ChildValue": Str
83
+ "@PublishYear":
84
+ "@Summary": The year of the publishment.
85
+ "!ChildValue": Int
86
+ "@Country":
87
+ "@Summary": The author's native language.
88
+ "!ChildValue": Str
89
+ ```
90
+
91
+ valid_library.yaml:
92
+
93
+ ```
94
+ "@Books":
95
+ "Alice's Adventures in Wonderland":
96
+ "@Author": Lewis Carroll
97
+ "@PublishYear": 1865
98
+ "@Country": UK
99
+ "The Little Prince":
100
+ "@Author": Antonie de Saint-Exupéry
101
+ "@PublishYear": 1945
102
+ "@Country": France
103
+ "Harry Potter":
104
+ "@Author": J.K.Rowling
105
+ "@PublishYear": 1997
106
+ "@Country": UK
107
+ ```
108
+
109
+ Use the `anoy` command to verify data types.
110
+
111
+ If data type issues are not found, it outputs nothing.
112
+
113
+ ```
114
+ >>> anoy library_config.yaml library.yaml
115
+ >>>
116
+ ```
117
+
118
+ If you mistyped `@Auther` as `@Auther`.
119
+
120
+ invalid_library.yaml:
121
+
122
+ ```
123
+ "@Books":
124
+ "Alice's Adventures in Wonderland":
125
+ "@Auther": Lewis Carroll
126
+ "@PublishYear": 1865
127
+ "@Country": UK
128
+ "The Little Prince":
129
+ "@Auther": Antonie de Saint-Exupéry
130
+ "@PublishYear": 1945
131
+ "@Country": France
132
+ "Harry Potter":
133
+ "@Auther": J.K.Rowling
134
+ "@PublishYear": 1997
135
+ "@Country": UK
136
+ ```
137
+
138
+ When there is an issue with the annotation YAML, `anoy` outputs as follows.
139
+
140
+ ```
141
+ >>> anoy library_config.yaml library.yaml
142
+ >>> Traceback (most recent call last):
143
+ ... (omission) ...
144
+ src.anoyModule.anoyErrors.ConfigurationYamlError: `@Auther` is not defined.
145
+ ```
146
+ # For Developers
147
+
148
+ ## Testing
149
+
150
+ This project uses `pytest`.
151
+
152
+ If you want to test, put in following command.
153
+
154
+ `pytest tests\unit\test_dictTraversal.py`
155
+
156
+ ## Next To Do
157
+
158
+ - [ ] test caseを全てのdata型に拡大。
159
+ - [ ] `!Float`のtest.
160
+ - [ ] `!AnnoMap`のtest.
161
+ - [ ] `!FreeMap`のtest.
162
+
163
+ ## Ideas
164
+
165
+ ```
166
+ - key01
167
+ - key02
168
+ - key03
169
+ ```
170
+
171
+ ```
172
+ - {key01:value01}
173
+ - {key02:value02}
174
+ - {key03:value03}
175
+ ```
176
+ を等価に扱うsystemが欲しい。
@@ -0,0 +1,10 @@
1
+ cli.py,sha256=kGdZrdmUrI0dCKrbU_dUjrIGNNueV0B8DwQ29zUwxiM,915
2
+ anoy-0.2.0.dist-info/licenses/LICENSE.txt,sha256=nsHvySI1U7YZgAX4K3rJsWli_GfXy2syPI-MtGPwjlo,1062
3
+ anoyModule/__init__.py,sha256=iWletkxm7jDc7oFqp4yDS_4fUYZBny04q2VxkTpIDHo,111
4
+ anoyModule/anoyErrors.py,sha256=2DST9_IenRIyXsABl6vWhMU0D5wqJ2aSs7zkm0DABm4,646
5
+ anoyModule/dictTraversal.py,sha256=zdBHrrHghnvL_58ateZMpFvEYhi3Qi1absvk2OSbb5U,26048
6
+ anoy-0.2.0.dist-info/METADATA,sha256=i4KpdNglLMltxEsdkfwJk4nB4kt60c4Xc5TjGkXIYdQ,3970
7
+ anoy-0.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
+ anoy-0.2.0.dist-info/entry_points.txt,sha256=_lpL6R97giGseZcV0r5QwdX4zRwoZLzRHUxyBZl_iYI,34
9
+ anoy-0.2.0.dist-info/top_level.txt,sha256=QExj2OlPPcJU_7B1gwNcL2iYOcgYJBpDZsUArRCaXJw,15
10
+ anoy-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ anoyModule
2
+ cli
anoyModule/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+
2
+ from .dictTraversal import DictTraversal
3
+ from .anoyErrors import AnoyError, AnoyTypeError, ConfigYamlError
@@ -1,5 +1,5 @@
1
1
 
2
- class AnnotationYamlError(Exception):
2
+ class AnoyError(Exception):
3
3
  """
4
4
  @Summ: annotation yaml上のError。
5
5
  """
@@ -7,20 +7,20 @@ class AnnotationYamlError(Exception):
7
7
  super().__init__(*args)
8
8
 
9
9
 
10
- class AnnotationYamlTypeError(Exception):
10
+ class AnoyTypeError(Exception):
11
11
  """
12
12
  @Summ: annotation yaml上のdata型のError。
13
13
  """
14
- def __init__(self,type:str,path:list):
14
+ def __init__(self,type:str,fileName:str,path:list):
15
15
  super().__init__()
16
16
  self.type=type
17
+ self.fileName=fileName
17
18
  self.path=path
18
19
 
19
20
  def __str__(self):
20
- return f"required {self.type} type at:\n {self.path}"
21
+ return f"{self.type} contradiction:\n {self.fileName}: {self.path}"
21
22
 
22
-
23
- class ConfigurationYamlError(Exception):
23
+ class ConfigYamlError(Exception):
24
24
  """
25
25
  @Summ: annotation yaml上のError。
26
26
  """
@@ -0,0 +1,642 @@
1
+ import yaml
2
+ from pathlib import Path
3
+
4
+ from .anoyErrors import AnoyError,ConfigYamlError,AnoyTypeError
5
+
6
+ class DictTraversal():
7
+ """
8
+ @Summ: 辞書型の中身を探索するclass.
9
+
10
+ @InsVars:
11
+ _configDict:
12
+ @Summ: config yamlを構文解析した後の値を格納する。
13
+ @Desc:
14
+ - !Childの値は{"!Child": {typeString(str):typOption(dict)}}という形式に直す。
15
+ - typeStringはdata型を表す文字列。
16
+ - typeOptionはdata型の詳細な設定を表すdict型である。
17
+ - つまり、str-format data typeもmap-format data typeに直すということ。
18
+ - map-format data typeが無いBool型は{"!Bool":{}}とする。
19
+ - annotation keyを使った否かを"isVisit" keyに記録する。
20
+ @Type: Dict
21
+ _visitQueue:
22
+ @Summ: 探索queue
23
+ @Desc: BFSなのでFIFO。
24
+ @Type: List
25
+ _pathQueue:
26
+ @Summ: 探索する要素の相対pathを格納する。
27
+ @Desc:
28
+ - visitQueueと要素番号を共有する。
29
+ - []でroot要素を表す。
30
+ @Type: List
31
+ _curFile:
32
+ @Summ: 現在探索中のANOY file名。
33
+ @ComeFrom: current file.
34
+ @Type: Str
35
+ _curPath:
36
+ @Summ: _curFile内での現在地。
37
+ @ComeFrom: current path/
38
+ @Type: List
39
+ """
40
+
41
+ def __init__(self,configDict:dict):
42
+ """
43
+ @Summ: constructor.
44
+ """
45
+ self._configDict=self.checkConfig(configDict)
46
+ print(self._configDict)
47
+ self._visitQueue=[]
48
+ self._pathQueue=[]
49
+ self._curFile=""
50
+ self._curPath=[]
51
+
52
+ def checkConfig(self,configDict:dict)->dict:
53
+ """
54
+ @Summ: config yamlの中身を構文解析する関数。
55
+
56
+ @Desc
57
+ - config yamlは、annotation keyかconfig keyの記述しか許さない。
58
+ - configDictに"isVisit" keyを追加し、annotation keyを使用したかを記録する。
59
+
60
+ @Args:
61
+ configDict:
62
+ @Summ: config yamlの中身。
63
+ @Type: Dict
64
+ @Returns:
65
+ @Summ: 型確認して、余計なものを取り除いたconfigDict。
66
+ @Type: dict
67
+ """
68
+ newConfigDict={} # 整形されたconfigDict
69
+ for annoKey in configDict.keys():
70
+ newAnnoValue={} #annotation keyに対応する値。
71
+ if(annoKey[0]!="@"):
72
+ raise ConfigYamlError(f"{annoKey} is invalid definition.")
73
+ valueDict=configDict[annoKey]
74
+ if(type(valueDict)!=dict):
75
+ raise ConfigYamlError(f"{annoKey} is invalid definition.")
76
+ for key,value in valueDict.items():
77
+ if(key[0]=="@"):
78
+ continue
79
+ elif(key=="!Parent"):
80
+ validConfParent=self.checkParent(annoKey,value)
81
+ newAnnoValue["!Parent"]=validConfParent
82
+ elif(key=="!Child"):
83
+ validConfChild=self.checkChild(annoKey,value)
84
+ newAnnoValue["!Child"]=validConfChild
85
+ else:
86
+ raise ConfigYamlError(f"{annoKey} is invalid definition.")
87
+ # isVisit keyの追加。
88
+ newAnnoValue["isVisit"]=False
89
+ newConfigDict[annoKey]=newAnnoValue
90
+ return newConfigDict
91
+
92
+ @classmethod
93
+ def checkParent(cls,annoKey,confParent):
94
+ """
95
+ @Summ: `!Parent`に対応する値を型確認する関数。
96
+
97
+ @Args:
98
+ annoKey:
99
+ @Summ: `!Parent`の親となるannotation key.
100
+ @Type: Str
101
+ confParent:
102
+ @Summ: `!Parent`の子。
103
+ @Type: Any
104
+ @Returns:
105
+ @Summ: `!Parent`のvalueとして有効な値。
106
+ @Type: List
107
+ """
108
+ if(type(confParent)!=list):
109
+ raise ConfigYamlError(f"{annoKey} is invalid definition.")
110
+ for item in confParent:
111
+ if(item is None):
112
+ continue
113
+ if(item[0]!="@"):
114
+ raise ConfigYamlError(f"{annoKey} is invalid definition.")
115
+ return confParent.copy()
116
+
117
+ @classmethod
118
+ def checkChild(cls,annoKey,confChild):
119
+ """
120
+ @Summ: `!Child`に対応する値を型確認する関数。
121
+
122
+ @Args:
123
+ annoKey:
124
+ @Summ: `!Child`の親となるannotation key.
125
+ @Type: Str
126
+ confChild:
127
+ @Summ: `!Child`の子。
128
+ @Type: Any
129
+ @Returns:
130
+ @Summ: `!Child`のvalueとして有効な値。
131
+ @Type: Dict
132
+ """
133
+ if(type(confChild)==str):
134
+ match confChild:
135
+ case "!Str":
136
+ return {"!Str":{"length":None,"min":None,"max":None}}
137
+ case "!Bool":
138
+ return {"!Bool":{}}
139
+ case "!Int":
140
+ return {"!Int":{"min":None,"max":None}}
141
+ case "!Float":
142
+ return {"!Float":{"min":None,"max":None}}
143
+ case "!List":
144
+ return {"!List":{"type":None,"length":None}}
145
+ case "!FreeMap":
146
+ return {"!FreeMap":{}}
147
+ case "!AnnoMap":
148
+ return {"!AnnoMap":[]}
149
+ case _:
150
+ raise ConfigYamlError(f"{annoKey} is invalid definition.")
151
+ elif(type(confChild)==dict):
152
+ confChildKey=list(confChild.keys())
153
+ if(len(confChildKey)!=1):
154
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
155
+ typeStr=confChildKey[0]
156
+ typeOption=confChild[typeStr]
157
+ match typeStr:
158
+ case "!Str":
159
+ if(type(typeOption)!=dict):
160
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
161
+ strLength=None
162
+ strMin=None
163
+ strMax=None
164
+ for strKey,strVal in typeOption.items():
165
+ match strKey:
166
+ case "length":
167
+ if(strMin is None and strMax is None):
168
+ strLength=strVal
169
+ else:
170
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
171
+ case "min":
172
+ if(strLength is None):
173
+ strMin=strVal
174
+ else:
175
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
176
+ case "max":
177
+ if(strLength is None):
178
+ strMax=strVal
179
+ else:
180
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
181
+ case _:
182
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
183
+ return {"!Str":{"length":strLength,"min":strMin,"max":strMax}}
184
+ case "!Int":
185
+ if(type(typeOption)!=dict):
186
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
187
+ intMin=None
188
+ intMax=None
189
+ for intKey,intVal in typeOption.items():
190
+ match intKey:
191
+ case "min":
192
+ intMin=intVal
193
+ case "max":
194
+ intMax=intVal
195
+ case _:
196
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
197
+ return {"!Int":{"min":intMin,"max":intMax}}
198
+ case "!Float":
199
+ if(type(typeOption)!=dict):
200
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
201
+ floatMin=None
202
+ floatMax=None
203
+ for floatKey,floatVal in typeOption.items():
204
+ match floatKey:
205
+ case "min":
206
+ floatMin=floatVal
207
+ case "max":
208
+ floatMax=floatVal
209
+ case _:
210
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
211
+ return {"!Float":{"min":floatMin,"max":floatMax}}
212
+ case "!Enum":
213
+ if(type(typeOption)!=list):
214
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
215
+ enumOption=[]
216
+ for item in typeOption:
217
+ if(type(item)==list):
218
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
219
+ elif(type(item)==dict):
220
+ keyList=list(item.keys())
221
+ if(len(keyList)!=1):
222
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
223
+ enumOption.append(keyList[0])
224
+ else:
225
+ enumOption.append(item)
226
+ return {"!Enum":enumOption}
227
+ case "!List":
228
+ if(type(typeOption)!=dict):
229
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
230
+ listType=None
231
+ listLength=None
232
+ for listKey,listVal in typeOption.items():
233
+ match listKey:
234
+ case "type":
235
+ listType=listVal
236
+ case "length":
237
+ listLength=listVal
238
+ case _:
239
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
240
+ return {"!List":{"type":listType,"length":listLength}}
241
+ case "!AnnoDict":
242
+ if(type(typeOption)!=list):
243
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
244
+ for i in len(typeOption):
245
+ item=typeOption[i]
246
+ if(item[0]!="@"):
247
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
248
+ return {"!AnnoDict":typeOption}
249
+ case _:
250
+ raise ConfigYamlError(f"`{annoKey}` has invalid definition.")
251
+ else:
252
+ raise ConfigYamlError(f"{annoKey} is invalid definition.")
253
+
254
+
255
+ def dirDFS(self,anoyPath:Path):
256
+ """
257
+ @Summ: directory内を深さ優先探索する関数。
258
+
259
+ @Desc:
260
+ - fileならばYAMLかどうかを確認して、内部のdict型を探索する。
261
+ - directoryならば、子要素を再帰的に探索する。
262
+
263
+ @Args:
264
+ anoyPath:
265
+ @Summ: 探索するfileやdirectoryのpath名。
266
+ @Type: Path
267
+ """
268
+ if(anoyPath.is_file()):
269
+ suffix=anoyPath.suffix
270
+ if(suffix==".yaml" or suffix==".yml" or suffix==".anoy"):
271
+ with open(anoyPath, mode="r", encoding="utf-8") as f:
272
+ anoyDict=yaml.safe_load(f)
273
+ self._curFile=anoyPath
274
+ self.dictBFS(anoyDict)
275
+ else:
276
+ for childPath in anoyPath.iterdir():
277
+ self.dirDFS(childPath)
278
+
279
+
280
+ def dictBFS(self,anoyDict:dict):
281
+ """
282
+ @Summ: anoyDictの中を幅優先探索を開始する関数。
283
+
284
+ @Desc:
285
+ - list型は単純に探索する。
286
+ - dict型は型確認しながら探索する。
287
+ - visitQueueには(key(str),value(any))のtupleを入れる。
288
+ - list型の時は、(key(int),value(any))になる。
289
+ @Args:
290
+ anoyDict:
291
+ @Summ: annotation yamlのdict型。
292
+ """
293
+ self._visitQueue=[(None,anoyDict)]
294
+ self._pathQueue=[[]]
295
+ while(True):
296
+ if(self._visitQueue==[]):
297
+ break
298
+ key,value=self._visitQueue.pop(0)
299
+ self._curPath=self._pathQueue.pop(0)
300
+ print(key,value)
301
+ print(self._curPath)
302
+ self.checkAnoy(key,value)
303
+
304
+ def checkAnoy(self,parentKey:str|None,childValue):
305
+ """
306
+ "@Summ": anoyの中身を探索する関数。
307
+
308
+ "@Desc":
309
+ - 型確認は"!Parent"と"!Child"の2つだ。
310
+ - parentKeyの`!Child`がchildValueを制限する。
311
+ - childValueの`!parent`がparentKeyを制限する。
312
+ - parentKeyがannotationKeyでない時は、"!Parent"も"!Child"も効力を発揮しないので無視。
313
+ - !Childが無い時は、childValue=Noneとして考える。
314
+ - `!Parent`による型確認は、childValueが`!AnnoMap`型の時のみ行われる。
315
+
316
+ "@Args":
317
+ parentKey:
318
+ "@Summ": 探索するdict型のkey。
319
+ "@Desc":
320
+ - nullは親要素が存在しないことを表す(つまりvalueがroot要素である)。
321
+ "@Type":
322
+ Union:
323
+ - Str
324
+ - null
325
+ childValue:
326
+ "@Summ": 探索するdict型のvalue。
327
+ "@Type": Any
328
+ "@Error":
329
+ - AnnotationYamlError
330
+ - AnnotationYamlTypeError
331
+ - ConfigurationYamlError
332
+ """
333
+ if(parentKey is None):
334
+ confChild=None #confChild=Noneの時は型確認をしない。
335
+ elif(type(parentKey)!=str):
336
+ confChild=None
337
+ elif(parentKey[0]=="@"):
338
+ confDictVal=self._configDict.get(parentKey)
339
+ if(confDictVal is None):
340
+ raise AnoyError(f"{parentKey} is not found.")
341
+ confChild=confDictVal.get("!Child")
342
+ else:
343
+ confChild=None
344
+ # anoyの型確認
345
+ if(confChild is None): #Noneの処理方法は不明。
346
+ # nestになるlistとdictだけ対処する。
347
+ if(type(childValue)==list):
348
+ for i in len(childValue):
349
+ element=childValue[i]
350
+ newPath=self._curPath+[i]
351
+ self._visitQueue.append((i,element))
352
+ self._pathQueue.append(newPath)
353
+ elif(type(childValue)==dict):
354
+ for key,value in childValue.items():
355
+ newPath=self._curPath+[key]
356
+ self._visitQueue.append((key,value))
357
+ self._pathQueue.append(newPath)
358
+ return
359
+ typeStr=list(confChild.keys())[0]
360
+ typeOption=confChild[typeStr]
361
+ match typeStr:
362
+ case "!Str":
363
+ self.checkStr(childValue,**typeOption)
364
+ case "!Bool":
365
+ self.checkBool(childValue)
366
+ case "!Int":
367
+ self.checkInt(childValue,**typeOption)
368
+ case "!Float":
369
+ self.checkFloat(childValue,**typeOption)
370
+ case "!FreeMap":
371
+ self.checkFreeMap(childValue)
372
+ case "!AnnoMap":
373
+ self.checkAnnoMap(parentKey,childValue,typeOption)
374
+ case "!List":
375
+ self.checkList(parentKey,childValue,elementType=typeOption["type"],length=typeOption["length"])
376
+ case "!Enum":
377
+ self.checkEnum(childValue,typeOption)
378
+ case _:
379
+ raise ConfigYamlError(f"{parentKey} is invalid definition.")
380
+
381
+ def checkStr(self,anoyValue,length=None,min=None,max=None):
382
+ """
383
+ @Summ: !Str型を型確認する関数。
384
+
385
+ @Desc:
386
+ - <length>と<min>、<length>と<max>の両立は不可能であるが、この関数ではその確認を行わない。
387
+ - 呼び出し時にその確認を行うべきである。
388
+
389
+ @Args:
390
+ anoyValue:
391
+ @Summ: 型確認する値。
392
+ length:
393
+ @Summ: 文字列の長さ。
394
+ @Desc: min,maxとの両立は不可能。
395
+ min:
396
+ @Summ: 文字列の長さの最小値。
397
+ @Desc:
398
+ - lengthとの両立は不可能。
399
+ - min-1からerror.
400
+ max:
401
+ @Summ: 文字列の長さの最大値。
402
+ @Desc:
403
+ - lengthとの両立は不可能。
404
+ - max+1からerror.
405
+ """
406
+ if(type(anoyValue)==str):
407
+ if(length is not None):
408
+ if(len(anoyValue)==length):
409
+ return
410
+ else:
411
+ raise AnoyTypeError("!Str",self._curFile,self._curPath)
412
+ else:
413
+ if(min is not None):
414
+ if(len(anoyValue)<min):
415
+ raise AnoyTypeError("!Str",self._curFile,self._curPath)
416
+ if(max is not None):
417
+ if(max<len(anoyValue)):
418
+ raise AnoyTypeError("!Str",self._curFile,self._curPath)
419
+ return
420
+ else:
421
+ raise AnoyTypeError("!Str",self._curFile,self._curPath)
422
+
423
+ def checkBool(self,anoyValue):
424
+ """
425
+ @Summ: !Bool型を型確認する関数。
426
+
427
+ @Args:
428
+ anoyValue:
429
+ @Summ: 型確認する値。
430
+ """
431
+ if(type(anoyValue)!=bool):
432
+ raise AnoyTypeError("!Bool",self._curFile,self._curPath)
433
+
434
+ def checkInt(self,anoyValue,min=None,max=None):
435
+ """
436
+ @Summ: !Int型を型確認する関数。
437
+
438
+ @Args:
439
+ anoyValue:
440
+ @Summ: 型確認する値。
441
+ min:
442
+ @Summ: 最小値。
443
+ max:
444
+ @Summ: 最大値。
445
+ """
446
+ if(type(anoyValue)==int):
447
+ if(min is not None):
448
+ if(anoyValue<min):
449
+ raise AnoyTypeError("!Int",self._curFile,self._curPath)
450
+ if(max is not None):
451
+ if(max<anoyValue):
452
+ raise AnoyTypeError("!Int",self._curFile,self._curPath)
453
+ return
454
+ else:
455
+ raise AnoyTypeError("!Int",self._curFile,self._curPath)
456
+
457
+ def checkFloat(self,anoyValue,min=None,max=None):
458
+ """
459
+ @Summ: !Float型を型確認する関数。
460
+
461
+ @Args:
462
+ anoyValue:
463
+ @Summ: 型確認する値。
464
+ min:
465
+ @Summ: 最小値。
466
+ max:
467
+ @Summ: 最大値。
468
+ """
469
+ if(type(anoyValue)==float):
470
+ if(min is not None):
471
+ if(anoyValue<min):
472
+ raise AnoyTypeError("!Float",self._curFile,self._curPath)
473
+ if(max is not None):
474
+ if(max<anoyValue):
475
+ raise AnoyTypeError("!Float",self._curFile,self._curPath)
476
+ return
477
+ else:
478
+ raise AnoyTypeError("!Float",self._curFile,self._curPath)
479
+
480
+ def checkFreeMap(self,anoyValue):
481
+ """
482
+ @Summ: !FreeMap型を型確認する関数。
483
+
484
+ @Args:
485
+ anoyValue:
486
+ @Summ: 型確認する値。
487
+ """
488
+ if(type(anoyValue)==dict):
489
+ for key,value in anoyValue.items():
490
+ newPath=self._curPath+[key]
491
+ self._visitQueue.append((key,value))
492
+ self._pathQueue.append(newPath)
493
+ if(key[0]=="@"):
494
+ raise AnoyTypeError("!FreeMap",self._curFile,newPath)
495
+ else:
496
+ raise AnoyTypeError("!FreeMap",self._curFile,self._curPath)
497
+
498
+ def checkAnnoMap(self,parentKey,anoyValue,annoKeyList:list=[]):
499
+ """
500
+ @Summ: !FreeMap型を型確認する関数。
501
+
502
+ @Desc:
503
+ - <annoKeyList>は最低限必要なannotation keyのlistが入る。
504
+ - 最低限なので、<annoKeyList>以外のannotation keyも許容される。
505
+
506
+ @Args:
507
+ parentKey:
508
+ @Summ: 親要素のannotation key。
509
+ @Type: Str
510
+ anoyValue:
511
+ @Summ: 型確認する値。
512
+ annoKeyList:
513
+ @Summ: annotation keyのlist。
514
+ @Type: List
515
+ @Default: []
516
+ """
517
+ if(type(anoyValue)==dict):
518
+ for key,value in anoyValue.items():
519
+ newPath=self._curPath+[key]
520
+ self._visitQueue.append((key,value))
521
+ self._pathQueue.append(newPath)
522
+ # !Parentの確認。
523
+ configValue=self._configDict.get(key)
524
+ if(configValue is None):
525
+ raise AnoyError(f"{parentKey} is not found.")
526
+ confParent=configValue.get("!Parent")
527
+ if(confParent is not None):
528
+ if(parentKey not in confParent):
529
+ raise AnoyTypeError("!Parent",self._curFile,newPath)
530
+ if(annoKeyList!=[]):
531
+ if(key not in annoKeyList):
532
+ raise AnoyTypeError("!AnnoMap",self._curFile,self._curPath)
533
+ else:
534
+ raise AnoyTypeError("!AnnoMap",self._curFile,self._curPath)
535
+
536
+ def checkList(self,parentKey,anoyValue,elementType:str=None,length:int=None):
537
+ """
538
+ @Summ: !List型を型確認する関数。
539
+
540
+ @Desc:
541
+ - <typeOption>は最低限必要なannotation keyのlistが入る。
542
+ - 最低限なので、<typeOption>以外のannotation keyも許容される。
543
+
544
+ @Args:
545
+ parentKey:
546
+ @Summ: 親のkey。
547
+ @Type: Str
548
+ anoyValue:
549
+ @Summ: 型確認する値。
550
+ elementType:
551
+ @Summ: list型の子要素のdata型。
552
+ @Desc:
553
+ - [!Bool,!Str,!Int,!Float]を指定できる。
554
+ - Noneの時はdata型を確認しない。
555
+ @Type: Str
556
+ length:
557
+ @Summ: listの長さ
558
+ @Type: Int
559
+ """
560
+ if(type(anoyValue)==list):
561
+ if(length is not None):
562
+ if(length!=len(anoyValue)):
563
+ raise AnoyTypeError("!List",self._curFile,self._curPath)
564
+ for i in range(len(anoyValue)):
565
+ element=anoyValue[i]
566
+ newPath=self._curPath+[i]
567
+ if(elementType is not None):
568
+ match elementType:
569
+ case "!Str":
570
+ if(type(element)!=str):
571
+ raise AnoyTypeError("!List",self._curFile,newPath)
572
+ case "!Bool":
573
+ if(type(element)!=bool):
574
+ raise AnoyTypeError("!List",self._curFile,newPath)
575
+ case "!Int":
576
+ if(type(element)!=int):
577
+ raise AnoyTypeError("!List",self._curFile,newPath)
578
+ case "!Float":
579
+ if(type(element)!=float):
580
+ raise AnoyTypeError("!List",self._curFile,newPath)
581
+ case _:
582
+ raise ConfigYamlError(f"{parentKey} is invalid definition.")
583
+ self._visitQueue.append((i,element))
584
+ self._pathQueue.append(newPath)
585
+ else:
586
+ raise AnoyTypeError("!List",self._curFile,self._curPath)
587
+
588
+ def checkEnum(self,anoyValue,optionList:list):
589
+ """
590
+ @Summ: !Enum型を型確認する関数。
591
+
592
+ @Desc:
593
+ - 他の言語のUnion型の役割も兼ねている。
594
+ - 選択できるdata型は、[null,!Bool,!Str,!Int,!Float,!List,!FreeMap]である。
595
+ - 入れ子の下層までは確認しない(浅いdata型確認)。
596
+
597
+ @Args:
598
+ anoyValue:
599
+ @Summ: 型確認する値。
600
+ optionList:
601
+ @Summ: Enum型の選択肢を格納するlist型。
602
+ @Type: List
603
+ """
604
+ for i in range(len(optionList)):
605
+ option=optionList[i]
606
+ if(option is None and anoyValue is None):
607
+ return
608
+ match option:
609
+ case "!Str":
610
+ if(type(anoyValue)==str):
611
+ return
612
+ case "!Bool":
613
+ if(type(anoyValue)==bool):
614
+ return
615
+ case "!Int":
616
+ if(type(anoyValue)==int):
617
+ return
618
+ case "!Float":
619
+ if(type(anoyValue)==float):
620
+ return
621
+ case "!List":
622
+ if(type(anoyValue)==list):
623
+ return
624
+ case "!FreeMap":
625
+ if(type(anoyValue)==dict):
626
+ return
627
+ case _:
628
+ if(anoyValue==option):
629
+ return
630
+ raise AnoyTypeError("!Enum",self._curFile,self._curPath)
631
+
632
+
633
+ if(__name__=="__main__"):
634
+ configPath=r"C:\Users\tomot\Backup\sourcecode\python\projects\annotation_yaml\tests\unit\case01\config01.yaml"
635
+ anoyPath=r"C:\Users\tomot\Backup\sourcecode\python\projects\annotation_yaml\tests\unit\case01\int_false.yaml"
636
+ with open(configPath,mode="r",encoding="utf-8") as f:
637
+ configDict=yaml.safe_load(f)
638
+ with open(anoyPath,mode="r",encoding="utf-8") as f:
639
+ anoyDict=yaml.safe_load(f)
640
+ tree01=DictTraversal(configDict)
641
+ tree01.dictBFS(anoyDict)
642
+
cli.py CHANGED
@@ -3,9 +3,9 @@ from pathlib import Path
3
3
  import argparse
4
4
  import yaml
5
5
 
6
- from dictTraversal import DictTraversal
6
+ from anoyModule import DictTraversal
7
7
 
8
- VERSION="v0.1.2"
8
+ VERSION="v0.2.0"
9
9
 
10
10
  def main():
11
11
  """
@@ -21,11 +21,9 @@ def main():
21
21
  anoyPath=Path(args.anoy)
22
22
  with open(configPath,mode="r",encoding="utf-8") as f:
23
23
  configDict=yaml.safe_load(f)
24
- with open(anoyPath,mode="r",encoding="utf-8") as f:
25
- anoyDict=yaml.safe_load(f)
26
- print("loadEnd")
27
24
  tree01=DictTraversal(configDict)
28
- tree01.startBFS(anoyDict)
25
+ # configのload終了。
26
+ tree01.dirDFS(anoyPath)
29
27
 
30
28
  if(__name__=="__main__"):
31
29
  main()
@@ -1,42 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: anoy
3
- Version: 0.1.2
4
- Summary: This is a library that provides simple type checking for YAML.
5
- Author-email: masaniki <masaniki.software@gmail.com>
6
- License-Expression: MIT
7
- Project-URL: Repository, https://github.com/masaniki/annotation-yaml
8
- Project-URL: Documentation, https://github.com/masaniki/annotation-yaml/tree/master/docs
9
- Project-URL: Issues, https://github.com/masaniki/annotation-yaml/issues
10
- Keywords: text,YAML,CLI,type,check
11
- Classifier: Development Status :: 1 - Planning
12
- Classifier: Environment :: Console
13
- Classifier: Natural Language :: Japanese
14
- Classifier: Natural Language :: English
15
- Classifier: Topic :: Text Processing
16
- Requires-Python: >=3.9
17
- Description-Content-Type: text/markdown
18
- License-File: LICENSE.txt
19
- Requires-Dist: PyYAML
20
- Dynamic: license-file
21
-
22
- # fileの説明
23
- # 原文
24
- [Original README](docs/README_JP.md)
25
-
26
- ## /docs
27
- documentを格納するdirecotry.
28
-
29
- ## /src
30
- ここでmoduleを開発する。
31
-
32
- ## testUnit
33
- 単体testを行うdirectory。
34
-
35
- ## testSystem
36
- 結合testを行うdirectory。
37
-
38
- ## sanbox.py
39
- pythonの挙動を確認する。
40
-
41
-
42
-
@@ -1,9 +0,0 @@
1
- anoyError.py,sha256=4smZo02X8YOv407FRyc12ADXW8foNrFr0pv0pRoQUxo,620
2
- cli.py,sha256=_eAJeiI8b6JvWhASDbCOuAspmo2qCaDvvVnnZ8SSCxU,1005
3
- dictTraversal.py,sha256=TaAhUUMUUUvRgZzEpiDUbkQp6WvF42QsTu_2NLGWNos,7793
4
- anoy-0.1.2.dist-info/licenses/LICENSE.txt,sha256=nsHvySI1U7YZgAX4K3rJsWli_GfXy2syPI-MtGPwjlo,1062
5
- anoy-0.1.2.dist-info/METADATA,sha256=2r-2BU8yC0MxDT7LXDGre5-fGpmYBP3kM6XU1eY4M_g,1130
6
- anoy-0.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
7
- anoy-0.1.2.dist-info/entry_points.txt,sha256=_lpL6R97giGseZcV0r5QwdX4zRwoZLzRHUxyBZl_iYI,34
8
- anoy-0.1.2.dist-info/top_level.txt,sha256=Ml0_35nDdV_uFyw0sUsvSho29qLH9ppoSOUQKuInsA4,28
9
- anoy-0.1.2.dist-info/RECORD,,
@@ -1,3 +0,0 @@
1
- anoyError
2
- cli
3
- dictTraversal
dictTraversal.py DELETED
@@ -1,187 +0,0 @@
1
- import yaml
2
-
3
- from anoyError import AnnotationYamlError,ConfigurationYamlError,AnnotationYamlTypeError
4
-
5
- class DictTraversal():
6
- """
7
- @Summ: 辞書型の中身を探索するclass
8
-
9
- @InsVars:
10
- _configDict:
11
- @Summ: configuration yamlの中身。
12
- @Type: Dict
13
- _configVisit:
14
- @Summ: configuration yaml内のannotationKeyを使った否かを記録する変数。
15
- @Desc: {annotationKey(str):訪れた⇒True(bool)}
16
- @Type: Dict
17
- _visitQueue:
18
- @Summ: 探索queue
19
- @Desc: BFSなのでFIFO。
20
- @Type: List
21
- _pathQueue:
22
- @Summ: 探索する要素の相対pathを格納する。
23
- @Desc:
24
- - visitQueueと要素番号を共有する。
25
- - []でroot要素を表す。
26
- @Type: List
27
- """
28
- def __init__(self,configDict:dict):
29
- """
30
- @Summ: constructor.
31
- """
32
- self._configDict=configDict
33
- self._visitQueue=[]
34
- self._pathQueue=[]
35
- self._configVisit={key:False for key in configDict.keys()}
36
-
37
- def startBFS(self,anoyDict:dict):
38
- """
39
- @Summ: 幅優先探索を開始する関数。
40
-
41
- @Desc:
42
- - list型は単純に探索する。
43
- - dict型は型確認しながら探索する。
44
- - visitQueueには(key(str),value(any))のtupleを入れる。
45
- - list型の時は、(key(int),value(any))になる。
46
- @Args:
47
- anoyDict:
48
- @Summ: annotation yamlのdict型。
49
- """
50
- self._visitQueue=[(None,anoyDict)]
51
- self._pathQueue=[[]]
52
- while(True):
53
- if(self._visitQueue==[]):
54
- break
55
- key,value=self._visitQueue.pop(0)
56
- pathList=self._pathQueue.pop(0)
57
- print(key,value)
58
- print(pathList)
59
- self.typeCheck(key,value,pathList)
60
-
61
- def typeCheck(self,parentKey:str|None,childValue,path:list):
62
- """
63
- "@Summ": annoDict内を探索する関数。
64
-
65
- "@Desc":
66
- - 型確認は"!ParentKey"と"!ChildValue"の2つだ。
67
- - annotationKeyが親でない時は、"!ParentKey"も"!ChildValue"も効力を発揮しないので無視。
68
- - ただし、annoKeyがNoneの時は、"!ParentKey"が効力を発揮する場合がある。
69
- - !ChildValueが無い時は何もしない。
70
- - !ChildValueが無い時は、childValue=nullとして考える。
71
- - valueがanoyDict型の時のみ、!ParentKeyの型確認が行われる。
72
-
73
- "@Args":
74
- parentKey:
75
- "@Summ": 探索するdict型のkey。
76
- "@Desc":
77
- - nullは親要素が存在しないことを表す(つまりvalueがroot要素である)。
78
- "@Type":
79
- Union:
80
- - Str
81
- - null
82
- childValue:
83
- "@Summ": 探索するdict型のvalue。
84
- "@Type": Any
85
- path:
86
- @Summ: 今まで経由したkeyのlist。
87
- @Type: List
88
- "@Error":
89
- - AnnotationYamlError
90
- - AnnotationYamlTypeError
91
- - ConfigurationYamlError
92
- """
93
- if(parentKey is None):
94
- confChildVal=None #confChild=Noneの時は型確認をしない。
95
- elif(type(parentKey)!=str):
96
- confChildVal=None
97
- elif(parentKey[0]=="@"):
98
- confChild=self._configDict.get(parentKey)
99
- if(confChild is None):
100
- raise ConfigurationYamlError(f"`{parentKey}` is not defined.")
101
- confChildVal=confChild.get("!ChildValue")
102
- else:
103
- confChildVal=None
104
- if(type(confChildVal)==dict):
105
- #Enum型の型確認。
106
- # config yaml側の型確認。
107
- confKeyList=list(confChildVal.keys())
108
- if(len(confKeyList)!=1 or confKeyList[0]!="Enum"):
109
- raise ConfigurationYamlError(f"`{parentKey}` has invalid definition.")
110
- confValueList=confChildVal["Enum"]
111
- if(type(confValueList)!=list):
112
- raise ConfigurationYamlError(f"`{parentKey}` has invalid definition.")
113
- # annotaion yaml側の型確認。
114
- for i in range(len(confValueList)):
115
- if(childValue==confValueList[i]):
116
- return
117
- raise AnnotationYamlTypeError("Enum",path)
118
- elif(type(childValue)==bool):
119
- if((confChildVal is None) or confChildVal=="Bool"):
120
- return
121
- else:
122
- raise AnnotationYamlTypeError("Bool",path)
123
- elif(type(childValue)==str):
124
- if((confChildVal is None or confChildVal=="Str")):
125
- return
126
- else:
127
- raise AnnotationYamlTypeError("Str",path)
128
- elif(type(childValue)==int):
129
- if((confChildVal is None) or confChildVal=="Int"):
130
- return
131
- else:
132
- raise AnnotationYamlTypeError("Int",path)
133
- elif(type(childValue)==float):
134
- if((confChildVal is None) or confChildVal=="Float"):
135
- return
136
- else:
137
- raise AnnotationYamlTypeError("Float",path)
138
- elif(type(childValue)==list):
139
- if((confChildVal is None) or confChildVal=="List"):
140
- for i in range(len(childValue)):
141
- newPath=path+[i]
142
- self._visitQueue.append((i,childValue[i]))
143
- self._pathQueue.append(newPath)
144
- return
145
- else:
146
- raise AnnotationYamlTypeError("List",path)
147
- elif(type(childValue)==dict):
148
- if(confChildVal is None):
149
- for key,childValue in childValue.items():
150
- newPath=path+[key]
151
- self._visitQueue.append((key,childValue))
152
- self._pathQueue.append(newPath)
153
- return
154
- elif(confChildVal=="FreeDict"):
155
- for key,childValue in childValue.items():
156
- newPath=path+[key]
157
- self._visitQueue.append((key,childValue))
158
- self._pathQueue.append(newPath)
159
- if(key[0]=="@"):
160
- raise AnnotationYamlTypeError("FreeDict",path)
161
- return
162
- elif(confChildVal=="AnnoDict"):
163
- for key,childValue in childValue.items():
164
- newPath=path+[key]
165
- self._visitQueue.append((key,childValue))
166
- self._pathQueue.append(newPath)
167
- confParent=self._configDict[key].get("!ParentKey")
168
- if(key[0]!="@"):
169
- raise AnnotationYamlTypeError("AnnoDict",path)
170
- if(confParent is None):
171
- pass
172
- elif(parentKey not in confParent):
173
- raise AnnotationYamlTypeError("AnnoDict",path)
174
- return
175
- else:
176
- raise AnnotationYamlError(f" invalid value is found at:\n `{path}`.")
177
-
178
- if(__name__=="__main__"):
179
- configPath=r"C:\Users\tomot\Backup\sourcecode\python\projects\annotation_yaml\tests\unit\case01\config01.yaml"
180
- anoyPath=r"C:\Users\tomot\Backup\sourcecode\python\projects\annotation_yaml\tests\unit\case01\int_false.yaml"
181
- with open(configPath,mode="r",encoding="utf-8") as f:
182
- configDict=yaml.safe_load(f)
183
- with open(anoyPath,mode="r",encoding="utf-8") as f:
184
- anoyDict=yaml.safe_load(f)
185
- tree01=DictTraversal(configDict)
186
- tree01.startBFS(anoyDict)
187
-
File without changes