Repository: tefra/xsdata
Pull request #890 (2023-12-20 18:25:01)

Avoid enforcing double quotes on literal values
## 📒 Description

For a long time I wanted the literal values to use double quotes, but this piece of code has produced so many bugs,  it's not worth it.

I plan to introduce ruff format for the generated code and people who actually use the pycode serializer, I suspect they use their own code formatting anyway.

Resolves #xxxx

## 🔗 What I've Done

> Write a description of the steps taken to resolve the issue


## 💬 Comments

> A place to write any comments to the reviewer.
>

## 🛫 Checklist

- [ ] Updated docs
- [ ] Added unit-tests
- [ ] [Sample tests](https://github.com/tefra/xsdata-samples) pass
- [ ] [W3C tests](https://github.com/tefra/xsdata-w3c-tests) pass


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=890) **Quality Gate passed**  
Kudos, no new issues were introduced!

[0 New issues](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=890&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=890&resolved=false&inNewCodePeriod=true)  
No data about Coverage  
[0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=890&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=890)



Patch:
diff --git a/xsdata/utils/objects.py b/xsdata/utils/objects.py
index 12c1361b..54377bbb 100644
--- a/xsdata/utils/objects.py
+++ b/xsdata/utils/objects.py
@@ -1,8 +1,6 @@
 import math
-import re
 from typing import Any
 from xml.etree.ElementTree import QName
-from xml.sax.saxutils import quoteattr
 
 
 def update(obj: Any, **kwargs: Any):
@@ -21,18 +19,10 @@ def attrsetter(obj: Any, attr: str, value: Any):
 
 
 def literal_value(value: Any) -> str:
-    if isinstance(value, str):
-        return quoteattr(value.encode("unicode_escape").decode("ASCII"))
-
     if isinstance(value, float):
         return str(value) if math.isfinite(value) else f'float("{value}")'
 
     if isinstance(value, QName):
         return f'QName("{value.text}")'
 
-    if isinstance(value, bytes):
-        # Use the contents of the bytes verbatim, but ensure that it is
-        # wrapped in double quotes
-        return re.sub(r"b'(.*)'$", r'b"\g<1>"', repr(value))
-
-    return repr(value).replace("'", '"')
+    return repr(value)


Test patch:
diff --git a/tests/fixtures/annotations/units.py b/tests/fixtures/annotations/units.py
index 587edcb7..24fdf89f 100644
--- a/tests/fixtures/annotations/units.py
+++ b/tests/fixtures/annotations/units.py
@@ -5,7 +5,7 @@ __NAMESPACE__ = "http://domain.org/schema/model/units"
 
 
 class unit(Enum):
-    M = "m"
-    KG = "kg"
-    VALUE = "%"
-    NA = "NA"
+    M = 'm'
+    KG = 'kg'
+    VALUE = '%'
+    NA = 'NA'
diff --git a/tests/fixtures/artists/metadata.py b/tests/fixtures/artists/metadata.py
index d5afed05..01b9ad24 100644
--- a/tests/fixtures/artists/metadata.py
+++ b/tests/fixtures/artists/metadata.py
@@ -46,7 +46,7 @@ class Alias:
         }
     )
     value: str = field(
-        default="",
+        default='',
         metadata={
             "required": True,
         }
@@ -97,7 +97,7 @@ class Gender:
         }
     )
     value: str = field(
-        default="",
+        default='',
         metadata={
             "required": True,
         }
diff --git a/tests/fixtures/books/books.py b/tests/fixtures/books/books.py
index e058b849..04132ae6 100644
--- a/tests/fixtures/books/books.py
+++ b/tests/fixtures/books/books.py
@@ -76,7 +76,7 @@ class BookForm:
     )
     lang: str = field(
         init=False,
-        default="en",
+        default='en',
         metadata={
             "type": "Attribute",
         }
diff --git a/tests/fixtures/docstrings/accessible/schema.py b/tests/fixtures/docstrings/accessible/schema.py
index 215a5f20..bc4a82bd 100644
--- a/tests/fixtures/docstrings/accessible/schema.py
+++ b/tests/fixtures/docstrings/accessible/schema.py
@@ -23,8 +23,8 @@ class DoubleQuotesSummary:
 
 
 class RootEnum(Enum):
-    A = "A"
-    B = "B"
+    A = 'A'
+    B = 'B'
 
 
 RootEnum.A.__doc__ = "Lorem ipsum dolor"
@@ -35,8 +35,8 @@ RootEnum.B.__doc__ = (
 
 
 class RootB(Enum):
-    YES = "Yes"
-    NO = "No"
+    YES = 'Yes'
+    NO = 'No'
 
 
 RootB.YES.__doc__ = (
@@ -47,8 +47,8 @@ RootB.NO.__doc__ = "Lorem ipsum dolor\nMy\\Ipsum"
 
 
 class RootD(Enum):
-    TRUE = "true"
-    FALSE = "false"
+    TRUE = 'true'
+    FALSE = 'false'
 
 
 @dataclass
diff --git a/tests/fixtures/docstrings/blank/schema.py b/tests/fixtures/docstrings/blank/schema.py
index a093af8a..1311ed05 100644
--- a/tests/fixtures/docstrings/blank/schema.py
+++ b/tests/fixtures/docstrings/blank/schema.py
@@ -18,18 +18,18 @@ class DoubleQuotesSummary:
 
 
 class RootEnum(Enum):
-    A = "A"
-    B = "B"
+    A = 'A'
+    B = 'B'
 
 
 class RootB(Enum):
-    YES = "Yes"
-    NO = "No"
+    YES = 'Yes'
+    NO = 'No'
 
 
 class RootD(Enum):
-    TRUE = "true"
-    FALSE = "false"
+    TRUE = 'true'
+    FALSE = 'false'
 
 
 @dataclass
diff --git a/tests/fixtures/docstrings/google/schema.py b/tests/fixtures/docstrings/google/schema.py
index adc5fc3f..aebe68ac 100644
--- a/tests/fixtures/docstrings/google/schema.py
+++ b/tests/fixtures/docstrings/google/schema.py
@@ -29,8 +29,8 @@ class RootEnum(Enum):
         B: Lorem ipsum dolor '''sit''' amet, consectetur adipiscing
             elit. Morbi dapibus. My\\Ipsum
     """
-    A = "A"
-    B = "B"
+    A = 'A'
+    B = 'B'
 
 
 class RootB(Enum):
@@ -40,13 +40,13 @@ class RootB(Enum):
             dolor sit amet, consectetur adipiscing elit. Etiam mollis.
         NO: Lorem ipsum dolor My\\Ipsum
     """
-    YES = "Yes"
-    NO = "No"
+    YES = 'Yes'
+    NO = 'No'
 
 
 class RootD(Enum):
-    TRUE = "true"
-    FALSE = "false"
+    TRUE = 'true'
+    FALSE = 'false'
 
 
 @dataclass
diff --git a/tests/fixtures/docstrings/numpy/schema.py b/tests/fixtures/docstrings/numpy/schema.py
index 28aea67a..54c3bb52 100644
--- a/tests/fixtures/docstrings/numpy/schema.py
+++ b/tests/fixtures/docstrings/numpy/schema.py
@@ -32,8 +32,8 @@ class RootEnum(Enum):
         Lorem ipsum dolor '''sit''' amet, consectetur adipiscing elit. Morbi
         dapibus. My\\Ipsum
     """
-    A = "A"
-    B = "B"
+    A = 'A'
+    B = 'B'
 
 
 class RootB(Enum):
@@ -46,13 +46,13 @@ class RootB(Enum):
     NO
         Lorem ipsum dolor My\\Ipsum
     """
-    YES = "Yes"
-    NO = "No"
+    YES = 'Yes'
+    NO = 'No'
 
 
 class RootD(Enum):
-    TRUE = "true"
-    FALSE = "false"
+    TRUE = 'true'
+    FALSE = 'false'
 
 
 @dataclass
diff --git a/tests/fixtures/docstrings/rst/schema.py b/tests/fixtures/docstrings/rst/schema.py
index bbd96df2..d8eafd39 100644
--- a/tests/fixtures/docstrings/rst/schema.py
+++ b/tests/fixtures/docstrings/rst/schema.py
@@ -28,8 +28,8 @@ class RootEnum(Enum):
     :cvar B: Lorem ipsum dolor '''sit''' amet, consectetur adipiscing
         elit. Morbi dapibus. My\\Ipsum
     """
-    A = "A"
-    B = "B"
+    A = 'A'
+    B = 'B'
 
 
 class RootB(Enum):
@@ -38,13 +38,13 @@ class RootB(Enum):
         dolor sit amet, consectetur adipiscing elit. Etiam mollis.
     :cvar NO: Lorem ipsum dolor My\\Ipsum
     """
-    YES = "Yes"
-    NO = "No"
+    YES = 'Yes'
+    NO = 'No'
 
 
 class RootD(Enum):
-    TRUE = "true"
-    FALSE = "false"
+    TRUE = 'true'
+    FALSE = 'false'
 
 
 @dataclass
diff --git a/tests/fixtures/dtd/models/complete_example.py b/tests/fixtures/dtd/models/complete_example.py
index 6fbade31..91312671 100644
--- a/tests/fixtures/dtd/models/complete_example.py
+++ b/tests/fixtures/dtd/models/complete_example.py
@@ -4,8 +4,8 @@ from typing import List, Optional
 
 
 class PostStatus(Enum):
-    DRAFT = "draft"
-    PUBLISHED = "published"
+    DRAFT = 'draft'
+    PUBLISHED = 'published'
 
 
 @dataclass
@@ -29,7 +29,7 @@ class Post:
         }
     )
     lang: str = field(
-        default="en",
+        default='en',
         metadata={
             "type": "Attribute",
             "namespace": "http://www.w3.org/XML/1998/namespace",
diff --git a/tests/fixtures/primer/order.py b/tests/fixtures/primer/order.py
index 60e1bd92..db649f92 100644
--- a/tests/fixtures/primer/order.py
+++ b/tests/fixtures/primer/order.py
@@ -127,7 +127,7 @@ class Usaddress:
     )
     country: str = field(
         init=False,
-        default="US",
+        default='US',
         metadata={
             "type": "Attribute",
         }
@@ -140,7 +140,7 @@ class Comment:
         name = "comment"
 
     value: str = field(
-        default="",
+        default='',
         metadata={
             "required": True,
         }
diff --git a/tests/formats/dataclass/serializers/test_code.py b/tests/formats/dataclass/serializers/test_code.py
index 20ecc416..5e25a447 100644
--- a/tests/formats/dataclass/serializers/test_code.py
+++ b/tests/formats/dataclass/serializers/test_code.py
@@ -24,22 +24,22 @@ class PycodeSerializerTests(TestCase):
             "books = Books(\n"
             "    book=[\n"
             "        BookForm(\n"
-            '            author="Hightower, Kim",\n'
-            '            title="The First Book",\n'
-            '            genre="Fiction",\n'
+            "            author='Hightower, Kim',\n"
+            "            title='The First Book',\n"
+            "            genre='Fiction',\n"
             "            price=44.95,\n"
             "            pub_date=XmlDate(2000, 10, 1),\n"
-            '            review="An amazing story of nothing.",\n'
-            '            id="bk001"\n'
+            "            review='An amazing story of nothing.',\n"
+            "            id='bk001'\n"
             "        ),\n"
             "        BookForm(\n"
-            '            author="Nagata, Suanne",\n'
-            '            title="Becoming Somebody",\n'
-            '            genre="Biography",\n'
+            "            author='Nagata, Suanne',\n"
+            "            title='Becoming Somebody',\n"
+            "            genre='Biography',\n"
             "            price=33.95,\n"
             "            pub_date=XmlDate(2001, 1, 10),\n"
-            '            review="A masterpiece of the fine art of gossiping.",\n'
-            '            id="bk002"\n'
+            "            review='A masterpiece of the fine art of gossiping.',\n"
+            "            id='bk002'\n"
             "        ),\n"
             "    ]\n"
             ")\n"
@@ -58,7 +58,7 @@ class PycodeSerializerTests(TestCase):
             "books = Books(\n"
             "    book=[\n"
             "        BookForm(\n"
-            '            author="me"\n'
+            "            author='me'\n"
             "        ),\n"
             "    ]\n"
             ")\n"
@@ -76,7 +76,7 @@ class PycodeSerializerTests(TestCase):
             "books = Books(\n"
             "    book=[\n"
             "        BookForm(\n"
-            '            author="Backslashes \\\\ One Two \\x12 Three"\n'
+            "            author='Backslashes \\\\ One Two \\x12 Three'\n"
             "        ),\n"
             "    ]\n"
             ")\n"
@@ -98,19 +98,12 @@ class PycodeSerializerTests(TestCase):
         self.assertEqual("{}", "".join(iterator))
 
         iterator = self.serializer.write_object({"foo": "bar"}, 0, set())
-        self.assertEqual('{\n    "foo": "bar",\n}', "".join(iterator))
+        self.assertEqual("{\n    'foo': 'bar',\n}", "".join(iterator))
 
     def test_write_object_with_enum(self):
         iterator = self.serializer.write_object(Namespace.SOAP11, 0, set())
         self.assertEqual("Namespace.SOAP11", "".join(iterator))
 
-    def test_write_bytes(self):
-        iterator = self.serializer.write_object(b"a", 0, set())
-        self.assertEqual('b"a"', "".join(iterator))
-
-        iterator = self.serializer.write_object(b"\xfa\"'", 0, set())
-        self.assertEqual('b"\\xfa"\\\'"', "".join(iterator))
-
     def test_build_imports_with_nested_types(self):
         expected = "from tests.fixtures.models import Parent\n"
         actual = self.serializer.build_imports({Parent.Inner})
diff --git a/tests/formats/dataclass/test_filters.py b/tests/formats/dataclass/test_filters.py
index 33ae45c6..09dbb9d5 100644
--- a/tests/formats/dataclass/test_filters.py
+++ b/tests/formats/dataclass/test_filters.py
@@ -308,7 +308,7 @@ class FiltersTests(FactoryTestCase):
 
     def test_field_default_value_with_type_str(self):
         attr = AttrFactory.create(types=[type_str], default="foo")
-        self.assertEqual('"foo"', self.filters.field_default_value(attr))
+        self.assertEqual("'foo'", self.filters.field_default_value(attr))
 
     def test_field_default_value_with_type_tokens(self):
         attr = AttrFactory.create(types=[type_int, type_str], default="1  \n bar")
@@ -350,13 +350,13 @@ class FiltersTests(FactoryTestCase):
 
     def test_field_default_value_with_type_decimal(self):
         attr = AttrFactory.create(types=[type_decimal], default="1.5")
-        self.assertEqual('Decimal("1.5")', self.filters.field_default_value(attr))
+        self.assertEqual("Decimal('1.5')", self.filters.field_default_value(attr))
 
         attr.default = "-inf"
-        self.assertEqual('Decimal("-Infinity")', self.filters.field_default_value(attr))
+        self.assertEqual("Decimal('-Infinity')", self.filters.field_default_value(attr))
 
         attr.default = "inf"
-        self.assertEqual('Decimal("Infinity")', self.filters.field_default_value(attr))
+        self.assertEqual("Decimal('Infinity')", self.filters.field_default_value(attr))
 
     def test_field_default_value_with_type_int(self):
         attr = AttrFactory.create(types=[type_int], default="1")
@@ -528,7 +528,7 @@ class FiltersTests(FactoryTestCase):
                 "wildcard": True,
                 "type": "Type[object]",
             },
-            {"default": '"aa"', "name": "bar", "type": "Type[str]"},
+            {"default": "'aa'", "name": "bar", "type": "Type[str]"},
             {
                 "default_factory": "list",
                 "name": "tok",