diff --git a/polyapi/poly_schemas.py b/polyapi/poly_schemas.py
index 30d5ab5..c370c77 100644
--- a/polyapi/poly_schemas.py
+++ b/polyapi/poly_schemas.py
@@ -121,7 +121,7 @@ def add_schema_file(
# Read current __init__.py content if it exists
init_content = ""
if os.path.exists(init_path):
- with open(init_path, "r") as f:
+ with open(init_path, "r", encoding='utf-8') as f:
init_content = f.read()
# Prepare new content to append to __init__.py
@@ -129,12 +129,12 @@ def add_schema_file(
# Use temporary files for atomic writes
# Write to __init__.py atomically
- with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=full_path, suffix=".tmp") as temp_init:
+ with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=full_path, suffix=".tmp", encoding='utf-8') as temp_init:
temp_init.write(new_init_content)
temp_init_path = temp_init.name
# Write to schema file atomically
- with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=full_path, suffix=".tmp") as temp_schema:
+ with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=full_path, suffix=".tmp", encoding='utf-8') as temp_schema:
temp_schema.write(schema_defs)
temp_schema_path = temp_schema.name
@@ -205,7 +205,7 @@ def create_schema(
def add_schema_to_init(full_path: str, spec: SchemaSpecDto):
init_the_init(full_path, code_imports="")
init_path = os.path.join(full_path, "__init__.py")
- with open(init_path, "a") as f:
+ with open(init_path, "a", encoding='utf-8') as f:
f.write(render_poly_schema(spec) + "\n\n")
diff --git a/polyapi/schema.py b/polyapi/schema.py
index 1523e7f..29ecbe3 100644
--- a/polyapi/schema.py
+++ b/polyapi/schema.py
@@ -93,7 +93,7 @@ def generate_schema_types(input_data: Dict, root=None):
with contextlib.redirect_stdout(None):
process_config(config, [tmp_input])
- with open(tmp_output) as f:
+ with open(tmp_output, encoding='utf-8') as f:
output = f.read()
output = clean_malformed_examples(output)
diff --git a/pyproject.toml b/pyproject.toml
index 53041fb..33131a4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.2", "wheel"]
[project]
name = "polyapi-python"
-version = "0.3.8"
+version = "0.3.9.dev1"
description = "The Python Client for PolyAPI, the IPaaS by Developers for Developers"
authors = [{ name = "Dan Fellin", email = "dan@polyapi.io" }]
dependencies = [
diff --git a/tests/test_schema.py b/tests/test_schema.py
index 223ec39..ae23ce3 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -1,5 +1,5 @@
import unittest
-from polyapi.schema import clean_malformed_examples, wrapped_generate_schema_types
+from polyapi.schema import clean_malformed_examples, wrapped_generate_schema_types, generate_schema_types
SCHEMA = {
"$schema": "http://json-schema.org/draft-06/schema#",
@@ -10,6 +10,14 @@
"definitions": {},
}
+CHARACTER_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-06/schema#",
+ "type": "object",
+ "properties": {"CHARACTER_SCHEMA_NAME": {"description": "This is — “bad”, right?", "type": "string"}},
+ "additionalProperties": False,
+ "definitions": {},
+}
+
APALEO_MALFORMED_EXAMPLE = 'from typing import List, TypedDict, Union\nfrom typing_extensions import Required\n\n\n# Body.\n# \n# example: {\n "from": "2024-04-21",\n "to": "2024-04-24",\n "grossDailyRate": {\n "amount": 160.0,\n "currency": "EUR"\n },\n "timeSlices": [\n {\n "blockedUnits": 3\n },\n {\n "blockedUnits": 0\n },\n {\n "blockedUnits": 7\n }\n ]\n}\n# x-readme-ref-name: ReplaceBlockModel\nBody = TypedDict(\'Body\', {\n # Start date and time from which the inventory will be blockedSpecify either a pure date or a date and time (without fractional second part) in UTC or with UTC offset as defined in ISO8601:2004\n # \n # Required property\n \'from\': Required[str],\n # End date and time until which the inventory will be blocked. Cannot be more than 5 years after the start date.Specify either a pure date or a date and time (without fractional second part) in UTC or with UTC offset as defined in ISO8601:2004\n # \n # Required property\n \'to\': Required[str],\n # x-readme-ref-name: MonetaryValueModel\n # \n # Required property\n \'grossDailyRate\': Required["_BodygrossDailyRate"],\n # The list of time slices\n # \n # Required property\n \'timeSlices\': Required[List["_BodytimeSlicesitem"]],\n}, total=False)\n\n\nclass _BodygrossDailyRate(TypedDict, total=False):\n """ x-readme-ref-name: MonetaryValueModel """\n\n amount: Required[Union[int, float]]\n """\n format: double\n\n Required property\n """\n\n currency: Required[str]\n """ Required property """\n\n\n\nclass _BodytimeSlicesitem(TypedDict, total=False):\n """ x-readme-ref-name: CreateBlockTimeSliceModel """\n\n blockedUnits: Required[Union[int, float]]\n """\n Number of units blocked for the time slice\n\n format: int32\n\n Required property\n """\n\n'
@@ -23,4 +31,10 @@ def test_fix_titles(self):
def test_clean_malformed_examples(self):
output = clean_malformed_examples(APALEO_MALFORMED_EXAMPLE)
- self.assertNotIn("# example: {", output)
\ No newline at end of file
+ self.assertNotIn("# example: {", output)
+
+ def test_character_encoding(self):
+ output = generate_schema_types(CHARACTER_SCHEMA, "Dict")
+ expected = 'from typing import TypedDict\n\n\nclass Dict(TypedDict, total=False):\n CHARACTER_SCHEMA_NAME: str\n """ This is — “bad”, right? """\n\n'
+ self.assertEqual(output, expected)
+
\ No newline at end of file