Browse Source

explicitly specify UTF-8 encoding when reading/writing files (fixes #4364)

Falko Schindler 2 months ago
parent
commit
e064e56d22

+ 1 - 1
examples/webserial/main.py

@@ -27,7 +27,7 @@ def page():
         'button': False,
     }
 
-    ui.add_body_html(f'<script>{(Path(__file__).parent / "script.js").read_text()}</script>')
+    ui.add_body_html(f'<script>{(Path(__file__).parent / "script.js").read_text(encoding="utf-8")}</script>')
 
     ui.button('Connect', on_click=connect).bind_visibility_from(state, 'connected', value=False)
     ui.button('Disconnect', on_click=disconnect).bind_visibility_from(state, 'connected')

+ 1 - 1
fetch_google_fonts.py

@@ -39,4 +39,4 @@ for font_url in re.findall(r'url\((.*?)\)', css):
 css = css.replace('https://fonts.gstatic.com/s/materialicons/v140', 'fonts')
 css = css.replace('https://fonts.gstatic.com/s/roboto/v30', 'fonts')
 css = css.replace("'", '"')
-Path('nicegui/static/fonts.css').write_text(css)
+Path('nicegui/static/fonts.css').write_text(css, encoding='utf-8')

+ 1 - 1
fetch_languages.py

@@ -3,7 +3,7 @@ from pathlib import Path
 
 codes = sorted(path.name.split('.')[0] for path in Path('nicegui/static/lang').glob('*.umd.prod.js'))
 
-with (Path(__file__).parent / 'nicegui' / 'language.py').open('w') as f:
+with (Path(__file__).parent / 'nicegui' / 'language.py').open('w', encoding='utf-8') as f:
     f.write('from typing import Literal\n')
     f.write('\n')
     f.write('Language = Literal[\n')

+ 3 - 3
fetch_sponsors.py

@@ -98,7 +98,7 @@ Path('website/sponsors.json').write_text(json.dumps({
     'top': [s['login'] for s in sponsors if s['tier_amount'] >= 100 and not s['tier_is_one_time']],
     'total': len(sponsors),
     'contributors': len(contributors),
-}, indent=2) + '\n')
+}, indent=2) + '\n', encoding='utf-8')
 
 sponsor_html = '<p align="center">\n'
 for sponsor in sponsors:
@@ -106,13 +106,13 @@ for sponsor in sponsors:
         sponsor_html += f'  <a href="{sponsor["url"]}"><img src="{sponsor["url"]}.png" width="50px" alt="{sponsor["name"]}" /></a>\n'
 sponsor_html += '</p>'
 readme_path = Path('README.md')
-readme_content = readme_path.read_text()
+readme_content = readme_path.read_text(encoding='utf-8')
 updated_content = re.sub(
     r'<!-- SPONSORS -->.*?<!-- SPONSORS -->',
     f'<!-- SPONSORS -->\n{sponsor_html}\n<!-- SPONSORS -->',
     readme_content,
     flags=re.DOTALL,
 )
-readme_path.write_text(updated_content)
+readme_path.write_text(updated_content, encoding='utf-8')
 
 print('README.md and sponsors.json updated successfully.')

+ 5 - 4
fetch_tailwind.py

@@ -53,11 +53,11 @@ def get_soup(url: str) -> BeautifulSoup:
     path = Path('/tmp/nicegui_tailwind') / url.split('/')[-1]
     path.parent.mkdir(parents=True, exist_ok=True)
     if path.exists():
-        html = path.read_text()
+        html = path.read_text(encoding='utf-8')
     else:
         req = requests.get(url, timeout=5)
         html = req.text
-        path.write_text(html)
+        path.write_text(html, encoding='utf-8')
     return BeautifulSoup(html, 'html.parser')
 
 
@@ -89,7 +89,8 @@ def generate_type_files(properties: List[Property]) -> None:
     for property_ in properties:
         if not property_.members:
             continue
-        with (Path(__file__).parent / 'nicegui' / 'tailwind_types' / f'{property_.snake_title}.py').open('w') as f:
+        with (Path(__file__).parent / 'nicegui' / 'tailwind_types' / f'{property_.snake_title}.py') \
+                .open('w', encoding='utf-8') as f:
             f.write('from typing import Literal\n')
             f.write('\n')
             f.write(f'{property_.pascal_title} = Literal[\n')
@@ -100,7 +101,7 @@ def generate_type_files(properties: List[Property]) -> None:
 
 def generate_tailwind_file(properties: List[Property]) -> None:
     """Generate the tailwind.py file."""
-    with (Path(__file__).parent / 'nicegui' / 'tailwind.py').open('w') as f:
+    with (Path(__file__).parent / 'nicegui' / 'tailwind.py').open('w', encoding='utf-8') as f:
         f.write('# pylint: disable=too-many-lines\n')
         f.write('from __future__ import annotations\n')
         f.write('\n')

+ 1 - 1
nicegui/dependencies.py

@@ -71,7 +71,7 @@ def register_vue_component(path: Path) -> Component:
         if key in vue_components and vue_components[key].path == path:
             return vue_components[key]
         assert key not in vue_components, f'Duplicate VUE component {key}'
-        v = vbuild.VBuild(path.name, path.read_text())
+        v = vbuild.VBuild(path.name, path.read_text(encoding='utf-8'))
         vue_components[key] = VueComponent(key=key, name=name, path=path, html=v.html, script=v.script, style=v.style)
         return vue_components[key]
     if path.suffix == '.js':

+ 1 - 1
nicegui/error.py

@@ -5,7 +5,7 @@ from .elements.column import Column as column
 from .elements.html import Html as html
 from .elements.label import Label as label
 
-SAD_FACE_SVG = (Path(__file__).parent / 'static' / 'sad_face.svg').read_text()
+SAD_FACE_SVG = (Path(__file__).parent / 'static' / 'sad_face.svg').read_text(encoding='utf-8')
 
 
 def error_content(status_code: int, exception: Union[str, Exception] = '') -> None:

+ 2 - 2
nicegui/functions/style.py

@@ -23,7 +23,7 @@ def add_css(content: Union[str, Path]) -> None:
     :param content: CSS content (string or file path)
     """
     if helpers.is_file(content):
-        content = Path(content).read_text()
+        content = Path(content).read_text(encoding='utf-8')
     add_head_html(f'<style>{content}</style>')
 
 
@@ -41,7 +41,7 @@ def add_scss(content: Union[str, Path], *, indented: bool = False) -> None:
         raise ImportError('Please run "pip install libsass" to use SASS or SCSS.')
 
     if helpers.is_file(content):
-        content = Path(content).read_text()
+        content = Path(content).read_text(encoding='utf-8')
     add_css(sass.compile(string=str(content).strip(), indented=indented))
 
 

+ 11 - 11
npm.py

@@ -53,7 +53,7 @@ def download_buffered(url: str) -> Path:
     return filepath
 
 
-DEPENDENCIES = (root_path / 'DEPENDENCIES.md').open('w')
+DEPENDENCIES = (root_path / 'DEPENDENCIES.md').open('w', encoding='utf-8')
 DEPENDENCIES.write('# Included Web Dependencies\n\n')
 KNOWN_LICENSES = {
     'UNKNOWN': 'UNKNOWN',
@@ -67,7 +67,7 @@ KNOWN_LICENSES = {
 # Create a hidden folder to work in.
 tmp = cleanup(root_path / '.npm')
 
-dependencies: Dict[str, dict] = json.loads((root_path / 'npm.json').read_text())
+dependencies: Dict[str, dict] = json.loads((root_path / 'npm.json').read_text(encoding='utf-8'))
 for key, dependency in dependencies.items():
     if names is not None and key not in names:
         continue
@@ -77,7 +77,7 @@ for key, dependency in dependencies.items():
 
     # Get package info from NPM.
     package_name = dependency.get('package', key)
-    npm_data = json.loads(download_buffered(f'https://registry.npmjs.org/{package_name}').read_text())
+    npm_data = json.loads(download_buffered(f'https://registry.npmjs.org/{package_name}').read_text(encoding='utf-8'))
     npm_version = dependency.get('version') or dependency.get('version', npm_data['dist-tags']['latest'])
     npm_tarball = npm_data['versions'][npm_version]['dist']['tarball']
     license_ = 'UNKNOWN'
@@ -91,7 +91,7 @@ for key, dependency in dependencies.items():
     # Handle the special case of tailwind. Hopefully remove this soon.
     if 'download' in dependency:
         download_path = download_buffered(dependency['download'])
-        content = download_path.read_text()
+        content = download_path.read_text(encoding='utf-8')
         MSG = (
             'console.warn("cdn.tailwindcss.com should not be used in production. '
             'To use Tailwind CSS in production, install it as a PostCSS plugin or use the Tailwind CLI: '
@@ -100,7 +100,7 @@ for key, dependency in dependencies.items():
         if MSG not in content:
             raise ValueError(f'Expected to find "{MSG}" in {download_path}')
         content = content.replace(MSG, '')
-        prepare(destination / dependency['rename']).write_text(content)
+        prepare(destination / dependency['rename']).write_text(content, encoding='utf-8')
 
     # Download and extract.
     tgz_file = prepare(Path(tmp, key, f'{key}.tgz'))
@@ -124,25 +124,25 @@ for key, dependency in dependencies.items():
             Path(tmp, key, extracted.name).rename(newfile)
 
             if 'GLTFLoader' in filename:
-                content = newfile.read_text()
+                content = newfile.read_text(encoding='utf-8')
                 MSG = '../utils/BufferGeometryUtils.js'
                 if MSG not in content:
                     raise ValueError(f'Expected to find "{MSG}" in {filename}')
                 content = content.replace(MSG, 'BufferGeometryUtils')
-                newfile.write_text(content)
+                newfile.write_text(content, encoding='utf-8')
 
             if 'DragControls.js' in filename:
-                content = newfile.read_text()
+                content = newfile.read_text(encoding='utf-8')
                 MSG = '_selected = findGroup( _intersections[ 0 ].object )'
                 if MSG not in content:
                     raise ValueError(f'Expected to find "{MSG}" in {filename}')
                 content = content.replace(MSG, MSG + ' || _intersections[ 0 ].object')
-                newfile.write_text(content)
+                newfile.write_text(content, encoding='utf-8')
 
             if 'mermaid.esm.min.mjs' in filename:
-                content = newfile.read_text()
+                content = newfile.read_text(encoding='utf-8')
                 content = re.sub(r'"\./chunks/mermaid.esm.min/(.*?)\.mjs"', r'"\1"', content)
-                newfile.write_text(content)
+                newfile.write_text(content, encoding='utf-8')
 
     # Delete destination folder if empty.
     if not any(destination.iterdir()):

+ 0 - 1
pyproject.toml

@@ -172,5 +172,4 @@ disable = [
     "W0102", # Dangerous default value as argument
     "W0718", # Catching too general exception
     "W1203", # Use % formatting in logging functions
-    "W1514" # Using open without explicitly specifying an encoding
 ]

+ 2 - 2
tests/test_download.py

@@ -25,7 +25,7 @@ def test_download_text_file(screen: Screen, test_route: str):  # pylint: disable
     screen.open('/')
     screen.click('Download')
     screen.wait(0.5)
-    assert (screen_plugin.DOWNLOAD_DIR / 'test.txt').read_text() == 'test'
+    assert (screen_plugin.DOWNLOAD_DIR / 'test.txt').read_text(encoding='utf-8') == 'test'
 
 
 def test_downloading_local_file_as_src(screen: Screen):
@@ -46,4 +46,4 @@ def test_download_raw_data(screen: Screen):
     screen.open('/')
     screen.click('download')
     screen.wait(0.5)
-    assert (screen_plugin.DOWNLOAD_DIR / 'test.txt').read_text() == 'test'
+    assert (screen_plugin.DOWNLOAD_DIR / 'test.txt').read_text(encoding='utf-8') == 'test'

+ 7 - 6
tests/test_storage.py

@@ -92,7 +92,7 @@ async def test_access_user_storage_from_fastapi(screen: Screen):
         assert response.status_code == 200
         assert response.text == '"OK"'
         await asyncio.sleep(0.5)  # wait for storage to be written
-        assert next(Path('.nicegui').glob('storage-user-*.json')).read_text('utf-8') == '{"msg":"yes"}'
+        assert next(Path('.nicegui').glob('storage-user-*.json')).read_text(encoding='utf-8') == '{"msg":"yes"}'
 
 
 def test_access_user_storage_on_interaction(screen: Screen):
@@ -106,7 +106,7 @@ def test_access_user_storage_on_interaction(screen: Screen):
     screen.open('/')
     screen.click('switch')
     screen.wait(0.5)
-    assert next(Path('.nicegui').glob('storage-user-*.json')).read_text('utf-8') == '{"test_switch":true}'
+    assert next(Path('.nicegui').glob('storage-user-*.json')).read_text(encoding='utf-8') == '{"test_switch":true}'
 
 
 def test_access_user_storage_from_button_click_handler(screen: Screen):
@@ -118,7 +118,8 @@ def test_access_user_storage_from_button_click_handler(screen: Screen):
     screen.open('/')
     screen.click('test')
     screen.wait(1)
-    assert next(Path('.nicegui').glob('storage-user-*.json')).read_text('utf-8') == '{"inner_function":"works"}'
+    assert \
+        next(Path('.nicegui').glob('storage-user-*.json')).read_text(encoding='utf-8') == '{"inner_function":"works"}'
 
 
 async def test_access_user_storage_from_background_task(screen: Screen):
@@ -131,7 +132,7 @@ async def test_access_user_storage_from_background_task(screen: Screen):
 
     screen.ui_run_kwargs['storage_secret'] = 'just a test'
     screen.open('/')
-    assert next(Path('.nicegui').glob('storage-user-*.json')).read_text('utf-8') == '{"subtask":"works"}'
+    assert next(Path('.nicegui').glob('storage-user-*.json')).read_text(encoding='utf-8') == '{"subtask":"works"}'
 
 
 def test_user_and_general_storage_is_persisted(screen: Screen):
@@ -165,7 +166,7 @@ def test_rapid_storage(screen: Screen):
     screen.open('/')
     screen.click('test')
     screen.wait(0.5)
-    assert Path('.nicegui', 'storage-general.json').read_text('utf-8') == '{"one":1,"two":2,"three":3}'
+    assert Path('.nicegui', 'storage-general.json').read_text(encoding='utf-8') == '{"one":1,"two":2,"three":3}'
 
 
 def test_tab_storage_is_local(screen: Screen):
@@ -277,7 +278,7 @@ def test_deepcopy(screen: Screen):
     screen.open('/')
     screen.should_contain('Loaded')
     screen.wait(0.5)
-    assert Path('.nicegui', 'storage-general.json').read_text('utf-8') == '{"a":{"b":0}}'
+    assert Path('.nicegui', 'storage-general.json').read_text(encoding='utf-8') == '{"a":{"b":0}}'
 
 
 def test_missing_storage_secret(screen: Screen):

+ 2 - 2
website/header.py

@@ -7,8 +7,8 @@ from . import svg
 from .search import Search
 from .star import add_star
 
-HEADER_HTML = (Path(__file__).parent / 'static' / 'header.html').read_text()
-STYLE_CSS = (Path(__file__).parent / 'static' / 'style.css').read_text()
+HEADER_HTML = (Path(__file__).parent / 'static' / 'header.html').read_text(encoding='utf-8')
+STYLE_CSS = (Path(__file__).parent / 'static' / 'style.css').read_text(encoding='utf-8')
 
 
 def add_head_html() -> None:

+ 1 - 1
website/main_page.py

@@ -8,7 +8,7 @@ from .examples import examples
 from .header import add_head_html, add_header
 from .style import example_link, features, heading, link_target, section_heading, subtitle, title
 
-SPONSORS = json.loads((Path(__file__).parent / 'sponsors.json').read_text())
+SPONSORS = json.loads((Path(__file__).parent / 'sponsors.json').read_text(encoding='utf-8'))
 
 
 def create() -> None:

+ 5 - 5
website/svg.py

@@ -3,11 +3,11 @@ from pathlib import Path
 from nicegui import ui
 
 PATH = Path(__file__).parent / 'static'
-HAPPY_FACE_SVG = (PATH / 'happy_face.svg').read_text()
-NICEGUI_WORD_SVG = (PATH / 'nicegui_word.svg').read_text()
-GITHUB_SVG = (PATH / 'github.svg').read_text()
-DISCORD_SVG = (PATH / 'discord.svg').read_text()
-REDDIT_SVG = (PATH / 'reddit.svg').read_text()
+HAPPY_FACE_SVG = (PATH / 'happy_face.svg').read_text(encoding='utf-8')
+NICEGUI_WORD_SVG = (PATH / 'nicegui_word.svg').read_text(encoding='utf-8')
+GITHUB_SVG = (PATH / 'github.svg').read_text(encoding='utf-8')
+DISCORD_SVG = (PATH / 'discord.svg').read_text(encoding='utf-8')
+REDDIT_SVG = (PATH / 'reddit.svg').read_text(encoding='utf-8')
 
 
 def face(half: bool = False) -> ui.html: