icu4py 0.1.0__cp313-cp313t-macosx_15_0_arm64.whl

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.
Binary file
Binary file
Binary file
icu4py/__init__.py ADDED
File without changes
@@ -0,0 +1,295 @@
1
+ #define PY_SSIZE_T_CLEAN
2
+ #include <Python.h>
3
+ #include <unicode/msgfmt.h>
4
+ #include <unicode/unistr.h>
5
+ #include <unicode/fmtable.h>
6
+ #include <unicode/locid.h>
7
+ #include <unicode/parsepos.h>
8
+ #include <unicode/ustring.h>
9
+
10
+ #include <cstring>
11
+ #include <memory>
12
+
13
+ namespace {
14
+
15
+ using icu::MessageFormat;
16
+ using icu::UnicodeString;
17
+ using icu::Formattable;
18
+ using icu::Locale;
19
+ using icu::FieldPosition;
20
+ using icu::StringPiece;
21
+
22
+ struct MessageFormatObject {
23
+ PyObject_HEAD
24
+ MessageFormat* formatter;
25
+ };
26
+
27
+ void MessageFormat_dealloc(MessageFormatObject* self) {
28
+ delete self->formatter;
29
+ Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
30
+ }
31
+
32
+ PyObject* MessageFormat_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
33
+ auto* self = reinterpret_cast<MessageFormatObject*>(type->tp_alloc(type, 0));
34
+ if (self != nullptr) {
35
+ self->formatter = nullptr;
36
+ }
37
+ return reinterpret_cast<PyObject*>(self);
38
+ }
39
+
40
+ int MessageFormat_init(MessageFormatObject* self, PyObject* args, PyObject* kwds) {
41
+ const char* pattern;
42
+ const char* locale_str;
43
+ Py_ssize_t pattern_len;
44
+
45
+ static const char* kwlist[] = {"pattern", "locale", nullptr};
46
+
47
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s#s",
48
+ const_cast<char**>(kwlist),
49
+ &pattern, &pattern_len, &locale_str)) {
50
+ return -1;
51
+ }
52
+
53
+ UErrorCode status = U_ZERO_ERROR;
54
+ UnicodeString upattern = UnicodeString::fromUTF8(StringPiece(pattern, pattern_len));
55
+ Locale locale(locale_str);
56
+
57
+ self->formatter = new MessageFormat(upattern, locale, status);
58
+
59
+ if (U_FAILURE(status)) {
60
+ delete self->formatter;
61
+ self->formatter = nullptr;
62
+ PyErr_Format(PyExc_ValueError, "Failed to create MessageFormat: %s",
63
+ u_errorName(status));
64
+ return -1;
65
+ }
66
+
67
+ return 0;
68
+ }
69
+
70
+ bool pyobject_to_formattable(PyObject* obj, Formattable& formattable) {
71
+ if (PyLong_Check(obj)) {
72
+ long long_val = PyLong_AsLongLong(obj);
73
+ if (long_val == -1 && PyErr_Occurred()) {
74
+ return false;
75
+ }
76
+ formattable = Formattable(static_cast<int64_t>(long_val));
77
+ return true;
78
+ }
79
+
80
+ if (PyUnicode_Check(obj)) {
81
+ Py_ssize_t size;
82
+ const char* str_val = PyUnicode_AsUTF8AndSize(obj, &size);
83
+ if (str_val == nullptr) {
84
+ return false;
85
+ }
86
+ formattable = Formattable(UnicodeString::fromUTF8(StringPiece(str_val, size)));
87
+ return true;
88
+ }
89
+
90
+ if (PyFloat_Check(obj)) {
91
+ double dbl_val = PyFloat_AsDouble(obj);
92
+ formattable = Formattable(dbl_val);
93
+ return true;
94
+ }
95
+
96
+ PyErr_SetString(PyExc_TypeError, "Parameter values must be int, float, or str");
97
+ return false;
98
+ }
99
+
100
+ bool dict_to_parallel_arrays(PyObject* dict, UnicodeString*& names,
101
+ Formattable*& values, int32_t& count) {
102
+ if (!PyDict_Check(dict)) {
103
+ PyErr_SetString(PyExc_TypeError, "Argument must be a dictionary");
104
+ return false;
105
+ }
106
+
107
+ count = static_cast<int32_t>(PyDict_Size(dict));
108
+ if (count == 0) {
109
+ names = nullptr;
110
+ values = nullptr;
111
+ return true;
112
+ }
113
+
114
+ auto names_ptr = std::make_unique<UnicodeString[]>(count);
115
+ auto values_ptr = std::make_unique<Formattable[]>(count);
116
+
117
+ Py_ssize_t pos = 0;
118
+ PyObject* key;
119
+ PyObject* value;
120
+ int32_t i = 0;
121
+ bool err = false;
122
+
123
+ #ifdef Py_GIL_DISABLED
124
+ Py_BEGIN_CRITICAL_SECTION(dict);
125
+ #endif
126
+ while (PyDict_Next(dict, &pos, &key, &value)) {
127
+ // Ensure we don't exceed allocated space
128
+ if (i >= count) {
129
+ PyErr_SetString(PyExc_RuntimeError, "Dictionary size changed during iteration");
130
+ err = true;
131
+ break;
132
+ }
133
+
134
+ if (key == nullptr) {
135
+ PyErr_SetString(PyExc_TypeError, "NULL key in dictionary");
136
+ err = true;
137
+ break;
138
+ }
139
+ if (value == nullptr) {
140
+ PyErr_SetString(PyExc_TypeError, "NULL value in dictionary");
141
+ err = true;
142
+ break;
143
+ }
144
+
145
+ Py_ssize_t key_size;
146
+ const char* key_str = PyUnicode_AsUTF8AndSize(key, &key_size);
147
+ if (key_str == nullptr) {
148
+ PyErr_SetString(PyExc_TypeError, "Dictionary keys must be strings");
149
+ err = true;
150
+ break;
151
+ }
152
+ names_ptr[i] = UnicodeString::fromUTF8(StringPiece(key_str, key_size));
153
+
154
+ if (!pyobject_to_formattable(value, values_ptr[i])) {
155
+ PyErr_SetString(PyExc_TypeError, "Failed to convert dictionary value to Formattable");
156
+ err = true;
157
+ break;
158
+ }
159
+ ++i;
160
+ }
161
+ #ifdef Py_GIL_DISABLED
162
+ Py_END_CRITICAL_SECTION();
163
+ #endif
164
+ if (err) {
165
+ return false;
166
+ }
167
+
168
+ names = names_ptr.release();
169
+ values = values_ptr.release();
170
+ return true;
171
+ }
172
+
173
+ PyObject* MessageFormat_format(MessageFormatObject* self, PyObject* args) {
174
+ PyObject* params_dict;
175
+
176
+ if (!PyArg_ParseTuple(args, "O!", &PyDict_Type, &params_dict)) {
177
+ return nullptr;
178
+ }
179
+
180
+ UnicodeString* argumentNames = nullptr;
181
+ Formattable* arguments = nullptr;
182
+ int32_t count = 0;
183
+
184
+ if (!dict_to_parallel_arrays(params_dict, argumentNames, arguments, count)) {
185
+ return nullptr;
186
+ }
187
+
188
+ auto names_guard = std::unique_ptr<UnicodeString[]>(argumentNames);
189
+ auto values_guard = std::unique_ptr<Formattable[]>(arguments);
190
+
191
+ UErrorCode status = U_ZERO_ERROR;
192
+ UnicodeString result;
193
+
194
+ if (count == 0) {
195
+ FieldPosition field_pos;
196
+ result = self->formatter->format(nullptr, 0, result, field_pos, status);
197
+ } else {
198
+ result = self->formatter->format(argumentNames, arguments, count, result, status);
199
+ }
200
+
201
+ if (U_FAILURE(status)) {
202
+ PyErr_Format(PyExc_RuntimeError, "Failed to format message: %s",
203
+ u_errorName(status));
204
+ return nullptr;
205
+ }
206
+
207
+ std::string utf8;
208
+ result.toUTF8String(utf8);
209
+ return PyUnicode_FromStringAndSize(utf8.c_str(), utf8.size());
210
+ }
211
+
212
+ PyMethodDef MessageFormat_methods[] = {
213
+ {"format", reinterpret_cast<PyCFunction>(MessageFormat_format), METH_VARARGS,
214
+ "Format the message with given parameters"},
215
+ {nullptr, nullptr, 0, nullptr}
216
+ };
217
+
218
+ PyTypeObject MessageFormatType = {
219
+ PyVarObject_HEAD_INIT(nullptr, 0)
220
+ "icu4py.messageformat.MessageFormat", /* tp_name */
221
+ sizeof(MessageFormatObject), /* tp_basicsize */
222
+ 0, /* tp_itemsize */
223
+ reinterpret_cast<destructor>(MessageFormat_dealloc), /* tp_dealloc */
224
+ 0, /* tp_vectorcall_offset */
225
+ nullptr, /* tp_getattr */
226
+ nullptr, /* tp_setattr */
227
+ nullptr, /* tp_as_async */
228
+ nullptr, /* tp_repr */
229
+ nullptr, /* tp_as_number */
230
+ nullptr, /* tp_as_sequence */
231
+ nullptr, /* tp_as_mapping */
232
+ nullptr, /* tp_hash */
233
+ nullptr, /* tp_call */
234
+ nullptr, /* tp_str */
235
+ nullptr, /* tp_getattro */
236
+ nullptr, /* tp_setattro */
237
+ nullptr, /* tp_as_buffer */
238
+ Py_TPFLAGS_DEFAULT, /* tp_flags */
239
+ "ICU MessageFormat", /* tp_doc */
240
+ nullptr, /* tp_traverse */
241
+ nullptr, /* tp_clear */
242
+ nullptr, /* tp_richcompare */
243
+ 0, /* tp_weaklistoffset */
244
+ nullptr, /* tp_iter */
245
+ nullptr, /* tp_iternext */
246
+ MessageFormat_methods, /* tp_methods */
247
+ nullptr, /* tp_members */
248
+ nullptr, /* tp_getset */
249
+ nullptr, /* tp_base */
250
+ nullptr, /* tp_dict */
251
+ nullptr, /* tp_descr_get */
252
+ nullptr, /* tp_descr_set */
253
+ 0, /* tp_dictoffset */
254
+ reinterpret_cast<initproc>(MessageFormat_init), /* tp_init */
255
+ nullptr, /* tp_alloc */
256
+ MessageFormat_new, /* tp_new */
257
+ };
258
+
259
+ int icu4py_messageformat_exec(PyObject* m) {
260
+ if (PyType_Ready(&MessageFormatType) < 0) {
261
+ return -1;
262
+ }
263
+
264
+ Py_INCREF(&MessageFormatType);
265
+ if (PyModule_AddObject(m, "MessageFormat",
266
+ reinterpret_cast<PyObject*>(&MessageFormatType)) < 0) {
267
+ Py_DECREF(&MessageFormatType);
268
+ return -1;
269
+ }
270
+
271
+ return 0;
272
+ }
273
+
274
+ PyModuleDef_Slot icu4py_messageformat_slots[] = {
275
+ {Py_mod_exec, reinterpret_cast<void*>(icu4py_messageformat_exec)},
276
+ #ifdef Py_GIL_DISABLED
277
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
278
+ #endif
279
+ {0, nullptr}
280
+ };
281
+
282
+ PyModuleDef icumodule = {
283
+ PyModuleDef_HEAD_INIT,
284
+ "icu4py.messageformat", /* m_name */
285
+ "", /* m_doc */
286
+ 0, /* m_size */
287
+ nullptr, /* m_methods */
288
+ icu4py_messageformat_slots, /* m_slots */
289
+ };
290
+
291
+ } // anonymous namespace
292
+
293
+ PyMODINIT_FUNC PyInit_messageformat() {
294
+ return PyModuleDef_Init(&icumodule);
295
+ }
@@ -0,0 +1,6 @@
1
+ from typing import final
2
+
3
+ @final
4
+ class MessageFormat:
5
+ def __init__(self, pattern: str, locale: str) -> None: ...
6
+ def format(self, params: dict[str, int | float | str]) -> str: ...
icu4py/py.typed ADDED
File without changes
@@ -0,0 +1,59 @@
1
+ Metadata-Version: 2.4
2
+ Name: icu4py
3
+ Version: 0.1.0
4
+ Summary: Bindings to the ICU (International Components for Unicode) library (ICU4C).
5
+ Author-email: Adam Johnson <me@adamj.eu>
6
+ License-Expression: MIT
7
+ Project-URL: Changelog, https://icu4py.readthedocs.io/en/latest/changelog.html
8
+ Project-URL: Documentation, https://icu4py.readthedocs.io/
9
+ Project-URL: Funding, https://adamj.eu/books/
10
+ Project-URL: Repository, https://github.com/adamchainz/icu4py
11
+ Keywords: i18n,icu,icu4c,internationalization,l10n,localization,unicode
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Framework :: Pytest
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Natural Language :: English
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/x-rst
26
+ License-File: LICENSE
27
+ Dynamic: license-file
28
+
29
+ ======
30
+ icu4py
31
+ ======
32
+
33
+ .. image:: https://img.shields.io/readthedocs/icu4py?style=for-the-badge
34
+ :target: https://icu4py.readthedocs.io/en/latest/
35
+
36
+ .. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/icu4py/main.yml.svg?branch=main&style=for-the-badge
37
+ :target: https://github.com/adamchainz/icu4py/actions?workflow=CI
38
+
39
+ .. image:: https://img.shields.io/badge/Coverage-100%25-success?style=for-the-badge
40
+ :target: https://github.com/adamchainz/icu4py/actions?workflow=CI
41
+
42
+ .. image:: https://img.shields.io/pypi/v/icu4py.svg?style=for-the-badge
43
+ :target: https://pypi.org/project/icu4py/
44
+
45
+ .. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge
46
+ :target: https://github.com/psf/black
47
+
48
+ .. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge
49
+ :target: https://github.com/pre-commit/pre-commit
50
+ :alt: pre-commit
51
+
52
+ ----
53
+
54
+ Bindings to the ICU (International Components for Unicode) library (ICU4C).
55
+
56
+ Documentation
57
+ =============
58
+
59
+ Please see https://icu4py.readthedocs.io/.
@@ -0,0 +1,13 @@
1
+ icu4py-0.1.0.dist-info/RECORD,,
2
+ icu4py-0.1.0.dist-info/WHEEL,sha256=T2IJr1odg32Yif_og9asFO0ibaxcQQpKbUznR6vTkXg,137
3
+ icu4py-0.1.0.dist-info/top_level.txt,sha256=bT8eoOy2eVL3nNv2IXqQo-KpPtbBoR415Kow3HGtId8,7
4
+ icu4py-0.1.0.dist-info/METADATA,sha256=9-bfldKxh0ZrD4T2JWZKqdHL5VTwQw4-g5H8f96jRKs,2286
5
+ icu4py-0.1.0.dist-info/licenses/LICENSE,sha256=blVuFA7Qdp2_CcTyGXLxA4CsHsAjAquxVc4O6ODqye4,1069
6
+ icu4py/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ icu4py/messageformat.cpython-313t-darwin.so,sha256=4IkA5Ykuewrk55FfvNjMVxD7fP2SgQ27yuFM482DwCg,76608
8
+ icu4py/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ icu4py/messageformat.pyi,sha256=V7QR4Cc0-Bxmv4wpMqsCODMHAQtDMpukbaupQxzb0XI,188
10
+ icu4py/messageformat.cpp,sha256=XsPfpoB9nmLfvJtz_kU3RA1aSHLdMDfIiPE_AYzbEas,8485
11
+ icu4py/.dylibs/libicui18n.78.1.dylib,sha256=OOEMixoqjJQqtMn0q11OiZk6IAoTtxN8hJnMO-oRCd4,3186736
12
+ icu4py/.dylibs/libicuuc.78.1.dylib,sha256=BPwKDdKlCXyntXJY2K2XbTZftFluaqY8tLbEc0a6V-0,1877408
13
+ icu4py/.dylibs/libicudata.78.1.dylib,sha256=wZJ4GSbUl5kLUvWWTZIZ0EviCfvZWfycc_O_Oqp5kNM,33389136
@@ -0,0 +1,6 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp313-cp313t-macosx_15_0_arm64
5
+ Generator: delocate 0.13.0
6
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adam Johnson
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 @@
1
+ icu4py