openclatura 0.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.
Files changed (230) hide show
  1. openclatura-0.1.0/.dockerignore +21 -0
  2. openclatura-0.1.0/.github/workflows/ci.yml +123 -0
  3. openclatura-0.1.0/.github/workflows/docker-image.yml +57 -0
  4. openclatura-0.1.0/.gitignore +68 -0
  5. openclatura-0.1.0/.pre-commit-config.yaml +18 -0
  6. openclatura-0.1.0/Dockerfile +55 -0
  7. openclatura-0.1.0/LICENSE +21 -0
  8. openclatura-0.1.0/PKG-INFO +241 -0
  9. openclatura-0.1.0/README.md +196 -0
  10. openclatura-0.1.0/docker/compose.yaml +14 -0
  11. openclatura-0.1.0/eval_failures/pubchem/all_failures.csv +5445 -0
  12. openclatura-0.1.0/eval_failures/pubchem/failures_seed_17.csv +1143 -0
  13. openclatura-0.1.0/eval_failures/pubchem/failures_seed_42.csv +1095 -0
  14. openclatura-0.1.0/eval_failures/pubchem/failures_seed_5.csv +1080 -0
  15. openclatura-0.1.0/eval_failures/pubchem/failures_seed_63.csv +1081 -0
  16. openclatura-0.1.0/eval_failures/pubchem/failures_seed_87.csv +1050 -0
  17. openclatura-0.1.0/eval_failures/pubchem/summary.csv +6 -0
  18. openclatura-0.1.0/eval_failures/zinc22/all_failures.csv +17524 -0
  19. openclatura-0.1.0/eval_failures/zinc22/failures_seed_17.csv +3491 -0
  20. openclatura-0.1.0/eval_failures/zinc22/failures_seed_42.csv +3443 -0
  21. openclatura-0.1.0/eval_failures/zinc22/failures_seed_5.csv +3605 -0
  22. openclatura-0.1.0/eval_failures/zinc22/failures_seed_63.csv +3531 -0
  23. openclatura-0.1.0/eval_failures/zinc22/failures_seed_87.csv +3458 -0
  24. openclatura-0.1.0/eval_failures/zinc22/summary.csv +6 -0
  25. openclatura-0.1.0/examples/README.md +27 -0
  26. openclatura-0.1.0/examples/eval_via_opsin.py +63 -0
  27. openclatura-0.1.0/examples/find_small_failures.py +183 -0
  28. openclatura-0.1.0/examples/opsin_eval_ZINC22.py +508 -0
  29. openclatura-0.1.0/examples/opsin_eval_pubchem.py +326 -0
  30. openclatura-0.1.0/examples/qm9_iupac_collect_token_confidence.py +204 -0
  31. openclatura-0.1.0/examples/random_sanity.py +125 -0
  32. openclatura-0.1.0/examples/sanity_examples.py +70 -0
  33. openclatura-0.1.0/examples/test_opsin_mac.py +96 -0
  34. openclatura-0.1.0/examples/test_opsin_mac_ZINC.py +217 -0
  35. openclatura-0.1.0/examples/test_opsin_mac_ZINC22.py +226 -0
  36. openclatura-0.1.0/examples/test_opsin_mac_ZINC22_light.py +240 -0
  37. openclatura-0.1.0/examples/test_qm9_opsin_batch.py +250 -0
  38. openclatura-0.1.0/pyproject.toml +130 -0
  39. openclatura-0.1.0/scripts/regenerate_goldens.py +49 -0
  40. openclatura-0.1.0/src/openclatura/__init__.py +110 -0
  41. openclatura-0.1.0/src/openclatura/additive.py +50 -0
  42. openclatura-0.1.0/src/openclatura/assembler.py +427 -0
  43. openclatura-0.1.0/src/openclatura/assembly_charge.py +105 -0
  44. openclatura-0.1.0/src/openclatura/assembly_parent.py +229 -0
  45. openclatura-0.1.0/src/openclatura/assembly_parts.py +122 -0
  46. openclatura-0.1.0/src/openclatura/assembly_prefixes.py +193 -0
  47. openclatura-0.1.0/src/openclatura/assembly_spiro.py +373 -0
  48. openclatura-0.1.0/src/openclatura/assembly_utils.py +44 -0
  49. openclatura-0.1.0/src/openclatura/chains.py +1154 -0
  50. openclatura-0.1.0/src/openclatura/charge_pair_roles.py +286 -0
  51. openclatura-0.1.0/src/openclatura/charge_specs.py +6 -0
  52. openclatura-0.1.0/src/openclatura/cli.py +181 -0
  53. openclatura-0.1.0/src/openclatura/component_group_rules.py +85 -0
  54. openclatura-0.1.0/src/openclatura/component_modifiers.py +281 -0
  55. openclatura-0.1.0/src/openclatura/component_namer.py +671 -0
  56. openclatura-0.1.0/src/openclatura/data/fused_emission_examples.json +40 -0
  57. openclatura-0.1.0/src/openclatura/data/fused_ion_templates.json +126 -0
  58. openclatura-0.1.0/src/openclatura/data/heteroatom_substituents.json +156 -0
  59. openclatura-0.1.0/src/openclatura/data/namer_rules.json +2657 -0
  60. openclatura-0.1.0/src/openclatura/data/parser_grammar_snapshot.json +463 -0
  61. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/alkanes.json +522 -0
  62. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/aminoAcids.json +1713 -0
  63. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/arylGroups.json +3037 -0
  64. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/arylSubstituents.json +1014 -0
  65. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/atomHydrides.json +292 -0
  66. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/carbohydrates.json +849 -0
  67. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/carboxylicAcids.json +1990 -0
  68. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/chargeAndOxidationNumberSpecifiers.json +349 -0
  69. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/cyclicUnsaturableHydrocarbon.json +213 -0
  70. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/elementaryAtoms.json +1128 -0
  71. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/fragments.json +10864 -0
  72. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/functionalTerms.json +1072 -0
  73. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/fusionComponents.json +1439 -0
  74. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/germanTokens.json +103 -0
  75. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/groupStemsAllowingAllSuffixes.json +53 -0
  76. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/groupStemsAllowingInlineSuffixes.json +403 -0
  77. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/heteroAtoms.json +3243 -0
  78. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/hwHeteroAtoms.json +732 -0
  79. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/hwSuffixes.json +506 -0
  80. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/infixes.json +209 -0
  81. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/inlineChargeSuffixes.json +63 -0
  82. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/inlineSuffixes.json +412 -0
  83. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/miscTokens.json +808 -0
  84. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/multiRadicalSubstituents.json +977 -0
  85. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/multipliers.json +1090 -0
  86. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/naturalProducts.json +302 -0
  87. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/nonCarboxylicAcids.json +2605 -0
  88. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/regexTokens.json +355 -0
  89. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/regexes.json +1668 -0
  90. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/simpleCyclicGroups.json +1313 -0
  91. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/simpleGroups.json +3291 -0
  92. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/simpleSubstituents.json +4082 -0
  93. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/substituents.json +138 -0
  94. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/suffixApplicability.json +1260 -0
  95. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/suffixPrefix.json +167 -0
  96. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/suffixRules.json +1587 -0
  97. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/suffixes.json +1003 -0
  98. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/unsaturators.json +71 -0
  99. openclatura-0.1.0/src/openclatura/data/parser_xml_resources/wordRules.json +1325 -0
  100. openclatura-0.1.0/src/openclatura/data/retained_fused_graph_templates.json +584 -0
  101. openclatura-0.1.0/src/openclatura/describer.py +614 -0
  102. openclatura-0.1.0/src/openclatura/engine.py +369 -0
  103. openclatura-0.1.0/src/openclatura/formatting.py +122 -0
  104. openclatura-0.1.0/src/openclatura/functional_groups.py +61 -0
  105. openclatura-0.1.0/src/openclatura/functional_prefixes.py +277 -0
  106. openclatura-0.1.0/src/openclatura/fused_ion_templates.py +311 -0
  107. openclatura-0.1.0/src/openclatura/fused_topology.py +592 -0
  108. openclatura-0.1.0/src/openclatura/grammar_snapshot_data.py +172 -0
  109. openclatura-0.1.0/src/openclatura/graph_io.py +110 -0
  110. openclatura-0.1.0/src/openclatura/group_atom_roles.py +58 -0
  111. openclatura-0.1.0/src/openclatura/heteroatom_subgraphs.py +790 -0
  112. openclatura-0.1.0/src/openclatura/heteroatom_substituent_specs.py +116 -0
  113. openclatura-0.1.0/src/openclatura/heterocumulene_roles.py +85 -0
  114. openclatura-0.1.0/src/openclatura/human_descriptor.py +549 -0
  115. openclatura-0.1.0/src/openclatura/hypervalent_roles.py +185 -0
  116. openclatura-0.1.0/src/openclatura/ionic_naming.py +666 -0
  117. openclatura-0.1.0/src/openclatura/locants.py +110 -0
  118. openclatura-0.1.0/src/openclatura/map_namer.md +183 -0
  119. openclatura-0.1.0/src/openclatura/molecule.py +243 -0
  120. openclatura-0.1.0/src/openclatura/name_assembly.py +2486 -0
  121. openclatura-0.1.0/src/openclatura/name_bindings.py +1445 -0
  122. openclatura-0.1.0/src/openclatura/name_operations.py +67 -0
  123. openclatura-0.1.0/src/openclatura/name_postprocessing.py +173 -0
  124. openclatura-0.1.0/src/openclatura/namer.py +1746 -0
  125. openclatura-0.1.0/src/openclatura/namer_config.py +34 -0
  126. openclatura-0.1.0/src/openclatura/naming_audit.py +65 -0
  127. openclatura-0.1.0/src/openclatura/naming_context.py +167 -0
  128. openclatura-0.1.0/src/openclatura/naming_data.py +64 -0
  129. openclatura-0.1.0/src/openclatura/nitrogen_roles.py +769 -0
  130. openclatura-0.1.0/src/openclatura/nomenclature.py +454 -0
  131. openclatura-0.1.0/src/openclatura/numbering.py +369 -0
  132. openclatura-0.1.0/src/openclatura/operations.py +52 -0
  133. openclatura-0.1.0/src/openclatura/opsin_verify.py +172 -0
  134. openclatura-0.1.0/src/openclatura/overview_namer.md +172 -0
  135. openclatura-0.1.0/src/openclatura/oxoacid_roles.py +164 -0
  136. openclatura-0.1.0/src/openclatura/oxoacid_templates.py +154 -0
  137. openclatura-0.1.0/src/openclatura/parent_pipeline.py +211 -0
  138. openclatura-0.1.0/src/openclatura/parent_selection.py +449 -0
  139. openclatura-0.1.0/src/openclatura/perception.py +734 -0
  140. openclatura-0.1.0/src/openclatura/peroxy_carbonyl_roles.py +171 -0
  141. openclatura-0.1.0/src/openclatura/polycycle_topology.py +809 -0
  142. openclatura-0.1.0/src/openclatura/principal_groups.py +132 -0
  143. openclatura-0.1.0/src/openclatura/principal_suffixes.py +32 -0
  144. openclatura-0.1.0/src/openclatura/resonance_compare.py +96 -0
  145. openclatura-0.1.0/src/openclatura/retained_fused_production.py +112 -0
  146. openclatura-0.1.0/src/openclatura/retained_fused_templates.py +576 -0
  147. openclatura-0.1.0/src/openclatura/retained_specs.py +44 -0
  148. openclatura-0.1.0/src/openclatura/ring_parent.py +92 -0
  149. openclatura-0.1.0/src/openclatura/ring_renderer.py +66 -0
  150. openclatura-0.1.0/src/openclatura/ring_systems.py +78 -0
  151. openclatura-0.1.0/src/openclatura/role_certificate.py +194 -0
  152. openclatura-0.1.0/src/openclatura/rule_layout.py +170 -0
  153. openclatura-0.1.0/src/openclatura/rules/__init__.py +0 -0
  154. openclatura-0.1.0/src/openclatura/rules/bonds.py +73 -0
  155. openclatura-0.1.0/src/openclatura/rules/elements.py +47 -0
  156. openclatura-0.1.0/src/openclatura/rules/elision.py +123 -0
  157. openclatura-0.1.0/src/openclatura/rules/locants.py +182 -0
  158. openclatura-0.1.0/src/openclatura/rules/multipliers.py +64 -0
  159. openclatura-0.1.0/src/openclatura/rules/retained.py +966 -0
  160. openclatura-0.1.0/src/openclatura/rules/stems.py +65 -0
  161. openclatura-0.1.0/src/openclatura/rules/substituents.py +34 -0
  162. openclatura-0.1.0/src/openclatura/rules/suffixes.py +48 -0
  163. openclatura-0.1.0/src/openclatura/small_ring_stereo.py +112 -0
  164. openclatura-0.1.0/src/openclatura/special_cases.py +2237 -0
  165. openclatura-0.1.0/src/openclatura/spiro_assembly.py +12 -0
  166. openclatura-0.1.0/src/openclatura/stereo_audit.py +108 -0
  167. openclatura-0.1.0/src/openclatura/stereo_descriptors.py +20 -0
  168. openclatura-0.1.0/src/openclatura/subgraph_tools.py +126 -0
  169. openclatura-0.1.0/src/openclatura/substituent_tokens.py +631 -0
  170. openclatura-0.1.0/src/openclatura/subtractive.py +63 -0
  171. openclatura-0.1.0/src/openclatura/suffix_stack.py +185 -0
  172. openclatura-0.1.0/src/openclatura/tests/test_analysis.py +5296 -0
  173. openclatura-0.1.0/src/openclatura/tests/test_public_api.py +274 -0
  174. openclatura-0.1.0/src/openclatura/tests_roundtrip/__init__.py +1 -0
  175. openclatura-0.1.0/src/openclatura/tests_roundtrip/roundtrip_helpers.py +136 -0
  176. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_acids.py +27 -0
  177. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_alcohols.py +24 -0
  178. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_alkanes.py +26 -0
  179. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_alkenes_alkynes.py +24 -0
  180. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_api.py +20 -0
  181. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_bridges.py +42 -0
  182. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_charged_fused_heteroaromatics.py +17 -0
  183. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_composite_bridges.py +24 -0
  184. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_fused_multiplicity.py +20 -0
  185. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_fused_parents.py +149 -0
  186. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_haloalkanes.py +21 -0
  187. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_heteroatom_substituents.py +23 -0
  188. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_heterocycles.py +73 -0
  189. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_imines.py +24 -0
  190. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_ketones.py +21 -0
  191. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_locant_display.py +21 -0
  192. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_nitro.py +22 -0
  193. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_numbering.py +20 -0
  194. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_oxyacids.py +27 -0
  195. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_p13_additive.py +22 -0
  196. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_p13_conjunctive.py +38 -0
  197. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_p13_fusion.py +23 -0
  198. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_p13_multiplicative.py +23 -0
  199. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_p13_operations.py +21 -0
  200. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_p13_replacement.py +22 -0
  201. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_p13_substitutive.py +28 -0
  202. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_p13_subtractive.py +23 -0
  203. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_polycycles.py +29 -0
  204. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_polycyclic_descriptors.py +23 -0
  205. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_polyfunctional_fragments.py +30 -0
  206. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_principal_group_breadth.py +37 -0
  207. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_replacement_parents.py +55 -0
  208. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_role_models.py +24 -0
  209. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_stereo.py +20 -0
  210. openclatura-0.1.0/src/openclatura/tests_roundtrip/test_substituents.py +25 -0
  211. openclatura-0.1.0/src/openclatura/token_grammar.py +88 -0
  212. openclatura-0.1.0/src/openclatura/trace_helpers.py +528 -0
  213. openclatura-0.1.0/src/openclatura/utils.py +48 -0
  214. openclatura-0.1.0/src/openclatura/von_baeyer.py +470 -0
  215. openclatura-0.1.0/src/openclatura/web/__init__.py +10 -0
  216. openclatura-0.1.0/src/openclatura/web/__main__.py +32 -0
  217. openclatura-0.1.0/src/openclatura/web/app.py +99 -0
  218. openclatura-0.1.0/test_opsin_qm9_batch.py +97 -0
  219. openclatura-0.1.0/testing_guide.md +52 -0
  220. openclatura-0.1.0/tests/datasets/test_pubchem_sample.py +63 -0
  221. openclatura-0.1.0/tests/datasets/test_qm9_sample.py +61 -0
  222. openclatura-0.1.0/tests/fixtures/diverse_corpus.csv +107 -0
  223. openclatura-0.1.0/tests/fixtures/diverse_corpus.golden.json +532 -0
  224. openclatura-0.1.0/tests/fuzz/test_smiles_strategies.py +212 -0
  225. openclatura-0.1.0/tests/integration/test_corpus_golden.py +62 -0
  226. openclatura-0.1.0/tests/integration/test_describer.py +196 -0
  227. openclatura-0.1.0/tests/integration/test_diverse_corpus.py +133 -0
  228. openclatura-0.1.0/tests/integration/test_human_descriptor.py +69 -0
  229. openclatura-0.1.0/tests/integration/test_web_app.py +81 -0
  230. openclatura-0.1.0/to-uninstall.txt +109 -0
@@ -0,0 +1,21 @@
1
+ .git
2
+ .github
3
+ .venv
4
+ venv
5
+ __pycache__
6
+ *.pyc
7
+ *.pyo
8
+ .pytest_cache
9
+ .ruff_cache
10
+ .mypy_cache
11
+ .coverage
12
+ htmlcov
13
+ .hypothesis
14
+ build
15
+ dist
16
+ *.egg-info
17
+ examples
18
+ tests
19
+ docs
20
+ *.md
21
+ !README.md
@@ -0,0 +1,123 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, refactor_namer]
6
+ pull_request:
7
+ branches: ["**"]
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ lint:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+ - name: Install ruff
22
+ run: pip install "ruff==0.15.15"
23
+ - name: Ruff check
24
+ run: ruff check src/openclatura
25
+ - name: Ruff format check
26
+ run: ruff format --check src/openclatura
27
+
28
+ test:
29
+ runs-on: ubuntu-latest
30
+ strategy:
31
+ fail-fast: false
32
+ matrix:
33
+ python-version: ["3.11", "3.12"]
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+
37
+ - uses: actions/setup-python@v5
38
+ with:
39
+ python-version: ${{ matrix.python-version }}
40
+ cache: pip
41
+
42
+ - name: Set up JDK (for OPSIN)
43
+ uses: actions/setup-java@v4
44
+ with:
45
+ distribution: temurin
46
+ java-version: "17"
47
+
48
+ - name: Install package
49
+ run: |
50
+ python -m pip install --upgrade pip
51
+ pip install -e ".[dev]"
52
+
53
+ - name: Run fast tests
54
+ run: |
55
+ pytest -m "not slow and not dataset and not golden" \
56
+ --maxfail=20 \
57
+ --durations=20
58
+
59
+ rdkit-compat:
60
+ # Strict golden-output regression across RDKit versions. Detects
61
+ # naming output that drifts with the RDKit upgrade cycle (aromaticity,
62
+ # kekulisation, H-count perception). Each matrix row pins a specific
63
+ # rdkit and runs only the `golden` test.
64
+ runs-on: ubuntu-latest
65
+ needs: [lint]
66
+ strategy:
67
+ fail-fast: false
68
+ matrix:
69
+ rdkit-version:
70
+ - "2024.03.6"
71
+ - "2024.09.6"
72
+ - "2025.03.6"
73
+ - "2025.09.5"
74
+ - "2026.3.2"
75
+ steps:
76
+ - uses: actions/checkout@v4
77
+
78
+ - uses: actions/setup-python@v5
79
+ with:
80
+ python-version: "3.12"
81
+ cache: pip
82
+
83
+ - name: Install package + pinned rdkit
84
+ run: |
85
+ python -m pip install --upgrade pip
86
+ pip install -e ".[dev]"
87
+ pip install --force-reinstall --no-deps "rdkit==${{ matrix.rdkit-version }}"
88
+
89
+ - name: Show installed versions
90
+ run: |
91
+ python -c "import rdkit, sys; print('python', sys.version); print('rdkit', rdkit.__version__)"
92
+
93
+ - name: Run golden corpus test
94
+ run: pytest -m golden -v --no-header -rA
95
+
96
+ docker:
97
+ runs-on: ubuntu-latest
98
+ needs: [lint]
99
+ steps:
100
+ - uses: actions/checkout@v4
101
+
102
+ - uses: docker/setup-buildx-action@v3
103
+
104
+ - name: Build image
105
+ uses: docker/build-push-action@v6
106
+ with:
107
+ context: .
108
+ push: false
109
+ load: true
110
+ tags: openclatura:ci
111
+
112
+ - name: Smoke-test the image
113
+ run: |
114
+ docker run -d --name openclatura-ci -p 8000:8000 openclatura:ci
115
+ for i in $(seq 1 30); do
116
+ if curl -fsS http://127.0.0.1:8000/healthz > /tmp/health.json; then
117
+ cat /tmp/health.json
118
+ exit 0
119
+ fi
120
+ sleep 2
121
+ done
122
+ docker logs openclatura-ci
123
+ exit 1
@@ -0,0 +1,57 @@
1
+ name: Build Docker image
2
+
3
+ on:
4
+ push:
5
+ branches: [main, tests]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+ packages: write
12
+
13
+ env:
14
+ IMAGE_NAME: ghcr.io/${{ github.repository }}
15
+
16
+ jobs:
17
+ docker:
18
+ runs-on: ubuntu-latest
19
+
20
+ steps:
21
+ - name: Check out repository
22
+ uses: actions/checkout@v6
23
+
24
+ - name: Set up Docker Buildx
25
+ uses: docker/setup-buildx-action@v3
26
+
27
+ - name: Log in to GitHub Container Registry
28
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
29
+ uses: docker/login-action@v4
30
+ with:
31
+ registry: ghcr.io
32
+ username: ${{ github.actor }}
33
+ password: ${{ secrets.GITHUB_TOKEN }}
34
+
35
+ - name: Build local image for smoke test
36
+ uses: docker/build-push-action@v7
37
+ with:
38
+ context: .
39
+ load: true
40
+ tags: openclatura:test
41
+
42
+ - name: Smoke test Docker image
43
+ run: |
44
+ docker run --rm -d -p 8000:8000 --name openclatura-test openclatura:test
45
+ sleep 5
46
+ curl --fail http://localhost:8000/healthz
47
+ docker stop openclatura-test
48
+
49
+ - name: Build and push image from main
50
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
51
+ uses: docker/build-push-action@v7
52
+ with:
53
+ context: .
54
+ push: true
55
+ tags: |
56
+ ${{ env.IMAGE_NAME }}:main
57
+ ${{ env.IMAGE_NAME }}:${{ github.sha }}
@@ -0,0 +1,68 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ .Python
8
+ build/
9
+ develop-eggs/
10
+ dist/
11
+ downloads/
12
+ eggs/
13
+ .eggs/
14
+ lib/
15
+ lib64/
16
+ parts/
17
+ sdist/
18
+ var/
19
+ wheels/
20
+ *.egg-info/
21
+ .installed.cfg
22
+ *.egg
23
+ MANIFEST
24
+
25
+ # Hatch
26
+ .hatch/
27
+
28
+ # Unit test / coverage / type checkers
29
+ .tox/
30
+ .nox/
31
+ .coverage
32
+ .coverage.*
33
+ .cache
34
+ .pytest_cache/
35
+ .mypy_cache/
36
+ .ruff_cache/
37
+ htmlcov/
38
+ coverage.xml
39
+ *.cover
40
+ .hypothesis/
41
+
42
+ # Environments
43
+ .env
44
+ .venv
45
+ env/
46
+ venv/
47
+ ENV/
48
+ .python-version
49
+
50
+ # IDEs
51
+ .vscode/
52
+ .idea/
53
+ *.swp
54
+ *.swo
55
+ .DS_Store
56
+
57
+ # Notebooks
58
+ .ipynb_checkpoints/
59
+
60
+ # Project-specific
61
+ *.log
62
+ *.jar
63
+ opsin*.jar
64
+ data/cache/
65
+ .cache/
66
+
67
+ # Docker
68
+ .docker/
@@ -0,0 +1,18 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.6.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-toml
9
+ - id: check-merge-conflict
10
+ - id: check-added-large-files
11
+ args: ["--maxkb=1024"]
12
+
13
+ - repo: https://github.com/astral-sh/ruff-pre-commit
14
+ rev: v0.6.9
15
+ hooks:
16
+ - id: ruff
17
+ args: [--fix]
18
+ - id: ruff-format
@@ -0,0 +1,55 @@
1
+ # syntax=docker/dockerfile:1.7
2
+
3
+ # ----- builder stage -----------------------------------------------------
4
+ FROM python:3.12-slim AS builder
5
+
6
+ ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
7
+ PIP_NO_CACHE_DIR=1 \
8
+ PYTHONDONTWRITEBYTECODE=1
9
+
10
+ WORKDIR /build
11
+
12
+ # Build deps for rdkit wheels: typically just runtime; the manylinux wheels
13
+ # carry their own libs. Keep this stage thin.
14
+ RUN apt-get update -qq \
15
+ && apt-get install -y --no-install-recommends build-essential \
16
+ && rm -rf /var/lib/apt/lists/*
17
+
18
+ COPY pyproject.toml README.md ./
19
+ COPY src ./src
20
+
21
+ # Install into a clean prefix so we can copy a slim layer into the runtime.
22
+ RUN python -m pip install --upgrade pip \
23
+ && python -m pip install --prefix=/install ".[web,opsin]"
24
+
25
+
26
+ # ----- runtime stage -----------------------------------------------------
27
+ FROM python:3.12-slim AS runtime
28
+
29
+ ENV PYTHONUNBUFFERED=1 \
30
+ PYTHONDONTWRITEBYTECODE=1 \
31
+ PATH="/usr/local/bin:${PATH}"
32
+
33
+ # Java runtime for OPSIN round-trip verification (py2opsin shells out to java;
34
+ # OPSIN itself requires Java >=8 so the newer JRE is fine).
35
+ RUN apt-get update -qq \
36
+ && apt-get install -y --no-install-recommends \
37
+ default-jre-headless \
38
+ curl \
39
+ ca-certificates \
40
+ && rm -rf /var/lib/apt/lists/*
41
+
42
+ # Bring in the installed package + deps from the builder stage.
43
+ COPY --from=builder /install /usr/local
44
+
45
+ # Non-root user.
46
+ RUN useradd --create-home --shell /bin/bash openclatura
47
+ USER openclatura
48
+ WORKDIR /home/openclatura
49
+
50
+ EXPOSE 8000
51
+
52
+ HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
53
+ CMD curl -fsS http://127.0.0.1:8000/healthz || exit 1
54
+
55
+ CMD ["python", "-m", "openclatura.web", "--host", "0.0.0.0", "--port", "8000"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lamalab-org
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: openclatura
3
+ Version: 0.1.0
4
+ Summary: Deterministic SMILES-to-IUPAC name generator based on the IUPAC Blue Book
5
+ Project-URL: Homepage, https://github.com/lamalab-org/openclatura
6
+ Project-URL: Repository, https://github.com/lamalab-org/openclatura
7
+ Project-URL: Issues, https://github.com/lamalab-org/openclatura/issues
8
+ Author: Adrian Mirza, Kevin Maik Jablonka, Rostislav Fedorov
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: chemistry,iupac,nomenclature,rdkit,smiles
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: rdkit>=2023.09
22
+ Provides-Extra: datasets
23
+ Requires-Dist: datasets>=2.0; extra == 'datasets'
24
+ Requires-Dist: huggingface-hub>=0.20; extra == 'datasets'
25
+ Requires-Dist: pandas>=2.0; extra == 'datasets'
26
+ Requires-Dist: tqdm>=4.65; extra == 'datasets'
27
+ Provides-Extra: dev
28
+ Requires-Dist: fastapi>=0.110; extra == 'dev'
29
+ Requires-Dist: httpx>=0.27; extra == 'dev'
30
+ Requires-Dist: hypothesis>=6.92; extra == 'dev'
31
+ Requires-Dist: pre-commit>=3.5; extra == 'dev'
32
+ Requires-Dist: py2opsin>=1.2; extra == 'dev'
33
+ Requires-Dist: pydantic>=2.5; extra == 'dev'
34
+ Requires-Dist: pytest-cov>=4.1; extra == 'dev'
35
+ Requires-Dist: pytest-xdist>=3.5; extra == 'dev'
36
+ Requires-Dist: pytest>=7.4; extra == 'dev'
37
+ Requires-Dist: ruff==0.15.15; extra == 'dev'
38
+ Provides-Extra: opsin
39
+ Requires-Dist: py2opsin>=1.2; extra == 'opsin'
40
+ Provides-Extra: web
41
+ Requires-Dist: fastapi>=0.110; extra == 'web'
42
+ Requires-Dist: pydantic>=2.5; extra == 'web'
43
+ Requires-Dist: uvicorn[standard]>=0.27; extra == 'web'
44
+ Description-Content-Type: text/markdown
45
+
46
+ # openclatura
47
+
48
+ **Open Nomenclature Framework**
49
+
50
+ `openclatura` is a deterministic SMILES-to-IUPAC name generator inspired by the IUPAC Blue Book 2013 recommendations.
51
+
52
+ Built on top of RDKit, the package walks the molecular graph, detects functional groups and ring systems, selects the principal parent, assigns locants, and constructs the corresponding substitutive IUPAC name. Every step is recorded in an inspectable
53
+ decision trace so the *why* of a name is recoverable, not just the *what*.
54
+
55
+ > **Status:** alpha. The naming engine handles a broad slice of organic
56
+ > structures (alkanes/alkenes/alkynes, common functional groups, simple
57
+ > heterocycles, fused/spiro/bridged systems, retained names from the Blue
58
+ > Book). PubChem/QM9/ZINC22 coverage is being measured; see `examples/`.
59
+
60
+ ## Install
61
+
62
+ ```bash
63
+ pip install openclatura
64
+ ```
65
+
66
+ Optional extras:
67
+
68
+ | extra | adds |
69
+ | ------------ | ----------------------------------------------------- |
70
+ | `[opsin]` | `py2opsin` for OPSIN-based round-trip verification |
71
+ | `[datasets]` | `datasets` + `tqdm` for PubChem/QM9-style evaluations |
72
+ | `[web]` | FastAPI + uvicorn for the HTTP service |
73
+ | `[dev]` | pytest, ruff, pre-commit, hypothesis, py2opsin |
74
+
75
+ ```bash
76
+ pip install "openclatura[opsin,datasets]"
77
+ ```
78
+
79
+ ## Quick start
80
+
81
+ ```python
82
+ from openclatura import name_smiles
83
+
84
+ name_smiles("CCO") # 'ethanol'
85
+ name_smiles("c1ccccc1") # 'benzene'
86
+ name_smiles("CC(=O)O") # 'acetic acid'
87
+ ```
88
+
89
+ ### Typed result with rules hit + OPSIN round-trip
90
+
91
+ For everything richer than the bare string, use `openclatura.name`:
92
+
93
+ ```python
94
+ from openclatura import name
95
+
96
+ result = name("CC(=O)Nc1ccccc1", include_trace=True, verify_opsin=True)
97
+
98
+ result.name # 'N-phenylacetamide'
99
+ result.smiles # 'CC(=O)Nc1ccccc1'
100
+ result.ok # True
101
+ result.rules_hit # ('P-44', 'P-45', 'P-41', 'P-61', 'P-67', ...)
102
+ result.rule_hints # ('Parent hydride / parent structure: Blue Book P-44 ...',)
103
+ result.opsin_check.status # 'matched' | 'mismatched' | 'skipped_no_java' | ...
104
+ result.verified # True when opsin_check is matched
105
+ ```
106
+
107
+ Errors do not raise — they are captured on `result.error`, which makes
108
+ the batch API safe to point at noisy datasets:
109
+
110
+ ```python
111
+ from openclatura import name_many
112
+
113
+ results = name_many(
114
+ ["CCO", "c1ccccc1", "definitely-not-a-smiles"],
115
+ processes="auto", # or an integer, or 1 for in-process
116
+ verify_opsin=False,
117
+ )
118
+ [r.name for r in results if r.ok]
119
+ ```
120
+
121
+ For the full decision trace (one `TraceStep` per phase: parse, perception,
122
+ parent selection, numbering, assembly, …):
123
+
124
+ ```python
125
+ from openclatura import analyze_smiles
126
+
127
+ analysis = analyze_smiles("CC(=O)Nc1ccccc1")
128
+ for step in analysis.decisions:
129
+ print(step.phase, step.decision, step.reason)
130
+ ```
131
+ ### CLI
132
+
133
+ ```bash
134
+ openclatura name "CC(=O)Nc1ccccc1" # → N-phenylacetamide
135
+ openclatura name "CC(=O)Nc1ccccc1" --json # JSON with trace + rules
136
+ openclatura batch smiles.txt --output names.jsonl --processes auto
137
+ ```
138
+ The CLI tool has OPSIN verification turned on by default. It can be turned off with
139
+
140
+ ```bash
141
+ openclatura name "CN1C=NC2=C1C(=O)N(C(=O)N2C)C" --no-verify
142
+ ```
143
+
144
+ ### Natural-language description (`describe`)
145
+
146
+ `openclatura.describe(smiles)` walks the same trace and renders a
147
+ deterministic, multi-paragraph explanation of how the name is built.
148
+ Useful for explainability views and for generating (SMILES, name,
149
+ description) training tuples:
150
+
151
+ ```python
152
+ from openclatura import describe
153
+
154
+ d = describe("CC(=O)Nc1ccccc1")
155
+ print(d) # multi-paragraph prose
156
+ d.rules_hit # ('P-44', 'P-45', 'P-41', 'P-61', 'P-67')
157
+ d.components[0] # DescribedComponent(phase='parse', text='RDKit parsed ...')
158
+ ```
159
+
160
+ Same input → same output. No LLM in the loop.
161
+
162
+ ## Human-like description
163
+
164
+ Openclatura can generate uncanny human-like descriptions of molecules.
165
+ ```python
166
+
167
+ from openclatura import describe_human
168
+
169
+ d = describe_human("CN1C=NC2=C1C(=O)N(C(=O)N2C)C")
170
+ print(d.text)
171
+
172
+ """ Processed SMILES: Cn1cnc2c1c(=O)n(C)c(=O)n2C
173
+ Atom ids in that SMILES: C{0}n{1}1c{2}n{3}c{4}2c{5}1c{6}(=O{7})n{8}(C{13})c{9}(=O{10})n{11}2C{12}
174
+
175
+ The molecule is named 2,4,7-trimethyl-2,4,7,9-tetraazabicyclo[4.3.0]nona-1(6),8-diene-3,5-dione.
176
+
177
+ The molecule is built around a 9-membered bicyclic [4.3.0] heteroskeleton.
178
+ Within that parent framework, there is nitrogen at positions 2 (atom id 11), 4 (atom id 8), 7 (atom id 1), and 9 (atom id 3).
179
+ Within that parent framework, there is a double bond between position 1 (atom id 4) and position 6 (atom id 5) and a double bond between position 8 (atom id 2) and position 9 (atom id 3).
180
+ The principal characteristic feature is oxo groups at positions 3 (atom id 9) and 5 (atom id 6).
181
+ Attached to this framework are methyl groups at positions 2 (atom id 11), 4 (atom id 8), and 7 (atom id 1). """
182
+
183
+ ```
184
+
185
+ ## Development
186
+
187
+ ```bash
188
+ git clone https://github.com/lamalab-org/iupac-name-generator
189
+ cd iupac-name-generator
190
+ pip install -e ".[dev]"
191
+
192
+ # run the unit + round-trip tests
193
+ pytest
194
+
195
+ # run only fast tests
196
+ pytest -m "not slow and not dataset and not golden"
197
+
198
+ # strict RDKit-version regression suite (also runs in the rdkit-compat CI job)
199
+ pytest -m golden
200
+
201
+ # lint and format
202
+ ruff check --fix src/openclatura
203
+ ruff format src/openclatura
204
+ ```
205
+
206
+ Java is required for the OPSIN-based round-trip checks (see `py2opsin`).
207
+
208
+ ## HTTP service (Docker)
209
+
210
+ The `[web]` extra ships a FastAPI app with `name`, `batch`, `describe`
211
+ and `healthz` endpoints. The bundled `Dockerfile` includes a headless JRE
212
+ so `verify_opsin=True` works out of the box.
213
+
214
+ ```bash
215
+ # build + run
216
+ docker build -t openclatura:local .
217
+ docker run --rm -p 8000:8000 openclatura:local
218
+
219
+ # or via compose
220
+ docker compose -f docker/compose.yaml up --build
221
+ ```
222
+
223
+ Call the API:
224
+
225
+ ```bash
226
+ curl -X POST localhost:8000/name -H 'content-type: application/json' \
227
+ -d '{"smiles":"CC(=O)Nc1ccccc1","include_trace":true,"verify_opsin":true}'
228
+
229
+ curl -X POST localhost:8000/batch -H 'content-type: application/json' \
230
+ -d '{"smiles":["CCO","c1ccccc1","CC(=O)O"],"processes":1}'
231
+
232
+ curl -X POST localhost:8000/describe -H 'content-type: application/json' \
233
+ -d '{"smiles":"CC(=O)Nc1ccccc1"}'
234
+ ```
235
+
236
+ OpenAPI docs are served at `http://localhost:8000/docs`.
237
+
238
+
239
+ ## License
240
+
241
+ MIT. See `LICENSE`.