Skip to content

Commit 0270dfa

Browse files
committed
fixes #485
1 parent 9857cf4 commit 0270dfa

File tree

5 files changed

+118
-68
lines changed

5 files changed

+118
-68
lines changed

CHANGELOG.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,62 @@
22

33
<!-- do not remove -->
44

5+
## 1.5.26
6+
7+
8+
### Bugs Squashed
9+
10+
- UnicodeDecodeError occurs when reading `settings.ini` file containing CJK characters on Windows, due to missing encoding parameter ([#483](https://github.com/fastai/fastcore/issues/483))
11+
- When I tried to preview/test/prepare an nbdev project with `settings.ini` (UTF-8 encorded) which containing some CKJ (or maybe other non-ascii) characters, an error such as `UnicodeDecodeError: 'cp932' codec can't decode byte 0x82 in position 725: illegal multibyte sequence` ocurred.
12+
13+
<details><summary>Example of settings and full error message</summary>
14+
15+
When a setting file containing a line like
16+
```
17+
description = サンプル プロジェクト (sample project)
18+
```
19+
20+
and nbdev_* command executed, output is like below:
21+
```
22+
$ nbdev_preview.exe
23+
Traceback (most recent call last):
24+
File "C:\Users\<user_home>\AppData\Local\Programs\Python\Python310\lib\runpy.py", line 196, in _run_module_as_main
25+
return _run_code(code, main_globals, None,
26+
File "C:\Users\<user_home>\AppData\Local\Programs\Python\Python310\lib\runpy.py", line 86, in _run_code
27+
exec(code, run_globals)
28+
File "<path_to_venv>\Scripts\nbdev_preview.exe\__main__.py", line 7, in <module>
29+
File "<path_to_venv>\lib\site-packages\fastcore\script.py", line 119, in _f
30+
return tfunc(**merge(args, args_from_prog(func, xtra)))
31+
File "<path_to_venv>\lib\site-packages\nbdev\quarto.py", line 278, in preview
32+
nbdev_quarto.__wrapped__(path, preview=True, **kwargs)
33+
File "<path_to_venv>\lib\site-packages\nbdev\quarto.py", line 256, in nbdev_quarto
34+
nbdev.doclinks._build_modidx(skip_exists=True)
35+
File "<path_to_venv>\lib\site-packages\nbdev\doclinks.py", line 74, in _build_modidx
36+
if dest is None: dest = get_config().lib_path
37+
File "<path_to_venv>\lib\site-packages\nbdev\config.py", line 199, in get_config
38+
cfg = Config(cfg_file.parent, cfg_file.name, extra_files=extra_files, types=_types)
39+
File "<path_to_venv>\lib\site-packages\fastcore\foundation.py", line 258, in __init__
40+
found = [Path(o) for o in self._cfg.read(L(extra_files)+[self.config_file])]#, encoding='utf-8')]
41+
File "C:\Users\<user_home>\AppData\Local\Programs\Python\Python310\lib\configparser.py", line 698, in read
42+
self._read(fp, filename)
43+
File "C:\Users\<user_home>\AppData\Local\Programs\Python\Python310\lib\configparser.py", line 1021, in _read
44+
for lineno, line in enumerate(fp, start=1):
45+
UnicodeDecodeError: 'cp932' codec can't decode byte 0x82 in position 725: illegal multibyte sequence
46+
```
47+
48+
Version info:
49+
Operating system: Windows 11 Pro (Japanese)
50+
Python 3.10.6
51+
nbdev 2.1.7
52+
53+
</details>
54+
55+
This error is likely caused due to no encoding being specified here:
56+
https://github.com/fastai/fastcore/blob/894bf94a3fcab91c85f05bc9a974a747533e9040/fastcore/foundation.py#L258
57+
58+
The error seems to be resolved by adding `encoding='utf-8'` to the argument of the ConfigParser.read() method.
59+
60+
561
## 1.5.25
662

763
### New Features

fastcore/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.5.25"
1+
__version__ = "1.5.26"

fastcore/docments.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,22 @@ def parse_docstring(sym):
3434
docs = docstring(sym)
3535
return AttrDict(**docscrape.NumpyDocString(docstring(sym)))
3636

37-
# %% ../nbs/06_docments.ipynb 17
37+
# %% ../nbs/06_docments.ipynb 16
3838
def isdataclass(s):
3939
"Check if `s` is a dataclass but not a dataclass' instance"
4040
return is_dataclass(s) and isclass(s)
4141

42-
# %% ../nbs/06_docments.ipynb 18
42+
# %% ../nbs/06_docments.ipynb 17
4343
def get_dataclass_source(s):
4444
"Get source code for dataclass `s`"
4545
return getsource(s) if not getattr(s, "__module__") == '__main__' else ""
4646

47-
# %% ../nbs/06_docments.ipynb 19
47+
# %% ../nbs/06_docments.ipynb 18
4848
def get_source(s):
4949
"Get source code for string, function object or dataclass `s`"
5050
return getsource(s) if isfunction(s) or ismethod(s) else get_dataclass_source(s) if isdataclass(s) else s
5151

52-
# %% ../nbs/06_docments.ipynb 20
52+
# %% ../nbs/06_docments.ipynb 19
5353
def _parses(s):
5454
"Parse Python code in string, function object or dataclass `s`"
5555
return parse(dedent(get_source(s)))
@@ -78,10 +78,10 @@ def _param_locs(s, returns=True):
7878
return res
7979
return None
8080

81-
# %% ../nbs/06_docments.ipynb 21
81+
# %% ../nbs/06_docments.ipynb 20
8282
empty = Parameter.empty
8383

84-
# %% ../nbs/06_docments.ipynb 22
84+
# %% ../nbs/06_docments.ipynb 21
8585
def _get_comment(line, arg, comments, parms):
8686
if line in comments: return comments[line].strip()
8787
line -= 1
@@ -95,7 +95,7 @@ def _get_full(anno, name, default, docs):
9595
if anno==empty and default!=empty: anno = type(default)
9696
return AttrDict(docment=docs.get(name), anno=anno, default=default)
9797

98-
# %% ../nbs/06_docments.ipynb 23
98+
# %% ../nbs/06_docments.ipynb 22
9999
def _merge_doc(dm, npdoc):
100100
if not npdoc: return dm
101101
if not dm.anno or dm.anno==empty: dm.anno = npdoc.type
@@ -108,14 +108,14 @@ def _merge_docs(dms, npdocs):
108108
if 'return' in dms: params['return'] = _merge_doc(dms['return'], npdocs['Returns'])
109109
return params
110110

111-
# %% ../nbs/06_docments.ipynb 24
111+
# %% ../nbs/06_docments.ipynb 23
112112
def _get_property_name(p):
113113
"Get the name of property `p`"
114114
if hasattr(p, 'fget'):
115115
return p.fget.func.__qualname__ if hasattr(p.fget, 'func') else p.fget.__qualname__
116116
else: return next(iter(re.findall(r'\'(.*)\'', str(p)))).split('.')[-1]
117117

118-
# %% ../nbs/06_docments.ipynb 25
118+
# %% ../nbs/06_docments.ipynb 24
119119
def get_name(obj):
120120
"Get the name of `obj`"
121121
if hasattr(obj, '__name__'): return obj.__name__
@@ -124,7 +124,7 @@ def get_name(obj):
124124
elif type(obj)==property: return _get_property_name(obj)
125125
else: return str(obj).split('.')[-1]
126126

127-
# %% ../nbs/06_docments.ipynb 27
127+
# %% ../nbs/06_docments.ipynb 26
128128
def qual_name(obj):
129129
"Get the qualified name of `obj`"
130130
if hasattr(obj,'__qualname__'): return obj.__qualname__
@@ -161,8 +161,8 @@ def docments(elt, full=False, **kwargs):
161161

162162
def _update_docments(f, r):
163163
if hasattr(f, '__delwrap__'): _update_docments(f.__delwrap__, r)
164-
r.update({k:v for k,v in _docments(f, **kwargs).items()
165-
if k in params and (full or v.get('docment',None))})
164+
r.update({k:v for k,v in _docments(f, **kwargs).items() if k in params
165+
and (v.get('docment', None) or not nested_idx(r, k, 'docment'))})
166166

167167
_update_docments(elt, r)
168168
if not full: r = {k:v['docment'] for k,v in r.items()}

nbs/06_docments.ipynb

Lines changed: 48 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -205,13 +205,6 @@
205205
"parse_docstring(add_np)"
206206
]
207207
},
208-
{
209-
"cell_type": "markdown",
210-
"metadata": {},
211-
"source": [
212-
"## Usage"
213-
]
214-
},
215208
{
216209
"cell_type": "code",
217210
"execution_count": null,
@@ -398,6 +391,13 @@
398391
"assert qual_name(docscrape) == 'fastcore.docscrape'"
399392
]
400393
},
394+
{
395+
"cell_type": "markdown",
396+
"metadata": {},
397+
"source": [
398+
"## Docments"
399+
]
400+
},
401401
{
402402
"cell_type": "code",
403403
"execution_count": null,
@@ -441,8 +441,8 @@
441441
"\n",
442442
" def _update_docments(f, r):\n",
443443
" if hasattr(f, '__delwrap__'): _update_docments(f.__delwrap__, r)\n",
444-
" r.update({k:v for k,v in _docments(f, **kwargs).items()\n",
445-
" if k in params and (full or v.get('docment',None))})\n",
444+
" r.update({k:v for k,v in _docments(f, **kwargs).items() if k in params\n",
445+
" and (v.get('docment', None) or not nested_idx(r, k, 'docment'))})\n",
446446
"\n",
447447
" _update_docments(elt, r)\n",
448448
" if not full: r = {k:v['docment'] for k,v in r.items()}\n",
@@ -465,17 +465,15 @@
465465
"data": {
466466
"text/markdown": [
467467
"```json\n",
468-
"{ 'a': 'The first operand',\n",
469-
" 'b': 'This is the second of the operands to the *addition* operator.\\n'\n",
470-
" 'Note that passing a negative value here is the equivalent of the '\n",
471-
" '*subtraction* operator.',\n",
472-
" 'return': \"The result is calculated using Python's builtin `+` operator.\"}\n",
468+
"{ 'a': 'the 1st number to add',\n",
469+
" 'b': 'the 2nd number to add',\n",
470+
" 'return': 'the result of adding `a` to `b`'}\n",
473471
"```"
474472
],
475473
"text/plain": [
476-
"{'a': 'The first operand',\n",
477-
" 'b': 'This is the second of the operands to the *addition* operator.\\nNote that passing a negative value here is the equivalent of the *subtraction* operator.',\n",
478-
" 'return': \"The result is calculated using Python's builtin `+` operator.\"}"
474+
"{'a': 'the 1st number to add',\n",
475+
" 'b': 'the 2nd number to add',\n",
476+
" 'return': 'the result of adding `a` to `b`'}"
479477
]
480478
},
481479
"execution_count": null,
@@ -484,6 +482,13 @@
484482
}
485483
],
486484
"source": [
485+
"def add(\n",
486+
" a:int, # the 1st number to add\n",
487+
" b=0, # the 2nd number to add\n",
488+
")->int: # the result of adding `a` to `b`\n",
489+
" \"The sum of two numbers.\"\n",
490+
" return a+b\n",
491+
"\n",
487492
"docments(add)"
488493
]
489494
},
@@ -505,27 +510,21 @@
505510
"```json\n",
506511
"{ 'a': { 'anno': 'int',\n",
507512
" 'default': <class 'inspect._empty'>,\n",
508-
" 'docment': 'The first operand'},\n",
509-
" 'b': { 'anno': 'int',\n",
510-
" 'default': <class 'inspect._empty'>,\n",
511-
" 'docment': 'This is the second of the operands to the *addition* '\n",
512-
" 'operator.\\n'\n",
513-
" 'Note that passing a negative value here is the equivalent '\n",
514-
" 'of the *subtraction* operator.'},\n",
513+
" 'docment': 'the 1st number to add'},\n",
514+
" 'b': { 'anno': <class 'int'>,\n",
515+
" 'default': 0,\n",
516+
" 'docment': 'the 2nd number to add'},\n",
515517
" 'return': { 'anno': 'int',\n",
516518
" 'default': <class 'inspect._empty'>,\n",
517-
" 'docment': \"The result is calculated using Python's builtin `+` \"\n",
518-
" 'operator.'}}\n",
519+
" 'docment': 'the result of adding `a` to `b`'}}\n",
519520
"```"
520521
],
521522
"text/plain": [
522-
"{'a': {'docment': 'The first operand',\n",
523-
" 'anno': 'int',\n",
524-
" 'default': inspect._empty},\n",
525-
" 'b': {'docment': 'This is the second of the operands to the *addition* operator.\\nNote that passing a negative value here is the equivalent of the *subtraction* operator.',\n",
523+
"{'a': {'docment': 'the 1st number to add',\n",
526524
" 'anno': 'int',\n",
527525
" 'default': inspect._empty},\n",
528-
" 'return': {'docment': \"The result is calculated using Python's builtin `+` operator.\",\n",
526+
" 'b': {'docment': 'the 2nd number to add', 'anno': int, 'default': 0},\n",
527+
" 'return': {'docment': 'the result of adding `a` to `b`',\n",
529528
" 'anno': 'int',\n",
530529
" 'default': inspect._empty}}"
531530
]
@@ -557,11 +556,11 @@
557556
"```json\n",
558557
"{ 'anno': <class 'int'>,\n",
559558
" 'default': <class 'inspect._empty'>,\n",
560-
" 'docment': 'The first operand'}\n",
559+
" 'docment': 'the 1st number to add'}\n",
561560
"```"
562561
],
563562
"text/plain": [
564-
"{'docment': 'The first operand', 'anno': int, 'default': inspect._empty}"
563+
"{'docment': 'the 1st number to add', 'anno': int, 'default': inspect._empty}"
565564
]
566565
},
567566
"execution_count": null,
@@ -663,11 +662,11 @@
663662
"data": {
664663
"text/markdown": [
665664
"```json\n",
666-
"{'a': 'First operand', 'b': '2nd operand'}\n",
665+
"{'a': 'First operand', 'b': '2nd operand', 'return': None}\n",
667666
"```"
668667
],
669668
"text/plain": [
670-
"{'a': 'First operand', 'b': '2nd operand'}"
669+
"{'a': 'First operand', 'b': '2nd operand', 'return': None}"
671670
]
672671
},
673672
"execution_count": null,
@@ -688,11 +687,11 @@
688687
"data": {
689688
"text/markdown": [
690689
"```json\n",
691-
"{'return': 'Integral result of addition operator'}\n",
690+
"{'return': 'Integral result of addition operator', 'self': None}\n",
692691
"```"
693692
],
694693
"text/plain": [
695-
"{'return': 'Integral result of addition operator'}"
694+
"{'self': None, 'return': 'Integral result of addition operator'}"
696695
]
697696
},
698697
"execution_count": null,
@@ -855,7 +854,7 @@
855854
" foo:str, # docment for parameter foo\n",
856855
" ):...\n",
857856
" \n",
858-
"test_eq(docments(_F.class_method), {'foo': 'docment for parameter foo'})"
857+
"test_eq(docments(_F.class_method), {'foo': 'docment for parameter foo', 'return': None})"
859858
]
860859
},
861860
{
@@ -874,18 +873,6 @@
874873
"from fastcore.meta import delegates"
875874
]
876875
},
877-
{
878-
"cell_type": "code",
879-
"execution_count": null,
880-
"metadata": {},
881-
"outputs": [],
882-
"source": [
883-
"def _a(a:int=2): return a # First\n",
884-
"\n",
885-
"@delegates(_a)\n",
886-
"def _b(b:str, **kwargs): return b, (_a(**kwargs)) # Second"
887-
]
888-
},
889876
{
890877
"cell_type": "code",
891878
"execution_count": null,
@@ -895,11 +882,11 @@
895882
"data": {
896883
"text/markdown": [
897884
"```json\n",
898-
"{'a': 'First', 'b': 'Second'}\n",
885+
"{'a': 'First', 'b': 'Second', 'return': None}\n",
899886
"```"
900887
],
901888
"text/plain": [
902-
"{'a': 'First', 'b': 'Second'}"
889+
"{'a': 'First', 'return': None, 'b': 'Second'}"
903890
]
904891
},
905892
"execution_count": null,
@@ -908,6 +895,11 @@
908895
}
909896
],
910897
"source": [
898+
"def _a(a:int=2): return a # First\n",
899+
"\n",
900+
"@delegates(_a)\n",
901+
"def _b(b:str, **kwargs): return b, (_a(**kwargs)) # Second\n",
902+
"\n",
911903
"docments(_b)"
912904
]
913905
},
@@ -923,7 +915,9 @@
923915
"\n",
924916
"@delegates(_c)\n",
925917
"def _d(c:int, # First\n",
926-
" b:str, **kwargs): return c, _c(b, **kwargs)"
918+
" b:str, **kwargs\n",
919+
" )->int:\n",
920+
" return c, _c(b, **kwargs)"
927921
]
928922
},
929923
{
@@ -934,7 +928,7 @@
934928
"source": [
935929
"#|hide\n",
936930
"test_eq(docments(_c, full=True)['b']['docment'],'Second')\n",
937-
"test_eq(docments(_d, full=True)['b']['docment'],None)\n",
931+
"test_eq(docments(_d, full=True)['b']['docment'],'Second')\n",
938932
"_argset = {'a', 'b', 'c', 'return'}\n",
939933
"test_eq(docments(_d, full=True).keys() & _argset, _argset) # _d has the args a,b,c and return"
940934
]

settings.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ author = Jeremy Howard and Sylvain Gugger
77
author_email = [email protected]
88
copyright = fast.ai
99
branch = master
10-
version = 1.5.25
10+
version = 1.5.26
1111
min_python = 3.7
1212
audience = Developers
1313
language = English

0 commit comments

Comments
 (0)