From 050840cf8886724d97b19f0d5709bdaefa5e0e91 Mon Sep 17 00:00:00 2001 From: Vinit Kumar Date: Mon, 5 Jan 2026 17:12:00 +0530 Subject: [PATCH 1/3] fix: code quality --- .cursor/commands/supermemory.md | 150 ++++++++++++++++++++++++++++++++ json2xml/dicttoxml.py | 61 ++++++------- json2xml/json2xml.py | 7 +- pyproject.toml | 11 +-- tests/test_dict2xml.py | 62 +++---------- tests/test_missing_coverage.py | 43 ++------- uv.lock | 72 ++++++++++++--- 7 files changed, 270 insertions(+), 136 deletions(-) create mode 100644 .cursor/commands/supermemory.md diff --git a/.cursor/commands/supermemory.md b/.cursor/commands/supermemory.md new file mode 100644 index 0000000..ddc4a9f --- /dev/null +++ b/.cursor/commands/supermemory.md @@ -0,0 +1,150 @@ +--- +description: Initialize Supermemory with comprehensive codebase knowledge +--- + +# Initializing Supermemory + +You are initializing persistent memory for this codebase. This is not just data collection - you're building context that will make you significantly more effective across all future sessions. + +## Understanding Context + +You are a **stateful** coding agent. Users expect to work with you over extended periods - potentially the entire lifecycle of a project. Your memory is how you get better over time and maintain continuity. + +## What to Remember + +### 1. Procedures (Rules & Workflows) +Explicit rules that should always be followed: +- "Never commit directly to main - always use feature branches" +- "Always run lint before tests" +- "Use conventional commits format" + +### 2. Preferences (Style & Conventions) +Project and user coding style: +- "Prefer functional components over class components" +- "Use early returns instead of nested conditionals" +- "Always add JSDoc to exported functions" + +### 3. Architecture & Context +How the codebase works and why: +- "Auth system was refactored in v2.0 - old patterns deprecated" +- "The monorepo used to have 3 modules before consolidation" +- "This pagination bug was fixed before - similar to PR #234" + +## Memory Scopes + +**Project-scoped** (`scope: "project"`): +- Build/test/lint commands +- Architecture and key directories +- Team conventions specific to this codebase +- Technology stack and framework choices +- Known issues and their solutions + +**User-scoped** (`scope: "user"`): +- Personal coding preferences across all projects +- Communication style preferences +- General workflow habits + +## Research Approach + +This is a **deep research** initialization. Take your time and be thorough (~50+ tool calls). The goal is to genuinely understand the project, not just collect surface-level facts. + +**What to uncover:** +- Tech stack and dependencies (explicit and implicit) +- Project structure and architecture +- Build/test/deploy commands and workflows +- Contributors & team dynamics (who works on what?) +- Commit conventions and branching strategy +- Code evolution (major refactors, architecture changes) +- Pain points (areas with lots of bug fixes) +- Implicit conventions not documented anywhere + +## Research Techniques + +### File-based +- README.md, CONTRIBUTING.md, AGENTS.md, CLAUDE.md +- Package manifests (package.json, Cargo.toml, pyproject.toml, go.mod, Gemfile) +- Config files (.eslintrc, tsconfig.json, .prettierrc) +- CI/CD configs (.github/workflows/) + +### Git-based +- `git log --oneline -20` - Recent history +- `git branch -a` - Branching strategy +- `git log --format="%s" -50` - Commit conventions +- `git shortlog -sn --all | head -10` - Main contributors + +### Explore Agent +Fire parallel explore queries for broad understanding: +``` +Task(explore, "What is the tech stack and key dependencies?") +Task(explore, "What is the project structure? Key directories?") +Task(explore, "How do you build, test, and run this project?") +Task(explore, "What are the main architectural patterns?") +Task(explore, "What conventions or patterns are used?") +``` + +## How to Do Thorough Research + +**Don't just collect data - analyze and cross-reference.** + +Bad (shallow): +- Run commands, copy output +- List facts without understanding + +Good (thorough): +- Cross-reference findings (if inconsistent, dig deeper) +- Resolve ambiguities (don't leave questions unanswered) +- Read actual file content, not just names +- Look for patterns (what do commits tell you about workflow?) +- Think like a new team member - what would you want to know? + +## Saving Memories + +Use the `supermemory` tool for each distinct insight: + +``` +supermemory(mode: "add", content: "...", type: "...", scope: "project") +``` + +**Types:** +- `project-config` - tech stack, commands, tooling +- `architecture` - codebase structure, key components, data flow +- `learned-pattern` - conventions specific to this codebase +- `error-solution` - known issues and their fixes +- `preference` - coding style preferences (use with user scope) + +**Guidelines:** +- Save each distinct insight as a separate memory +- Be concise but include enough context to be useful +- Include the "why" not just the "what" when relevant +- Update memories incrementally as you research (don't wait until the end) + +**Good memories:** +- "Uses Bun runtime and package manager. Commands: bun install, bun run dev, bun test" +- "API routes in src/routes/, handlers in src/handlers/. Hono framework." +- "Auth uses Redis sessions, not JWT. Implementation in src/lib/auth.ts" +- "Never use `any` type - strict TypeScript. Use `unknown` and narrow." +- "Database migrations must be backward compatible - we do rolling deploys" + +## Upfront Questions + +Before diving in, ask: +1. "Any specific rules I should always follow?" +2. "Preferences for how I communicate? (terse/detailed)" + +## Reflection Phase + +Before finishing, reflect: +1. **Completeness**: Did you cover commands, architecture, conventions, gotchas? +2. **Quality**: Are memories concise and searchable? +3. **Scope**: Did you correctly separate project vs user knowledge? + +Then ask: "I've initialized memory with X insights. Want me to continue refining, or is this good?" + +## Your Task + +1. Ask upfront questions (research depth, rules, preferences) +2. Check existing memories: `supermemory(mode: "list", scope: "project")` +3. Research based on chosen depth +4. Save memories incrementally as you discover insights +5. Reflect and verify completeness +6. Summarize what was learned and ask if user wants refinement \ No newline at end of file diff --git a/json2xml/dicttoxml.py b/json2xml/dicttoxml.py index b324215..7386f99 100644 --- a/json2xml/dicttoxml.py +++ b/json2xml/dicttoxml.py @@ -7,7 +7,7 @@ from decimal import Decimal from fractions import Fraction from random import SystemRandom -from typing import Any, Union, cast +from typing import Any, cast from defusedxml.minidom import parseString @@ -43,33 +43,24 @@ def get_unique_id(element: str) -> str: Returns: str: The unique ID. """ - ids: list[str] = [] # initialize list of unique ids - this_id = make_id(element) - dup = True - while dup: - if this_id not in ids: - dup = False - ids.append(this_id) - else: - this_id = make_id(element) - return ids[-1] - - -ELEMENT = Union[ - str, - int, - float, - bool, - complex, - Decimal, - Fraction, - numbers.Number, - Sequence[Any], - datetime.datetime, - datetime.date, - None, - dict[str, Any], -] + return make_id(element) + + +ELEMENT = ( + str + | int + | float + | bool + | complex + | Decimal + | Fraction + | numbers.Number + | Sequence[Any] + | datetime.datetime + | datetime.date + | None + | dict[str, Any] +) def get_xml_type(val: ELEMENT) -> str: @@ -332,7 +323,7 @@ def dict2xml_str( cdata: bool, item_name: str, item_wrap: bool, - parentIsList: bool, + parent_is_list: bool, parent: str = "", list_headers: bool = False, ) -> str: @@ -340,7 +331,6 @@ def dict2xml_str( parse dict2xml """ ids: list[str] = [] # initialize list of unique ids - ", ".join(str(key) for key in item) subtree = "" # Initialize subtree with default empty string if attr_type: @@ -358,12 +348,12 @@ def dict2xml_str( rawitem, ids, attr_type, item_func, cdata, item_wrap, item_name, list_headers=list_headers ) - if parentIsList and list_headers: + if parent_is_list and list_headers: if len(val_attr) > 0 and not item_wrap: attrstring = make_attrstring(val_attr) return f"<{parent}{attrstring}>{subtree}" return f"<{parent}>{subtree}" - elif item.get("@flat", False) or (parentIsList and not item_wrap): + elif item.get("@flat", False) or (parent_is_list and not item_wrap): return subtree attrstring = make_attrstring(val_attr) @@ -553,7 +543,7 @@ def convert_list( cdata=cdata, item_name=item_name, item_wrap=item_wrap, - parentIsList=True, + parent_is_list=True, parent=parent, list_headers=list_headers ) @@ -640,7 +630,7 @@ def dicttoxml( item_wrap: bool = True, item_func: Callable[[str], str] = default_item_func, cdata: bool = False, - xml_namespaces: dict[str, Any] = {}, + xml_namespaces: dict[str, Any] | None = None, list_headers: bool = False, xpath_format: bool = False, ) -> bytes: @@ -794,6 +784,9 @@ def dicttoxml( ] return "".join(output).encode("utf-8") + if xml_namespaces is None: + xml_namespaces = {} + output = [] namespace_str = "" for prefix in xml_namespaces: diff --git a/json2xml/json2xml.py b/json2xml/json2xml.py index 4800962..cbc8479 100644 --- a/json2xml/json2xml.py +++ b/json2xml/json2xml.py @@ -30,9 +30,14 @@ def __init__( self.item_wrap = item_wrap self.xpath_format = xpath_format - def to_xml(self) -> Any | None: + def to_xml(self) -> str | bytes | None: """ Convert to xml using dicttoxml.dicttoxml and then pretty print it. + + Returns: + str: Pretty-printed XML string when pretty=True. + bytes: Raw XML bytes when pretty=False. + None: When data is empty or None. """ if self.data: xml_data = dicttoxml.dicttoxml( diff --git a/pyproject.toml b/pyproject.toml index c5ae32b..d20af01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,10 +31,6 @@ dependencies = [ "defusedxml", "urllib3", "xmltodict>=0.12.0", - "pytest", - "pytest-cov", - "coverage", - "setuptools", ] [project.urls] @@ -44,8 +40,13 @@ Homepage = "https://github.com/vinitkumar/json2xml" include = ["json2xml"] [project.optional-dependencies] -test = [ +dev = [ "pytest>=8.4.1", + "pytest-cov", + "pytest-xdist", + "coverage", + "ruff", + "setuptools", ] [tool.pytest.ini_options] diff --git a/tests/test_dict2xml.py b/tests/test_dict2xml.py index 1d279f8..59b484f 100644 --- a/tests/test_dict2xml.py +++ b/tests/test_dict2xml.py @@ -297,7 +297,7 @@ def test_dict2xml_str_list_header(self) -> None: cdata=False, item_name="item", item_wrap=False, - parentIsList=True, + parent_is_list=True, parent=parent, list_headers=True, ) @@ -604,7 +604,7 @@ class CustomClass: cdata=False, item_name="test", item_wrap=False, - parentIsList=False + parent_is_list=False ) def test_convert_dict_invalid_type(self) -> None: @@ -773,50 +773,16 @@ def test_dicttoxml_with_cdata(self) -> None: result = dicttoxml.dicttoxml(data, cdata=True, attr_type=False, root=False) assert b"" == result - def test_get_unique_id_with_duplicates(self) -> None: - """Test get_unique_id when duplicates are generated.""" - # We need to modify the original get_unique_id to simulate a pre-existing ID list - import json2xml.dicttoxml as module - - # Save original function - original_get_unique_id = module.get_unique_id - - # Track make_id calls - call_count = 0 - original_make_id = module.make_id - - def mock_make_id(element: str, start: int = 100000, end: int = 999999) -> str: - nonlocal call_count - call_count += 1 - if call_count == 1: - return "test_123456" # First call - will collide - else: - return "test_789012" # Second call - unique - - # Patch get_unique_id to use a pre-populated ids list - def patched_get_unique_id(element: str) -> str: - # Start with a pre-existing ID to force collision - ids = ["test_123456"] - this_id = module.make_id(element) - dup = True - while dup: - if this_id not in ids: - dup = False - ids.append(this_id) - else: - this_id = module.make_id(element) # This exercises line 52 - return ids[-1] - - module.make_id = mock_make_id # type: ignore[assignment] - module.get_unique_id = patched_get_unique_id # type: ignore[assignment] + def test_get_unique_id_returns_valid_format(self) -> None: + """Test get_unique_id returns properly formatted ID.""" + result = dicttoxml.get_unique_id("test_element") - try: - result = dicttoxml.get_unique_id("test") - assert result == "test_789012" - assert call_count == 2 - finally: - module.make_id = original_make_id - module.get_unique_id = original_get_unique_id + # Verify format: element_NNNNNN + assert isinstance(result, str) + assert result.startswith("test_element_") + numeric_part = result.replace("test_element_", "") + assert numeric_part.isdigit() + assert 100000 <= int(numeric_part) <= 999999 def test_convert_with_bool_direct(self) -> None: """Test convert function with boolean input directly.""" @@ -893,7 +859,7 @@ def test_dict2xml_str_with_attr_type(self) -> None: cdata=False, item_name="test", item_wrap=False, - parentIsList=False + parent_is_list=False ) assert 'type="dict"' in result @@ -908,7 +874,7 @@ def test_dict2xml_str_with_primitive_dict(self) -> None: cdata=False, item_name="test", item_wrap=False, - parentIsList=False + parent_is_list=False ) assert "nested" in result @@ -1035,7 +1001,7 @@ def mock_is_primitive(val: Any) -> bool: cdata=False, item_name="test", item_wrap=False, - parentIsList=False + parent_is_list=False ) assert "test" in result finally: diff --git a/tests/test_missing_coverage.py b/tests/test_missing_coverage.py index ab0ed0a..fa13daf 100644 --- a/tests/test_missing_coverage.py +++ b/tests/test_missing_coverage.py @@ -21,16 +21,11 @@ pass -class TestGetUniqueIdDuplicateGeneration: - """Test line 52: duplicate ID generation in get_unique_id loop""" +class TestGetUniqueId: + """Test get_unique_id function.""" - def test_get_unique_id_generates_id_when_duplicates_occur(self) -> None: - """Test that get_unique_id handles the while loop by regenerating IDs on duplicates. - - Line 52 (this_id = make_id(element)) is executed when a duplicate is found. - Since make_id uses SystemRandom, we can't guarantee duplicates, but we can - ensure the function returns a valid ID in the correct format. - """ + def test_get_unique_id_returns_valid_format(self) -> None: + """Test that get_unique_id returns a valid ID in the correct format.""" result = get_unique_id("test_element") # Verify it returns a string in the expected format @@ -43,15 +38,8 @@ def test_get_unique_id_generates_id_when_duplicates_occur(self) -> None: assert numeric_part.isdigit() assert 100000 <= int(numeric_part) <= 999999 - def test_get_unique_id_duplicate_id_regeneration(self) -> None: - """Test line 52: trigger duplicate ID regeneration. - - Line 52 (this_id = make_id(element)) only executes if this_id is in the ids list. - We can't easily trigger this with the current implementation since ids starts empty, - but we test the function's robustness when make_id returns duplicates. - """ - # Call get_unique_id multiple times - while theoretically one could duplicate - # due to SystemRandom, the function handles this correctly + def test_get_unique_id_multiple_calls(self) -> None: + """Test that get_unique_id works correctly with multiple calls.""" ids_generated = [get_unique_id("test") for _ in range(10)] # All generated IDs should be properly formatted @@ -304,25 +292,6 @@ class CustomType: assert get_xpath31_tag_name(CustomType()) == "string" -class TestGetUniqueIdDuplicateIteration: - """Test the while loop iteration in get_unique_id (line 47-52)""" - - def test_get_unique_id_returns_valid_format(self) -> None: - """Test that get_unique_id returns properly formatted ID.""" - from json2xml.dicttoxml import get_unique_id - - # Call multiple times to ensure consistency - ids = [get_unique_id("element") for _ in range(5)] - - # All should be unique - assert len(set(ids)) == 5 - - # All should follow the format - for id_val in ids: - assert isinstance(id_val, str) - assert id_val.startswith("element_") - numeric_part = id_val.split("_")[-1] - assert numeric_part.isdigit() class TestConvertToXpath31AllPaths: diff --git a/uv.lock b/uv.lock index fe2f87a..619e362 100644 --- a/uv.lock +++ b/uv.lock @@ -136,6 +136,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -150,32 +159,34 @@ name = "json2xml" version = "5.3.1" source = { editable = "." } dependencies = [ - { name = "coverage" }, { name = "defusedxml" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "setuptools" }, { name = "urllib3" }, { name = "xmltodict" }, ] [package.optional-dependencies] -test = [ +dev = [ + { name = "coverage" }, { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-xdist" }, + { name = "ruff" }, + { name = "setuptools" }, ] [package.metadata] requires-dist = [ - { name = "coverage" }, + { name = "coverage", marker = "extra == 'dev'" }, { name = "defusedxml" }, - { name = "pytest" }, - { name = "pytest", marker = "extra == 'test'", specifier = ">=8.4.1" }, - { name = "pytest-cov" }, - { name = "setuptools" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.4.1" }, + { name = "pytest-cov", marker = "extra == 'dev'" }, + { name = "pytest-xdist", marker = "extra == 'dev'" }, + { name = "ruff", marker = "extra == 'dev'" }, + { name = "setuptools", marker = "extra == 'dev'" }, { name = "urllib3" }, { name = "xmltodict", specifier = ">=0.12.0" }, ] -provides-extras = ["test"] +provides-extras = ["dev"] [[package]] name = "packaging" @@ -236,6 +247,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, ] +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, + { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, + { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, + { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, + { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, + { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, + { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, + { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, + { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, + { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, + { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, +] + [[package]] name = "setuptools" version = "80.9.0" From 54a6eed8989ec7095c496ae199bd9c7aad193ddd Mon Sep 17 00:00:00 2001 From: Vinit Kumar Date: Mon, 5 Jan 2026 21:55:43 +0530 Subject: [PATCH 2/3] fix: code cleanup based on Agents.md guidelines - Delete dead code (unused string join in dict2xml_str) - Move test deps from main dependencies to [project.optional-dependencies] - Fix mutable default argument (xml_namespaces={} -> None) - Simplify get_unique_id (remove dead while loop logic) - Fix vague return type (Any -> str | bytes | None) - Fix inconsistent naming (parentIsList -> parent_is_list) - Remove unused Union import, modernize type alias with | syntax - Add isinstance checks in tests to satisfy type checker --- tests/test_json2xml.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/test_json2xml.py b/tests/test_json2xml.py index 84c06d3..66b646a 100644 --- a/tests/test_json2xml.py +++ b/tests/test_json2xml.py @@ -100,7 +100,7 @@ def test_no_wrapper(self) -> None: '{"login":"mojombo","id":1,"avatar_url":"https://avatars0.githubusercontent.com/u/1?v=4"}' ) xmldata = json2xml.Json2xml(data, root=False, pretty=False).to_xml() - if xmldata: + if xmldata and isinstance(xmldata, bytes): assert xmldata.startswith(b'mojombo') pytest.raises(ExpatError, xmltodict.parse, xmldata) @@ -218,7 +218,7 @@ def test_encoding_pretty_print(self) -> None: '{"login":"mojombo","id":1,"avatar_url":"https://avatars0.githubusercontent.com/u/1?v=4"}' ) xmldata = json2xml.Json2xml(data, pretty=True).to_xml() - if xmldata: + if xmldata and isinstance(xmldata, str): assert 'encoding="UTF-8"' in xmldata def test_encoding_without_pretty_print(self) -> None: @@ -226,14 +226,14 @@ def test_encoding_without_pretty_print(self) -> None: '{"login":"mojombo","id":1,"avatar_url":"https://avatars0.githubusercontent.com/u/1?v=4"}' ) xmldata = json2xml.Json2xml(data, pretty=False).to_xml() - if xmldata: + if xmldata and isinstance(xmldata, bytes): assert b'encoding="UTF-8"' in xmldata def test_xpath_format_basic(self) -> None: """Test XPath 3.1 json-to-xml format with basic types.""" data = {"name": "John", "age": 30, "active": True} xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml() - if xmldata: + if xmldata and isinstance(xmldata, bytes): assert b'xmlns="http://www.w3.org/2005/xpath-functions"' in xmldata assert b'John' in xmldata assert b'30' in xmldata @@ -243,7 +243,7 @@ def test_xpath_format_nested_dict(self) -> None: """Test XPath 3.1 format with nested dictionaries.""" data = {"person": {"name": "Alice", "age": 25}} xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml() - if xmldata: + if xmldata and isinstance(xmldata, bytes): assert b'' in xmldata assert b'Alice' in xmldata assert b'25' in xmldata @@ -252,7 +252,7 @@ def test_xpath_format_array(self) -> None: """Test XPath 3.1 format with arrays.""" data = {"numbers": [1, 2, 3]} xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml() - if xmldata: + if xmldata and isinstance(xmldata, bytes): assert b'' in xmldata assert b'1' in xmldata assert b'2' in xmldata @@ -262,14 +262,14 @@ def test_xpath_format_null(self) -> None: """Test XPath 3.1 format with null values.""" data = {"value": None} xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml() - if xmldata: + if xmldata and isinstance(xmldata, bytes): assert b'' in xmldata def test_xpath_format_mixed_array(self) -> None: """Test XPath 3.1 format with mixed type arrays.""" data = {"items": ["text", 42, True, None]} xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml() - if xmldata: + if xmldata and isinstance(xmldata, bytes): assert b'' in xmldata assert b'text' in xmldata assert b'42' in xmldata @@ -284,7 +284,7 @@ def test_xpath_format_complex_nested(self) -> None: ] } xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml() - if xmldata: + if xmldata and isinstance(xmldata, bytes): assert b'' in xmldata assert b'70805774' in xmldata assert b'1001' in xmldata @@ -295,7 +295,7 @@ def test_xpath_format_escaping(self) -> None: """Test XPath 3.1 format properly escapes special characters.""" data = {"text": ""} xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml() - if xmldata: + if xmldata and isinstance(xmldata, bytes): assert b"<script>" in xmldata assert b"'xss'" in xmldata @@ -303,7 +303,7 @@ def test_xpath_format_with_pretty_print(self) -> None: """Test XPath 3.1 format works with pretty printing.""" data = {"name": "Test"} xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=True).to_xml() - if xmldata: + if xmldata and isinstance(xmldata, str): assert 'xmlns="http://www.w3.org/2005/xpath-functions"' in xmldata assert 'Test' in xmldata @@ -311,6 +311,6 @@ def test_xpath_format_root_array(self) -> None: """Test XPath 3.1 format with root-level array.""" data = [1, 2, 3] xmldata = json2xml.Json2xml(data, xpath_format=True, pretty=False).to_xml() - if xmldata: + if xmldata and isinstance(xmldata, bytes): assert b'' in xmldata assert b'1' in xmldata From 5a0698fc526e320a8a77963fec1b5164c482f1c4 Mon Sep 17 00:00:00 2001 From: Vinit Kumar Date: Mon, 5 Jan 2026 22:05:02 +0530 Subject: [PATCH 3/3] fix: only type-check json2xml module in CI (not tests) uvx ty runs in isolated environment and cannot resolve pytest imports in test files. Type checking the main module is sufficient. --- .github/workflows/pythonpackage.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 6b354a7..86adf1b 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -119,3 +119,5 @@ jobs: - name: Run ruff run: uvx ruff check json2xml tests + - name: Run type check + run: uvx ty check json2xml