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