egglog 11.0.0__tar.gz → 11.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of egglog might be problematic. Click here for more details.

Files changed (141) hide show
  1. {egglog-11.0.0 → egglog-11.1.0}/.github/workflows/CI.yml +5 -5
  2. egglog-11.1.0/.github/workflows/update-changelog.yml +66 -0
  3. {egglog-11.0.0 → egglog-11.1.0}/.github/workflows/version.yml +10 -10
  4. {egglog-11.0.0 → egglog-11.1.0}/Cargo.lock +1 -1
  5. {egglog-11.0.0 → egglog-11.1.0}/Cargo.toml +1 -1
  6. {egglog-11.0.0 → egglog-11.1.0}/PKG-INFO +1 -1
  7. {egglog-11.0.0 → egglog-11.1.0}/docs/changelog.md +4 -2
  8. {egglog-11.0.0 → egglog-11.1.0}/docs/reference/contributing.md +9 -0
  9. {egglog-11.0.0 → egglog-11.1.0}/docs/reference/usage.md +8 -0
  10. {egglog-11.0.0 → egglog-11.1.0}/pyproject.toml +12 -7
  11. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/builtins.py +22 -22
  12. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/egraph.py +1 -1
  13. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/exp/program_gen.py +2 -2
  14. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/version_compat.py +1 -1
  15. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_high_level.py +3 -3
  16. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_program_gen.py +1 -1
  17. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_runtime.py +2 -2
  18. {egglog-11.0.0 → egglog-11.1.0}/src/lib.rs +6 -1
  19. {egglog-11.0.0 → egglog-11.1.0}/uv.lock +24 -1
  20. egglog-11.0.0/.github/workflows/update-changelog.yml +0 -52
  21. {egglog-11.0.0 → egglog-11.1.0}/.cargo/config.toml +0 -0
  22. {egglog-11.0.0 → egglog-11.1.0}/.github/dependabot.yml +0 -0
  23. {egglog-11.0.0 → egglog-11.1.0}/.gitignore +0 -0
  24. {egglog-11.0.0 → egglog-11.1.0}/.pre-commit-config.yaml +0 -0
  25. {egglog-11.0.0 → egglog-11.1.0}/.python-version +0 -0
  26. {egglog-11.0.0 → egglog-11.1.0}/.readthedocs.yaml +0 -0
  27. {egglog-11.0.0 → egglog-11.1.0}/CITATION.cff +0 -0
  28. {egglog-11.0.0 → egglog-11.1.0}/LICENSE +0 -0
  29. {egglog-11.0.0 → egglog-11.1.0}/Makefile +0 -0
  30. {egglog-11.0.0 → egglog-11.1.0}/README.md +0 -0
  31. {egglog-11.0.0 → egglog-11.1.0}/conftest.py +0 -0
  32. {egglog-11.0.0 → egglog-11.1.0}/docs/.gitignore +0 -0
  33. {egglog-11.0.0 → egglog-11.1.0}/docs/_templates/comments.html +0 -0
  34. {egglog-11.0.0 → egglog-11.1.0}/docs/conf.py +0 -0
  35. {egglog-11.0.0 → egglog-11.1.0}/docs/environment.yml +0 -0
  36. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/.gitignore +0 -0
  37. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/2023_07_presentation.ipynb +0 -0
  38. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/2023_11_09_portland_state.ipynb +0 -0
  39. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/2023_11_17_pytensor.ipynb +0 -0
  40. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/2023_11_pydata_lightning_talk.ipynb +0 -0
  41. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/2023_12_02_congruence_closure-1.png +0 -0
  42. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/2023_12_02_congruence_closure-2.png +0 -0
  43. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/2023_12_02_congruence_closure.md +0 -0
  44. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/2024_03_17_community_talk.ipynb +0 -0
  45. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/2024_03_17_map.svg +0 -0
  46. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/big_graph.svg +0 -0
  47. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/define_and_define.md +0 -0
  48. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/ecosystem-graph.png +0 -0
  49. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/egg.png +0 -0
  50. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/indexing_pushdown.ipynb +0 -0
  51. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/moa.png +0 -0
  52. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/optional_values.md +0 -0
  53. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation/pldi_2023_presentation.ipynb +0 -0
  54. {egglog-11.0.0 → egglog-11.1.0}/docs/explanation.md +0 -0
  55. {egglog-11.0.0 → egglog-11.1.0}/docs/how-to-guides.md +0 -0
  56. {egglog-11.0.0 → egglog-11.1.0}/docs/index.md +0 -0
  57. {egglog-11.0.0 → egglog-11.1.0}/docs/reference/bindings.md +0 -0
  58. {egglog-11.0.0 → egglog-11.1.0}/docs/reference/egglog-translation.md +0 -0
  59. {egglog-11.0.0 → egglog-11.1.0}/docs/reference/high-level.md +0 -0
  60. {egglog-11.0.0 → egglog-11.1.0}/docs/reference/python-integration.md +0 -0
  61. {egglog-11.0.0 → egglog-11.1.0}/docs/reference.md +0 -0
  62. {egglog-11.0.0 → egglog-11.1.0}/docs/tutorials/getting-started.ipynb +0 -0
  63. {egglog-11.0.0 → egglog-11.1.0}/docs/tutorials/screenshot-1.png +0 -0
  64. {egglog-11.0.0 → egglog-11.1.0}/docs/tutorials/screenshot-2.png +0 -0
  65. {egglog-11.0.0 → egglog-11.1.0}/docs/tutorials/sklearn.ipynb +0 -0
  66. {egglog-11.0.0 → egglog-11.1.0}/docs/tutorials.md +0 -0
  67. {egglog-11.0.0 → egglog-11.1.0}/modify_changelog.py +0 -0
  68. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/__init__.py +0 -0
  69. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/bindings.pyi +0 -0
  70. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/config.py +0 -0
  71. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/conversion.py +0 -0
  72. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/declarations.py +0 -0
  73. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/deconstruct.py +0 -0
  74. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/egraph_state.py +0 -0
  75. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/examples/README.rst +0 -0
  76. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/examples/__init__.py +0 -0
  77. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/examples/bignum.py +0 -0
  78. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/examples/bool.py +0 -0
  79. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/examples/eqsat_basic.py +0 -0
  80. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/examples/fib.py +0 -0
  81. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/examples/higher_order_functions.py +0 -0
  82. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/examples/lambda_.py +0 -0
  83. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/examples/matrix.py +0 -0
  84. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/examples/multiset.py +0 -0
  85. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/examples/ndarrays.py +0 -0
  86. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/examples/resolution.py +0 -0
  87. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/examples/schedule_demo.py +0 -0
  88. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/exp/__init__.py +0 -0
  89. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/exp/array_api.py +0 -0
  90. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/exp/array_api_jit.py +0 -0
  91. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/exp/array_api_loopnest.py +0 -0
  92. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/exp/array_api_numba.py +0 -0
  93. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/exp/array_api_program_gen.py +0 -0
  94. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/exp/siu_examples.py +0 -0
  95. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/ipython_magic.py +0 -0
  96. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/pretty.py +0 -0
  97. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/py.typed +0 -0
  98. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/runtime.py +0 -0
  99. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/thunk.py +0 -0
  100. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/type_constraint_solver.py +0 -0
  101. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/visualizer.css +0 -0
  102. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/visualizer.js +0 -0
  103. {egglog-11.0.0 → egglog-11.1.0}/python/egglog/visualizer_widget.py +0 -0
  104. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_array_api/TestLoopNest.test_index_codegen[code].py +0 -0
  105. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_array_api/TestLoopNest.test_index_codegen[expr].py +0 -0
  106. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_array_api/test_jit[add][code].py +0 -0
  107. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_array_api/test_jit[add][expr].py +0 -0
  108. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_array_api/test_jit[add][initial_expr].py +0 -0
  109. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_array_api/test_jit[lda][code].py +0 -0
  110. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_array_api/test_jit[lda][expr].py +0 -0
  111. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_array_api/test_jit[lda][initial_expr].py +0 -0
  112. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_array_api/test_jit[tuple][code].py +0 -0
  113. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_array_api/test_jit[tuple][expr].py +0 -0
  114. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_array_api/test_jit[tuple][initial_expr].py +0 -0
  115. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_array_api/test_program_compile[tuple][code].py +0 -0
  116. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_array_api/test_program_compile[tuple][expr].py +0 -0
  117. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_bindings/TestEGraph.test_parse_program.py +0 -0
  118. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_program_gen/test_to_string.py +0 -0
  119. {egglog-11.0.0 → egglog-11.1.0}/python/tests/__snapshots__/test_program_gen/test_to_string_function_three.py +0 -0
  120. {egglog-11.0.0 → egglog-11.1.0}/python/tests/conftest.py +0 -0
  121. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_array_api.py +0 -0
  122. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_bindings.py +0 -0
  123. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_convert.py +0 -0
  124. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_deconstruct.py +0 -0
  125. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_modify_changelog.py +0 -0
  126. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_no_import_star.py +0 -0
  127. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_pretty.py +0 -0
  128. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_py_object_sort.py +0 -0
  129. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_type_constraint_solver.py +0 -0
  130. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_typing.py +0 -0
  131. {egglog-11.0.0 → egglog-11.1.0}/python/tests/test_unstable_fn.py +0 -0
  132. {egglog-11.0.0 → egglog-11.1.0}/rust-toolchain.toml +0 -0
  133. {egglog-11.0.0 → egglog-11.1.0}/src/conversions.rs +0 -0
  134. {egglog-11.0.0 → egglog-11.1.0}/src/egraph.rs +0 -0
  135. {egglog-11.0.0 → egglog-11.1.0}/src/error.rs +0 -0
  136. {egglog-11.0.0 → egglog-11.1.0}/src/py_object_sort.rs +0 -0
  137. {egglog-11.0.0 → egglog-11.1.0}/src/serialize.rs +0 -0
  138. {egglog-11.0.0 → egglog-11.1.0}/src/termdag.rs +0 -0
  139. {egglog-11.0.0 → egglog-11.1.0}/src/utils.rs +0 -0
  140. {egglog-11.0.0 → egglog-11.1.0}/stubtest_allow +0 -0
  141. {egglog-11.0.0 → egglog-11.1.0}/test-data/unit/check-high-level.test +0 -0
@@ -25,7 +25,7 @@ jobs:
25
25
  - "3.11"
26
26
  - "3.10"
27
27
  steps:
28
- - uses: actions/checkout@v4
28
+ - uses: actions/checkout@v5
29
29
  - uses: astral-sh/setup-uv@v6
30
30
  with:
31
31
  enable-cache: true
@@ -38,7 +38,7 @@ jobs:
38
38
  mypy:
39
39
  runs-on: ubuntu-latest
40
40
  steps:
41
- - uses: actions/checkout@v4
41
+ - uses: actions/checkout@v5
42
42
  - uses: astral-sh/setup-uv@v6
43
43
  with:
44
44
  enable-cache: true
@@ -53,9 +53,9 @@ jobs:
53
53
  strategy:
54
54
  matrix:
55
55
  # Run on codspeed for walltime and ubuntu for instrumenentation
56
- runner: [ codspeed-macro, ubuntu-latest ]
56
+ runner: [ ubuntu-latest ] # codspeed-macro, disable for now till we set up custom runner for more minutes
57
57
  steps:
58
- - uses: actions/checkout@v4
58
+ - uses: actions/checkout@v5
59
59
  - name: Set up Python
60
60
  uses: actions/setup-python@v5
61
61
  with:
@@ -77,7 +77,7 @@ jobs:
77
77
  docs:
78
78
  runs-on: ubuntu-latest
79
79
  steps:
80
- - uses: actions/checkout@v4
80
+ - uses: actions/checkout@v5
81
81
  - uses: astral-sh/setup-uv@v6
82
82
  with:
83
83
  enable-cache: true
@@ -0,0 +1,66 @@
1
+ name: Update Changelog
2
+
3
+ on:
4
+ issue_comment:
5
+ types: [created]
6
+
7
+ jobs:
8
+ update-changelog:
9
+ # Only run if this is a comment on a PR containing "@actions-user changelog"
10
+ # and not a comment made by GitHub Action to avoid infinite loops
11
+ if: github.event.issue.pull_request && contains(github.event.comment.body, '@actions-user changelog') && github.actor != 'github-actions[bot]'
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: write
15
+ pull-requests: write
16
+
17
+ steps:
18
+ - name: Get PR details
19
+ id: pr-details
20
+ uses: actions/github-script@v7
21
+ with:
22
+ script: |
23
+ const { data: pullRequest } = await github.rest.pulls.get({
24
+ owner: context.repo.owner,
25
+ repo: context.repo.repo,
26
+ pull_number: context.issue.number
27
+ });
28
+ core.setOutput('number', pullRequest.number);
29
+ core.setOutput('title', pullRequest.title);
30
+ core.setOutput('head_ref', pullRequest.head.ref);
31
+
32
+ - name: Checkout repository
33
+ uses: actions/checkout@v5
34
+ with:
35
+ # Checkout the PR head ref
36
+ ref: ${{ steps.pr-details.outputs.head_ref }}
37
+ token: ${{ secrets.GITHUB_TOKEN }}
38
+
39
+ - name: Set up Python
40
+ uses: actions/setup-python@v5
41
+ with:
42
+ python-version: '3.12'
43
+
44
+ - name: Update changelog
45
+ run: |
46
+ python modify_changelog.py update_changelog \
47
+ "${{ steps.pr-details.outputs.number }}" \
48
+ '${{ steps.pr-details.outputs.title }}'
49
+
50
+ - name: Check for changes
51
+ id: changes
52
+ run: |
53
+ if git diff --quiet docs/changelog.md; then
54
+ echo "changed=false" >> $GITHUB_OUTPUT
55
+ else
56
+ echo "changed=true" >> $GITHUB_OUTPUT
57
+ fi
58
+
59
+ - name: Commit and push changes
60
+ if: steps.changes.outputs.changed == 'true'
61
+ run: |
62
+ git config --local user.email "action@github.com"
63
+ git config --local user.name "GitHub Action"
64
+ git add docs/changelog.md
65
+ git commit -m "Add changelog entry for PR #${{ steps.pr-details.outputs.number }}"
66
+ git push
@@ -35,7 +35,7 @@ jobs:
35
35
  outputs:
36
36
  version: ${{ steps.bump.outputs.version }}
37
37
  steps:
38
- - uses: actions/checkout@v4
38
+ - uses: actions/checkout@v5
39
39
  - run: |
40
40
  git config user.name github-actions[bot]
41
41
  git config user.email 41898282+github-actions[bot]@users.noreply.github.com
@@ -59,11 +59,11 @@ jobs:
59
59
  needs: [bump]
60
60
  if: ${{ always() }}
61
61
  steps:
62
- - uses: actions/checkout@v4
62
+ - uses: actions/checkout@v5
63
63
  if: ${{ needs.bump.result == 'success' }}
64
64
  with:
65
65
  ref: version-${{ needs.bump.outputs.version }}
66
- - uses: actions/checkout@v4
66
+ - uses: actions/checkout@v5
67
67
  if: ${{ needs.bump.result == 'skipped' }}
68
68
  - uses: PyO3/maturin-action@v1.49.3
69
69
  with:
@@ -83,11 +83,11 @@ jobs:
83
83
  needs: [bump]
84
84
  if: ${{ always() }}
85
85
  steps:
86
- - uses: actions/checkout@v4
86
+ - uses: actions/checkout@v5
87
87
  if: ${{ needs.bump.result == 'success' }}
88
88
  with:
89
89
  ref: version-${{ needs.bump.outputs.version }}
90
- - uses: actions/checkout@v4
90
+ - uses: actions/checkout@v5
91
91
  if: ${{ needs.bump.result == 'skipped' }}
92
92
  - name: Setup QEMU
93
93
  uses: docker/setup-qemu-action@v3
@@ -108,11 +108,11 @@ jobs:
108
108
  needs: [bump]
109
109
  if: ${{ always() }}
110
110
  steps:
111
- - uses: actions/checkout@v4
111
+ - uses: actions/checkout@v5
112
112
  if: ${{ needs.bump.result == 'success' }}
113
113
  with:
114
114
  ref: version-${{ needs.bump.outputs.version }}
115
- - uses: actions/checkout@v4
115
+ - uses: actions/checkout@v5
116
116
  if: ${{ needs.bump.result == 'skipped' }}
117
117
  - uses: PyO3/maturin-action@v1.49.3
118
118
  with:
@@ -130,11 +130,11 @@ jobs:
130
130
  needs: [bump]
131
131
  if: ${{ always() }}
132
132
  steps:
133
- - uses: actions/checkout@v4
133
+ - uses: actions/checkout@v5
134
134
  if: ${{ needs.bump.result == 'success' }}
135
135
  with:
136
136
  ref: version-${{ needs.bump.outputs.version }}
137
- - uses: actions/checkout@v4
137
+ - uses: actions/checkout@v5
138
138
  if: ${{ needs.bump.result == 'skipped' }}
139
139
  - uses: PyO3/maturin-action@v1.49.3
140
140
  with:
@@ -169,7 +169,7 @@ jobs:
169
169
  permissions: write-all
170
170
  needs: [release, bump]
171
171
  steps:
172
- - uses: actions/checkout@v4
172
+ - uses: actions/checkout@v5
173
173
  with:
174
174
  ref: version-${{ needs.bump.outputs.version }}
175
175
  - run: |
@@ -404,7 +404,7 @@ dependencies = [
404
404
 
405
405
  [[package]]
406
406
  name = "egglog_python"
407
- version = "11.0.0"
407
+ version = "11.1.0"
408
408
  dependencies = [
409
409
  "core-relations",
410
410
  "egglog",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "egglog_python"
3
- version = "11.0.0"
3
+ version = "11.1.0"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: egglog
3
- Version: 11.0.0
3
+ Version: 11.1.0
4
4
  Classifier: Environment :: MacOS X
5
5
  Classifier: Environment :: Win32 (MS Windows)
6
6
  Classifier: Intended Audience :: Developers
@@ -4,13 +4,15 @@ _This project uses semantic versioning_
4
4
 
5
5
  ## UNRELEASED
6
6
 
7
- ## 11.0.0 (2025-08-08)
7
+ ## 11.1.0 (2025-08-21)
8
8
 
9
+ - Allow changing number of threads with env variable [#330](https://github.com/egraphs-good/egglog-python/pull/330)
10
+ ## 11.0.0 (2025-08-08)
9
11
  - Change conversion between binary operators to consider converting both types [#320](https://github.com/egraphs-good/egglog-python/pull/320)
10
12
  - Add ability to parse egglog expressions into Python values [#319](https://github.com/egraphs-good/egglog-python/pull/319)
11
13
  - Deprecates `.eval()` method on primitives in favor of `.value` which can be used with pattern matching.
12
14
  - Support methods like on expressions [#315](https://github.com/egraphs-good/egglog-python/pull/315)
13
- - Automatically Create Changelog Entry for PRs [#313](https://github.com/egraphs-good/egglog-python/pull/313)
15
+ - Automatically Create Changelog Entry for PRs [#313](https://github.com/egraphs-good/egglog-python/pull/313) [#317](https://github.com/egraphs-good/egglog-python/pull/317)
14
16
  - Upgrade egglog which includes new backend.
15
17
  - Fixes implementation of the Python Object sort to work with objects with dupliating hashes but the same value.
16
18
  Also changes the representation to be an index into a list instead of the ID, making egglog programs more deterministic.
@@ -77,6 +77,15 @@ make docs
77
77
  To debug the Rust parts of this project, follow the [PyO3 debugging guide](https://pyo3.rs/main/debugging.html#debugger-specific-setup).
78
78
  Debug symbols are turned on by default.
79
79
 
80
+ ### Performance
81
+
82
+ [`py-spy`](https://github.com/benfred/py-spy) is installed as a development dependency and can be used to profile Python code.
83
+ If there is a performance sensitive piece of code, you could isolate it in a file and profile it locally with:
84
+
85
+ ```bash
86
+ uv run py-spy record --format speedscope -- python tmp.py
87
+ ```
88
+
80
89
  ### Making changes
81
90
 
82
91
  All changes that impact users should be documented in the `docs/changelog.md` file. Please also add tests for any new features
@@ -22,6 +22,14 @@ pip install anywidget
22
22
 
23
23
  It follows [SPEC 0](https://scientific-python.org/specs/spec-0000/) in terms of what Python versions are supported.
24
24
 
25
+ ## Parallelism and threads
26
+
27
+ The underlying Rust library uses Rayon for parallelism. You can control the worker thread count via the environment variable `RAYON_NUM_THREADS`. If this variable is not set or is invalid, the Python bindings default to using a single thread (`1`).
28
+
29
+ ```shell
30
+ export RAYON_NUM_THREADS=4 # use 4 threads
31
+ ```
32
+
25
33
  (community)=
26
34
 
27
35
  ## Community
@@ -234,15 +234,15 @@ preview = true
234
234
 
235
235
  [tool.mypy]
236
236
  ignore_missing_imports = true
237
- warn_redundant_casts = true
238
- check_untyped_defs = true
239
- strict_equality = true
240
- warn_unused_configs = true
241
237
  allow_redefinition = true
242
238
  exclude = ["__snapshots__", "_build", "^conftest.py$"]
243
- # mypy_path = "python"
244
- # explicit_package_bases = true
245
- # namespace_packages = true
239
+ warn_unused_configs = true
240
+ disallow_subclassing_any = true
241
+ check_untyped_defs = true
242
+ warn_redundant_casts = true
243
+ warn_unused_ignores = true
244
+ strict_equality = true
245
+ extra_checks = true
246
246
 
247
247
  [tool.maturin]
248
248
  python-source = "python"
@@ -274,3 +274,8 @@ exclude_also = [
274
274
 
275
275
  [tool.uv.workspace]
276
276
  members = ["egglog"]
277
+
278
+ [dependency-groups]
279
+ dev = [
280
+ "py-spy>=0.4.1",
281
+ ]
@@ -91,7 +91,7 @@ class String(BuiltinExpr):
91
91
  def eval(self) -> str:
92
92
  return self.value
93
93
 
94
- @method(preserve=True) # type: ignore[misc]
94
+ @method(preserve=True) # type: ignore[prop-decorator]
95
95
  @property
96
96
  def value(self) -> str:
97
97
  if (value := get_literal_value(self)) is not None:
@@ -122,7 +122,7 @@ class Bool(BuiltinExpr, egg_sort="bool"):
122
122
  def eval(self) -> bool:
123
123
  return self.value
124
124
 
125
- @method(preserve=True) # type: ignore[misc]
125
+ @method(preserve=True) # type: ignore[prop-decorator]
126
126
  @property
127
127
  def value(self) -> bool:
128
128
  if (value := get_literal_value(self)) is not None:
@@ -165,7 +165,7 @@ class i64(BuiltinExpr): # noqa: N801
165
165
  def eval(self) -> int:
166
166
  return self.value
167
167
 
168
- @method(preserve=True) # type: ignore[misc]
168
+ @method(preserve=True) # type: ignore[prop-decorator]
169
169
  @property
170
170
  def value(self) -> int:
171
171
  if (value := get_literal_value(self)) is not None:
@@ -239,14 +239,14 @@ class i64(BuiltinExpr): # noqa: N801
239
239
  def __invert__(self) -> i64: ...
240
240
 
241
241
  @method(egg_fn="<")
242
- def __lt__(self, other: i64Like) -> Unit: # type: ignore[empty-body,has-type]
242
+ def __lt__(self, other: i64Like) -> Unit: # type: ignore[has-type]
243
243
  ...
244
244
 
245
245
  @method(egg_fn=">")
246
246
  def __gt__(self, other: i64Like) -> Unit: ...
247
247
 
248
248
  @method(egg_fn="<=")
249
- def __le__(self, other: i64Like) -> Unit: # type: ignore[empty-body,has-type]
249
+ def __le__(self, other: i64Like) -> Unit: # type: ignore[has-type]
250
250
  ...
251
251
 
252
252
  @method(egg_fn=">=")
@@ -292,7 +292,7 @@ class f64(BuiltinExpr): # noqa: N801
292
292
  def eval(self) -> float:
293
293
  return self.value
294
294
 
295
- @method(preserve=True) # type: ignore[misc]
295
+ @method(preserve=True) # type: ignore[prop-decorator]
296
296
  @property
297
297
  def value(self) -> float:
298
298
  if (value := get_literal_value(self)) is not None:
@@ -341,14 +341,14 @@ class f64(BuiltinExpr): # noqa: N801
341
341
  def __rmod__(self, other: f64Like) -> f64: ...
342
342
 
343
343
  @method(egg_fn="<")
344
- def __lt__(self, other: f64Like) -> Unit: # type: ignore[empty-body,has-type]
344
+ def __lt__(self, other: f64Like) -> Unit: # type: ignore[has-type]
345
345
  ...
346
346
 
347
347
  @method(egg_fn=">")
348
348
  def __gt__(self, other: f64Like) -> Unit: ...
349
349
 
350
350
  @method(egg_fn="<=")
351
- def __le__(self, other: f64Like) -> Unit: # type: ignore[empty-body,has-type]
351
+ def __le__(self, other: f64Like) -> Unit: # type: ignore[has-type]
352
352
  ...
353
353
 
354
354
  @method(egg_fn=">=")
@@ -387,7 +387,7 @@ class Map(BuiltinExpr, Generic[T, V]):
387
387
  def eval(self) -> dict[T, V]:
388
388
  return self.value
389
389
 
390
- @method(preserve=True) # type: ignore[misc]
390
+ @method(preserve=True) # type: ignore[prop-decorator]
391
391
  @property
392
392
  def value(self) -> dict[T, V]:
393
393
  d = {}
@@ -457,7 +457,7 @@ class Set(BuiltinExpr, Generic[T]):
457
457
  def eval(self) -> set[T]:
458
458
  return self.value
459
459
 
460
- @method(preserve=True) # type: ignore[misc]
460
+ @method(preserve=True) # type: ignore[prop-decorator]
461
461
  @property
462
462
  def value(self) -> set[T]:
463
463
  if (args := get_callable_args(self, Set[T])) is not None:
@@ -527,7 +527,7 @@ class MultiSet(BuiltinExpr, Generic[T]):
527
527
  def eval(self) -> list[T]:
528
528
  return self.value
529
529
 
530
- @method(preserve=True) # type: ignore[misc]
530
+ @method(preserve=True) # type: ignore[prop-decorator]
531
531
  @property
532
532
  def value(self) -> list[T]:
533
533
  if (args := get_callable_args(self, MultiSet[T])) is not None:
@@ -582,7 +582,7 @@ class Rational(BuiltinExpr):
582
582
  def eval(self) -> Fraction:
583
583
  return self.value
584
584
 
585
- @method(preserve=True) # type: ignore[misc]
585
+ @method(preserve=True) # type: ignore[prop-decorator]
586
586
  @property
587
587
  def value(self) -> Fraction:
588
588
  match get_callable_args(self, Rational):
@@ -651,11 +651,11 @@ class Rational(BuiltinExpr):
651
651
  @method(egg_fn="cbrt")
652
652
  def cbrt(self) -> Rational: ...
653
653
 
654
- @method(egg_fn="numer") # type: ignore[misc]
654
+ @method(egg_fn="numer") # type: ignore[prop-decorator]
655
655
  @property
656
656
  def numer(self) -> i64: ...
657
657
 
658
- @method(egg_fn="denom") # type: ignore[misc]
658
+ @method(egg_fn="denom") # type: ignore[prop-decorator]
659
659
  @property
660
660
  def denom(self) -> i64: ...
661
661
 
@@ -666,7 +666,7 @@ class BigInt(BuiltinExpr):
666
666
  def eval(self) -> int:
667
667
  return self.value
668
668
 
669
- @method(preserve=True) # type: ignore[misc]
669
+ @method(preserve=True) # type: ignore[prop-decorator]
670
670
  @property
671
671
  def value(self) -> int:
672
672
  match get_callable_args(self, BigInt.from_string):
@@ -744,14 +744,14 @@ class BigInt(BuiltinExpr):
744
744
  def bits(self) -> BigInt: ...
745
745
 
746
746
  @method(egg_fn="<")
747
- def __lt__(self, other: BigIntLike) -> Unit: # type: ignore[empty-body,has-type]
747
+ def __lt__(self, other: BigIntLike) -> Unit: # type: ignore[has-type]
748
748
  ...
749
749
 
750
750
  @method(egg_fn=">")
751
751
  def __gt__(self, other: BigIntLike) -> Unit: ...
752
752
 
753
753
  @method(egg_fn="<=")
754
- def __le__(self, other: BigIntLike) -> Unit: # type: ignore[empty-body,has-type]
754
+ def __le__(self, other: BigIntLike) -> Unit: # type: ignore[has-type]
755
755
  ...
756
756
 
757
757
  @method(egg_fn=">=")
@@ -793,7 +793,7 @@ class BigRat(BuiltinExpr):
793
793
  def eval(self) -> Fraction:
794
794
  return self.value
795
795
 
796
- @method(preserve=True) # type: ignore[misc]
796
+ @method(preserve=True) # type: ignore[prop-decorator]
797
797
  @property
798
798
  def value(self) -> Fraction:
799
799
  match get_callable_args(self, BigRat):
@@ -862,11 +862,11 @@ class BigRat(BuiltinExpr):
862
862
  @method(egg_fn="cbrt")
863
863
  def cbrt(self) -> BigRat: ...
864
864
 
865
- @method(egg_fn="numer") # type: ignore[misc]
865
+ @method(egg_fn="numer") # type: ignore[prop-decorator]
866
866
  @property
867
867
  def numer(self) -> BigInt: ...
868
868
 
869
- @method(egg_fn="denom") # type: ignore[misc]
869
+ @method(egg_fn="denom") # type: ignore[prop-decorator]
870
870
  @property
871
871
  def denom(self) -> BigInt: ...
872
872
 
@@ -893,7 +893,7 @@ class Vec(BuiltinExpr, Generic[T]):
893
893
  def eval(self) -> tuple[T, ...]:
894
894
  return self.value
895
895
 
896
- @method(preserve=True) # type: ignore[misc]
896
+ @method(preserve=True) # type: ignore[prop-decorator]
897
897
  @property
898
898
  def value(self) -> tuple[T, ...]:
899
899
  if get_callable_args(self, Vec.empty) is not None:
@@ -972,7 +972,7 @@ class PyObject(BuiltinExpr):
972
972
  def eval(self) -> object:
973
973
  return self.value
974
974
 
975
- @method(preserve=True) # type: ignore[misc]
975
+ @method(preserve=True) # type: ignore[prop-decorator]
976
976
  @property
977
977
  def value(self) -> object:
978
978
  expr = cast("RuntimeExpr", self).__egg_typed_expr__.expr
@@ -1767,7 +1767,7 @@ def _rewrite_or_rule_generator(gen: RewriteOrRuleGenerator, frame: FrameType) ->
1767
1767
  combined = {**gen.__globals__, **frame.f_locals}
1768
1768
  hints = get_type_hints(gen, combined, combined)
1769
1769
  args = [_var(p.name, hints[p.name], egg_name=None) for p in signature(gen).parameters.values()]
1770
- return list(gen(*args)) # type: ignore[misc]
1770
+ return list(gen(*args))
1771
1771
 
1772
1772
 
1773
1773
  FactLike = Fact | BaseExpr
@@ -79,7 +79,7 @@ class Program(Expr):
79
79
  Triggers compilation of the program.
80
80
  """
81
81
 
82
- @method(merge=lambda old, _new: old) # type: ignore[misc]
82
+ @method(merge=lambda old, _new: old) # type: ignore[prop-decorator]
83
83
  @property
84
84
  def parent(self) -> Program:
85
85
  """
@@ -108,7 +108,7 @@ class EvalProgram(Expr):
108
108
  """
109
109
 
110
110
  # Only allow it to be set once, b/c hash of functions not stable
111
- @method(merge=lambda old, _new: old) # type: ignore[misc]
111
+ @method(merge=lambda old, _new: old) # type: ignore[prop-decorator]
112
112
  @property
113
113
  def as_py_object(self) -> PyObject:
114
114
  """
@@ -39,7 +39,7 @@ if BEFORE_3_11:
39
39
  if isinstance(t, typevar_types) and t not in tvars:
40
40
  tvars.append(t)
41
41
  # **MONKEYPATCH CHANGE HERE TO ADD RuntimeClass**
42
- if isinstance(t, (typing._GenericAlias, typing.GenericAlias, types.UnionType, RuntimeClass)): # type: ignore[name-defined]
42
+ if isinstance(t, (typing._GenericAlias, typing.GenericAlias, types.UnionType, RuntimeClass)):
43
43
  tvars.extend([t for t in t.__parameters__ if t not in tvars])
44
44
  return tuple(tvars)
45
45
 
@@ -895,12 +895,12 @@ class TestMatch:
895
895
 
896
896
  __match_args__ = ("a", "b")
897
897
 
898
- @method(preserve=True) # type: ignore[misc]
898
+ @method(preserve=True) # type: ignore[prop-decorator]
899
899
  @property
900
900
  def a(self) -> int:
901
901
  return 1
902
902
 
903
- @method(preserve=True) # type: ignore[misc]
903
+ @method(preserve=True) # type: ignore[prop-decorator]
904
904
  @property
905
905
  def b(self) -> str:
906
906
  return "hi"
@@ -923,7 +923,7 @@ class TestMatch:
923
923
 
924
924
  __match_args__ = ("a",)
925
925
 
926
- @method(preserve=True) # type: ignore[misc]
926
+ @method(preserve=True) # type: ignore[prop-decorator]
927
927
  @property
928
928
  def a(self) -> int:
929
929
  raise AttributeError
@@ -23,7 +23,7 @@ class Math(Expr):
23
23
 
24
24
  def __neg__(self) -> Math: ...
25
25
 
26
- @method(cost=1000) # type: ignore[misc]
26
+ @method(cost=1000) # type: ignore[prop-decorator]
27
27
  @property
28
28
  def program(self) -> Program: ...
29
29
 
@@ -51,11 +51,11 @@ def test_classmethod_call():
51
51
  )
52
52
  Map = RuntimeClass(Thunk.value(decls), TypeRefWithVars("Map"))
53
53
  with pytest.raises(TypeConstraintError):
54
- Map.create() # type: ignore[operator]
54
+ Map.create()
55
55
  i64 = RuntimeClass(Thunk.value(decls), TypeRefWithVars("i64"))
56
56
  unit = RuntimeClass(Thunk.value(decls), TypeRefWithVars("unit"))
57
57
  assert (
58
- Map[i64, unit].create().__egg_typed_expr__ # type: ignore[union-attr, operator]
58
+ Map[i64, unit].create().__egg_typed_expr__ # type: ignore[union-attr]
59
59
  == TypedExprDecl(
60
60
  JustTypeRef("Map", (JustTypeRef("i64"), JustTypeRef("unit"))),
61
61
  CallDecl(ClassMethodRef("Map", "create"), (), (JustTypeRef("i64"), JustTypeRef("unit"))),
@@ -11,8 +11,13 @@ use pyo3::prelude::*;
11
11
  /// Bindings for egglog rust library
12
12
  #[pymodule]
13
13
  fn bindings(m: &Bound<'_, PyModule>) -> PyResult<()> {
14
+ // Configure Rayon thread pool from env var, defaulting to 1 if unset/invalid.
15
+ let num_threads = std::env::var("RAYON_NUM_THREADS")
16
+ .ok()
17
+ .and_then(|s| s.parse::<usize>().ok())
18
+ .unwrap_or(1);
14
19
  rayon::ThreadPoolBuilder::new()
15
- .num_threads(1)
20
+ .num_threads(num_threads)
16
21
  .build_global()
17
22
  .unwrap();
18
23
 
@@ -765,6 +765,11 @@ test = [
765
765
  { name = "syrupy" },
766
766
  ]
767
767
 
768
+ [package.dev-dependencies]
769
+ dev = [
770
+ { name = "py-spy" },
771
+ ]
772
+
768
773
  [package.metadata]
769
774
  requires-dist = [
770
775
  { name = "ablog", marker = "extra == 'docs'" },
@@ -803,12 +808,15 @@ requires-dist = [
803
808
  { name = "typing-extensions" },
804
809
  ]
805
810
 
811
+ [package.metadata.requires-dev]
812
+ dev = [{ name = "py-spy", specifier = ">=0.4.1" }]
813
+
806
814
  [[package]]
807
815
  name = "exceptiongroup"
808
816
  version = "1.3.0"
809
817
  source = { registry = "https://pypi.org/simple" }
810
818
  dependencies = [
811
- { name = "typing-extensions", marker = "python_full_version < '3.13'" },
819
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
812
820
  ]
813
821
  sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 }
814
822
  wheels = [
@@ -2494,6 +2502,21 @@ wheels = [
2494
2502
  { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335 },
2495
2503
  ]
2496
2504
 
2505
+ [[package]]
2506
+ name = "py-spy"
2507
+ version = "0.4.1"
2508
+ source = { registry = "https://pypi.org/simple" }
2509
+ sdist = { url = "https://files.pythonhosted.org/packages/19/e2/ff811a367028b87e86714945bb9ecb5c1cc69114a8039a67b3a862cef921/py_spy-0.4.1.tar.gz", hash = "sha256:e53aa53daa2e47c2eef97dd2455b47bb3a7e7f962796a86cc3e7dbde8e6f4db4", size = 244726 }
2510
+ wheels = [
2511
+ { url = "https://files.pythonhosted.org/packages/14/e3/3a32500d845bdd94f6a2b4ed6244982f42ec2bc64602ea8fcfe900678ae7/py_spy-0.4.1-py2.py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:809094208c6256c8f4ccadd31e9a513fe2429253f48e20066879239ba12cd8cc", size = 3682508 },
2512
+ { url = "https://files.pythonhosted.org/packages/4f/bf/e4d280e9e0bec71d39fc646654097027d4bbe8e04af18fb68e49afcff404/py_spy-0.4.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:1fb8bf71ab8df95a95cc387deed6552934c50feef2cf6456bc06692a5508fd0c", size = 1796395 },
2513
+ { url = "https://files.pythonhosted.org/packages/df/79/9ed50bb0a9de63ed023aa2db8b6265b04a7760d98c61eb54def6a5fddb68/py_spy-0.4.1-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee776b9d512a011d1ad3907ed53ae32ce2f3d9ff3e1782236554e22103b5c084", size = 2034938 },
2514
+ { url = "https://files.pythonhosted.org/packages/53/a5/36862e3eea59f729dfb70ee6f9e14b051d8ddce1aa7e70e0b81d9fe18536/py_spy-0.4.1-py2.py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:532d3525538254d1859b49de1fbe9744df6b8865657c9f0e444bf36ce3f19226", size = 2658968 },
2515
+ { url = "https://files.pythonhosted.org/packages/08/f8/9ea0b586b065a623f591e5e7961282ec944b5fbbdca33186c7c0296645b3/py_spy-0.4.1-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4972c21890b6814017e39ac233c22572c4a61fd874524ebc5ccab0f2237aee0a", size = 2147541 },
2516
+ { url = "https://files.pythonhosted.org/packages/68/fb/bc7f639aed026bca6e7beb1e33f6951e16b7d315594e7635a4f7d21d63f4/py_spy-0.4.1-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6a80ec05eb8a6883863a367c6a4d4f2d57de68466f7956b6367d4edd5c61bb29", size = 2763338 },
2517
+ { url = "https://files.pythonhosted.org/packages/e1/da/fcc9a9fcd4ca946ff402cff20348e838b051d69f50f5d1f5dca4cd3c5eb8/py_spy-0.4.1-py2.py3-none-win_amd64.whl", hash = "sha256:d92e522bd40e9bf7d87c204033ce5bb5c828fca45fa28d970f58d71128069fdc", size = 1818784 },
2518
+ ]
2519
+
2497
2520
  [[package]]
2498
2521
  name = "pycparser"
2499
2522
  version = "2.22"
@@ -1,52 +0,0 @@
1
- name: Update Changelog
2
-
3
- on:
4
- pull_request:
5
- types: [opened, edited]
6
-
7
- jobs:
8
- update-changelog:
9
- # Only run if this is not a PR from a fork to avoid permission issues
10
- # and not a commit made by GitHub Action to avoid infinite loops
11
- if: github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'github-actions[bot]'
12
- runs-on: ubuntu-latest
13
- permissions:
14
- contents: write
15
- pull-requests: write
16
-
17
- steps:
18
- - name: Checkout repository
19
- uses: actions/checkout@v4
20
- with:
21
- # Checkout the PR head ref
22
- ref: ${{ github.event.pull_request.head.ref }}
23
- token: ${{ secrets.GITHUB_TOKEN }}
24
-
25
- - name: Set up Python
26
- uses: actions/setup-python@v5
27
- with:
28
- python-version: '3.12'
29
-
30
- - name: Update changelog
31
- run: |
32
- python modify_changelog.py update_changelog \
33
- "${{ github.event.pull_request.number }}" \
34
- "${{ github.event.pull_request.title }}"
35
-
36
- - name: Check for changes
37
- id: changes
38
- run: |
39
- if git diff --quiet docs/changelog.md; then
40
- echo "changed=false" >> $GITHUB_OUTPUT
41
- else
42
- echo "changed=true" >> $GITHUB_OUTPUT
43
- fi
44
-
45
- - name: Commit and push changes
46
- if: steps.changes.outputs.changed == 'true'
47
- run: |
48
- git config --local user.email "action@github.com"
49
- git config --local user.name "GitHub Action"
50
- git add docs/changelog.md
51
- git commit -m "Add changelog entry for PR #${{ github.event.pull_request.number }}"
52
- git push
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes