transifex-python 3.7.0__tar.gz → 3.8.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.
- {transifex_python-3.7.0/transifex_python.egg-info → transifex_python-3.8.0}/PKG-INFO +48 -1
- transifex_python-3.8.0/README.md +95 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/test_cds.py +302 -3
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/test_init.py +11 -0
- transifex_python-3.8.0/tests/native/django/test_apps.py +20 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/_version.py +3 -3
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/__init__.py +8 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/cds.py +134 -24
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/core.py +10 -1
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/apps.py +2 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/settings.py +6 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0/transifex_python.egg-info}/PKG-INFO +48 -1
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex_python.egg-info/SOURCES.txt +1 -0
- transifex_python-3.7.0/README.md +0 -48
- {transifex_python-3.7.0 → transifex_python-3.8.0}/LICENSE +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/MANIFEST.in +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/setup.cfg +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/setup.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/api/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/api/constants.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/api/payloads.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/api/test_apis.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/api/test_bulk.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/api/test_exceptions.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/api/test_plural_lists.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/api/test_queryset.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/api/test_relationships.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/api/test_single_resource.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/common/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/common/test_is_plural.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/common/test_strings.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/conftest.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/test_cache.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/test_core.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/test_daemon.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/test_parsing.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/test_rendering.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/test_tools/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/test_tools/test_migrations/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/test_tools/test_migrations/test_mark.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/test_tools/test_migrations/test_models.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/test_tools/test_migrations/test_review.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/core/test_tools/test_migrations/test_save.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/django/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/django/settings.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/django/test_commands/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/django/test_commands/test_invalidatetransifex.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/django/test_commands/test_migratetransifex.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/django/test_commands/test_pushtransifex.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/django/test_templatetag.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/django/test_tools/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/django/test_tools/test_migrations/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/django/test_tools/test_migrations/test_templatetags.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/django/test_utils/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/django/test_utils/test_init.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/tests/native/django/test_utils/test_templates.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/api/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/api/exceptions.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/api/jsonapi/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/api/jsonapi/apis.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/api/jsonapi/auth.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/api/jsonapi/collections.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/api/jsonapi/compat.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/api/jsonapi/exceptions.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/api/jsonapi/resources.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/api/jsonapi/utils.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/common/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/common/_compat.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/common/console.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/common/strings.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/common/utils.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/cache.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/consts.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/daemon.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/compat.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/management/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/management/commands/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/management/commands/transifex.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/management/common.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/management/utils/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/management/utils/base.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/management/utils/invalidate.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/management/utils/migrate.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/management/utils/push.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/management/utils/try_templatetag.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/templatetags/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/templatetags/transifex.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/templatetags/utils.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/tools/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/tools/migrations/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/tools/migrations/templatetags.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/utils/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/django/utils/templates.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/parsing.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/rendering.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/settings.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/tools/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/tools/migrations/__init__.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/tools/migrations/execution.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/tools/migrations/gettext.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/tools/migrations/mark.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/tools/migrations/models.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/tools/migrations/review.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex/native/tools/migrations/save.py +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex_python.egg-info/dependency_links.txt +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex_python.egg-info/requires.txt +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/transifex_python.egg-info/top_level.txt +0 -0
- {transifex_python-3.7.0 → transifex_python-3.8.0}/versioneer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: transifex-python
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.8.0
|
|
4
4
|
Summary: Transifex Python Toolkit
|
|
5
5
|
Home-page: https://github.com/transifex/transifex-python
|
|
6
6
|
Author: Transifex
|
|
@@ -69,6 +69,53 @@ To learn more about using Transifex Python toolkit check:
|
|
|
69
69
|
* For a general overview visit [Transifex Native overview](https://developers.transifex.com/docs/native?utm_campaign=tx-native&utm_source=github&utm_medium=link)
|
|
70
70
|
* For some common questions & answers check our [Transifex Native community](https://community.transifex.com/c/transifex-native/17)
|
|
71
71
|
|
|
72
|
+
# Django settings reference
|
|
73
|
+
|
|
74
|
+
The Transifex Native Django sdk is controlled via a set of configuration options defined in Django settings:
|
|
75
|
+
|
|
76
|
+
- TRANSIFEX_TOKEN: API token that connects your application to a Transifex project.
|
|
77
|
+
Must be set for both pushing source strings and fetching translations.
|
|
78
|
+
|
|
79
|
+
- TRANSIFEX_SECRET: Secret used together with the token for authenticated operations against CDS (e.g. pushing source content, invalidating cache).
|
|
80
|
+
|
|
81
|
+
- TRANSIFEX_CDS_HOST: Override the default CDS host (https://cds.svc.transifex.net).
|
|
82
|
+
|
|
83
|
+
- TRANSIFEX_FILTER_STATUS: Optional CDS filter[status] parameter used when fetching translations (e.g. "reviewed", "proofread"). If not set, CDS returns all available statuses.
|
|
84
|
+
|
|
85
|
+
- TRANSIFEX_FILTER_TAGS: Optional CDS filter[tags] parameter used when fetching translations. Use this to limit fetched content to specific tags.
|
|
86
|
+
|
|
87
|
+
- TRANSIFEX_MISSING_POLICY: Custom “missing translation” policy class.
|
|
88
|
+
Defaults to the built-in SourceStringPolicy when not provided.
|
|
89
|
+
|
|
90
|
+
- TRANSIFEX_ERROR_POLICY: Custom error handling policy class.
|
|
91
|
+
Defaults to SourceErrorPolicy.
|
|
92
|
+
|
|
93
|
+
- TRANSIFEX_CACHE
|
|
94
|
+
Custom cache implementation. Defaults to an in-memory cache (MemoryCache) if not provided. Can be used to integrate with a shared cache (e.g. Redis, memcached).
|
|
95
|
+
|
|
96
|
+
- SKIP_TRANSLATIONS_SYNC: If True, disables automatic translation sync (OTA) for this environment.
|
|
97
|
+
|
|
98
|
+
- TRANSIFEX_SYNC_INTERVAL: Interval in seconds for the background sync daemon that fetches translations. Default: 30 * 60 (30 minutes). Set to 0 to disable periodic sync and only fetch on startup.
|
|
99
|
+
|
|
100
|
+
- TRANSIFEX_FETCH_ALL_LANGUAGES: When True, fetch translations for all languages configured in CDS. When False (default), only fetch translations for languages listed in Django’s LANGUAGES setting.
|
|
101
|
+
|
|
102
|
+
- TRANSIFEX_FETCH_TIMEOUT:
|
|
103
|
+
Approximate maximum time in seconds for one fetch of languages and
|
|
104
|
+
translations from CDS, including retries. It can be exceeded, e.g. by DNS
|
|
105
|
+
resolution, a host with several IP addresses, or a response that keeps
|
|
106
|
+
sending data slowly (the read timeout applies between received bytes, not to
|
|
107
|
+
the whole response). Languages are fetched in LANGUAGES order; those not
|
|
108
|
+
reached in time are skipped until the next sync, or for the life of the
|
|
109
|
+
process if TRANSIFEX_SYNC_INTERVAL is 0.
|
|
110
|
+
0 (default) = no global timeout: if CDS keeps answering 202, the fetch, and
|
|
111
|
+
so startup, waits until it stops. Set it when translations are fetched at
|
|
112
|
+
startup, e.g. under gunicorn.
|
|
113
|
+
|
|
114
|
+
- TRANSIFEX_REQUEST_TIMEOUT:
|
|
115
|
+
Timeout in seconds for each request made when fetching from CDS, either a
|
|
116
|
+
number greater than 0 or a `(connect, read)` tuple as accepted by `requests`.
|
|
117
|
+
Invalid values are logged and the default is used. Defaults to `(10, 60)`.
|
|
118
|
+
|
|
72
119
|
# License
|
|
73
120
|
|
|
74
121
|
Licensed under Apache License 2.0, see `LICENSE` file.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+

|
|
2
|
+
[](https://codecov.io/gh/transifex/transifex-python)
|
|
3
|
+
|
|
4
|
+
# Transifex Python Toolkit
|
|
5
|
+
|
|
6
|
+
Transifex Python Toolkit is a collection of tools that allow you to easily localize your Django and Python applications using Transifex. The toolkit features fetching translations over the air (OTA) to your apps.
|
|
7
|
+
|
|
8
|
+
This project adheres to the Contributor Covenant [code of conduct](/CODE_OF_CONDUCT.md). To contribute to Transifex Python Toolkit, please check out the [contribution guidelines](/CONTRIBUTING.md).
|
|
9
|
+
|
|
10
|
+
# Upgrade to v2
|
|
11
|
+
|
|
12
|
+
If you are upgrading from the `1.x.x` version, please read this [migration guide](https://github.com/transifex/transifex-python/blob/HEAD/UPGRADE_TO_V2.md), as there are breaking changes in place.
|
|
13
|
+
|
|
14
|
+
# Quick starting guide
|
|
15
|
+
|
|
16
|
+
1. Install toolkit in your code `$ pip install transifex-python`
|
|
17
|
+
2. Add a provided TOKEN and SECRET in your config, connecting your application with a Transifex project
|
|
18
|
+
3. Add internationalization commands in your code
|
|
19
|
+
```HTML+Django
|
|
20
|
+
<!-- Django app template example -->
|
|
21
|
+
|
|
22
|
+
{% load transifex %}
|
|
23
|
+
<p>{% t "Hello!" %}</p>
|
|
24
|
+
<p>{% t "I want to be translated." %}</p>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
# Django view sample
|
|
29
|
+
from transifex.native.django import t
|
|
30
|
+
|
|
31
|
+
output = {
|
|
32
|
+
"msg1": t("Welcome aboard!"),
|
|
33
|
+
"msg2": t("It's great to have you here!"),
|
|
34
|
+
}
|
|
35
|
+
return JsonResponse(output)
|
|
36
|
+
```
|
|
37
|
+
4. Push strings to your connected Transifex project `./manage.py transifex push`
|
|
38
|
+
5. When translations are added in your Transifex project are automatically made available
|
|
39
|
+
|
|
40
|
+
To learn more about using Transifex Python toolkit check:
|
|
41
|
+
* [Quickstart guide for Django web application](https://developers.transifex.com/docs/django-sdk?utm_campaign=tx-native&utm_source=github&utm_medium=link)
|
|
42
|
+
* [Quickstart guide for Python application](https://developers.transifex.com/docs/python-sdk?utm_campaign=tx-native&utm_source=github&utm_medium=link)
|
|
43
|
+
* For a general overview visit [Transifex Native overview](https://developers.transifex.com/docs/native?utm_campaign=tx-native&utm_source=github&utm_medium=link)
|
|
44
|
+
* For some common questions & answers check our [Transifex Native community](https://community.transifex.com/c/transifex-native/17)
|
|
45
|
+
|
|
46
|
+
# Django settings reference
|
|
47
|
+
|
|
48
|
+
The Transifex Native Django sdk is controlled via a set of configuration options defined in Django settings:
|
|
49
|
+
|
|
50
|
+
- TRANSIFEX_TOKEN: API token that connects your application to a Transifex project.
|
|
51
|
+
Must be set for both pushing source strings and fetching translations.
|
|
52
|
+
|
|
53
|
+
- TRANSIFEX_SECRET: Secret used together with the token for authenticated operations against CDS (e.g. pushing source content, invalidating cache).
|
|
54
|
+
|
|
55
|
+
- TRANSIFEX_CDS_HOST: Override the default CDS host (https://cds.svc.transifex.net).
|
|
56
|
+
|
|
57
|
+
- TRANSIFEX_FILTER_STATUS: Optional CDS filter[status] parameter used when fetching translations (e.g. "reviewed", "proofread"). If not set, CDS returns all available statuses.
|
|
58
|
+
|
|
59
|
+
- TRANSIFEX_FILTER_TAGS: Optional CDS filter[tags] parameter used when fetching translations. Use this to limit fetched content to specific tags.
|
|
60
|
+
|
|
61
|
+
- TRANSIFEX_MISSING_POLICY: Custom “missing translation” policy class.
|
|
62
|
+
Defaults to the built-in SourceStringPolicy when not provided.
|
|
63
|
+
|
|
64
|
+
- TRANSIFEX_ERROR_POLICY: Custom error handling policy class.
|
|
65
|
+
Defaults to SourceErrorPolicy.
|
|
66
|
+
|
|
67
|
+
- TRANSIFEX_CACHE
|
|
68
|
+
Custom cache implementation. Defaults to an in-memory cache (MemoryCache) if not provided. Can be used to integrate with a shared cache (e.g. Redis, memcached).
|
|
69
|
+
|
|
70
|
+
- SKIP_TRANSLATIONS_SYNC: If True, disables automatic translation sync (OTA) for this environment.
|
|
71
|
+
|
|
72
|
+
- TRANSIFEX_SYNC_INTERVAL: Interval in seconds for the background sync daemon that fetches translations. Default: 30 * 60 (30 minutes). Set to 0 to disable periodic sync and only fetch on startup.
|
|
73
|
+
|
|
74
|
+
- TRANSIFEX_FETCH_ALL_LANGUAGES: When True, fetch translations for all languages configured in CDS. When False (default), only fetch translations for languages listed in Django’s LANGUAGES setting.
|
|
75
|
+
|
|
76
|
+
- TRANSIFEX_FETCH_TIMEOUT:
|
|
77
|
+
Approximate maximum time in seconds for one fetch of languages and
|
|
78
|
+
translations from CDS, including retries. It can be exceeded, e.g. by DNS
|
|
79
|
+
resolution, a host with several IP addresses, or a response that keeps
|
|
80
|
+
sending data slowly (the read timeout applies between received bytes, not to
|
|
81
|
+
the whole response). Languages are fetched in LANGUAGES order; those not
|
|
82
|
+
reached in time are skipped until the next sync, or for the life of the
|
|
83
|
+
process if TRANSIFEX_SYNC_INTERVAL is 0.
|
|
84
|
+
0 (default) = no global timeout: if CDS keeps answering 202, the fetch, and
|
|
85
|
+
so startup, waits until it stops. Set it when translations are fetched at
|
|
86
|
+
startup, e.g. under gunicorn.
|
|
87
|
+
|
|
88
|
+
- TRANSIFEX_REQUEST_TIMEOUT:
|
|
89
|
+
Timeout in seconds for each request made when fetching from CDS, either a
|
|
90
|
+
number greater than 0 or a `(connect, read)` tuple as accepted by `requests`.
|
|
91
|
+
Invalid values are logged and the default is used. Defaults to `(10, 60)`.
|
|
92
|
+
|
|
93
|
+
# License
|
|
94
|
+
|
|
95
|
+
Licensed under Apache License 2.0, see `LICENSE` file.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from operator import itemgetter
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
+
import requests
|
|
4
5
|
import responses
|
|
5
|
-
from
|
|
6
|
-
from
|
|
6
|
+
from urllib3.exceptions import ReadTimeoutError
|
|
7
|
+
from mock import patch, MagicMock
|
|
8
|
+
from transifex.native.cds import DEFAULT_REQUEST_TIMEOUT, CDSHandler
|
|
7
9
|
from transifex.native.parsing import SourceString
|
|
8
10
|
|
|
9
|
-
|
|
10
11
|
class TestCDSHandler(object):
|
|
11
12
|
|
|
12
13
|
def _lang_lists_equal(self, list_1, list_2):
|
|
@@ -635,3 +636,301 @@ class TestCDSHandler(object):
|
|
|
635
636
|
for x in ('Unprocessable Entity', 'None')
|
|
636
637
|
]
|
|
637
638
|
assert patched_logger.error.call_args[0][0] in messages
|
|
639
|
+
|
|
640
|
+
@staticmethod
|
|
641
|
+
def _json_response(status_code, payload=None):
|
|
642
|
+
response = MagicMock()
|
|
643
|
+
response.ok = status_code < 400
|
|
644
|
+
response.status_code = status_code
|
|
645
|
+
response.headers = {}
|
|
646
|
+
response.json.return_value = payload
|
|
647
|
+
return response
|
|
648
|
+
|
|
649
|
+
@patch('transifex.native.cds.time.sleep')
|
|
650
|
+
@patch('transifex.native.cds.time.monotonic')
|
|
651
|
+
@patch('transifex.native.cds.requests.get')
|
|
652
|
+
def test_retry_get_request_raises_timeout_on_202_at_deadline(
|
|
653
|
+
self, mock_get, mock_time, mock_sleep
|
|
654
|
+
):
|
|
655
|
+
cds_handler = CDSHandler(['el'], 'some_token', fetch_timeout=5)
|
|
656
|
+
mock_get.return_value = self._json_response(202)
|
|
657
|
+
# deadline 0 -> 5; requests at 0.1 and 2.2; after the second 202
|
|
658
|
+
# the 2s retry delay would pass the deadline
|
|
659
|
+
mock_time.side_effect = [0, 0.1, 0.1, 2.2, 3.5]
|
|
660
|
+
|
|
661
|
+
with pytest.raises(requests.Timeout):
|
|
662
|
+
cds_handler.retry_get_request('https://some.host/languages')
|
|
663
|
+
|
|
664
|
+
assert mock_get.call_count == 2
|
|
665
|
+
assert mock_sleep.call_count == 1
|
|
666
|
+
|
|
667
|
+
@patch('transifex.native.cds.time.sleep')
|
|
668
|
+
@patch('transifex.native.cds.requests.get')
|
|
669
|
+
def test_retry_get_request_returns_unexpected_status(
|
|
670
|
+
self, mock_get, mock_sleep
|
|
671
|
+
):
|
|
672
|
+
cds_handler = CDSHandler(['el'], 'some_token')
|
|
673
|
+
response = self._json_response(600)
|
|
674
|
+
mock_get.return_value = response
|
|
675
|
+
|
|
676
|
+
assert cds_handler.retry_get_request('https://some.host/x') is response
|
|
677
|
+
assert mock_get.call_count == 1
|
|
678
|
+
mock_sleep.assert_not_called()
|
|
679
|
+
|
|
680
|
+
@patch('transifex.native.cds.logger')
|
|
681
|
+
@patch('transifex.native.cds.time.sleep')
|
|
682
|
+
@patch('transifex.native.cds.time.monotonic')
|
|
683
|
+
@patch('transifex.native.cds.requests.get')
|
|
684
|
+
def test_fetch_translations_202_at_deadline_is_a_timeout(
|
|
685
|
+
self, mock_get, mock_time, mock_sleep, patched_logger
|
|
686
|
+
):
|
|
687
|
+
cds_handler = CDSHandler(['el'], 'some_token', fetch_timeout=1)
|
|
688
|
+
cds_handler.etags.set('el', 'old-etag')
|
|
689
|
+
mock_get.return_value = self._json_response(202, {'data': {}})
|
|
690
|
+
# deadline 0 -> 1; checked at 0.1, request at 0.2, 202 at 0.3 leaves
|
|
691
|
+
# less than the 2s retry delay
|
|
692
|
+
mock_time.side_effect = [0, 0.1, 0.2, 0.3]
|
|
693
|
+
|
|
694
|
+
translations = cds_handler.fetch_translations(language_code='el')
|
|
695
|
+
|
|
696
|
+
assert translations == {'el': (False, {})}
|
|
697
|
+
assert cds_handler.etags.get('el') == 'old-etag'
|
|
698
|
+
patched_logger.error.assert_called_once_with(
|
|
699
|
+
'Error retrieving translations from CDS: Timeout')
|
|
700
|
+
|
|
701
|
+
@patch('transifex.native.cds.time.monotonic')
|
|
702
|
+
@patch('transifex.native.cds.requests.get')
|
|
703
|
+
def test_request_timeout_is_capped_by_deadline(self, mock_get, mock_time):
|
|
704
|
+
mock_get.return_value = self._json_response(200, {'data': []})
|
|
705
|
+
mock_time.side_effect = [0, 3]
|
|
706
|
+
|
|
707
|
+
CDSHandler(['el'], 'some_token', fetch_timeout=5).fetch_languages()
|
|
708
|
+
|
|
709
|
+
assert mock_get.call_args[1]['timeout'] == (2, 2)
|
|
710
|
+
|
|
711
|
+
@patch('transifex.native.cds.time.monotonic')
|
|
712
|
+
@patch('transifex.native.cds.requests.get')
|
|
713
|
+
def test_no_request_once_deadline_has_passed(self, mock_get, mock_time):
|
|
714
|
+
mock_time.side_effect = [0, 6]
|
|
715
|
+
cds_handler = CDSHandler(['el'], 'some_token', fetch_timeout=5)
|
|
716
|
+
|
|
717
|
+
with pytest.raises(requests.Timeout):
|
|
718
|
+
cds_handler.retry_get_request('https://some.host/languages')
|
|
719
|
+
mock_get.assert_not_called()
|
|
720
|
+
|
|
721
|
+
@patch('transifex.native.cds.logger')
|
|
722
|
+
def test_invalid_fetch_timeout_disables_it(self, patched_logger):
|
|
723
|
+
for value, expected in ((None, 0), ('30', 30), (2, 2), ('abc', 0)):
|
|
724
|
+
cds_handler = CDSHandler(['el'], 'some_token', fetch_timeout=value)
|
|
725
|
+
assert cds_handler.fetch_timeout == expected
|
|
726
|
+
patched_logger.error.assert_called_once_with(
|
|
727
|
+
'Invalid fetch_timeout `abc`, fetch timeout disabled')
|
|
728
|
+
|
|
729
|
+
@patch('transifex.native.cds.requests.get')
|
|
730
|
+
def test_fetch_requests_use_request_timeout(self, mock_get):
|
|
731
|
+
mock_get.return_value = self._json_response(200, {'data': []})
|
|
732
|
+
|
|
733
|
+
for request_timeout, expected in (
|
|
734
|
+
(None, (10, 60)),
|
|
735
|
+
((3, 7), (3, 7)),
|
|
736
|
+
(5, 5),
|
|
737
|
+
):
|
|
738
|
+
mock_get.reset_mock()
|
|
739
|
+
kwargs = {} if request_timeout is None else {
|
|
740
|
+
'request_timeout': request_timeout}
|
|
741
|
+
cds_handler = CDSHandler(['el'], 'some_token', **kwargs)
|
|
742
|
+
|
|
743
|
+
cds_handler.fetch_languages()
|
|
744
|
+
cds_handler.fetch_translations(language_code='el')
|
|
745
|
+
|
|
746
|
+
timeouts = [c[1]['timeout'] for c in mock_get.call_args_list]
|
|
747
|
+
assert timeouts == [expected, expected]
|
|
748
|
+
|
|
749
|
+
@patch('transifex.native.cds.logger')
|
|
750
|
+
@patch('transifex.native.cds.requests.get')
|
|
751
|
+
def test_fetch_logs_request_timeouts(self, mock_get, patched_logger):
|
|
752
|
+
cds_handler = CDSHandler(['el'], 'some_token')
|
|
753
|
+
|
|
754
|
+
for exception in (requests.ReadTimeout(), requests.ConnectTimeout()):
|
|
755
|
+
mock_get.side_effect = exception
|
|
756
|
+
patched_logger.reset_mock()
|
|
757
|
+
|
|
758
|
+
assert cds_handler.fetch_languages() == []
|
|
759
|
+
assert cds_handler.fetch_translations(language_code='el') == {
|
|
760
|
+
'el': (False, {})
|
|
761
|
+
}
|
|
762
|
+
assert [c[0][0] for c in patched_logger.error.call_args_list] == [
|
|
763
|
+
'Error retrieving languages from CDS: Timeout',
|
|
764
|
+
'Error retrieving translations from CDS: Timeout',
|
|
765
|
+
]
|
|
766
|
+
|
|
767
|
+
@patch('transifex.native.cds.logger')
|
|
768
|
+
@patch('transifex.native.cds.time.monotonic')
|
|
769
|
+
@patch('transifex.native.cds.requests.get')
|
|
770
|
+
def test_fetch_timeout_bounds_whole_fetch(
|
|
771
|
+
self, mock_get, mock_time, patched_logger
|
|
772
|
+
):
|
|
773
|
+
cds_handler = CDSHandler(['el', 'en'], 'some_token', fetch_timeout=5)
|
|
774
|
+
mock_get.side_effect = [
|
|
775
|
+
self._json_response(200, {'data': [{'code': 'el'},
|
|
776
|
+
{'code': 'en'}]}),
|
|
777
|
+
self._json_response(200, {'data': {'key': {'string': 'x'}}}),
|
|
778
|
+
]
|
|
779
|
+
# deadline 0 -> 5; languages request at 0.5, first language checked
|
|
780
|
+
# and requested at 1, second language checked at 10
|
|
781
|
+
mock_time.side_effect = [0, 0.5, 1, 1, 10]
|
|
782
|
+
|
|
783
|
+
translations = cds_handler.fetch_translations()
|
|
784
|
+
|
|
785
|
+
assert mock_get.call_count == 2
|
|
786
|
+
assert sorted(translations.values(), key=lambda v: v[0]) == [
|
|
787
|
+
(False, {}),
|
|
788
|
+
(True, {'key': {'string': 'x'}}),
|
|
789
|
+
]
|
|
790
|
+
skipped = [lang for lang, (refresh, _) in translations.items()
|
|
791
|
+
if not refresh]
|
|
792
|
+
patched_logger.error.assert_called_once_with(
|
|
793
|
+
'Error retrieving translations from CDS: fetch timeout reached, '
|
|
794
|
+
'skipped `{}`'.format(skipped[0])
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
@patch('transifex.native.cds.time.monotonic')
|
|
798
|
+
@patch('transifex.native.cds.requests.get')
|
|
799
|
+
def test_number_request_timeout_is_capped_by_deadline(
|
|
800
|
+
self, mock_get, mock_time
|
|
801
|
+
):
|
|
802
|
+
mock_get.return_value = self._json_response(200, {'data': []})
|
|
803
|
+
mock_time.side_effect = [0, 3]
|
|
804
|
+
cds_handler = CDSHandler(['el'], 'some_token', fetch_timeout=5,
|
|
805
|
+
request_timeout=30)
|
|
806
|
+
|
|
807
|
+
cds_handler.fetch_languages()
|
|
808
|
+
|
|
809
|
+
assert mock_get.call_args[1]['timeout'] == 2
|
|
810
|
+
|
|
811
|
+
@patch('transifex.native.cds.time.sleep')
|
|
812
|
+
@patch('transifex.native.cds.time.monotonic')
|
|
813
|
+
@patch('transifex.native.cds.requests.get')
|
|
814
|
+
def test_retry_get_request_returns_5xx_at_deadline(
|
|
815
|
+
self, mock_get, mock_time, mock_sleep
|
|
816
|
+
):
|
|
817
|
+
response = self._json_response(503)
|
|
818
|
+
mock_get.return_value = response
|
|
819
|
+
# deadline 0 -> 1; request at 0.1, 503 at 0.2 leaves less than 2s
|
|
820
|
+
mock_time.side_effect = [0, 0.1, 0.2]
|
|
821
|
+
cds_handler = CDSHandler(['el'], 'some_token', fetch_timeout=1)
|
|
822
|
+
|
|
823
|
+
assert cds_handler.retry_get_request('https://some.host/x') is response
|
|
824
|
+
assert mock_get.call_count == 1
|
|
825
|
+
mock_sleep.assert_not_called()
|
|
826
|
+
|
|
827
|
+
@patch('transifex.native.cds.time.monotonic')
|
|
828
|
+
@patch('transifex.native.cds.requests.get')
|
|
829
|
+
def test_none_request_timeout_parts_are_capped_by_deadline(
|
|
830
|
+
self, mock_get, mock_time
|
|
831
|
+
):
|
|
832
|
+
mock_get.return_value = self._json_response(200, {'data': []})
|
|
833
|
+
|
|
834
|
+
for request_timeout, expected in (((2, None), (2, 3)), (None, 3)):
|
|
835
|
+
mock_time.side_effect = [0, 2]
|
|
836
|
+
cds_handler = CDSHandler(['el'], 'some_token', fetch_timeout=5,
|
|
837
|
+
request_timeout=request_timeout)
|
|
838
|
+
|
|
839
|
+
assert cds_handler.fetch_languages() == []
|
|
840
|
+
assert mock_get.call_args[1]['timeout'] == expected
|
|
841
|
+
|
|
842
|
+
@patch('transifex.native.cds.time.sleep')
|
|
843
|
+
@patch('transifex.native.cds.requests.get')
|
|
844
|
+
def test_retry_get_request_5xx_retries_without_deadline(
|
|
845
|
+
self, mock_get, mock_sleep
|
|
846
|
+
):
|
|
847
|
+
response = self._json_response(503)
|
|
848
|
+
mock_get.return_value = response
|
|
849
|
+
|
|
850
|
+
cds_handler = CDSHandler(['el'], 'some_token')
|
|
851
|
+
assert cds_handler.retry_get_request('https://some.host/x') is response
|
|
852
|
+
|
|
853
|
+
assert mock_get.call_count == 4
|
|
854
|
+
assert [c[0][0] for c in mock_sleep.call_args_list] == [2, 4, 6]
|
|
855
|
+
|
|
856
|
+
@patch('transifex.native.cds.time.sleep')
|
|
857
|
+
@patch('transifex.native.cds.time.monotonic')
|
|
858
|
+
@patch('transifex.native.cds.requests.get')
|
|
859
|
+
def test_retry_get_request_202_then_200_within_deadline(
|
|
860
|
+
self, mock_get, mock_time, mock_sleep
|
|
861
|
+
):
|
|
862
|
+
ok = self._json_response(200, {'data': []})
|
|
863
|
+
mock_get.side_effect = [self._json_response(202), ok]
|
|
864
|
+
# deadline 0 -> 10; requests at 0.1 and 2.2, 202 checked at 0.2
|
|
865
|
+
mock_time.side_effect = [0, 0.1, 0.2, 2.2]
|
|
866
|
+
cds_handler = CDSHandler(['el'], 'some_token', fetch_timeout=10)
|
|
867
|
+
|
|
868
|
+
assert cds_handler.retry_get_request('https://some.host/x') is ok
|
|
869
|
+
mock_sleep.assert_called_once_with(2)
|
|
870
|
+
|
|
871
|
+
@patch('transifex.native.cds.requests.get')
|
|
872
|
+
def test_fetch_translations_follows_configured_language_order(
|
|
873
|
+
self, mock_get
|
|
874
|
+
):
|
|
875
|
+
languages = self._json_response(200, {'data': [
|
|
876
|
+
{'code': 'el'}, {'code': 'en'}, {'code': 'fr'}, {'code': 'de'}]})
|
|
877
|
+
content = self._json_response(200, {'data': {}})
|
|
878
|
+
|
|
879
|
+
for fetch_all_langs, expected in (
|
|
880
|
+
(False, ['fr', 'el']),
|
|
881
|
+
(True, ['fr', 'el', 'en', 'de']),
|
|
882
|
+
):
|
|
883
|
+
mock_get.reset_mock()
|
|
884
|
+
mock_get.side_effect = [languages] + [content] * 4
|
|
885
|
+
cds_handler = CDSHandler(['fr', 'it', 'el', 'fr'], 'some_token',
|
|
886
|
+
host='https://some.host',
|
|
887
|
+
fetch_all_langs=fetch_all_langs)
|
|
888
|
+
|
|
889
|
+
cds_handler.fetch_translations()
|
|
890
|
+
|
|
891
|
+
requested = [c[0][0].rsplit('/', 1)[1]
|
|
892
|
+
for c in mock_get.call_args_list[1:]]
|
|
893
|
+
assert requested == expected
|
|
894
|
+
|
|
895
|
+
@patch('transifex.native.cds.requests.get')
|
|
896
|
+
def test_fetch_all_langs_without_configured_languages(self, mock_get):
|
|
897
|
+
mock_get.side_effect = [
|
|
898
|
+
self._json_response(200, {'data': [{'code': 'el'}]}),
|
|
899
|
+
self._json_response(200, {'data': {}}),
|
|
900
|
+
]
|
|
901
|
+
cds_handler = CDSHandler(None, 'some_token',
|
|
902
|
+
host='https://some.host',
|
|
903
|
+
fetch_all_langs=True)
|
|
904
|
+
|
|
905
|
+
assert cds_handler.fetch_translations() == {'el': (True, {})}
|
|
906
|
+
|
|
907
|
+
@patch('transifex.native.cds.logger')
|
|
908
|
+
def test_invalid_request_timeout_uses_default(self, patched_logger):
|
|
909
|
+
for value, expected in (
|
|
910
|
+
(None, None),
|
|
911
|
+
(5, 5),
|
|
912
|
+
((3, None), (3, None)),
|
|
913
|
+
([3, 30], (3, 30)),
|
|
914
|
+
(0, DEFAULT_REQUEST_TIMEOUT),
|
|
915
|
+
('30', DEFAULT_REQUEST_TIMEOUT),
|
|
916
|
+
(True, DEFAULT_REQUEST_TIMEOUT),
|
|
917
|
+
((1, 2, 3), DEFAULT_REQUEST_TIMEOUT),
|
|
918
|
+
):
|
|
919
|
+
cds_handler = CDSHandler(['el'], 'some_token',
|
|
920
|
+
request_timeout=value)
|
|
921
|
+
assert cds_handler.request_timeout == expected
|
|
922
|
+
assert patched_logger.error.call_count == 4
|
|
923
|
+
|
|
924
|
+
@patch('transifex.native.cds.logger')
|
|
925
|
+
@patch('transifex.native.cds.requests.get')
|
|
926
|
+
def test_read_timeout_in_body_is_logged_as_timeout(
|
|
927
|
+
self, mock_get, patched_logger
|
|
928
|
+
):
|
|
929
|
+
mock_get.side_effect = requests.ConnectionError(
|
|
930
|
+
ReadTimeoutError(None, None, 'Read timed out.'))
|
|
931
|
+
cds_handler = CDSHandler(['el'], 'some_token',
|
|
932
|
+
host='https://some.host')
|
|
933
|
+
|
|
934
|
+
assert cds_handler.fetch_translations('el') == {'el': (False, {})}
|
|
935
|
+
patched_logger.error.assert_called_once_with(
|
|
936
|
+
'Error retrieving translations from CDS: Timeout')
|
|
@@ -52,3 +52,14 @@ class TestModuleInit(object):
|
|
|
52
52
|
assert isinstance(_tx._missing_policy, SourceStringPolicy)
|
|
53
53
|
assert isinstance(_tx._error_policy, SourceStringErrorPolicy)
|
|
54
54
|
assert isinstance(_tx._cache, MemoryCache)
|
|
55
|
+
|
|
56
|
+
def test_init_passes_timeouts_to_cds_handler(self, reset_tx):
|
|
57
|
+
native.init('mytoken', ['lang1'], fetch_timeout=7,
|
|
58
|
+
request_timeout=(1, 2))
|
|
59
|
+
assert _tx._cds_handler.fetch_timeout == 7
|
|
60
|
+
assert _tx._cds_handler.request_timeout == (1, 2)
|
|
61
|
+
|
|
62
|
+
def test_init_default_request_timeout(self, reset_tx):
|
|
63
|
+
native.init('mytoken', ['lang1'])
|
|
64
|
+
assert _tx._cds_handler.fetch_timeout == 0
|
|
65
|
+
assert _tx._cds_handler.request_timeout == (10, 60)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from django.apps import apps
|
|
2
|
+
from mock import patch
|
|
3
|
+
from transifex.native.django import settings as native_settings
|
|
4
|
+
from transifex.native.django.apps import NativeConfig
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _native_config():
|
|
8
|
+
return next(config for config in apps.get_app_configs()
|
|
9
|
+
if isinstance(config, NativeConfig))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@patch.object(native_settings, 'TRANSIFEX_REQUEST_TIMEOUT', (1, 2))
|
|
13
|
+
@patch.object(native_settings, 'TRANSIFEX_FETCH_TIMEOUT', 7)
|
|
14
|
+
@patch('transifex.native.django.apps.init')
|
|
15
|
+
def test_ready_passes_timeout_settings(mock_init):
|
|
16
|
+
_native_config().ready()
|
|
17
|
+
|
|
18
|
+
kwargs = mock_init.call_args[1]
|
|
19
|
+
assert kwargs['fetch_timeout'] == 7
|
|
20
|
+
assert kwargs['request_timeout'] == (1, 2)
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "
|
|
11
|
+
"date": "2026-11-13T15:12:29+0200",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "3.
|
|
14
|
+
"full-revisionid": "55901f750a65c35413540bf26f11f31562a75e4f",
|
|
15
|
+
"version": "3.8.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -8,6 +8,8 @@ def init(
|
|
|
8
8
|
fetch_all_langs=False,
|
|
9
9
|
filter_tags=None,
|
|
10
10
|
filter_status=None,
|
|
11
|
+
fetch_timeout=0,
|
|
12
|
+
request_timeout=None,
|
|
11
13
|
):
|
|
12
14
|
"""Initialize the framework.
|
|
13
15
|
|
|
@@ -25,6 +27,10 @@ def init(
|
|
|
25
27
|
:param bool fetch_all_langs: force pull all remote languages
|
|
26
28
|
:param str filter_tags: fetch only content with tags
|
|
27
29
|
:param str filter_status: fetch only content with specific translation status
|
|
30
|
+
:param int fetch_timeout: maximum time in seconds to wait when fetching
|
|
31
|
+
translations or locales from CDS
|
|
32
|
+
:param request_timeout: seconds for each CDS fetch request, a number or
|
|
33
|
+
a (connect, read) tuple; defaults to (10, 60)
|
|
28
34
|
"""
|
|
29
35
|
if not tx.initialized:
|
|
30
36
|
tx.init(
|
|
@@ -38,6 +44,8 @@ def init(
|
|
|
38
44
|
fetch_all_langs=fetch_all_langs,
|
|
39
45
|
filter_tags=filter_tags,
|
|
40
46
|
filter_status=filter_status,
|
|
47
|
+
fetch_timeout=fetch_timeout,
|
|
48
|
+
request_timeout=request_timeout,
|
|
41
49
|
)
|
|
42
50
|
|
|
43
51
|
|